继承是面向对象中实现类层次代码复用的机制,在保留父类特性的基础上扩展出新功能,由此产生的新类称为派生类。
一、为什么需要继承?
1.1 问题引入
假设我们要设计学生和老师两个类,你会发现它们有很多共同的属性:姓名、年龄、地址、电话等。如果没有继承,代码会大量重复:
cpp
#include <iostream>
#include <string>
using namespace std;
// 没有继承的写法 - 代码冗余严重
class Student {
public:
void identity() {
cout << "身份认证:" << _name << endl;
}
void study() {
cout << _name << "正在学习" << endl;
}
protected:
string _name = "张三";
int _age = 18;
string _address;
string _tel;
int _stuId; // 学号(学生特有)
};
class Teacher {
public:
void identity() {
cout << "身份认证:" << _name << endl;
}
void teach() {
cout << _name << "正在授课" << endl;
}
protected:
string _name = "李老师";
int _age = 35;
string _address;
string _tel;
string _title; // 职称(老师特有)
};
int main() {
Student s;
Teacher t;
s.identity();
t.identity();
return 0;
}

1.2 继承解决方案
使用继承,将公共部分提取到基类(Person)中
cpp
#include <iostream>
#include <string>
using namespace std;
// 基类(父类)- 存放公共部分
class Person {
public:
Person(const string& name = "无名", int age = 0)
: _name(name), _age(age) {
//cout << "Person构造函数: " << _name << endl;
}
void identity() {
cout << "身份认证:" << _name << endl;
}
void showInfo() {
cout << "姓名:" << _name << ",年龄:" << _age << endl;
}
protected:
string _name;
int _age;
string _address;
string _tel;
};
// 派生类(子类)- 继承Person
class Student : public Person {
public:
Student(const string& name, int age, int stuId)
: Person(name, age), _stuId(stuId)
{
cout << "Student构造函数: " << _name << endl;
}
void study() {
cout << _name << "(学号:" << _stuId << ")正在学习" << endl;
}
protected:
int _stuId; // 学号(学生特有)
};
class Teacher : public Person {
public:
Teacher(const string& name, int age, const string& title)
: Person(name, age), // 直接一步到位
_title(title)
{
cout<< "Teacher构造函数: " << _name << endl;
}
void teach() {
cout << _name << "(职称:" << _title << ")正在授课" << endl;
}
protected:
string _title; // 职称(老师特有)
};
int main() {
cout << "=== 创建学生 ===" << endl;
Student s("小明", 18, 2024001);
s.identity(); // 继承自Person
s.study(); // Student自己的函数
s.showInfo(); // 继承自Person
cout << "\n=== 创建老师 ===" << endl;
Teacher t("王老师", 35, "教授");
t.identity(); // 继承自Person
t.teach(); // Teacher自己的函数
t.showInfo(); // 继承自Person
return 0;
}

