如果一个类没有自己的构造函数,编译器会在需要的时候为其合成一个出来,俗称:合成默认构造函数。但是请注意是在需要的时候,并不是所有情况。
请看下面代码:
1 #include2 using namespace std; 3 class Foo { 4 public: 5 int val; 6 Foo *pnext; 7 }; 8 9 void foo_bar() 10 { 11 Foo bar; 12 if (bar.val || bar.pnext) 13 { 14 cout << "数据被编译器初始化了" << endl; 15 } 16 } 17 int main() 18 { 19 foo_bar(); 20 while (1); 21 return 0; 22 }
分析:对于上述情况,编译器并不会认为是需要的,因为初始化数据成员应该是程序员的职责,而并非是编译器的职责。所以必须自己写一个构造函数出来为其数据成员进行初始化。
假设编译器认为需要的时候到了,这时候为其合成了一个默认构造函数出来,也不会对该类的数据成员进行初始化,因为这并不是编译器的职责。比如下面代码:
1 #include2 using namespace std; 3 class Foo { 4 public: 5 Foo(){ 6 cout << "默认构造函数被调用" << endl; 7 }; 8 }; 9 10 class Bar { 11 public: 12 Foo foo; 13 char *str; 14 }; 15 void foo_bar() 16 { 17 Bar bar; 18 if (bar.str) 19 { 20 cout << "类Bar的数据成员被编译器初始化了" << endl; 21 } 22 } 23 int main() 24 { 25 foo_bar(); 26 while (1); 27 return 0; 28 }
输出:
和第一种情况比,这时候的编译器没有报错,但是编译器还是没有为数据成员进行初始化。
上面的代码引出第一种编译器会自动合成默认构造函数的情况:如果一个类中包含另一个类,对于后者,有自己定义的默认构造函数。而前者没有,那么此时编译器会为其合成一个出来,但是合成的目的仅仅只是为了调用后者的默认构造函数,对于自己类的数据成员,必须由程序员自己初始化。
所以合理的应该是这样的:
1 #include2 using namespace std; 3 class Foo { 4 public: 5 Foo() { 6 cout << "默认构造函数被调用" << endl; 7 }; 8 9 }; 10 11 class Bar { 12 public: 13 Foo foo; 14 char *str; 15 Bar() 16 { 17 //编译器自动安插代码段foo.Foo::Foo(); 18 str = new char;//程序员为自己的数据成员赋值。 19 } 20 21 }; 22 void foo_bar() 23 { 24 Bar bar; 25 if (bar.str) 26 { 27 cout << "类Bar的数据成员被编译器初始化了" << endl; 28 } 29 } 30 int main() 31 { 32 foo_bar(); 33 while (1); 34 return 0; 35 }
输出:
分析:
对于类Bar,现在有了自己的构造函数,虽然没有显示的定义类Foo的,编译器还是为其合成了一个出来,因为编译器认为这是需要的时候,这种需要仅仅是需要调用类Foo的默认构造函数,但是对于自己的数据成员,它依然置之不顾。
如果一个类包含有多个类成员,这些类成员都有自己的构造函数(假设默认的不带参数和带参数的构造函数都存在),但是包含了多个类成员的类并没有定义自己的构造函数,编译器此时就会为其合成一个出来,为的就是调用其类成员,且调用顺序按照类成员定义的顺序来依次调用,比如以下情况:
1 #include2 using namespace std; 3 class Dopey { 4 public: 5 Dopey() { 6 cout << "默认构造函数Dopey()被调用" << endl; 7 } 8 }; 9 class Sneezy { 10 public: 11 Sneezy(int) { 12 cout << "构造函数Sneezy(int)被调用" << endl; 13 } 14 Sneezy(){ 15 cout << "默认构造函数Sneezy()被调用" << endl; 16 } 17 }; 18 class Bashful { 19 public: 20 Bashful() { 21 cout << "默认构造函数Bashful()被调用" << endl; 22 } 23 }; 24 class Snow_White 25 { 26 public: 27 Dopey dopey; 28 Sneezy sneezy; 29 Bashful bashful; 30 private: 31 int mumble; 32 }; 33 int main() 34 { 35 36 Snow_White s; 37 while (1); 38 return 0; 39 }
输出:
如果该类为自己的数据成员定义了自己的构造函数来初始化它们,如果不明确调用父类的构造函数,编译器依旧会在该构造函数中安插代码,为了调用父类的默认构造函数(编译器调用的都是默认的版本,也就是父类中定义的都是不带参的构造函数或者带了默认参数的构造函数,其他情况则需要程序员自己显示调用),如下情况:
1 #include2 using namespace std; 3 class Dopey { 4 public: 5 Dopey() { 6 cout << "默认构造函数Dopey()被调用" << endl; 7 } 8 }; 9 class Sneezy { 10 public: 11 Sneezy(int) { 12 cout << "构造函数Sneezy(int)被调用" << endl; 13 } 14 Sneezy(){ 15 cout << "默认构造函数Sneezy()被调用" << endl; 16 } 17 }; 18 class Bashful { 19 public: 20 Bashful() { 21 cout << "默认构造函数Bashful()被调用" << endl; 22 } 23 }; 24 class Snow_White 25 { 26 public: 27 Dopey dopey; 28 Sneezy sneezy; 29 Bashful bashful; 30 Snow_White() :sneezy(1024) 31 { 32 /* 33 编译器安插: 34 dopey.Dopey::Dopey(); 35 sneey.Sneey::Sneey(1024); 36 bashful.Bashful::bashful(); 37 */ 38 mumble = 2048; 39 } 40 private: 41 int mumble; 42 }; 43 int main() 44 { 45 Snow_White s; 46 while (1); 47 return 0; 48 }
输出:
上述是类中含有类成员的情况,继承的情况和它类似,如果一个类继承了另外一个类,但是前者没有自己的构造函数,而后者有自己的默认构造函数,那么对于前者,编译器会在需要的时候合成一个出来,目的仅仅是为了调用后者的默认构造函数,前者的数据成员仍然需要程序源自己初始化,对于多继承同样如此,下面举出多继承的例子:
1 #include2 using namespace std; 3 class Dopey { 4 public: 5 Dopey() { 6 cout << "默认构造函数Dopey()被调用" << endl; 7 } 8 }; 9 class Sneezy { 10 public: 11 Sneezy(int) { 12 cout << "构造函数Sneezy(int)被调用" << endl; 13 } 14 Sneezy(){ 15 cout << "默认构造函数Sneezy()被调用" << endl; 16 } 17 }; 18 class Bashful { 19 public: 20 Bashful(int x = 2) { 21 cout << "默认构造函数Bashful()被调用" << endl; 22 } 23 }; 24 class Snow_White:public Dopey,public Sneezy,public Bashful 25 { 26 public: 27 Snow_White() :Sneezy(1024) 28 { 29 /* 30 编译器安插: 31 Dopey dopey; 32 dopey.Dopey::Dopey(); 33 Sneezy sneezy; 34 sneezy.Sneey::Sneey(1024); 35 Bashful bashful; 36 bashful.Bashful::bashful(); 37 */ 38 mumble = 2048; 39 } 40 private: 41 int mumble; 42 }; 43 int main() 44 { 45 Snow_White s; 46 while (1); 47 return 0; 48 }
输出: