C++之继承
class 子类: [继承方式] 父类{}; eg:
class A { int name;
protected:
int pro; public: A():name(0), pro(2){cout<<'A';} void f(){cout << name;} }; class B: public A { int name; public: B():name(1){cout<<'B';} void f(){A::f(); cout << name;} };
关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。
1. 继承中的 构造函数、析构函数
构造函数和析构函数不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的这些方法)。
因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。
1)子类对象构造原则如下:
- 1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
- 2. 如果子类定义了构造方法,不论是无参数还是带参数,如果子类的构造函数没有显示调用父类的构造函数,则会先调用
a)父类的默认无参构造函数 或 b)父类自己的无参构造函数,c)若父类只定义了自己的有参构造函数,则会出错(子类必须显示调用此带参构造方法)。
- 3. 如果子类显式调用父类带参数的构造方法,需要用初始化父类成员对象的方式。
2)子类对象构造时,调用顺序如下
- 基类构造函数,按照被继承时声明的顺序;
- 成员对象初始化,按照类中声明的顺序;
- 派生类构造函数体。
------------------------------------------------------------------------------------------------------------------------------
析构时,析构函数调用顺序和构造函数的调用顺序相反。
2. 继承后的访问权限
* 父类protected成员:不能在父类外访问,但是可以在子类内访问
用 using 关键字可以改变基类protected、public成员在派生类中的访问权限。
class B: public A { int n; public: using A::pro; // protected -> public B():name(1){A::pro = 5; cout<<'B';} void f(){A::f(); cout << name;} }; B b; b.name; // 这里使用的是父类的pro(值为5(2->5))
3. 同名变量、同名函数的隐藏
子类中有和父类中的同名变量时,编译器将默认使用子类的变量,父类的同名变量将被隐藏。
B b; b.name; // 默认使用子类的变量
子类和父类中有同名函数(非虚函数)时,不论参数列表是怎样的,都将调用子类中定义的函数,父类函数将被隐藏(不算重载,这是在不同的域)。
如果子类定义的同名函数的参数列表和实际调用的不一致,将报错(不会调用父类的同名函数)
b.f(); // 调用子类的函数
* 如果子类中定义为 void f(int x);,编译器将报错,子类中名为 f 的函数,没有无参的,无法调用
构成隐藏时,若要调用父类的变量或函数,可以用父类类型进行限定调用。
b.A::name; // 通过限定符调用父类的同名变量 b.A::f();
4. 对象模型
子类的内存模型可以看成是父类成员变量和新增成员变量的总和,所有成员函数仍在另外一个区域——代码区,由所有对象共享。
在子类对象中,包含所有父类的成员变量,无论是否有变量隐藏现象存在,这样访问效率高。
如继承关系为 A -> B -> C,则有: ——细节参考博客
5. 父类变量、指针、引用指向子类对象时的不同
B b; // case 1 A d1 = b; d1.func(); // 调用顺序为:A构造、B构造、A的func、A析构(析构d1)、B析构、A析构 // case 2 A* d2 = b; d2.func(); // 调用顺序为:A构造、B构造、B的func、B析构、A析构 // case 3 A& d3 = b; d3.func(); // 同case2