构造函数的回顾
关于构造函数
——类的构造函数用于对象的初始化
——构造函数与类同名并且没有返回值
——构造函数在对象定义时自动被调用
问题:
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
", t1.getI());
printf("t1.mj = %d
", 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
", t1.getI());
printf("t1.mj = %d
", 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没有申请成功,但是下面又使用了它。