C++笔记:关于基类、派生类的构造
创建派生类对象时,先自动创建一个基类对象,调用基类构造函数,然后调用派生类构造函数;派生类生命周期结束时,先调用派生类的析构函数,然后调用基类的析构函数;另外注意:类的构造函数不能被继承
来看一个最简单的例子:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(){cout<<"i am a\n";} 8 ~A(){cout<<"a is over\n";}; 9 private: 10 int n; 11 12 }; 13 14 class B:public A 15 { 16 public: 17 B(){cout<<"i am b\n";} 18 ~B(){cout<<"b is over\n";} 19 }; 20 21 class C:public B 22 { 23 public: 24 C(){cout<<"i am c\n";} 25 ~C(){cout<<"c is over\n";} 26 }; 27 28 int main() 29 { 30 A a; 31 B b; 32 C c; 33 return 0; 34 }
以上是最简单的不带参数的情况,下面讨论复杂点的情况
既然构造函数不能被继承,派生类所继承的基类成员必须自己初始化
但问题在于大部分基类都有private成员变量,派生类根本无法访问,从而也不可能用派生类的构造函数初始化
C++解决办法:在派生类的构造函数中调用基类的构造函数
首先看一个简单的例子
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(){cout<<"i am a\n";} 8 ~A(){cout<<"a is over\n";}; 9 private: 10 int n; 11 12 }; 13 14 class B:public A 15 { 16 public: 17 B(){cout<<"i am b\n";} 18 B(int n){num=n;cout<<"i am also b\n";} 19 ~B(){cout<<"b is over\n";} 20 private: 21 int num; 22 }; 23 24 int main() 25 { 26 A a; 27 B b(4); 28 return 0; 29 }
这时隐式调用了基类无参数的构造函数
如果这样写也没问题:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 ~A(){cout<<"a is over\n";}; 8 private: 9 int num; 10 11 }; 12 13 class B:public A 14 { 15 public: 16 B(){cout<<"i am b\n";} 17 B(int n){num=n;cout<<"i am also b\n";} 18 ~B(){cout<<"b is over\n";} 19 private: 20 int num; 21 }; 22 23 int main() 24 { 25 A a; 26 B b(4); 27 return 0; 28 }
运行结果:
因为如果定义一个对象时没有提供构造函数,编译器会使用默认构造函数;所以虽然屏幕上没有显示,但调用了默认的基类构造函数
但是这样写会出问题:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 //A(){} 8 A(int n){num=n;cout<<"i am also a\n";}//ERROR! 9 ~A(){cout<<"a is over\n";}; 10 private: 11 int num; 12 13 }; 14 15 class B:public A 16 { 17 public: 18 B(){cout<<"i am b\n";} 19 B(int n){num=n;cout<<"i am also b\n";} 20 ~B(){cout<<"b is over\n";} 21 private: 22 int num; 23 }; 24 25 int main() 26 { 27 A a; 28 B b(4); 29 return 0; 30 }
这里回到了默认构造函数那个老问题,我们先撇开派生类不谈
如果定义了一个类,没有构造函数(一个都没有),编译器会使用默认构造函数,你可以直接创建一个对象a,直接A a;
但是一旦定义了构造函数,创建对象的时候编译器就会根据定义时的参数列表寻找最匹配的构造函数,如果它没有找到最合适的(你有构造函数,但是没有适合我的)就会报错
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(){cout<<"i am a\n";} 8 A(int n){num=n;cout<<"i am a-num\n";} 9 A(char c){cc=c;cout<<"i am a-char\n";} 10 ~A(){cout<<"a is over\n";}; 11 private: 12 int num; 13 char cc; 14 15 }; 16 17 int main() 18 { 19 A a('l'),b(12); 20 return 0; 21 }
运行结果:
对比:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(){cout<<"i am a\n";} 8 A(int n){num=n;cout<<"i am a-num\n";} 9 //A(char c){cc=c;cout<<"i am a-char\n";} 10 void print(){cout<<num<<endl;} 11 ~A(){cout<<"a is over\n";}; 12 private: 13 int num; 14 char cc; 15 16 }; 17 18 int main() 19 { 20 A a('1'),b(12); 21 a.print();//ACS II of '1' 22 b.print(); 23 return 0; 24 }
结果:
下面程序报错:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(){cout<<"i am a\n";} 8 A(int n){num=n;cout<<"i am a-num\n";} 9 A(char c){cc=c;cout<<"i am a-char\n";} 10 void print(){cout<<num<<endl;} 11 ~A(){cout<<"a is over\n";}; 12 private: 13 int num; 14 char cc; 15 16 }; 17 18 int main() 19 { 20 A a("lalala");//no matching function! 21 return 0; 22 }
这就需要我们考虑以参数列表的形式显式调用基类构造函数:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(int n){num=n;cout<<"i am also a\n";} 8 ~A(){cout<<"a is over\n";} 9 void print(){cout<<"The number is:"<<num<<endl;} 10 private: 11 int num; 12 13 }; 14 15 class B:public A 16 { 17 public: 18 B(int m,int n):A(m){num=n;cout<<"i am also b\n";} 19 ~B(){cout<<"b is over\n";} 20 void print(){cout<<"The number is:"<<num<<endl;} 21 private: 22 int num; 23 }; 24 25 int main() 26 { 27 B b(2,3); 28 b.print(); 29 b.A::print(); 30 return 0; 31 }
运行结果:
下面简单介绍函数初始化列表:
《C++ Primer》中提到在以下三种情况下需要使用构造函数初始化列表
1)需要初始化的类的成员变量是对象的情况;
2)需要初始化的类的成员变量由const修饰的或初始化的类的引用成员变量;
3) 派生类初始化基类的成员
这里就主要涉及到第三点,派生类初始化基类的成员变量,需要在(也只能在)参数初始化列表中显式调用基类的构造函数;另外,如果不用初始化列表,进入构造函数体后,进行成员变量的赋值操作,赋值和初始化效率是不同的,类对自己的成员进行一次隐式的默认构造函数的调用和一次赋值操作运算符的调用,当类中成员较多时,这样效率会很低
注意:构造函数需要初始化的数据成员,不论是否显式出现在成员初始化列表中,都会在该处完成初始化,初始化顺序与其在类中的声明顺序一致,与列表的顺序无关
格式:
classname::classname(parameterlist):baseclassname(argumentlist),member1(argumentlist),member2(argumentlist)...{ }
实例:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A(int n1,int n2){num1=n1;num2=n2;} 8 void print(){cout<<"num1:"<<num1<<' '<<"num2:"<<num2<<endl;} 9 public: 10 int num1,num2; 11 12 }; 13 14 class B:public A 15 { 16 public: 17 B(int n1,int n2,int n3,int n4); 18 void print(){cout<<"num1:"<<num1<<' '<<"num2:"<<num2<<endl;} 19 private: 20 int num1,num2; 21 }; 22 B::B(int n1,int n2,int n3,int n4):A(n1,n2),num1(n3),num2(n4){} 23 24 int main() 25 { 26 B b(2,3,4,5); 27 b.A::print();28 b.print(); 29 return 0; 30 }
运行结果:
基本就这些,大佬们不吝赐教鸭~
参考链接: