|
继承与派生
在第006问中介绍到C++的三大特性包括封装、继承、多态;
- 继承是面向对象程序设计中代码复用和软件抽象的重要表现形式
- 继承允许在类的基础上定义新的类,被继承的类叫基类, 新的类称之为子类/派生类(is-a)
- 子类/派生类拥有所有基类的特性,并且可以给自身增加特性
- 子类的定义与基类的定义没有区别,除了增加一个类继承列表,如例1
// 例1
class Base {
public:
Base(int a) : a_(a) {}
int GetA() {return a_;}
private:
int a_;
};
class Derived : public Base {
public:
Derived(int a, int b) : Base(a), b_(b) {}
int GetB() {return b_;}
private:
int b_;
};
继承中类的访问控制
- 类声明本身就是有访问控制的,包括public、protected、private,具体含义如表1
访问控制 | public | protected | private | 类本身 | Yes | Yes | Yes | 类对象 | Yes | No | No | 子类 | Yes | Yes | No | protected:主要可以控制类对象对于数据成员的访问,类的实例化对象不可访问,但子类是可以获得访问权;
其他两个好理解不解释
- 不同继承方式影响子类对于基类成员的访问权限,具体的继承方式和访问权限关系如表2
继承控制 | public | protect | private | 父类public成员 | public | protect | private | 父类protect成员 | protect | protect | private | 父类private成员 | No | No | No |
- 友元关系不能被继承
- 可以通过作用域限定符,显式指明访问基类成员;如例2
// 例2
#include <iostream>
class Base1 {
public:
Base1(int a) : a_(a) {}
int a_;
};
class Derived : public Base1 {
public:
Derived(int a, int d) : Base1(a), a_(d) {}
int a_;
};
int main() {
Derived tmp(1, 4);
std::cout << &#34;dericed a: &#34; << tmp.a_ << &#34;, base a: &#34; << tmp.Base1::a_ << std::endl;
return 0;
}
派生类的构造与析构
- 默认情况下子类需要定义自己的构造函数,不会继承基类的构造函数
- 可使用using关键字继承基类构造函数,但只能初始化基类成员,子类新增成员需要自己完成初始化,如例3
// 如例3
clase Base {
public:
Base(int a) : a_(a) {}
int a_;
};
class Derived : public Base {
public:
using Base::Base; // 继承基类构造函数
};
- 基类构造函数
- 子类初始化列表中的其余项
- 构造函数体
- 若基类构造函数有参或无默认参数,需要显示调用;若无参数或者有默认参数可不显示调用基类构造函数
- 基类的析构函数不能被继承,子类如需要析构函数,可自行定义析构函数;为什么基类的析构函数不能被继承, 因为这样就会被释放两次
- 子类析构函数中不需要显示调用基类的析构函数,编译器会隐式调用
- 子类对象析构时,先执行子类析构在执行基类析构
多重继承与虚继承
- 子类继承自多个基类,多个基类又有公共的基类,那么子类对象中就会存在多份公共基类成员,存在冗余
// 例4
#include <iostream>
class Base1 {
public:
Base1(int a) : a_(a) {}
int a_;
};
class Base2 : public Base1 {
public:
Base2(int a, int b) : Base1(a), b_(b) {}
int b_;
};
class Base3 : public Base1 {
public:
Base3(int a, int c) : Base1(a), c_(c) {}
int c_;
};
class Derived : public Base2, public Base3 {
public:
Derived(int a, int b, int c, int d) : Base2(a, b), Base3(a, c), d_(d) {}
int d_;
};
int main() {
Derived tmp(1, 2, 3, 4);
std::cout << &#34;a: &#34; << tmp.a_ << &#34;, b: &#34; << tmp.b_ << &#34;, c: &#34; << tmp.c_ << std::endl;
return 0;
}
output:
error: request for member &#39;a_&#39; is ambiguous
std::cout << &#34;a: &#34; << tmp.a_ << &#34;, b: &#34; << tmp.b_ << &#34;, c: &#34; << tmp.c_ << std::endl;
^~
继承示意图如图1

图 1
多重继承下内存分布如图2

图 2
如上如所示,子类对象就保存了两份公共基类的成员,存在冗余。
多继承中的二义性
如例4所示,由于子类存在两份公共基类成员,访问公共基类成员产生二义性,无法通过编译,需要引入虚基类机制消除二义性。
- 继承公共基类时,加virtual关键字,表示Base1为虚基类;
- 派生类Derived构造函数时,不仅要对直接基类进行初始化,还要负责虚基类的初始化;
// 例5
#include <iostream>
class Base1 {
public:
Base1(int a) : a_(a) {}
int a_;
};
class Base2 : virtual public Base1 {
public:
Base2(int a, int b) : Base1(a), b_(b) {}
int b_;
};
class Base3 : virtual public Base1 {
public:
Base3(int a, int c) : Base1(a), c_(c) {}
int c_;
};
class Derived : public Base2, public Base3 {
public:
Derived(int a, int b, int c, int d) : Base1(a), Base2(a, b), Base3(a, c), d_(d) {}
int d_;
};
int main() {
Derived tmp(1, 2, 3, 4);
std::cout << &#34;a: &#34; << tmp.a_ << &#34;, b: &#34; << tmp.b_ << &#34;, c: &#34; << tmp.c_ << std::endl;
return 0;
}
output:
a: 1, b: 2, c: 3
此时内存模型示意如图

图 3
总结
- 理解类的访问控制和继承控制
- 理解类继承过程中构造和析构的顺序
- 理解类多重继承(菱形继承)中的二义性问题,以及解决方案
参考
- C++ Inheritance
- C++之访问控制与继承 - 哆来咪的文章 - 知乎
|
|