目录
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段 (提升代码复用方法还有宏, 内联,模板,const等),它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类(子类), 被继承的称为基类(父类)。
子类继承了父类除构造函数和析构函数之外所有的成员变量和成员方法, 不论是公有的还是私有的、保护的. 相当于除了构造和析构函数之外所有成员全都拷贝一份到子类中. (但能不能访问和有继承到是另一回事.)
继承的形式:
class base {};
class a:public base {
//公有继承
};
class a:protected base{
//保护继承
};
class a:private base{
//私有继承
};
class a:base{
//不写继承方法,默认为私有继承
};
class a:public base,private base1{
//多继承
};
上表中的不可见也是不可访问. 父类的公有、保护成员无论是何种继承方式在子类中都是可见的, 就算降级为了私有成员, 子类中也当然可以访问自己的私有成员. 而父类中的私有成员子类不论是何种方式继承, 子类都不能直接访问. 只能是父类中公有或保护成员调用了父类的私有成员时, 子类访问了这个父类的公有或保护才相当于"间接访问"了 (虽然不可访问不可见, 但是还是继承下来的, 就像隐性基因).
(如果我们说公有继承等级大于保护继承大于私有继承)上表说明了继承到子类中的成员在子类中会按继承方式降级. 即子类继承父类的成员后子类中继承到的父类的成员不会高于继承方式的等级.
而子类的对象则是按照之前学的, 子类的对象在类外只能访问子类的公有成员. (比如说父类的公有成员被保护继承方式的子类继承后, 降级为了子类的保护成员, 这时子类对象则不能访问这个保护成员)
子类的内存布局:
子类的大小也按照内存对齐规则.
class base{
private:
char a;
};
class a{
private:
int b;
};
void main(){
cout<<sizeof(a)<<endl;
//将打印8.
}
这也印证了: 虽然父类的私有成员继承到子类中是不可访问不可见, 但是还是继承下来的, 就像隐性基因
子类对象实例化的构造析构问题:
class base {
public:
base() {
cout << "base()" << endl;
}
~base() {
cout << "~base()" << endl;
}
private:
int _base;
};
class a :public base {
public:
a(int a) {
cout << "a()" << endl;
}
~a() {
cout << "~a()" << endl;
}
private:
int _a;
};
class b :public base {
public:
b(int b) {
cout << "b()" << endl;
}
~b() {
cout << "~b()" << endl;
}
private:
int _b;
};
class c :public b,public a {
public:
c():b(1),c(2) {
cout << "c()" << endl;
}
~c() {
cout << "~c()" << endl;
}
};
int main() {
c c1;
return 0;
}
上图结果说明了: 构造子类对象时会先构造父类对象, 析构顺序严格按照与构造顺序相反的规则,
(按照继承时的继承方式声明顺序构造, 和初始化列表的初始化顺序无关.)
派生类对象给基类对象/基类指针/基类引用赋值问题:
赋值兼容规则: 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
void main(){
base b; //基类
D d; //派生类
//子类对象可以给父类对象赋值
b=d;
//子类对象地址可以给父类指针赋值
base* pb=&d;
//子类对象可以初始化父类的引用
base& rb=d;
}
因为子类给父类赋值只是成员变量赋值, 成员方法是不属于子类的存储空间的,所以子类成员方法不会赋值给父类. 所以赋值后父类也不能调用子类的方法:
class base {
public:
void fun() {
cout << "base::fun()" << endl;
}
};
class a :public base {
public:
void afun() {
cout << "a::fun1()" << endl;
}
};
int main() {
base base1;
base1.fun();
a a1;
a1.fun();
a1.afun();
base1 = a1;
base1.afun(); //不能访问子类成员
base* pbase = &a1;
pbase->afun(); //不能访问子类成员
base& rbase = a1;
rbase.afun(); //不能访问子类成员
return 0;
}
基类对象不能赋值给派生类对象 (强转也不行)
基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。(ps:这个我们有会讲,这里了解一下)
同名隐藏:
子类中如果有和父类同名的函数, 只要函数名相同, 不管返回值和参数相不相同一律被同名隐藏(即子类对象只能调用同名函数中子类中的, 父类中的同名函数被隐藏无法调用)
class base {
public:
void fun() {
cout << "base::fun()" << endl;
}
void fun(int i) {
cout << "base::fun(int)" << endl;
}
};
class a:public base {
public:
};
int main() {
a a1;
a1.fun();
a1.fun(1);
}
****
class base {
public:
void fun() {
cout << "base::fun()" << endl;
}
void fun(int i) {
cout << "base::fun(int)" << endl;
}
};
class a:public base {
public:
void fun() {
cout<<"a::fun()"<<endl;
}
};
int main() {
a a1;
a1.fun();
a1.fun(1); //这个是错的,
//子类中无fun(int)的函数无法调用, 同时父类中所有只要名为fun的函数都被隐藏了.
a1.base::fun();
a1.base::fun(1);
}
友元关系不能被继承:
class b;
class a{
friend class b;
private:
int _a;
};
class b{
public:
void fun(a &a){
cout<<a._a<<endl; //能访问_a
}
};
class c:public b{
public:
void fun1(a &a){
cout<<a._a<<endl; //不能访问_a
}
};
基类的静态成员:
class base {
private:
static int _base=0;
};
class a:public base {
private:
int _a=0;
};
class b :public base {
private:
int _b=0;
};
int main() {
a a1;
b b1;
}
可以发现, 不同子类继承的同一个基类的是不同份的. 但如果_base是静态成员.则_base成了全局变量, 所有的子类继承的就是同一份.
菱形继承 和 虚继承:
class base {
public:
base() {
cout << "base()" << endl;
}
~base() {
cout << "~base()" << endl;
}
public:
int _base=0;
};
class a :public base {
public:
a() {
cout << "a()" << endl;
}
~a() {
cout << "~a()" << endl;
}
public:
int _a=1;
};
class b :public base {
public:
b() {
cout << "b()" << endl;
}
~b() {
cout << "~b()" << endl;
}
public:
int _b=2;
};
class c :public b,public a {
public:
c() {
cout << "c()" << endl;
}
~c() {
cout << "~c()" << endl;
}
public:
int _c=2;
};
int main() {
c c1;
cout<<c1._c<<endl;
cout<<c1._a<<endl;
cout<<c1._b<<endl;
cout<<c1._base<<endl;//编译出错, 访问不明确
cout<<c1.a::_base<<endl;
cout<<c1.b::_base<<endl;
return 0;
}
就像前面说的, a,b各继承了一份_base, 那么c1._base就会出现访问不明确的编译错误.
这时我们使用虚继承就能解决这个问题:
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
为了防止二义性需要在出现一个类(爷类)被多继承,而这多个类(多个父类)又被另一个类(子类)同时继承时, 这多个类(父类)使用虚继承方式继承.
class base {
public:
base() {
cout << "base()" << endl;
}
~base() {
cout << "~base()" << endl;
}
public:
int _base=0;
};
class a :virtual public base {
public:
a() {
cout << "a()" << endl;
}
~a() {
cout << "~a()" << endl;
}
public:
int _a=1;
};
class b :virtual public base {
public:
b() {
cout << "b()" << endl;
}
~b() {
cout << "~b()" << endl;
}
public:
int _b=2;
};
class c :public b,public a {
public:
c() {
cout << "c()" << endl;
}
~c() {
cout << "~c()" << endl;
}
public:
int _c=2;
};
int main() {
c c1;
cout<<c1._c<<endl;
cout<<c1._a<<endl;
cout<<c1._b<<endl;
cout<<c1._base<<endl;
cout<<c1.a::_base<<endl;
cout<<c1.b::_base<<endl;
return 0;
}
下图是菱形虚拟继承的内存对象成员模型:这里可c对象中将base放到的了对象组成的最下面,这个base同时属于a和b,那么a和b如何去找到公共的base呢?这里是通过了a和b的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的base。
所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。
继承和组合:
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
class car{};
class benchi:public car{};
class baoma :public car{};
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
class luntai{};
class fadongji{};
class chejia{};
class car{
private:
luntai _luntai;
fadongji _fadongji;
chejia _chejia;
};