今天在看《effective C++:55 第三版》条款18:80页的时候,看到用外覆类型定义类型参数的例子:
class Month{
public:
static Month Jan(){return Month(1);}
private:
explicit Month(int num){val=num;}
int val;
};
然后想到前几天做到的一个笔试题:
struct CLS
{
int m_i;
CLS(int i): m_i( i ) { }
CLS()
{
CLS( 0 );
}
};
int main(void)
{
CLS obj;
cout << obj.m_i << endl;
return 0;
}
这题的答案是,不能输出0,因为在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。
#include<iostream>
using namespace std;
class Month{
private:
int val;
public:
explicit Month(int num):val(num){<span style="white-space:pre"> </span>//a
}<span style="white-space:pre"> </span>//b
explicit Month(){<span style="white-space:pre"> </span>//c
Month(1);<span style="white-space:pre"> </span>//d
}<span style="white-space:pre"> </span>//e
~Month(){<span style="white-space:pre"> </span>//f
cout<<"val:"<<val<<endl;<span style="white-space:pre"> </span>//g
}<span style="white-space:pre"> </span>//h
};
int main(void)
{
Month A;<span style="white-space:pre"> </span>
return 0;
}
这个程序的运行结果是这样的(32位机,win7系统):
但我预先设想的结果是只输出第二行的(也就是val未定义),不会输出第一行
跟踪了一下程序的运行轨迹,发现顺序是这样的:
1、在执行Month A;语句的时候,程序跳转到b处,继续往下执行,调用Month(1);语句
2、程序跳转到a处,执行完毕跳转回d处,往下执行到e处,程序跳转到f处,开始执行析构函数
3、执行完毕,程序跳至e处,继续执行,程序返回return 0;处
4、继续执行,程序又跳至f处,执行析构函数,执行完毕,程序跳回main函数,程序执行完毕;
通过以上程序执行过程,发现程序在缺省构造函数中执行Month(1);时,其实是相当于实例化对象(虽然没有名称),又一次在栈上分配了内存空间M2(前一次在栈上分配空间是执行Month A时,称为M1),并执行了构造函数Month(int num):val(num),同时执行了构造函数初始化列表将val初始化了。但此时是M2中的val初始化了,和M1中的val没有关系。
在执行完缺省构造函数Month()、程序退出Month()之前,由于之前调用了构造函数初始化对象,此时就必须调用析构函数,所以就有了程序的输出结果中的第一行结果;
程序返回main函数体之后,执行return 0;语句,预示着程序将会退出,这时再一次调用析构函数,所以就有了输出结果中的第二行结果,即val值没有初始化;
通过以上分析,我感觉对于笔试题而言,在缺省构造函数内部调用带参构造函数时,不是将带参构造函数当做普通函数执行函数调用,而是再一次实例化对象,在内存中重新开辟空间,并执行了带参构造函数后的初始化列表;但是在缺省构造函数执行完毕之后,预示着刚刚的实例化对象的生命周期的结束,程序调用析构函数释放了刚刚分配的内存空间;
也就是说:在缺省构造函数中调用带参构造函数,实际上是在一块新分配的栈空间中操作,对原有空间中的数据没有任何影响。