## 一.继承中的同名覆盖
1. **重载** 局限于函数,而且需要通过参数和返回值来区分。
2. **同名覆盖** 相同的名字出现在不同作用域中。对于函数,则是参数和返回值完全一样。在继承的情况下,派生类声明的同名成员覆盖基类声明的同名成员。
>注意,这只是名字的覆盖,在对象的内存空间中,派生类声明的同名成员与基类的同名成员仍会占据不同的空间。
3. **同名覆盖下访问基类的成员** 可以通过在成员名前加上基类作用域来访问隐藏的成员。
>[warning] 下面案例可运行在任意编译器上。
```c++
#include <iostream>
using namespace std;
struct Base
{
int x_;
Base(int x) :x_(x) {}
};
struct Derived : public Base {
int x_;
Derived(int x1, int x2) :Base(x1), x_(x2) {}
};
int main()
{
Derived d(2,5);
cout<<d.x_<<endl; //派生类同名成员覆盖基类同名成员
cout<<d.Base::x_<<endl; //访问基类同名成员
return 0;
}
```
>[test]
>5
>2
## 二.虚基类
### 1.虚基类的功能
>继承带来的一系列冲突和冗余问题:
>+ 如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。
>+ 在引用这些同名的成员时,必须在成员前增加基类作用域。
虚基类使得在继承间接共同基类时只保留一份成员,解决数据冗余的问题。
### 2.虚基类的声明
在派生类声明时,为继承方式中添加 `virtual` 关键字。
### 3.最派生类
含有虚基类的多重继承体系中,负责对虚基类进行初始化的类。(一般情况下,用 `A` 类创建对象, `A` 就是最派生类。)
当创建一个类类型对象时,编译器会从这个对象开始,向上查找其继承关系。如果有虚基类,则非最派生类在构造时,不会对基类进行构造,直到最派生类构造。
如果虚基类没有无参构造函数,则 **所有直接和间接派生类的构造函数需要在成员初始化器列表中加上虚基类的构造函数** 。但只有最派生类会执行虚基类的构造函数。
### 4.虚基类的浅层原理
微软的编译器(vc6和vs)引入了虚基类表指针。每个对象如果有一个或多个虚基类,就会由编译器安插一个指针,指向虚基类表。这个虚基类类表是一个数组,它有 `虚基类数目+1` 个元素。第一个元素为虚基类表指针在该派生类的偏移量,后面的元素则是虚基类相对于虚基类表指针位置的偏移量。
其他的编译器,则可能采用在虚函数表中放入虚基类偏移量的方法。
#### 例1
现在有下面5个类:
```c++
struct A
{
int x_;
A(int x) :x_(x) {}
};
struct B1 :virtual public A {
int y1_;
B1(int x, int y) :A(x), y1_(y) {}
};
struct B2 :virtual public A {
int y2_;
B2() :A(0), y2_(8) {}
};
struct C : public B1,public B2 {
int z_;
C(int x, int y,int z) :A(x),B1(x,y),B2(), z_(z) {}
};
struct D : public C {
int w_;
D(int x, int y,int z,int w_) :A(x),C(x,y,z), w_(z) {}
};
```
它们的继承关系树像这样:
![](https://img.kancloud.cn/ae/4b/ae4ba7836465537eca224419339b8c02_771x611.png)
当我们构造一个C类的对象,则C类就是最派生类。此时C类对象需要承担虚基类A的构造。
当我们构造一个D类的对象,则D类就是最派生类。此时D类对象需要承担虚基类A的构造。
当我们构造一个B1类的对象,则B1类就是最派生类。此时B1类对象需要承担虚基类A的构造。
当我们构造一个B2类的对象,则B2类就是最派生类。此时B2类对象需要承担虚基类A的构造。
#### 例2
```C++
struct A1
{
int a;
A1(int x=0) : a(x) {}
};
struct A2
{
int b;
A2(int x = 0) : b(x) {}
};
struct A3
{
int c;
A3(int x = 0) : c(x) {}
};
struct A4
{
int d;
A4(int x = 0) : d(x) {}
};
struct B :public A2, virtual public A4, public A3, virtual public A1
{
int y2_;
B(int x = 0) : y2_(x){}
};
int main()
{
return 0;
}
```
使用 `Visual Studio` 打开上述代码,并在命令行选项中添加 `/d1 reportSingleClassLayoutB `,这样,在编译时,通过输出窗口可以看到 类B的内存布局:
```c
class B size(24):
+---
0 | +--- (base class A2)
0 | | b
| +---
4 | +--- (base class A3)
4 | | c
| +---
8 | {vbptr}
12 | y2_
+---
+--- (virtual base A4)
16 | d
+---
+--- (virtual base A1)
20 | a
+---
B::$vbtable@:
0 | -8
1 | 8 (Bd(B+8)A4)
2 | 12 (Bd(B+8)A1)
vbi: class offset o.vbptr o.vbte fVtorDisp
A4 16 8 4 0
A1 20 8 8 0
```
上面的信息展示出了B的内存布局、虚基类表 `B::$vbtable@` 以及虚基类表的信息 `vbi` 。
在相对位置为 `8` 的位置有一个 `vbptr` ,它是虚基类表指针。再通过看虚基类表中,我们可以看到,第0个元素是虚基类相对于该派生类的偏移量,第1-2个元素则是相应虚基类的偏移量。
- 阅读说明
- 1.1 概述
- C++基础
- 1.2 变量与常量
- 1.2.1 变量
- 1.2.2 字面值常量
- 字符型常量
- 数值型常量
- 1.2.3 cv限定符
- 1.3 作用域
- 1.3.1 标识符
- 1.3.2 *命名空间
- 1.3.3 作用域
- 1.3.4 可见性
- 1.4 数据类型
- 1.4.1 概述
- 1.4.2 处理类型
- 类型别名
- * auto说明符
- * decltype说明符
- 1.4.3 数组
- 1.4.4 指针
- 1.4.5 引用
- 1.5 表达式
- 1.5.1 概述
- 1.5.2 值的类别
- 1.5.3 *初始化
- 1.5.4 运算符
- 算术运算符
- 逻辑和关系运算符
- 赋值运算符
- 递增递减运算符
- 成员访问运算符
- 位运算符
- 其他运算符
- 1.5.5 *常量表达式
- 1.5.6 类型转换
- 第2章 面向过程编程
- 2.1 流程语句
- 2.1.1 条件语句
- 2.1.2 循环语句
- 2.1.3 跳转语句
- 2.1.4 *异常处理
- 2.2 函数
- 2.2.1 概述
- 2.2.2 函数参数
- 2.2.3 内置函数
- 2.2.4 函数重载
- 2.2.5 * 匿名函数
- 2.3 存储和生命期
- 2.3.1 生命周期与存储区域
- 2.3.2 动态内存
- 2.4 *预处理命令
- 第3章 面向对象编程
- 3.1 概述
- 3.2 类和对象
- 3.3 成员
- 3.3.1 访问限制
- 3.3.2 常成员
- 3.3.3 静态成员
- 3.3.4 成员指针
- 3.3.5 this指针
- 3.4 特殊的成员函数
- 3.4.1 概述
- 3.4.2 构造函数
- 3.4.3 析构函数
- 3.4.4 拷贝语义
- 3.4.5 * 移动语义
- 3.5 友元
- 3.6 运算符重载与类型转换
- 3.6.1 概述
- 3.6.2 重载方法
- 3.6.3 类型转换
- 3.7 继承与多态性
- 3.7.1 概述
- 3.7.2 派生类
- 3.7.3 子类型
- 3.7.4 虚基类
- 3.7.5 虚函数
- 3.7.6 抽象类
- 3.8 模板与泛型
- 3.8.1 概述
- 3.8.2 模板类型
- 3.8.3 *模板参数
- 3.8.4 *模板编译
- 3.8.5 *模板推断
- 3.8.6 *实例化与特例化
- 第4章 C++标准库
- 4.1 概述
- 4.2 输入输出流
- 4.2.1 概述
- 4.2.2 *流的状态
- 4.2.3 *常用流
- 4.2.4 *格式化I/O
- 4.2.5 *低级I/O
- 4.2.6 *随机访问
- 4.3 *C输入输出
- 4.3.1 *字符输入输出
- 4.3.2 *格式化输入输出
- 4.4 * 容器
- 4.4.1 * 概述
- 4.4.2 * 基本操作
- 4.4.3 * 顺序容器
- 4.4.4 * 迭代器
- 4.4.5 * 容器适配器
- 4.5 * 泛型算法
- 4.6 * 内存管理
- 4.6.1 * 自动指针
- 4.7 * 其他设施