继承的语法
利用继承可以减少重复代码出现;
语法:class 子类 : 继承方式 父类 {};
// 基类(父类)
class Animal {
public:
void eat() { cout << "Animal is eating" << endl; }
void sleep() { cout << "Animal is sleeping" << endl; }
};
// 派生类(子类)
class Dog : public Animal {
// Dog 继承自 Animal
public:
void bark() { cout << "Dog is barking" << endl; }
};
int main() {
Dog myDog;
myDog.eat(); // 继承自 Animal
myDog.sleep(); // 继承自 Animal
myDog.bark(); // Dog 自己的方法
return 0;
}
继承方式
不同的继承方式决定了基类成员在派生类中的访问权限;
- 公共继承(class 子类 : public 父类)
- 保护继承(class 子类 : protected 父类)
- 私有继承(class 子类 : private 父类)
三者区别:
| 基类成员权限 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
公共继承 (public)
特点: 保持基类的访问权限不变
class Base {
public:
int publicData = 1;
protected:
int protectedData = 2;
private:
int privateData = 3;
};
class Derived : public Base {
public:
void test() {
publicData = 10; // ✓ 仍然是 public
protectedData = 20; // ✓ 仍然是 protected
// privateData = 30; // ✗ 私有成员不可访问
}
};
void testPublic() {
Derived d;
d.publicData = 100; // ✓ 外部可以访问 public 成员
// d.protectedData = 200; // ✗ 外部不能访问 protected 成员
}
保护继承 (protected)
特点: 基类的 public 和 protected 成员都变为 protected
class Base {
public:
int publicData = 1;
protected:
int protectedData = 2;
private:
int privateData = 3;
};
class Derived : protected Base {
public:
void test() {
publicData = 10; // ✓ 变为 protected,类内可访问
protectedData = 20; // ✓ 仍然是 protected
// privateData = 30; // ✗ 私有成员不可访问
}
};
void testProtected() {
Derived d;
// d.publicData = 100; // ✗ 外部不能访问,已变为 protected
}
class GrandChild : public Derived {
public:
void test() {
publicData = 100; // ✓ 子类可以访问 protected 成员
protectedData = 200; // ✓ 子类可以访问 protected 成员
}
};
私有继承 (private)
class Base {
public:
int publicData = 1;
protected:
int protectedData = 2;
private:
int privateData = 3;
};
class Derived : private Base {
public:
void test() {
publicData = 10; // ✓ 变为 private,类内可访问
protectedData = 20; // ✓ 变为 private,类内可访问
// privateData = 30; // ✗ 私有成员不可访问
}
};
void testPrivate() {
Derived d;
// d.publicData = 100; // ✗ 外部不能访问,已变为 private
}
class GrandChild : public Derived {
public:
void test() {
// publicData = 100; // ✗ 孙类不能访问,已变为 private
// protectedData = 200; // ✗ 孙类不能访问,已变为 private
}
};
继承之后子类的大小
继承只影响访问权限,不影响数据大小;private 成员虽然不能访问,但仍被继承并占用内存。
子类大小 = 基类大小 + 派生类新增成员大小 + 对齐填充;与继承方式无关。
举个栗子:
class Base {
public:
int m_A; // 4 字节
protected:
int m_B; // 4 字节
private:
int m_C; // 4 字节
};
class PublicSon : public Base {
public:
int m_D; // 4 字节
};
class ProtectedSon : protected Base {
public:
int m_D; // 4 字节
};
class PrivateSon : private Base {
public:
int m_D; // 4 字节
};
void testSize() {
cout << "Base 大小:" << sizeof(Base) << endl; // 12 字节
cout << "PublicSon 大小:" << sizeof(PublicSon) << endl; // 16 字节
cout << "ProtectedSon 大小:" << sizeof(ProtectedSon) << endl; // 16 字节
cout << "PrivateSon 大小:" << sizeof(PrivateSon) << endl; // 16 字节
}
上述代码的内存分布:
// 内存布局(所有继承方式都一样)
class AnySon : [any_inheritance] Base {
public:
int m_D;
};
// 内存结构:
// [Base 部分:m_A(4) + m_B(4) + m_C(4)] + [Son 部分:m_D(4)] = 16 字节
// ↑ ↑
// 基类成员 派生类成员
继承中的析构和构造
- 构造顺序:(基类) ➡ 成员对象 ➡ 子类 (派生类)
- 析构顺序:子类 (派生类) ➡ 成员对象 ➡ 父类 (基类)
没有成员对象构造、析构顺序:
class Base1 {
public:
Base1() { cout << "Base1 构造" << endl; }
~Base1() { cout << "Base1 析构" << endl; }
};
class Base2 {
public:
Base2() { cout << "Base2 构造" << endl; }
~Base2() { cout << "Base2 析构" << endl; }
};
class Derived : public Base1, public Base2 {
// 继承顺序决定构造顺序
public:
Derived() { cout << "Derived 构造" << endl; }
~Derived() { cout << "Derived 析构" << endl; }
};
void test() {
Derived d;
// 输出:
// Base1 构造 ← 按继承列表顺序
// Base2 构造
// Derived 构造
// Derived 析构 ← 析构顺序相反
// Base2 析构
// Base1 析构
}
有成员对象构造、析构顺序:
class Component1 {
public:
Component1() { cout << "Component1 构造" << endl; }
~Component1() { cout << "Component1 析构" << endl; }
};
class Component2 {
public:
Component2() { cout << "Component2 构造" << endl; }
~Component2() { cout << "Component2 析构" << endl; }
};
class Base {
public:
Base() { cout << "Base 构造" << endl; }
~Base() { cout << "Base 析构" << endl; }
};
class Derived : public Base {
public:
Derived() { cout << "Derived 构造" << endl; }
~Derived() { cout << "Derived 析构" << endl; }
private:
Component1 comp1; // 成员对象按声明顺序构造
Component2 comp2;
};
void test() {
Derived d;
// 输出:
// Base 构造 ← 1. 基类构造
// Component1 构造 ← 2. 成员对象按声明顺序构造
// Component2 构造
// Derived 构造 ← 3. 派生类构造函数体
// Derived 析构 ← 析构顺序完全相反
// Component2 析构
// Component1 析构
// Base 析构
}
继承中的同名成员的处理
- 派生类的同名成员会隐藏基类的所有同名成员(包括重载版本)
- 作用域解析: 使用 :: 明确指定调用哪个函数;
- 静态成员与普通成员相同:派生类的同名静态成员会隐藏基类的所有同名静态成员
class Base {
public:
Base(){ this->m_A = 100; }
void func(){ cout << "Base 中的 Func 调用" << endl; }
void func(int a){ cout << "Base 中的 Func(int a) 调用" << endl; }
int m_A;
};
class Son : public Base {
public:
Son(){ this->m_A = 200; }
void func(){ cout << "Son 中的 Func 调用" << endl; }
int m_A;
};
void test01() {
Son s;
cout << s.m_A << endl; // 调用 Son 类的 m_A
cout << "Base 中的 m_A " << s.Base::m_A << endl; // 调用 Base 类中的 m_A
// 同名的成员函数,子类会隐藏掉父类中的所有版本
s.func(); // 调用 Son 类的 func
s.Base::func(10); // 调用 Base 类的 func
}
int main(){
test01();
system("pause");
return EXIT_SUCCESS;
}
多继承
多继承允许一个类同时从多个基类继承,获得多个基类的特性。
语法:class 子类 : 继承方式 父类 1,继承方式 父类 2,... {};
class Base1 {
public:
int data1 = 10;
void func1() { cout << "Base1::func1()" << endl; }
};
class Base2 {
public:
int data2 = 20;
void func2() { cout << "Base2::func2()" << endl; }
};
// 多继承语法:用逗号分隔多个基类
class Derived : public Base1, public Base2 {
public:
int data3 = 30;
void showAll() {
cout << "data1: " << data1 << endl; // 来自 Base1
cout << "data2: " << data2 << endl; // 来自 Base2
cout << "data3: " << data3 << endl; // 自己的成员
}
};
void testBasic() {
Derived d;
d.func1(); // 调用 Base1 的方法
d.func2(); // 调用 Base2 的方法
d.showAll(); // 调用自己的方法
}
多继承的构造和析构
- 构造顺序: 按继承列表中的顺序依次调用基类构造函数,最后调用派生类构造函数
- 析构顺序: 与构造顺序完全相反
class Base1 {
public:
Base1() { cout << "Base1 构造函数调用" << endl; }
~Base1() { cout << "Base1 析构函数调用" << endl; }
};
class Base2 {
public:
Base2() { cout << "Base2 构造函数调用" << endl; }
~Base2() { cout << "Base2 析构函数调用" << endl; }
};
class Derived : public Base1, public Base2 {
// 继承顺序:Base1, Base2
public:
Derived() { cout << "Derived 构造函数调用" << endl; }
~Derived() { cout << "Derived 析构函数调用" << endl; }
};
void testOrder() {
cout << "=== 创建对象 ===" << endl;
Derived d;
cout << "=== 对象销毁 ===" << endl;
}
// 输出:
// === 创建对象 ===
// Base1 构造函数调用 ← 按继承列表顺序
// Base2 构造函数调用
// Derived 构造函数调用
// === 对象销毁 ===
// Derived 析构函数调用 ← 析构顺序相反
// Base2 析构函数调用
// Base1 析构函数调用
菱形继承的问题和解决方法
菱形继承的问题
- 什么是菱形继承:
- 菱形继承是指一个派生类通过多个路径继承同一个基类,形成菱形的继承结构。
- 通俗的说:在这种继承结构中,羊和驼都继承自动物,而
羊驼又从羊和驼继承。这就可能导致羊驼继承动物的两个实例,我们喊一声动物不知道喊的是羊还是驼,造成二义性。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 动物类
class Animal {
public:
int age = 0;
string name = "动物";
};
class Sheep : public Animal {
public:
void bleat() { cout << name << "咩咩叫" << endl; }
};
class Tuo : public Animal {
public:
void spit() { cout << name << "吐口水" << endl; }
};
class SheepTuo : public Sheep, public Tuo {
public:
void showInfo() {
// cout << age; // ✗ 错误!二义性
// 问题:SheepTuo 对象包含两份 Animal 数据
cout << "Sheep 的 age: " << Sheep::age << endl;
cout << "Tuo 的 age: " << Tuo::age << endl;
}
};
void testProblem() {
SheepTuo st;
st.Sheep::age = 5;
st.Tuo::age = 8;
cout << "对象大小:" << sizeof(st) << endl; // 包含两份 Animal 数据
// 必须明确指定路径
cout << "通过 Sheep 访问:" << st.Sheep::age << endl; // 5
cout << "通过 Tuo 访问:" << st.Tuo::age << endl; // 8
}
/***********************************************************************************/
// 普通多继承的内存布局
class SheepTuo : public Sheep, public Tuo {
// 内存布局:
// [Sheep 部分:Animal 数据] + [Tuo 部分:Animal 数据] + [SheepTuo 数据]
// ↑ ↑
// 重复的 Animal 数据 重复的 Animal 数据
};
使用虚继承解决菱形继承问题 (virtual)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 动物类
class Animal {
public:
int m_Age; // 年龄
};
// virtual 加上后 继承方式 数据虚继承
// Animal 类 变为 虚基类
// 羊类
class Sheep : virtual public Animal{};
// 驼
class Tuo : virtual public Animal {};
// 羊驼
class SheepTuo : public Sheep, public Tuo{};
void test01() {
SheepTuo st;
st.Sheep::m_Age = 10;
st.Tuo::m_Age = 20;
cout << "age = " << st.Sheep::m_Age << endl;
cout << "age = " << st.Tuo::m_Age << endl;
cout << "age = " << st.m_Age << endl; // m_Age 只需要一份即可,菱形继承导致数据有一份浪费
}
// 虚继承内部工作原理
void test02() {
SheepTuo st;
st.m_Age = 100;
// 通过 sheep 找到偏移量值
cout << "通过 sheep 找到的偏移量为:" << *(int *)((int*)*(int *)&st + 1) << endl;
// 通过 Tuo 找到偏移量值
cout << "通过 Tuo 找到的偏移量为:" << *(int *)((int *)*((int *)&st + 1) + 1) << endl;
// 通过偏移量 求出 m_Age 的值
cout << "age = " << ((Animal *)((char *)&st + (*(int *)((int*)*(int *)&st + 1))))->m_Age << endl;
}
int main(){
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
当我们访问无论是访问 sheep 中的 m_Age 还是 Tuo 中的 m_Age,实际上都是访问的 Animal 的 m_Age; 如果在 sheep 中定义一个同名的 m_Age,会将 Animal 中的 m_Age 隐藏,但是还是会继承;
使用虚函数之后的内存布局:
- 虚基类表指针 - 每个虚继承的类都有一个 vbptr
- 偏移量存储 - 虚基类表存储从派生类到虚基类的偏移量
- 间接访问 - 访问虚基类成员需要通过虚基类表进行间接寻址
- 性能开销 - 额外的指针和间接访问增加了开销
- 编译器优化 - 编译器自动生成虚基类表和访问代码
虚继承的实现原理
每个虚继承的类都有一个虚基类表,存储在只读数据区 每个继承虚继承的类都有一个虚基类表指针 vbptr,指向一个虚基类表
继承虚表中成员:
- offset_to_top:从当前虚基类表指针(vbptr)所在位置到完整派生类对象顶部的偏移量。
- offset_to_virtual_base:到虚基类的偏移
class Animal {
public:
int m_Age;
};
class Sheep : virtual public Animal {
// 内存布局:
// [vbptr] → 虚基类表指针
// [Animal 数据] → 虚基类数据
};
class Tuo : virtual public Animal {
// 内存布局:
// [vbptr] → 虚基类表指针
// [Animal 数据] → 虚基类数据
};
class SheepTuo : public Sheep, public Tuo {
// 内存布局:
// [Sheep 部分:vbptr_Sheep] → 指向 Sheep 的虚基类表
// [Tuo 部分:vbptr_Tuo] → 指向 Tuo 的虚基类表
// [Animal 数据:m_Age] → 共享的虚基类数据
};
访问过程:
- 虚基类表指针 - 每个虚继承的类都有一个 vbptr
- 偏移量存储 - 虚基类表存储从派生类到虚基类的偏移量
- 间接访问 - 访问虚基类成员需要通过虚基类表进行间接寻址
- 性能开销 - 额外的指针和间接访问增加了开销
- 编译器优化 - 编译器自动生成虚基类表和访问代码
