构造函数的回顾
关于构造函数
——类的构造函数用于对象的初始化
——构造函数与类同名并且没有返回值
——构造函数在对象定义时自动被调用
问题:
1. 如何判断构造函数的执行结果?
目前来说,没有办法来判断构造函数的执行结果
2. 在构造函数中执行return语句会发生什么?
在构造函数中可以存在return语句,return之后下面的代码就无法执行,会影响对象的初始状态
3. 构造函数执行结束是否意味着对象构造成功?
对象的诞生与构造函数的执行结果是没有任何关系的。如果构造函数没有执行成功,只会影响它的初始状态,并不影响对象的创建。
异常的构造函数
#include <stdio.h> class Test { int mi; int mj;public: Test(int i, int j) { mi = i; return; mj = j; } int getI() { return mi; } int getJ() { return mj; } }; int main() { Test t1(1, 2); printf("t1.mi = %d\n", t1.getI()); printf("t1.mj = %d\n", t1.getJ()); return 0; }
在构造函数中执行return语句:对象会被创建成功,但是对象的初始状态会发生异常。例如在这里的本意是将对象的成员mj赋值为2,但是得到的结果却是随机值。
下面可以这样做:
#include <stdio.h> class Test { int mi; int mj; bool mStatus; public: Test(int i, int j) : mStatus(false) { mi = i; return; mj = j; mStatus = true; } int getI() { return mi; } int getJ() { return mj; } int status() { return mStatus; } }; int main() { Test t1(1, 2); if( t1.status() ) { printf("t1.mi = %d\n", t1.getI()); printf("t1.mj = %d\n", t1.getJ()); } return 0; }
这种解决方案确实可以,就是强行的让构造函数有一个返回值。并且手工的调用status来得到构造函数的返回值。这个似乎可以解决问题,但是不够优美。
通过上面的实验,可以得到:
构造函数
——只提供自动初始化成员变量的机会
——不能保证初始化逻辑一定成功
——执行return语句后构造函数立即结束。
构造函数能决定的只是对象的初始状态,而不是对象的诞生。
半成品对象的概念
——初始化操作不能按照预期完成而得到的对象
——半成品对象是合法的C++对象,也是Bug的重要来源
办成品对象的危害
还是以之前创建的那个数组类为例,进行分析。
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for(int i=0; i<len; i++) { m_pointer[i] = 0; } m_length = len; } IntArray::IntArray(const IntArray& obj) { m_length = obj.m_length; m_pointer = new int[obj.m_length]; for(int i=0; i<obj.m_length; i++) { m_pointer[i] = obj.m_pointer[i]; } } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index < length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index < length()); if( ret ) { m_pointer[index] = value; } return ret; } IntArray::~IntArray() { delete[]m_pointer; }
分析下面这段代码:
IntArray::IntArray(int len) { m_pointer = new int[len]; //申请堆空间 for(int i=0; i<len; i++) { m_pointer[i] = 0; } m_length = len; }
问题:每次申请堆空间时,你能保证每次都申请成功吗?也许1万次了,出现几次不成功。看上去概率很低,但是我们也不能容忍,你可能会这样去做。
IntArray::IntArray(int len) { m_pointer = new int[len]; //申请堆空间 if(m_pointer) { for(int i=0; i<len; i++) { m_pointer[i] = 0; } } m_length = len; }
这样当申请失败了,就不需要执行花括号里面的代码了。看似没有什么问题,也合情合理。
注意:前面已经说过,构造函数内执行是否成功,与对象是否创建没有什么关系。上面这个例子中构造函数体内没有执行成功,肯定会影响对象的初始状态,但是用户并不知道这个对象的成员有异常。当用户拿到这个类时,它并不知道你花括号里面的内容没有执行,该怎么用就怎么用。
看下面的使用:
int main()
{
IntArray a(5);
a.set(0,1);
}
编译没有问题,运行时悲剧就产生了。段错误。出现的原因就是在构造函数中,m_pointer没有申请成功,但是下面又使用了它。