在C++中,所谓继承是在一个已存在的类的基础上新建一个新的类。
已存在的类称为 基类 或 父类。
新建的类称为 派生类 或 子类。
一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此基类和派生类是相对而言的。
一个派生类只从一个基类派生,这称为单继承。
一个派生类有两个或多个基类的称为多重继承。
派生类是基类的具体化,而基类是派生类的抽象。
声明派生类的一般形式为:
class 派生类名: [继承方式] 基类名 {派生类新加的成员};
继承方式包括:public(公用的),private(私有的)和protected(受保护的),此项是可选的,如果不写,默认为private。
派生类的成员包括从基类继承过来的成员和自己增加的成员两部分。
构造一个派生类包括以下3部分工作:
- 从基类接收成员 (派生类把基类的全部成员(不包括构造和析构)接收过来)
- 调整从基类接收的成员
- 在声明派生类时增加的成员
在声明派生类时,一般还应自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
不同的继承方式决定了基类成员在派生类中的访问属性:
- 公用继承 (基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有)
- 私有继承 (基类的公用成员和保护成员在派生类中成了私有成员,其私有成员仍为基类私有)
- 受保护继承 (基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有)
在派生类中,成员有4种不同的访问属性:
- 公用的,派生类内和派生类外都可以访问
- 受保护的,派生类内可以访问,派生类外不能访问,其下一层的派生类可以访问
- 私有的,派生类内可以访问,派生类外不能访问
- 不可访问,派生类内和派生类外都不能访问
如果基类声明了私有成员,那么任何派生类都不能直接访问它们,若希望在派生类中访问它们,应该把它们声明为保护成员。
保护基类的所有成员在派生类中都被保护起来,类外不能访问,其公用成员和保护成员可以被其派生类的成员函数访问。
对于私有继承和保护继承,在直接派生类中,这两种继承方式实际上是相同的:
在类外不能访问任何成员,在派生类中可以通过成员函数访问基类的公用成员和保护成员。
但是如果继续派生,在新的派生类中,两种继承方式的作用就不同了。
保护成员和私有成员的不同之处,在于把保护成员的访问范围扩展到派生类中。
基类的私有成员被派生类继承后变为不可访问的成员,派生类中的一切成员都无法访问它们。
C++允许一个派生类同时继承多个基类,这种行为称为多重继承。
派生类的构造函数名(总参数列表):基类1构造函数(参数表列),基类2构造函数(参数表列){派生类中新增成员}
调用基类构造函数的顺序是按照声明派生类时基类出现的顺序来决定的。
多重继承引起的二义性问题:
其中最常见的问题就是就是继承的成员同名而引起的二义性问题。 //可以用基类名来限定访问的是哪个基类的成员
如果派生类中有成员和基类成员同名:
派生类中新增加的同名成员覆盖了基类中的同名成员。
不同的成员函数,只有在函数名和参数个数相同、类型相匹配的情况下才发生同名覆盖,如果只有函数名相同而参数不同,不会发生同名覆盖,而属于函数重载。
对于这种情况,访问A类中从N继承下来的成员,应通过类N的直接派生类名来指出要访问的是类N的哪一个派生类中的基类成员。// c1.A::a=3;
父类具有静态成员变量仍然遵循派生类中的访问控制原则.
菱形继承:
#include <iostream>
using namespace std;
class A {
public:
A(int a) {
this->a = a;
}
private:
int a;
};
class B :virtual public A {
public:
B(int a,int b):A(a) {
this->b = b;
}
private:
int b;
};
class C :virtual public A {
public:
C(int a,int c):A(c) {
this->c = c;
}
private:
int c;
};
class D :public B, public C {
public:
D(int a, int b, int c, int d) :A(a),B(a, b), C(a, c){
this->d = d;
}
private:
int d;
};
int main() {
D d1(1, 2, 3, 4);
system("pause");
}
D的构造函数与以往的使用的方法不同,最后的派生类不仅要负责对直接基类的初始化,还要负责对虚基类初始化,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略其他派生类对虚基类构造函数的调用。
B类对象的内存布局:
D类对象的内存布局:
在非虚继承的情况下,假设有A,B,C三个类,A是B的基类,B是C的基类,那么对象C的内存布局如下:
定义一个B类指针,传递B类对象或者传递C类对象的地址,都可以正常寻址。
但是,在虚继承的情况下,内存布局改变了,如果定义一个B类指针,传递给指针一个B类对象和传递一个D类对象的地址,寻址是不一样的,所以引入了VBPTR来进行寻址。