注意:
必须调用父类构造函数,让父类自己初始化自己的成员
cpp
// ✅ 正确!唯一写法!
Student(const string& name, int age, int stuId)
: Person(name, age), // 调用父类构造,让父类初始化 _name _age
_stuId(stuId) // 子类初始化自己的成员
{}
我自己写的错误示例:
cpp
// ❌ 错误!绝对不能这么写!
Student(const string& name, int age, int stuId)
:_name(name), _age(age), _stuId(stuId)
子类 不能在初始化列表里直接给父类的成员变量赋值!
_name和_age是父类 Person 的成员- 父类成员,必须由父类的构造函数来初始化
- 子类无权在初始化列表里直接初始化父类成员!
二、继承的三种方式
访问权限和继承权限是不同的概念:
- 访问权限(成员本身的权限)
作用:控制 "外面能不能直接用"
public:谁都能访问protected:自己 + 子类能访问private:只有自己能访问
这是成员自己的属性,跟继承没关系。
- 继承权限(继承方式)
作用:**控制 "继承过来后,成员变成什么权限"**三种继承方式:
public继承protected继承private继承
它只改变成员在子类里的最终权限,不改变基类本身。
在类的继承中,有以下几点特性:
1 基类的构造函数与析构函数不能被继承
2 派生类对基类成员的继承没有选择权,不能选则继承或不继承某些成员
3 派生类中可以添加新成员,用于实现新功能,让派生类的功能在基类上有所扩展。
2.1 访问限定符与继承方式的关系
cpp
#include <iostream>
#include <string>
using namespace std;
class Base {
public:
int pub = 1; // 公有成员
protected:
int pro = 2; // 保护成员
private:
int pri = 3; // 私有成员
};
// 1. public继承
class PubDerived : public Base {
public:
void test() {
cout << pub << endl; // ✅ 可以访问:pub → public
cout << pro << endl; // ✅ 可以访问:pro → protected
// cout << pri << endl; // ❌ 错误:基类private成员在派生类不可见
}
};
// 2. protected继承
class ProDerived : protected Base {
public:
void test() {
cout << pub << endl; // ✅ 可以访问:pub → protected
cout << pro << endl; // ✅ 可以访问:pro → protected
// cout << pri << endl; // ❌ 错误:不可见
}
};
// 3. private继承
class PriDerived : private Base {
public:
void test() {
cout << pub << endl; // ✅ 可以访问:pub → private
cout << pro << endl; // ✅ 可以访问:pro → private
// cout << pri << endl; // ❌ 错误:不可见
}
};
int main() {
PubDerived pubObj;
pubObj.pub = 10; // ✅ public成员在类外可访问
// pubObj.pro = 20; // ❌ protected成员类外不可访问
ProDerived proObj;
// proObj.pub = 10; // ❌ 变成protected后,类外不可访问
PriDerived priObj;
// priObj.pub = 10; // ❌ 变成private后,类外不可访问
cout << "=== 结论 ===" << endl;
cout << "实际开发中,99%的情况使用public继承" << endl;
return 0;
}

总结:
- private 成员
子类永远无法直接访问,不管什么继承方式。
- public 继承
父类的访问权限原样保留:
- public → 依然 public
- protected → 依然 protected
- private → 依然不能直接访问
你这句话说得特别到位:"公有继承就是基类的成员在子类也有一份, 子类的访问权限和基类一样。**"**只是父类 private 永远碰不到,构造析构不继承。
- protected 继承
- 父类 public、protected → 在子类里都变成 protected正确,父类 private子类永远无法直接访问
4. private 继承
- 父类所有可继承成员 → 在子类都变成 private 正确,父类 private子类永远无法直接访问
虽然父类 private子类永远无法直接访问,但子类的成员函数可以调用父类的public,protected权限的 成员函数 通过父类间接访问
例子:
cpp
class A {
private:
int c = 100; // 父类私有
public:
void show_c() {
// 父类自己的函数,可以访问 c
cout << c << endl;
}
};
class B : public A {
public:
void func() {
// c = 200; ❌ 不能直接访问父类 private
show_c(); // ✅ 可以调用父类 public 函数
// 间接访问 c
}
};
int main() {
B a;
a.show_c();
a.func();
}

三、基类和派生类之间的转换(切片)
cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person(const string& name = "") : _name(name) {}
string _name;
int _age = 0;
};
class Student : public Person {
public:
Student(const string& name, int stuId)
: Person(name), _stuId(stuId) {}
int _stuId;
};
int main() {
Student s("小明", 2024001);
// 1. 子类对象赋值给父类对象(切片)
Person p1 = s; // 只拷贝Person部分
cout << "p1._name: " << p1._name << endl; // 小明
// 2. 子类对象赋值给父类指针(指向子类中的父类部分)
Person* p2 = &s;
cout << "p2->_name: " << p2->_name << endl; // 小明
// p2->_stuId; // ❌ 错误:父类指针不能访问子类成员
// 3. 子类对象赋值给父类引用
Person& p3 = s;
cout << "p3._name: " << p3._name << endl; // 小明
// 4. ❌ 父类对象不能赋值给子类对象
// Student s2 = p1; // 编译错误
// 5. 父类指针指向子类时,可以强制转换回子类指针(需要确保确实指向子类)
Person* p4 = &s;
Student* s2 = (Student*)p4; // C风格强制转换
cout << "s2->_stuId: " << s2->_stuId << endl; // 2024001
cout << "\n=== 切片示意图 ===" << endl;
cout << "子类对象: [Person部分][Student部分]" << endl;
cout << "父类指针: 指向 [Person部分] ← 切片" << endl;
return 0;
}

