继承的好处:减少重复代码
语法: class 子类 : 继承方式 父类
class A : public B;
- A 类称为子类或派生类
- B 类称为父类或基类
例
// 基类:父类
class Parent {
public:
void show() {
cout << "I am a parent class." << endl;
}
};
// 派生类:子类
class Child : public Parent {
public:
void display() {
show(); // 调用父类的show函数
cout << "I am a child class." << endl;
}
};
派生类中的成员,包含两大部分: 一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
- 公有继承
- 保护继承
- 私有继承
问题:从父类继承过来的成员,哪些属于子类对象中?
答:
- 父类中所有非静态成员属性都会被子类继承下去
- 父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
可利用开发人员命令提示工具查看对象模型
跳转盘符 F:
跳转文件路径 cd 具体路径下
查看命名
cl /d1 reportSingleClassLayout类名 文件名
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
答:先构造父类,再构造子类,析构的顺序与构造的顺序相反
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
子类对象加作用域形式如下(Base是父类,s是子类)
成员属性:
s.Base::m_A;
成员函数:
s.Base::func();
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致(Base是父类,s是子类)
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
同名静态成员属性
void test01()
{
//1、通过对象访问
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
//第一个::代表通过类名方式访问 第二个::代表访问父类作用域下
//2、通过类名访问
cout << "Son 下 m_A = " << Son::m_A << endl;
}
同名静态成员函数
//同名静态成员函数
void test02()
{
//1、通过对象访问
cout << "通过对象访问" << endl;
Son s;
s.func();
s.Base::func();
//2、通过类名访问
cout << "通过类名访问" << endl;
Son::func();
Son::Base::func();
}
//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
//如果想访问父类中被隐藏同名成员,需要加作用域
C++允许一个类继承多个类
语法:class 子类:继承方式_父类1,继承方式_父类2…
多继承可能会引发父类中有同名成员出现,需要加作用域区分
例:
class Son : public Base1, public Base2
当父类中出现同名成员,需要加作用域进行区分
当出现以下继承结构时:
A
/ \
B C
\ /
D
特点:
class A {
public:
int m_age;
};
class B : public A {};
class C : public A {};
class D : public B, public C {};
void test() {
D d;
// d.m_age = 10; // 错误:ambiguous access
d.B::m_age = 10; // √ 明确指定路径
d.C::m_age = 20; // √ 不同路径不同存储位置
}
使用虚继承: 此时A作为最大的继承,叫做虚基类
class B : virtual public A {}; // 虚继承
class C : virtual public A {}; // 虚继承
class D : public B, public C {};
D(int age) : A(age), B(), C() {} // 直接初始化虚基类
// 普通继承结构
class A { int a; }; // size=4
class B : public A {}; // size=4
class C : public A {}; // size=4
class D : public B, public C {}; // size=8
内存布局:
+---+---+
|B::A|C::A| // 两份A的成员
+---+---+
// 虚继承结构
class A { int a; }; // size=4
class B : virtual public A {}; // size=4+8=12(64位)
class C : virtual public A {}; // size=4+8=12(64位)
class D : public B, public C {}; // size=12+12+4=28 → 内存对齐后32
内存布局(64位系统):
+----------------+----------------+-----+
| B的虚基表指针 | C的虚基表指针 | A::a |
+----------------+----------------+-----+
D d;
d.a = 10; // 访问路径:
1. 通过B的vbptr找到虚基表
2. 查表得到A的偏移量(+16字节)
3. 访问实际存储位置
// D类的虚基表内容
B的虚基表:
+-------------------+
| 0 (自身偏移量) | → 通常为0
| 16 (A的偏移量) |
+-------------------+
C的虚基表:
+-------------------+
| 0 (自身偏移量) |
| 20 (A的偏移量) | → 实际可能因对齐不同
+-------------------+
使用VS开发者命令提示符:
cl /d1 reportSingleClassLayoutD test.cpp
输出示例:
class D size(32):
+---
0 | +--- (base class B)
0 | | {vbptr}
| +---
8 | +--- (base class C)
8 | | {vbptr}
| +---
16 | |
20 | +--- (virtual base A)
20 | | a
| +---
特征 | 说明 |
---|---|
内存开销 | 每个虚继承类增加一个指针大小(64位+8字节) |
访问代价 | 多一次指针跳转 |
初始化特殊性 | 最终子类直接初始化虚基类 |
多级虚继承 | 虚基表会记录多级偏移量 |
继承的好处:
// 基类(父类/超类)
class Parent {
public:
void show() {
cout << "I am a parent class." << endl;
}
};
// 派生类(子类)
class Child : public Parent { // 公有继承
public:
void display() {
show(); // 调用继承自父类的成员函数
cout << "I am a child class." << endl;
}
};
继承方式 | 基类public成员 | 基类protected成员 | 基类private成员 |
---|---|---|---|
public | 仍为public | 仍为protected | 不可见 |
protected | 变为protected | 仍为protected | 不可见 |
private | 变为private | 变为private | 不可见 |
重要特性:
内存模型特点:
cd /d 文件目录
cl /d1 reportSingleClassLayout类名 文件名.cpp
执行顺序:
多继承构造顺序:按继承列表从左到右执行基类构造
class Son : public Base1, public Base2 {
// 构造顺序:Base1() → Base2() → Son()
};
访问规则:
// 成员属性
s.Base::m_A;
// 成员函数
s.Base::func();
// 函数重载特例
class Base {
public:
void func(int a) { ... }
};
class Son : public Base {
public:
void func() { ... } // 隐藏基类所有func版本
};
Son s;
s.func(); // √ 正确
s.func(10); // × 错误,基类重载被隐藏
s.Base::func(10); // √ 正确
访问方式:
// 通过对象访问
cout << s.Base::m_A;
// 通过类名访问
cout << Son::Base::m_A;
// 静态成员函数
Son::Base::func();
重要特性:静态成员同样遵循名称隐藏规则,但内存中只存在一份基类静态成员副本
语法格式:
class 派生类 : 继承方式 基类1, 继承方式 基类2...
{
// 类主体
};
注意事项:
A
/ \
B C
\ /
D
解决方案:虚继承
class B : virtual public A {...};
class C : virtual public A {...};
class D : public B, public C {...};
Child(int a, int b) : Parent(a), m_b(b) {...}
Parent* p = new Child(); // 向上转型(安全)
// Child* c = new Parent(); // 向下转型(危险,需要dynamic_cast)
class Parent {
public:
virtual void show() { ... } // 虚函数
};
class Son : public Base {
public:
using Base::func; // 暴露基类func重载
void func() { ... }
};