记录一下我对C++类构造函数的理解。
首先,构造函数分成两种,默认构造函数和非默认构造函数(好吧,就这么叫它)。
默认构造函数只能有一个,如果没有自定义构造函数,那么编译器将自动生成一个默认构造函数,当然这个构造函数不会做任何事情。如果程序定义了构造函数(包括默认和非默认),编译器都不再自动提供默认构造函数。
如class C, 对应自动生成的默认构造函数为C() { };
程序员可以自定义默认构造函数,而且只能定义一个默认构造函数。如定义class A.
Class A
{
public:
// A() { } //constructor1,
do nothing,默认构造函数
// A(int
m=1):v1(m);//construct2,默认构造函数,提供了部分的默认初始化值
// A(int m=1, int n=2):v1(m),
v2(n);//construct3, 默认构造函数,提供了所有的默认初始化值
// A(int m);
//construct4,非默认构造函数, 提供一个参数
// A(int m, int n);
//construct5, 非默认构造函数, 提供两个参数
private:
int v1;
int v2;
}
下面具体分析一下各种定义方式会带来的问题:
1)不定义任何构造函数,这个上面说过,编译器会自动构造一个默认构造函数,等价于定义了construct1。
2) 只定义construct2, 可以正确编译运行。但是无法以形式A a(1,2)定义对象,这是显然的。
3) 只定义construct3, 可以正确编译运行。
4) 定义2个及以上默认构造函数的情况。
想来同时提供construct2和construct3是不会矛盾的,而且符合重载的要求,但是事实并非如此。
同时定义construct2和construct3, 以形式 A a;定义对象将会导致编译器报错。
error: call of overloaded ‘A()’
is ambiguous
note: candidates
are: A::A(int, int)
note:
A::A(int)
以形式A a(1);定义对象将会导致报错:
error: call of
overloaded ‘A(int)’ is
ambiguous
note: candidates are: A::A(int,
int)
note:
A::A(int)
note:
A::A(const A&)
然而,以形式 A
a(1,2);定义对象则可以正确通过编译并运行。 这是什么原因?编译器工作的原理是什么?不知道....
5)
从重载的角度分析,construct2和construct3签名一致,construct4和construct5签名一致,不能同时定义。实验结果证实确实如此。
6) construct4和5恰为构造函数重载的典型实例,当然能正确编译运行。
7 ) 只提供非默认构造函数,只有construct4或5或同时有4和5而无1/2/3
这将导致无法使用 A
a;的形式来声明对象。因为以A a的形式声明对象是调用默认构造函数来构造对象的。编译器找不到默认构造函数自然会报错。
与此同时对象数组的声明A
a[10];也将得到报错,原理同上。
那么如下声明并初始化对象数组会是什么结果呢?
A a[3] = {
A(1,1), A(2,2), A(3,3)
}
//(再只定义了construct5的情况下)
在C++ Primer
Plus中有如下语句:“要创建类对象数组,则这个类必须有默认构造函数。”,“初始化对象数组的方案位,首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。因此要创建类对象数组,则这个类必须有默认构造函数。”
在我看来这话不尽然正确,或许Stephen写书的时候使用的编译器是如他所说,但是我在g++中做的实现不是如此。上述对象数组的定义在g++下能够通过编译,并运行正确。g++的策略是,如果花括号中明确调用了构造函数来初始化对象,且该构造函数已被定义,那么将直接使用构造函数来创建对象元素,无临时对象和复制的过程。然而,如果花括号中有任一元素没有显示的使用已定义的构造函数初始化,那么编译器将调用默认构造函数来初始化该对象元素,这时如果没有定义默认构造函数,将无法通过编译。
那么在上述情形中 A a[4]
= { A(1,1), A(2,2), A(3,3)},
将无法通过编译,因为A[3]实际上要通过调用默认构造函数来初始化,而这里又没有定义默认构造函数。
关于默认构造函数的调用。
实际上在g++中,未提供任何构造函数时,初始化一个对象,编译器并不去调用自动提供的那个什么也不做的构造函数,而仅仅是在内存中为对象分配了空间。(这应该是编译器的优化手段,不同编译器不同。)如果程序自己定义了一个A(){};虽然也什么都不做,但是初始化对象时,该构造函数却会被执行。这就带来了额外的开销,所以如果真的不定义任何构造函数的话,那么就干脆也别定义A(){};这么个空构造函数了,无意义,反而降低效率。
构造函数的参数传递,参数的第一个值为隐藏的this指针,指向对象的首地址,其次才是传进去的参数。
类对象的大小:无任何属性域的对象,大小为1, 即类class A{};
有属性域的类,大小为属性大小之和。this指针不占内存。