派生类对象可以直接赋值 / 初始化给基类对象 / 引用 / 指针,派生类中独有的成员会被切掉,只保留基类部分,这就是对象切片。
派生类切片 → 切掉独有部分,只留基类成员,安全向上转换。
四、继承中的作用域(隐藏规则)
cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person() : _num(111) {}
void print() {
cout << "Person::print()" << endl;
}
void func(int x) {
cout << "Person::func(int) = " << x << endl;
}
protected:
string _name = "Person";
int _num; // 身份证号
};
class Student : public Person {
public:
Student() : _num(999) {}
void print() {
cout << "Student::print()" << endl;
}
void func() { // 函数名相同,参数不同也构成隐藏
cout << "Student::func()" << endl;
}
void show() {
// 同名成员:子类会隐藏父类的
cout << "子类_num: " << _num << endl; // 999
cout << "父类_num: " << Person::_num << endl; // 111(显式指定)
// 同名函数:子类隐藏父类
print(); // 调用子类的
Person::print(); // 调用父类的
func(); // 调用子类的(无参)
// func(10); // ❌ 错误:被隐藏了,找不到
Person::func(10); // ✅ 显式指定可以调用
}
protected:
int _num; // 学号(与父类同名)
};
int main() {
Student s;
s.show();
cout << "\n=== 隐藏规则总结 ===" << endl;
cout << "1. 子类和父类有同名成员(变量/函数),子类会隐藏父类" << endl;
cout << "2. 函数同名即构成隐藏(不看参数)" << endl;
cout << "3. 可以用 父类::成员 显式访问被隐藏的成员" << endl;
cout << "4. 建议:尽量不要在子类定义同名成员" << endl;
return 0;
}

子类和基类的作用域不一样,所以子类有和基类一样的成员变量或者成员函数时,构不成重载(重载要作用域一样),根据就近原则,用子类的,除非显示调用父类的
作用域不同 → 不能重载 → 同名就隐藏 → 就近用子类 → 父类加::
五、派生类的默认成员函数(完整示例)
这是面试中的高频考点!
cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
// 构造函数
Person(const string& name = "无名")
: _name(name) {
cout << "Person构造函数: " << _name << endl;
}
// 拷贝构造函数
Person(const Person& p)
: _name(p._name) {
cout << "Person拷贝构造函数: " << _name << endl;
}
// 赋值运算符重载
Person& operator=(const Person& p) {
cout << "Person赋值运算符: " << p._name << " -> " << _name << endl;
if (this != &p) {
_name = p._name;
}
return *this;
}
// 析构函数
~Person() {
cout << "Person析构函数: " << _name << endl;
}
protected:
string _name;
};
class Student : public Person {
public:
// 构造函数:必须调用基类构造函数初始化基类部分
Student(const string& name, int stuId)
: Person(name) // 显式调用基类构造函数
, _stuId(stuId) {
cout << "Student构造函数: " << name << ", 学号:" << _stuId << endl;
}
// 拷贝构造函数:必须调用基类拷贝构造
Student(const Student& s)
: Person(s) // 切片:Student对象赋值给Person引用
, _stuId(s._stuId) {
cout << "Student拷贝构造函数: " << _stuId << endl;
}
// 赋值运算符:必须显式调用基类的赋值运算符
Student& operator=(const Student& s) {
cout << "Student赋值运算符" << endl;
if (this != &s) {
Person::operator=(s); // 显式调用基类赋值运算符
_stuId = s._stuId;
}
return *this;
}
// 析构函数:会自动调用基类析构(无需显式调用)
~Student() {
cout << "Student析构函数: 学号" << _stuId << endl;
// 会自动调用 ~Person()
}
protected:
int _stuId;
};
int main() {
cout << "=== 1. 创建对象(构造) ===" << endl;
Student s1("小明", 1001);
cout << "\n=== 2. 拷贝构造 ===" << endl;
Student s2(s1);
cout << "\n=== 3. 赋值运算 ===" << endl;
Student s3("小红", 1002);
s1 = s3;
cout << "\n=== 4. 销毁对象(析构) ===" << endl;
// 对象会按创建顺序逆序析构
return 0;
}

1 子类对象 = 父类部分 + 子类自己部分
子类构造函数初始化列表必须先调用父类构造函数(用匿名对象的方法:父类名(值)),不然父类的成员变量无法初始化(上面说的的子类不能直接初始化父类成员),除非你不用父类的成员变量。
cpp
// 构造函数:必须调用基类构造函数初始化基类部分
Student(const string& name, int stuId)
: Person(name) // 显式调用基类构造函数
, _stuId(stuId) {
cout << "Student构造函数: " << name << ", 学号:" << _stuId << endl;
}
name要初始化成学生自己的名字
2 拷贝构造时,s2 里面也包含父类的 name 变量,父类部分必须由父类的拷贝构造来初始化,所以子类必须调用父类的拷贝构造!
cpp
// 拷贝构造函数
Person(const Person& p)
: _name(p._name) {
cout << "Person拷贝构造函数: " << _name << endl;
}
cpp
// 拷贝构造函数:必须调用基类拷贝构造
Student(const Student& s)
: Person(s) // 切片:Student对象赋值给Person引用
, _stuId(s._stuId) {
cout << "Student拷贝构造函数: " << _stuId << endl;
}
cout << "\n=== 2. 拷贝构造 ===" << endl;
Student s2(s1);
这里的 Person(s) 还顺便复习了 切片: s 是 Student的引用,参数匹配父类的拷贝构造, 这里的 p 看到的只是 s 里面的 Person 部分 ,切掉子类部分,只保留父类部分
如果不写 Person(s) 会发生什么?编译器会**自动调用父类的【默认构造】**Person(),而不是拷贝构造!
cpp
Student(const Student& s)
: Person() // 编译器自动插入
, _stuId(s._stuId)
{}
结果:
- s2 的名字 = 无名
- s2 的学号 = 1001这就错了!
3
- 构造:子类必须在初始化列表调用父类构造
- 拷贝构造:子类必须调用父类拷贝构造
- 赋值重载:子类必须显式调用父类赋值重载
Person::operator=(s) - 析构:不用调用,编译器自动调用父类析构
3赋值重载:编译器不会自动帮你调用父类赋值!必须手动写!
cpp
// 赋值运算符重载
Person& operator=(const Person& p) {
cout << "Person赋值运算符: " << p._name << " -> " << _name << endl;
if (this != &p) {
_name = p._name;
}
return *this;
}
// 赋值运算符:必须显式调用基类的赋值运算符
Student& operator=(const Student& s) {
cout << "Student赋值运算符" << endl;
if (this != &s) {
Person::operator=(s); // 显式调用基类赋值运算符
_stuId = s._stuId;
}
return *this;
}
子类赋值 = 父类赋值 + 子类赋值,缺一不可!
① 切片发生了!
s是Student- 传给父类的
operator=(const Person& p) - 编译器自动切片 → 只把父类部分传给父类赋值函数
② 调用父类的赋值重载
把 _name 正确赋值过去。
如果不写:
- 子类学号赋值成功
- 父类名字根本没赋值!还是原来的值!
注意:
Person::operator=(s); // 显式调用基类赋值运算符
不加Person::子类和父类有同名函数 → 父类被直接隐藏!,发生无限递归,栈溢出
总结:
-
构造顺序:基类 → 派生类
-
析构顺序:派生类 → 基类
-
拷贝构造必须调用基类拷贝构造
-
赋值运算符必须显式调用基类赋值运算符
六、菱形继承与虚继承(重点难点)
6.1 菱形继承的问题
cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string _name = "Person";
int _age = 0;
};
// Student和Teacher都继承Person
class Student : public Person {
public:
int _stuId = 0;
};
class Teacher : public Person {
public:
int _teacherId = 0;
};
// Assistant同时继承Student和Teacher
class Assistant : public Student, public Teacher {
public:
string _major = "计算机";
};
int main() {
Assistant a;
// ❌ 二义性:_name有两个副本(一份来自Student,一份来自Teacher)
// a._name = "张三"; // 编译错误:对_name的访问不明确
// ✅ 需要显式指定从哪个路径访问
a.Student::_name = "张三(学生身份)";
a.Teacher::_name = "张三(老师身份)";
// 数据冗余:同一个Person对象有两份
cout << "Student::_name地址: " << &a.Student::_name << endl;
cout << "Teacher::_name地址: " << &a.Teacher::_name << endl;
cout << "两个地址不同,说明有两份数据" << endl;
// 内存大小:包含两份Person的成员
cout << "\n对象大小: " << sizeof(Assistant) << " 字节" << endl;
// string(32字节) * 2份 = 64字节 + 其他成员
return 0;
}

对象内存布局:

分析:
cpp
Person
/ \
/ \
Student Teacher
\ /
\ /
Assistant
编译器看见:class Assistant : public Student, public Teacher
它就严格按从左到右执行:
1. 先构造 左边第一个:Student
- 构造 Student 必须先构造它的父类 Person
- 输出:Person 构造
- 然后构造 Student 自己
- 输出:Student 构造
2. 再构造 右边第二个:Teacher
- 构造 Teacher 必须先构造它的父类 Person
- 输出:Person 构造 (又来一次!)
- 然后构造 Teacher 自己
- 输出:Teacher 构造
3. 最后构造 自己:Assistant
- 输出:Assistant 构造
所以:
_name存在 两个不同的地址- 编译器不知道你要哪一个 → 二义性错误
- 数据冗余、浪费空间、容易出错
6.2 虚继承解决菱形继承
cpp
#include <iostream>
#include <string>
using namespace std;
// 使用虚继承(virtual)
class Person {
public:
Person() : _name("Person") {
cout << "Person构造函数" << endl;
}
string _name;
int _age = 0;
};
// 虚继承Person
class Student : virtual public Person {
public:
Student() : _stuId(0) {
cout << "Student构造函数" << endl;
}
int _stuId;
};
// 虚继承Person
class Teacher : virtual public Person {
public:
Teacher() : _teacherId(0) {
cout << "Teacher构造函数" << endl;
}
int _teacherId;
};
class Assistant : public Student, public Teacher {
public:
Assistant() : _major("计算机") {
cout << "Assistant构造函数" << endl;
}
string _major;
};
int main() {
cout << "=== 菱形虚继承 ===" << endl;
Assistant a;
// ✅ 可以直接访问,没有二义性
a._name = "张三";
cout << "姓名: " << a._name << endl;
// ✅ 只有一份Person数据
cout << "&a._name: " << &a._name << endl;
cout << "&a.Student::_name: " << &a.Student::_name << endl;
cout << "&a.Teacher::_name: " << &a.Teacher::_name << endl;
cout << "三个地址相同,说明只有一份数据" << endl;
cout << "\n=== 构造顺序(虚继承) ===" << endl;
// 输出会显示构造顺序:最远的基类先构造
return 0;
}


虚继承写在 Student 和 Teacher 上,它的作用只有一个:告诉编译器:
Student 和 Teacher 不要各自复制一份 Person,而是共享同一份 Person,所以
cpp
cout << "&a._name: " << &a._name << endl;
cout << "&a.Student::_name: " << &a.Student::_name << endl;
cout << "&a.Teacher::_name: " << &a.Teacher::_name << endl;
他们地址完全一样。a.Student::_name意思是:我要访问的是从 Student 继承下来的 _name
作用:虚继承 = 只有一份公共的 Person 基类
让最终子类 Assistant 里,Person 只保留一份! 不再有两份 _name,不再有二义性!
6.3 虚继承的构造顺序(重要)
cpp
#include <iostream>
using namespace std;
class Grand {
public:
Grand() { cout << "Grand构造" << endl; }
};
class Base1 : virtual public Grand {
public:
Base1() { cout << "Base1构造" << endl; }
};
class Base2 : virtual public Grand {
public:
Base2() { cout << "Base2构造" << endl; }
};
class Derived : public Base1, public Base2 {
public:
Derived() { cout << "Derived构造" << endl; }
};
int main() {
cout << "虚继承构造顺序:最远的虚基类最先构造" << endl;
Derived d;
// 输出顺序:
// Grand构造 ← 最远的基类
// Base1构造
// Base2构造
// Derived构造
return 0;
}
因为虚继承保证 最高层基类只构造一份
所以必须让 **最终孙子类(Derived)直接负责构造爷爷(Grand)**不能让 Base1、Base2 各自去构造,否则又会重复!
- 最远的基类由最终派生类直接构造
- 只构造一次,不会重复构造
6.4 虚继承底层原理
cpp
class A { int _a; };
// 虚继承
class B : virtual public A { int _b; };
class C : virtual public A { int _c; };
class D : public B, public C { int _d; };
不是虚继承监视:

