一、概念
1.控制类对象初始化过程的一个或几个特殊的成员函数,叫做构造函数。
2.构造函数的任务是构造初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
3.构造函数名与类名相同,不能被const修饰。
二、合成的默认构造函数
1.它的产生:如果没有为类显式的定义任一构造函数,那么编译器合适地时候会为我们隐式的定义一个默认构造函数,它又被称为合成的默认构造函数。
2.它按如下规则初始化类的数据成员:
1)存在类内初始值时,用它来初始化成员。
2)否则,默认初始化该成员。
对第二条:默认初始化类成员进行说明,及默认初始化完成的工作和缺陷。
完成的工作:
1)默认初始化的默认值到底是什么,由变量类型和和变量定义的位置共同决定。
2)如果默认初始化的是内置类型或复合类型(如数组、指针),它的值由定义的位置决定,在函数体外定义被初始化为0,在块中(如函数体)被定义则内置类型将不被初始化,它的值是未定义的,试图访问和使用此值都将引发错误。
class Demo//环境:VS2015 WIN732位
{
public:
int a;
double b;
char c;
};
Demo d1;//在函数体外部定义,a初始化为0,b初始化为0,c为一个空字符。
int main(int argc,char** argv)
{
Demo d2;//此处定义对象d2,没有问题,调试可以发现其成员变量值未定义。
cout << d1.a << "," << d1.b << "," << d1.c <<endl;//可以输出,0,0,(空格)
cout << d2.a << "," << d2.b << "," << d2.c << endl; //编译器器报错:使用了未初始化的局部变量d2.
return 0;
}
3)如果默认初始化的是类类型,则会调用此类的默认构造函数。函数体外定义封闭类时,可以调用成员对象的合成默认构造函数,但在函数体内定义时无法完成,需显式构造成员对象默认构造函数,才能完成封闭类的默认初始化。若此对象成员类没有默认构造函数,则无法初始化该成员,编译器无法生成该封闭类的合成默认构造函数。
class A
{
public:
//A(){}
int a;
};
class B //有成员对象的类称为封闭类(B就是一个)
{
public:
int b;
A aa;
};
B b1;
int main()
{
B b2;
cout << b1.aa.a << " " << b1.b << endl; //输出0 0
cout << b2.aa.a << " " << b2.b << endl; //编译错误:使用了未初始化的变量b2.如果去掉第四行注释,则输出绝对值很大的负数
return 0;
}
以上发生的错误就出现合成默认构造函数不适用的场合,因此类一般都要提供自己的默认构造函数,并且所有构造函数都应该显式地初始化每个内置类型成员。
三、默认构造函数
1.若存在函数体为空的默认构造函数,相当于显示定义合成默认构造函数,他们默认构造的功能是相同的。
四、构造函数初始化列表
作用:负责为新创建的对象的一个或几个数据成员赋初值。
注意下面摘抄字《C++ primer》第5版中文版P266中的内容:
1.当某个数据成员被构造函数初始值列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化。
2没有出现在构造函数初始值列表中的成员将通过相应的类内初始值(如果存在的话)初始化,或者执行默认初始化。
我对这两句话的理解:极限思考,如果构造函数没有初始值列表(或者说初始值列表为空),不考虑内类初始值,那么所有成员变量都将先执行默认初始化,再执行构造函数函数体。
看到P258页发现这句话,如果没有在构造函数的初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。
class A
{
public:
A() { a=2; }
A(int _a ) { a = _a; }
int a;
};
class B
{
public:
B(int _b)
{
cout << "构造函数中1: " << this->aa.a <<" "<< this->b << endl;
b = _b;
cout << "构造函数中2: " << this->aa.a << " " << this->b << endl;
aa = A(_b);
cout << "构造函数中3: " << this->aa.a << " " << this->b << endl;
}
int b;
A aa;
};
int main()
{
B b(3);
cout <<"最终结果 : "<< b.aa.a << " " << b.b << endl;
return 0;
}
运行结果:
可见,确实是先执行了默认构造函数,再执行函数体。
也可以得出结论,使用初始值列表的效率要高于在函数体中先建立对象再赋值给成员变量的初始化操作。
补充:
如果成员是const,引用或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
五、抽象基类的构造函数
前面提到了构造函数的作用,是完成类数据成员初始化。那么没有数据成员是不是就不需要构造函数了呢?
一个类需不需要构造函数要看具体情况,和是不是抽象类(虚基类)没有关系。一个类格构造函数的作用是对其成员进行初始化,抽象类也有可能包含需要初始化的成员,也需要进行初始化。
看到一些回答写,取决于用途:用作接口,肯定不写。用作基类,有成员变量的话一般需要写。
感觉还是有点道理的。
本文主要参考了:
《C++ Primer》关于构造函数的内容