1、默认构造函数和构造函数
(1)构造函数:C++用于构建类的新对象时需要调用的函数,该函数无返回类型!(注意:是“无”! 不是空!(void))。
(2)默认构造函数:默认构造函数是在调用时不需要显示地传入实参的构造函数。
一个类如果自己没有定义构造函数,则会有一个无参且函数体也是空的默认构造函数。只要程序员定义了构造函数,编译器就不会再提供默认构造函数了。
定义默认构造函数有两种方式,一是定义一个无参的构造函数,二是定义所有参数都有默认值的构造函数。
class testClass{public:testClass();/* 默认构造函数 */testClass(int a, char b); /* 构造函数 */testClass(int a=10,char b='c');/* 默认构造函数 */private:int m_a;char m_b;};
(3)编译器在什么情况下会生成默认构造函数?
下面几种情况下,编译需要生成默认构造函数:
当该类的类对象数据成员有默认构造函数时。当该类的基类有默认构造函数时。当该类的基类为虚基类时。当该类有虚函数时。
(4)避免“无参数的默认构造函数”和“带缺省参数的默认构造函数”同时存在
无参数的默认构造函数和带缺省参数的默认构造函数同时存在时,编译器会产生二义性,从而生成编译错误
class Sample {public:// 默认构造函数Sample() {// do somethingprintf("Sample()");}// 默认构造函数Sample(int m = 10) {// do somethingprintf("Sample(int m = 10)");}};int main(){Sample s; // error C2668: “Sample::Sample”: 对重载函数的调用不明确return 0;}
2、构造函数初始列表
C++类中成员函数一般有两种初始化,一种是在类的构造函数内部初始化,另一种是利用类的构造函数的初始化列表进行初始化。初始化和赋值对内置类型的成员没有什么大的区别。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。
初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:
(1)内置数据类型,如int,float等;复合类型(指针,引用)
在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
(2)用户定义类型(类类型)
结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)。因此,能使用初始化列表的时候尽量使用初始化列表。
class Test2{public:Test1 test1 ;Test2(Test1 &t1):test1(t1){} //调用拷贝构造函数初始化test1,省去了调用默认构造函数的过程}
3、必须用带有初始化列表的构造函数:
(1)成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
class Test1{public:Test1(int a):i(a){}//没有默认构造函数int i;};class Test2{public:Test1 test1 ;Test2(Test1 &t1){test1 = t1 ;}};
以上代码无法通过编译,因为Test2的构造函数中test1 = t1这一行实际上分成两步执行:
调用Test1的默认构造函数来初始化test1
由于Test1没有默认的构造函数,所以1 无法执行,故而编译错误。正确的代码如下,使用初始化列表代替赋值操作
class Test1{public:Test1(int a):i(a){} //没有默认构造函数int i;};class Test2{public:Test1 test1 ;Test2(int x):test1(x){} //使用初始化列表}
(2)const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
#include <iostream>using namespace std;class A{public:#if 0//因为成员变量中含有引用和常量则不能如此定义该构造函数A(){cout<<"aaaaaaaa"<<endl;}#endif#if 1A(int x1 = 0):x(x1),rx(x),pi(3.14) { }//带有初始化列表的默认构造函数void print(){cout<<"x="<<x<<" rx="<<rx<<" pi="<<pi<<endl; }~A(){}private:int x;int ℞ //声明为引用时则必须在构造函数基/成员初始值设定项列表中初始化const double pi; //声明为const时则必须在构造函数基/成员初始值设定项列表中初始化#endif};int main(){A a(20);a.print();A a1;//因为A有默认构造函数,则可以如此调用a1.print();system("pause");return 0;}
注:如果一个构造函数为所以参数都提供默认实参,则它实际上也定义了默认构造函数
(3)基类未声明默认构造函数
#include <iostream>using namespace std;class Animal{public:Animal(int weight,int height): //基类未声明默认构造函数m_weight(weight),m_height(height){cout<<"Animal "<<"weight="<<weight<<" height="<<height<<endl;}private:int m_weight;int m_height;};class Dog: public Animal{public:Dog(int weight = 10,int height = 20,int type = 1):Animal(weight,height){//必须使用初始化列表增加对父类的初始化,否则父类Animal无合适构造函数cout<<"Dog"<<endl;}private:int m_type;};int main(){Dog d;system("pause");return 0;}
最简单的解决方法是将Animal的构造函数声明默认构造函数:Animal(int weight = 0,int height = 0);
4、初始化列表的成员初始化顺序:
C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
class CMyClass {CMyClass(int x, int y);int m_x;int m_y;};CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){}
你可能以为上面的代码将会首先做m_y=I,然后做m_x=m_y,最后它们有相同的值。但是编译器先初始化m_x,然后是m_y,,因为它们是按这样的顺序声明的。结果是m_x将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。
如果可以的话尽可能避免使用某些成员初始化其他成员。而最好用构造函数的参数作为成员的初始值,这样做的好处是可以比考虑成员的初始化顺序。例如以上可改为:
class CMyClass {CMyClass(int x, int y);int m_x;int m_y;};CMyClass::CMyClass(int x, int y) : m_x(x), m_y(y) {}