虚继承监视:

-
虚继承:只保留一份间接基类
- B、C 虚继承 A
- 每个虚继承的类,编译器生成一张 vbtable(虚基类表)( 全局只有一张!所有 B对象共用这一张表**)**
- 表中存放:偏移量 ------ 用来找到唯一的那份 A
- 编译器会在 B、C 对象 里加一个 vbptr(虚基类指针)( 存的是虚基类表的地址**)**
**访问原理:**通过 vbptr → 查虚基类表 → 得到偏移 → 找到公共的基类成员
普通继承(非虚):A 的成员 直接放在 B 内部
cpp
B 对象:
[A _a] + [B _b]
虚继承:A 的成员 _a 依然存在! 只是不放在 B 主体内部 ,而是放在对象的最末尾,变成共享区域。
cpp
B 对象:
[ vbptr ] ----------> B 的虚基类表 vbtable
[ _b ] [ 偏移量:到 A 的距离 ]
[ _a ] <----------- 用偏移量找到这里
虚继承不是去掉基类成员,而是把基类成员挪到对象末尾,变成共享的一份!
创建一个 C 对象:
cpp
C 对象:
┌─────────────┐
│ vbptr │
├─────────────┤
│ _c │
├─────────────┤
│ _a │ ✅ 同样有 A!
└─────────────┘
创建 D 对象(B + C 虚继承 A)
cpp
D 对象:
┌─────────────┐
│ B部分(vbptr+_b)
├─────────────┤
│ C部分(vbptr+_c)
├─────────────┤
│ _a ← 【唯一一份!共享!】
├─────────────┤
│ _d
└─────────────┘
cpp
D 对象(物理内存地址从低到高):
+-------------------------+ ← 低地址(对象开头)
| B 部分(vbptr + _b) |
+-------------------------+
| C 部分(vbptr + _c) |
+-------------------------+
| D 自己的成员 _d |
+-------------------------+
| 虚基类 A(_a = 100) | ← 高地址(对象末尾)
+-------------------------+
只有 D 这种多继承派生类,才会把 A 合并成唯一一份
-
最终对象布局
- D 对象中:B 部分 (vbptr) + C 部分 (vbptr) + 唯一一份 A + D 自身成员
- A 不再重复,无冗余、无二义性
**访问原理:**通过 vbptr → 查虚基类表 → 得到偏移 → 找到公共的基类成员
七、继承与友元、静态成员
cpp
#include <iostream>
using namespace std;
class Person {
public:
friend void show(const Person& p);
static int cnt;
Person() { cnt++; }
protected:
string name = "Person";
};
int Person::cnt = 0;
class Student : public Person {};
void show(const Person& p) {
cout << "访问:" << p.name << endl;
}
int main() {
// 静态:整个继承体系共用一个
Person p1, p2;
Student s1, s2;
cout << "总数:" << Person::cnt << endl; // 4
cout << Student::cnt << endl;;
cout<<s2.cnt<<endl;
cout << "地址一样:" << &Person::cnt << " " << &Student::cnt << endl;
// 友元只作用于 Person,不继承给 Student
Person p;
show(p); // 可以
// Student s;
// show(s); // 不行!友元不继承
}

1 静态成员属于类,不属于对象,存贮在静态变量/全局区,独一份
所有对象共享:父类、子类全都共用这一个
cpp
static int cnt;
- 不是每个 Person 自带一个 cnt
- 是 整个 Person 类、整个继承体系共用这一个
- 所以创建 p1、p2、s1、s2 时每构造一次,同一份 cnt++
- 结果自然就是 4
2 友元关系不能被继承,父类友元函数不是子类的友元函数不能访问子类的私有,保护变量
八、继承 vs 组合(设计原则)
cpp
#include <iostream>
#include <string>
#include<vector>
using namespace std;
// ========== 组合示例(has-a关系) ==========
// 轮胎类
class Tire {
public:
Tire(const string& brand = "米其林", int size = 17)
: _brand(brand), _size(size) {
cout << "Tire构造: " << _brand << " " << _size << "寸" << endl;
}
void show() {
cout << "轮胎品牌: " << _brand << ", 尺寸: " << _size << "寸" << endl;
}
private:
string _brand;
int _size;
};
// 发动机类
class Engine {
public:
Engine(int power = 200) : _power(power) {
cout << "Engine构造: " << _power << "马力" << endl;
}
void show()
{
cout << "Engine构造: " << _power << "马力" << endl;
}
private:
int _power;
};
class Car {
public:
// 给 tire 和 engine 也写上初始化!
Car(const string& brand) : _brand(brand), _tire(), _engine() {
cout << "Car构造: " << _brand << endl;
}
void show() {
cout << "汽车品牌: " << _brand << endl;
_tire.show();
_engine.show();
}
private:
string _brand;
Tire _tire;
Engine _engine;
};
// ========== 继承示例(is-a关系) ==========
// 交通工具类
class Vehicle {
public:
virtual void run() {
cout << "交通工具在运行" << endl;
}
};
// 汽车也是一种交通工具 - is-a关系
class Benz : public Vehicle {
public:
void run() override {
cout << "奔驰汽车在飞驰" << endl;
}
void luxury() {
cout << "提供豪华配置" << endl;
}
};
class BMW : public Vehicle {
public:
void run() override {
cout << "宝马汽车在狂飙" << endl;
}
void sport() {
cout << "提供运动模式" << endl;
}
};
// ========== 实际开发建议 ==========
// 栈的实现:既可以用继承,也可以用组合
template<typename T>
class StackByInheritance : public vector<T> { // 继承方式
public:
void push(const T& val) {
vector<T>::push_back(val);
}
void pop() {
vector<T>::pop_back();
}
T& top() {
return vector<T>::back();
}
};
template<typename T>
class StackByComposition { // 组合方式(推荐)
public:
void push(const T& val) {
_v.push_back(val);
}
void pop() {
_v.pop_back();
}
T& top() {
return _v.back();
}
private:
vector<T> _v; // 组合一个vector
};
int main() {
cout << "=== 组合示例(has-a) ===" << endl;
Car myCar("特斯拉");
cout << "\n=== 继承示例(is-a) ===" << endl;
Vehicle* v1 = new Benz();
Vehicle* v2 = new BMW();
v1->run(); // 多态
v2->run();
cout << "\n=== 设计原则 ===" << endl;
cout << "1. 优先使用组合(耦合度低,更灵活)" << endl;
cout << "2. 只有当确实是is-a关系时才使用继承" << endl;
cout << "3. 需要多态时必须使用继承" << endl;
cout << "4. 类之间的关系既适合继承也适合组合时,优先用组合" << endl;
delete v1;
delete v2;
return 0;
}
注意:为什么继承 vector 时,必须写 vector<T>::push_back(val)
模板本身不是真正的类 / 函数,只是一张图纸。 因为编译时父类还没生成,所以调用父类函数必须写 父类<T>时,编译器才会根据图纸生成真正的代码(也就是按需实例化)

- 两种关系(最核心)
① 组合:has-a (有一个)
- 汽车 有一个 轮胎
- 汽车 有一个 发动机
- 写法:类里面包含另一个类对象
构造顺序(组合):先构造成员对象 → 再构造自己
cpp
1. 两种关系(最核心)
① 组合:has-a (有一个)
汽车 有一个 轮胎
汽车 有一个 发动机
写法:类里面包含另一个类对象
② 继承:is-a (是一个)
- 奔驰 是一种 交通工具
- 宝马 是一种 交通工具
- 写法:
class Benz : public Vehicle
- 设计原则(最重要!)
优先使用组合,少用继承!
- 组合:耦合低、安全、灵活
- 继承:耦合高、会继承所有接口,容易被乱用
例子:栈(Stack)
- 用组合:只开放 push/pop/top
- 用继承:能调用 vector 所有函数(不安全)
终极口诀
- is-a 用继承
- has-a 用组合
- 能组合绝不继承