
往期《C++初阶》回顾:《C++初阶》目录导航
往期《C++进阶》回顾:
/------------ 继承多态 ------------/
【普通类/模板类的继承 + 父类&子类的转换 + 继承的作用域 + 子类的默认成员函数】
嗨✧(≖ ◡ ≖✿) ,小伙伴们大家好呀!今天是平平无奇的一天,哦不对,今天其实是阳光明媚的一天呢。 (●°u°●) 」
嗯,在这么美好的日子里,我们要继续学习 【final + 继承与友元 + 继承与静态成员 + 继承模型 + 继承和组合】 的内容啦。想必大家现在已经满怀期待了吧(◔◡◔✿),那我们就开始学习吧!✲゚。⋆٩(◕‿◕。)۶⋆。゚✲゚*
通过将类的构造函数设为私有 或 使用C++11 引入的final关键字 实现,两种方式原理不同,但都能阻止继承。
1. 私有构造函数 + 静态创建(传统技巧,C++11 前常用)
把类的构造函数设为 private ,外部无法直接创建对象
再通过 静态成员函数 提供对象创建入口
class NonInheritable
{
private:
NonInheritable() // 私有构造函数,外部无法直接调用
{ }
NonInheritable(const NonInheritable&) = delete; // 若需要拷贝构造,也设为私有(可选)
public:
static NonInheritable create() // 静态函数,提供创建对象的唯一入口
{
return NonInheritable();
}
};
// 错误:派生类 Sub 构造时,需调用基类 NonInheritable 的构造函数,但基类构造函数私有,无法访问
class Sub : public NonInheritable
{
};原理:
private ,派生类无法访问该构造函数,编译器直接报错,达到 “禁止继承” 效果。
2. 利用 final 关键字(推荐,简洁直观)
在类名或虚函数后加 final ,可限制继承或重写
class FinalClass final
{
// ...
};
// 错误:编译报错,无法继承 final 修饰的类
class SubClass : public FinalClass
{
}; 原理:final 是 C++11 为限制继承设计的关键字,编译器会直接拦截派生操作,强制保证类 “不可被继承”。
继承:用于构建类的层次关系、实现功能复用与扩展。友元:用于突破封装、让特定函数/类访问私有成员。 二者的关系主要体现在 友元关系无法被继承 这一核心规则上。
友元关系不能继承具体分两种情况:
父类的友元函数/类,仅能访问:
父类自身的私有成员子类从父类继承的成员(因为这些成员本质属于父类的 “基因” ) 无法访问子类:新增的私有/保护成员(子类自己扩展的 “独特内容”,父类友元没权限触及 )
#include <iostream>
using namespace std;
/*---------------------定义:“基类:Base类”---------------------*/
class Base
{
friend class FriendClass; //声明 FriendClass 为友元类,允许其访问 Base 的私有成员
private:
int _baseData = 10; // 基类私有成员
};
/*---------------------定义:“派生类:Derived类”---------------------*/
class Derived : public Base
{
private:
int _derivedData = 20; // 派生类新增私有成员
};
/*---------------------定义:“友元类:FriendClass类”---------------------*/
class FriendClass
{
public:
void access(Base& b)
{
cout << "访问Base::_baseData: " << b._baseData << endl; //可访问 Base 对象的私有成员 _baseData
}
void access(Derived& d)
{
cout << "访问Derived::_baseData (inherited): " << d._baseData << endl; //可访问 Derived 对象中从 Base 继承的私有成员 _baseData
cout << "访问Derived::_derivedData: " << d._derivedData << endl; //但无法访问 Derived 自身新增的私有成员 _derivedData
// 错误:FriendClass 不是 Derived 的友元,无法访问 _derivedData
}
};
int main()
{
//1.创建:“基类 + 派生类 + 基类的友元类”的对象
Base b;
Derived d;
FriendClass fc;
//2.调用友元函数访问 Base 对象的私有成员
fc.access(b);
//3.调用友元函数访问 Derived 对象的私有成员
fc.access(d); // 只能访问从 Base 继承的部分,无法访问 Derived 自身新增的私有成员
return 0;
}
若子类想让某个类/函数访问自己的私有成员,必须自己重新声明友元 ,父类的友元关系不会 “传递” 给子类。
代码示例1:
#include <iostream>
using namespace std;
/*---------------------定义:“基类:Base类”---------------------*/
class Base
{
friend void friendFunc(Base& b); //声明 friendFunc 为友元函数,允许其访问 Base 的私有成员
private:
int _baseData = 10; // 基类私有成员
};
/*---------------------定义:“派生类:Derived类”---------------------*/
class Derived : public Base
{
private:
int _derivedData = 20; // 派生类新增私有成员
// friend void friendFunc(Derived& d); //注意:若不单独声明,friendFunc 无法访问 Derived 的私有成员
};
/*---------------------定义:“友元函数:friendFunc函数”---------------------*/
void friendFunc(Base& b)
{
cout << "访问Base::_baseData: " << b._baseData << endl; //可访问 Base 对象的私有成员
}
// 测试函数:演示友元关系的非继承性
void test()
{
//1.创建派生类的对象d
Derived d;
//2.调用友元函数
// friendFunc(d); // 错误:friendFunc 不是 Derived 的友元,无法访问其私有成员
friendFunc(static_cast<Base&>(d)); // 正确:可将 Derived 对象隐式转换为 Base&,但只能访问 Base 部分
//注意:friendFunc 接受 Base& 参数,Derived 对象可隐式转换为 Base&
}
int main()
{
//1.创建:“基类 + 派生类”的对象
Base b;
Derived d;
//2.调用友元函数访问 Base 对象的私有成员(合法)
friendFunc(b);
//3.调用测试函数,验证对 Derived 对象的访问限制
test();
return 0;
}
代码示例2:
#include <iostream>
#include <string>
using namespace std;
// 前向声明
class Student;
/*---------------------定义:“基类:Person类”---------------------*/
class Person
{
public:
friend void Display(const Person& p, const Student& s); // 声明 Display 为友元函数,允许访问 Person 的 protected 成员
protected:
string _name; // 姓名
};
/*---------------------定义:“派生类:Student类”---------------------*/
class Student : public Person
{
protected:
int _num; // 学号
};
/*---------------------定义:“友元函数:Display函数”---------------------*/
void Display(const Person& p, const Student& s)
{
//1.访问 Person 的 protected 成员 _name
cout << p._name << endl;
//2.尝试访问 Student 的 protected 成员 _num(此处会触发编译错误)
cout << s._num << endl;
}
int main()
{
//1.创建:“基类 + 派生类”的对象
Person p;
Student s;
//2.调用友元函数,触发访问权限检查
Display(p, s);
return 0;
}
在 C++ 中,继承与静态成员的关系主要体现在静态成员的
全局唯一性和可继承性上。 核心规则:静态成员被所有派生类共享
代码示例1:所有派生类共享同一实例
#include <iostream>
using namespace std;
/*---------------------定义:“基类:Base类”---------------------*/
class Base
{
public:
//1.声明基类的静态变量
static int s_value;
};
//2.初始化基类的静态变量
int Base::s_value = 10;
/*---------------------定义:“派生类:Derived1类”---------------------*/
class Derived1 : public Base
{
};
/*---------------------定义:“派生类:Derived2类”---------------------*/
class Derived2 : public Base
{
};
int main()
{
//1.输出“修改前”基类和派生类的静态变量s_value ---> 所有类共享同一静态变量
cout << "输出“修改前”基类和派生类的静态变量s_value" << endl;
cout << "Base::s_value=" << Base::s_value << endl;
cout << "Derived1::s_value=" << Derived1::s_value << endl;
cout << "Derived2::s_value=" << Derived2::s_value << endl << endl;
//2.输出“修改后”基类和派生类的静态变量s_value ---> 修改静态变量会影响所有类
cout << "输出“修改后”基类和派生类的静态变量s_value" << endl;
Derived1::s_value = 20; //s_value 在内存中只有一份,无论通过基类还是派生类访问,操作的都是同一个变量。
cout << "Base::s_value=" << Base::s_value << endl;
cout << "Derived2::s_value=" << Derived2::s_value << endl;
return 0;
}
代码示例2:所有派生类共享同一实例
#include <iostream>
#include <string>
using namespace std;
/*---------------------定义:“基类:Base类”---------------------*/
class Person
{
public:
string _name; //非静态成员变量的类内定义
static int _count; //静态成员变量的类内声明
};
//静态成员变量类外初始化
int Person::_count = 0;
/*---------------------定义:“派生类:Derived类”---------------------*/
class Student : public Person
{
protected:
int _stuNum;
};
int main()
{
/*-----------------创建对象-----------------*/
//1.创建:“基类 + 派生类”的对象
Person p;
Student s;
/*-----------------打印验证-----------------*/
//1.验证非静态成员 _name:派生类对象和基类对象各有一份,地址不同
cout << "验证非静态成员 _name" << endl;
cout << &p._name << endl;
cout << &s._name << endl << endl;
//2.验证静态成员 _count:派生类和基类共用同一份,地址相同
cout << "验证静态成员 _count" << endl;
cout << &p._count << endl;
cout << &s._count << endl << endl;
/*-----------------类名访问-----------------*/
//3.公有静态成员,基类和派生类通过类作用域访问
cout << "通过类名访问静态成员变量 _count" << endl;
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}
#include <iostream>
using namespace std;
/*---------------------定义:“基类:Base类”---------------------*/
class Base
{
public:
static void print()
{
cout << "Base::staticPrint()" << endl;
}
};
/*---------------------定义:“派生类:Derived类”---------------------*/
class Derived : public Base
{
};
int main()
{
//1.直接通过类名调用静态函数
Base::print();
Derived::print();
//2.也可通过对象调用(但不推荐,易混淆)
Derived d;
d.print();
return 0;
}
继承模型:指的是 面向对象编程(OOP)中,子类如何从父类继承成员(属性、方法等),以及这些成员在内存中如何布局、访问规则如何生效的 底层机制
常见继承模型分类:单继承模型 和 多继承模型,二者模型差异显著。
单继承模型:派生类仅从一个基类继承。
以下从基本语法、内存布局、访问规则角度展开解析:
一、单继承的基本语法
/*-----------------------------语法-----------------------------*/
class 基类
{
// 基类成员
};
class 派生类 : 继承方式 基类
{
// 派生类新增成员
};
/*-----------------------------示例-----------------------------*/
class Person
{
/* ... */
};
class Student : public Person // 单继承
{
/* ... */
}; 
二、单继承的内存布局
class Base
{
int _baseData; // 基类成员
};
class Derived : public Base
{
int _derivedData; // 派生类新增成员
};内存布局示意:(逻辑上)
Derived 对象内存:
+-------------------+
| Base 部分 |
| _baseData | // 基类成员,先存储
+-------------------+
| Derived 部分 |
| _derivedData | // 派生类新增成员,后存储
+-------------------+关键点:
&d == &(d.Base部分))三、单继承的访问规则 单继承中,public继承基类的(
public/protected/private)成员的访问,规则如下: 基类成员权限派生类内部能否访问类外部(通过对象)能否访问public能能protected能不能private不能(需通过基类接口)不能 示例验证:
class Base
{
public:
int publicData;
protected:
int protectedData;
private:
int privateData;
};
class Derived : public Base
{
public:
void test()
{
publicData = 1; // 允许:基类 public 成员
protectedData = 2; // 允许:基类 protected 成员
// privateData = 3; // 错误:基类 private 成员不可访问
}
};
int main()
{
Derived d;
d.publicData = 10; // 允许:public 成员可通过对象访问
// d.protectedData = 20; // 错误:protected 成员不可通过对象访问
return 0;
}
多继承模型:派生类同时从多个基类继承(如:class A : public B, public C {})
以下从基本语法、内存布局角度展开解析:
一、多继承的基本语法
/*-----------------------------语法-----------------------------*/
class 基类1
{
/* ... */
};
class 基类2
{
/* ... */
};
class 派生类 : 继承方式1 基类1, 继承方式2 基类2
{
// 派生类新增成员
};
/*-----------------------------示例-----------------------------*/
class Student
{
/* 基类1 */
};
class Teacher
{
/* 基类2 */
};
class Assistant : public Student, public Teacher
{
/* 派生类 */
}
二、多继承的内存布局
class Base1
{
int _data1;
};
class Base2
{
int _data2;
};
class Derived : public Base1, public Base2
{
int _derivedData;
};内存布局示意:(逻辑上)
Derived 对象内存:
+-------------------+
| Base1 部分 |
| _data1 | // 第一个基类,先存储
+-------------------+
| Base2 部分 |
| _data2 | // 第二个基类,后存储
+-------------------+
| Derived 部分 |
| _derivedData | // 派生类新增成员
+-------------------+关键点:
Base1)的地址相同
菱形继承:是多继承体系下容易出现的一种特殊继承结构,因继承关系形似菱形而得名,会引发 数据冗余和访问二义性 等问题。
一、菱形继承的基本语法 菱形继承是多继承的一种特殊情况,典型结构为:
此时,继承关系形成一个菱形(或钻石形)结构,示例如下:
/*-----------------------------语法-----------------------------*/
class Person
{
/* 公共基类 */
};
class Student : public Person
{
/* 中间类1 */
};
class Teacher : public Person
{
/* 中间类2 */
};
class Assistant : public Student, public Teacher
{
/* 最终派生类 */
}
特别注意:上述这种结构只是菱形继承的典型表现形式,实际上,判断是否为菱形继承,并非看继承的形式一定得是严格的 “菱形” 或 “钻石形” 外观才叫菱形继承。 简单来说,只要继承结构满足下面的条件,就属于菱形继承。
存在一个公共基类被多次继承最终派生类通过不同路径继承了同一个基类比如说下面的这种继承结构,就是一种菱形继承,其会导致最终派生类中包含多个相同的公共基类的子对象。

二、菱形继承的内存布局
示例代码
class Person
{
public:
string _name;
};
class Student : public Person
{
/* ... */
};
class Teacher : public Person
{
/* ... */
};
class Assistant : public Student, public Teacher
{
/* ... */
};内存布局:
Assistant 对象内存:
+-------------------+
| Student 部分 |
| Person::_name | // 第一份 _name
+-------------------+
| Teacher 部分 |
| Person::_name | // 第二份 _name
+-------------------+
| Assistant 部分 |
+-------------------+
访问歧义:
Assistant ta;
ta._name = "Alice"; // 错误:哪份 _name?Student 的还是 Teacher 的?
ta.Student::_name = "Alice"; // 显式指定路径,可解决歧义三、菱形继承的核心问题 1. 数据冗余
Assistan 的对象中,会包含多份公共基类 Person 的成员。 Person 有成员 _nameAssistant 对象中会通过 Student 继承一份 _nameTeacher 继承一份 _name,造成内存浪费2. 访问二义性
Person 的成员时,编译器无法确定到底该访问哪一份(是 Student 继承来的,还是 Teacher 继承来的 )class Person
{
public:
string _name;
};
class Student : public Person
{
/* ... */
};
class Teacher : public Person
{
/* ... */
};
class Assistant : public Student, public Teacher
{
/* ... */
};
int main()
{
Assistant ta;
// 错误:编译器不知道访问 Student::_name 还是 Teacher::_name
ta._name = "jack";
return 0;
}
虚继承:是 C++ 中解决多继承问题的核心机制,尤其用于处理菱形继承带来的数据冗余和访问二义性问题。
virtual 关键字,确保多个派生路径中公共基类的成员仅在最终派生类中保留一份一、虚继承的基本语法
class Person
{
public:
string _name;
};
class Student : virtual public Person // Student 虚继承 Person
{
/* ... */
};
class Teacher : virtual public Person // Teacher 虚继承 Person
{
/* ... */
};
class Assistant : public Student, public Teacher // Assistant 继承 Student 和 Teacher
{
/* ... */
};对比:
Assistant 对象包含两份 Person 的 _name 成员,访问 ta._name 会报错(二义性)Assistant 对象仅包含一份 Person 的 _name 成员,访问 ta._name 明确且唯一二、虚继承的底层原理
虚继承通过虚基类指针(vbptr) 和虚基表(vbtable) 实现。
1. 内存布局变化(以菱形继承为例)
假设类结构为 Assistant继承Student和Teacher,Student和Teacher虚继承Person,则各对象的内存布局:
Person 类:
Person 的成员被统一存放在最终派生类 Assistant 对象内存的最下方,仅一份Student 和 Teacher 类:
Student、Teacher 的对象中,会新增一个虚基类指针(vbptr),指向虚基表(vbtable)Person 成员的偏移量,通过偏移量可找到唯一的 Person 成员Assistant 类:
Student 和 Teacher 的子对象(各含一个 vbptr)示例解析(简化理解)
Person 有成员 _nameStudent、Teacher 虚继承 PersonAssistant 继承 Student、Teacher则 Assistant 对象内存布局大致为:
Assistant 对象内存:
+------------------------+
| Student 部分(含 vbptr) |
+------------------------+
| Teacher 部分(含 vbptr) |
+------------------------+
| Person 部分(_name) | // 仅一份
+------------------------+
| Assistant 新增成员 |
+------------------------+
2. 访问虚基类成员的过程
当访问 Assistant 对象的 _name 时:
Assistant 通过 Student 或 Teacher 的 vbptr 找到对应的虚基表Person::_name 在 Assistant 对象中的偏移量Person::_name 成员三、虚继承的构造顺序 虚继承会改变类构造函数的调用顺序:
#include <iostream>
#include <string>
using namespace std;
/*---------------------定义:“基类:Person类”---------------------*/
class Person
{
public:
Person(const string& name)
: _name(name)
{
cout << "Person 构造函数,name = " << _name << endl;
}
string _name; // 姓名
};
/*---------------------定义:“中间派生类:Student类”---------------------*/
class Student : virtual public Person
{
public:
Student(const string& name)
: Person(name)
{
cout << "Student 构造函数" << endl;
}
};
/*---------------------定义:“中间派生类:Teacher类”---------------------*/
class Teacher : virtual public Person
{
public:
Teacher(const string& name)
: Person(name)
{
cout << "Teacher 构造函数" << endl;
}
};
/*---------------------定义:“最终派生类:Assistant类”---------------------*/
class Assistant : public Student, public Teacher
{
public:
Assistant(const string& name)
:Person(name)
,Student(name)
,Teacher(name)
{
cout << "Assistant 构造函数" << endl;
}
/* 构造函数:
*
* 1.显式初始化虚基类 Person(这是必要的,否则会调用 Person 的默认构造函数)
* 2.调用 Student 和 Teacher 的构造函数(但它们对 Person 的初始化会被忽略)
*/
};
int main()
{
cout << "=== 创建助教对象 ===" << endl;
Assistant assistant("张三");
/* 创建 Assistant 对象时的构造顺序:
*
* 1.虚基类 Person(由 Assistant 直接初始化)
* 2.非虚基类 Student(其对 Person 的初始化被忽略)
* 3.非虚基类 Teacher(其对 Person 的初始化被忽略)
* 4.Assistant 自身
*/
cout << "\n=== 访问姓名信息 ===" << endl;
cout << "姓名: " << assistant._name << endl; // 由于虚继承,_name 仅存在一份实例,无需指定作用域,直接访问
cout << "学生姓名: " << assistant.Student::_name << endl;
cout << "教师姓名: " << assistant.Teacher::_name << endl; // 以上两种方式通过作用域限定符访问,但实际上指向同一内存位置
cout << "\n=== 程序结束 ===" << endl;
return 0;
}
注:若最终派生类未显式调用虚基类构造函数,编译器会自动调用其默认构造函数。
#include <iostream>
#include <string>
using namespace std;
/*---------------------定义:“公共基类:Person类”---------------------*/
class Person
{
public:
//1.实现:“构造函数”---> 用 C 风格字符串初始化姓名
Person(const char* name)
: _name(name) // 初始化列表初始化成员 _name
{
cout << "Person 构造函数调用,姓名:" << _name << endl;
}
string _name; // 姓名
};
/*---------------------定义:“中间派生类:Student类”---------------------*/
class Student : virtual public Person
{
public:
//1.实现:“构造函数”---> 初始化 Person 基类、学号
Student(const char* name, int num)
: Person(name) // 调用 Person 构造函数初始化从公共基类继承的部分
, _num(num) // 初始化学号成员
{
cout << "Student 构造函数调用,学号:" << _num << endl;
}
protected:
int _num; // 学号
};
/*---------------------定义:“中间派生类:Teacher类”---------------------*/
class Teacher : virtual public Person
{
public:
//1.实现:“构造函数”---> 初始化 Person 基类、职工编号
Teacher(const char* name, int id)
: Person(name) // 调用 Person 构造函数初始化公共基类部分
, _id(id) // 初始化职工编号成员
{
cout << "Teacher 构造函数调用,职工编号:" << _id << endl;
}
protected:
int _id; // 职工编号
};
/*---------------------定义:“最终派生类:Assistant类”---------------------*/
class Assistant : public Student, public Teacher
{
public:
//1.实现:“构造函数”---> 需显式初始化公共基类 Person,再初始化 Student、Teacher
Assistant(const char* name1, const char* name2, const char* name3)
: Person(name3) // 直接初始化公共基类 Person,这是虚继承的关键要求
, Student(name1, 1) // 调用 Student 构造函数,学号固定传 1(示例逻辑)
, Teacher(name2, 2) // 调用 Teacher 构造函数,职工编号固定传 2(示例逻辑)
{
cout << "Assistant 构造函数调用" << endl;
}
protected:
string _majorCourse; // 主修课程
};
int main()
{
cout << "----------创建 Assistant 对象:----------" << endl;
Assistant a("张三", "李四", "王五");
cout << "----------打印Assistant 对象中 Person 部分的姓名:----------" << endl;
cout << a._name << endl;
//注意:由于虚继承,Person 的 _name 由 Assistant 构造函数中 Person(name3) 决定
//所以 _name 的值是 "王五"
return 0;
}
虚继承与非虚继承的对比:
特性 | 非虚继承(普通继承) | 虚继承 |
|---|---|---|
基类成员数量 | 每个派生路径均保留一份基类成员 | 最终派生类仅保留一份基类成员 |
二义性问题 | 存在(如:菱形继承) | 解决(仅一份基类成员) |
构造函数调用 | 由直接派生类调用基类构造函数 | 由最终派生类直接调用虚基类构造函数 |
内存布局 | 简单(无虚基类指针) | 复杂(含虚基类指针和虚基表) |
适用场景 | 单继承或无公共基类的多继承 | 菱形继承或需要共享基类成员的场景 |

在 C++ 标准库的 IO 类模板继承体系里:
basic_iostream 是特殊的 —— 它多继承了 basic_ostream 和 basic_istream那我们都知道的一件事情就是:当一个继承关系中先是进行一些单继承,然后又进行了一个多继承的情况的话,就会出现:菱形继承的问题 那下面我们就来看一看,标准的IO库是怎么解决菱形继承问题的!!!
//basic_ostream 类模板:表示基本输出流
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{
};
//basic_istream 类模板:表示基本输入流
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{
};
/* 注意事项:
* 1.CharT 表示字符类型(如:char、wchar_t 等)
* 2.Traits 表示字符特性,默认使用标准库的 char_traits,用于提供字符相关的基本操作(如:比较、复制等)
*
* 作用:
* 1.虚继承自 basic_ios<CharT, Traits>,目的是在多重继承场景下(如:basic_iostream)避免基类 basic_ios 的成员重复
* 2.同样虚继承自 basic_ios<CharT, Traits>,和 basic_ostream 配合解决多重继承时的基类成员冗余问题
*/关于上面的代码,下面说法正确的是( ) A.
p1 == p2 == p3B.p1 < p2 < p3C.p1 == p3 != p2D.p1 != p2 != p3
#include <iostream>
using namespace std;
class Base1
{
public:
int _b1;
};
class Base2
{
public:
int _b2;
};
class Derive : public Base1, public Base2
{
public:
int _d;
};
int main()
{
// 创建 Derive 类的对象 d,该对象包含 Base1、Base2 以及自身的成员
Derive d;
// 定义 Base1 类型指针 p1,指向 Derive 对象 d 中从 Base1 继承的部分
// 因为 Derive 公有继承 Base1,所以可以安全地将 Derive 对象指针转换为 Base1 指针
Base1* p1 = &d;
// 定义 Base2 类型指针 p2,指向 Derive 对象 d 中从 Base2 继承的部分
// 同理,Derive 公有继承 Base2,可转换为 Base2 指针
Base2* p2 = &d;
// 定义 Derive 类型指针 p3,直接指向 Derive 对象 d 的起始地址
Derive* p3 = &d;
return 0;
}解析:
在多继承场景中,派生类 Derive 的对象 d 内存布局会包含:
Base1、Base2 的成员大致如下(简化示意 ):
Derive 对象 d 的内存:
+-------------------+
| Base1 部分 | // 包含 _b1
+-------------------+
| Base2 部分 | // 包含 _b2
+-------------------+
| Derive 自身 _d |
+-------------------+p1: Base1* 类型指针,指向 d 中 Base1 部分的起始地址d 的起始地址相同(因为 Base1 是第一个基类 )p2: Base2* 类型指针,指向 d 中 Base2 部分的起始地址Base2 排在 Base1 之后,其地址比 d 的起始地址大一个 Base1 的大小(即:偏移了 sizeof(Base1) )p3:Derive* 类型指针,指向 d 的起始地址因此:
p1 和 p3 地址相同(都指向 d 起始 )p2 因偏移,地址与 p1、p3 不同
答案【C】
在 C++ 面向对象设计中,继承(Inheritance) 和 组合(Composition) 是实现代码复用、构建复杂类结构的两种核心手段。
1. 继承(“是一个” 关系,is-a )
含义:派生类(子类)直接继承基类(父类)的成员(属性、方法),可复用基类逻辑并扩展新功能。
关系:Derived 是一个 Base(如:Student 是一个 Person )
语法:
class Base
{
/* 基类成员 */
};
class Derived : public Base
{
/* 派生类成员,可复用 Base 的成员 */
};2. 组合(“有一个” 关系,has-a )
含义:一个类(宿主类)通过包含其他类的对象来复用功能,被包含的类(成员对象)是宿主类的 “组件”。
关系:Host 有一个 Component(如:Car 有一个 Engine )
语法:
class Component
{
/* 组件类成员 */
};
class Host
{
Component comp; // 组合 Component 对象
};继承与组合的两种复用模式对比 继承:白箱复用 继承的核心是:基于基类实现派生出新类,让派生类复用基类的功能。这种复用模式被称为
“白箱复用”,关键特点是:
protected 成员、实现逻辑 )对派生类是 “透明可见” 的。 组合:黑箱复用
组合的核心是:通过 “组装 / 组合” 已有对象,构建出更复杂的功能。这种复用模式被称为 “黑箱复用”,关键特点是:
特性 | 继承(Inheritance) | 组合(Composition) |
|---|---|---|
复用方式 | 直接继承基类的成员,派生类与基类强耦合 | 包含其他类的对象,宿主类与成员对象弱耦合 |
关系语义 | is-a(派生类是基类的特殊化) | has-a(宿主类包含成员对象作为组件) |
成员访问 | 派生类可直接访问基类的 protected 成员 | 宿主类需通过成员对象的接口访问其成员 |
生命周期 | 派生类对象创建时,基类子对象先构造 | 成员对象的生命周期由宿主类对象管理 |
继承和组合的优缺点: 1. 继承的优缺点
直接复用逻辑:无需额外代码,派生类可直接使用基类的属性和方法支持多态:通过虚函数,派生类可重写基类行为,实现运行时多态强耦合:派生类依赖基类的实现细节,基类修改可能破坏派生类菱形继承问题:多继承易导致成员冗余、访问二义性(需虚继承解决)2. 组合的优缺点
弱耦合:宿主类与成员对象接口解耦,成员对象修改不影响宿主类灵活复用:可动态替换成员对象(若用指针/引用),适配不同场景避免菱形继承:不涉及继承层次,天然无多继承的复杂问题间接访问:需通过成员对象的接口访问其功能,代码可能更繁琐不支持多态:默认无法直接重写成员对象的行为(需结合指针 + 多态实现)在设计模式中,“组合优于继承”(Composition over Inheritance) 是重要原则,核心思想是:优先用组合实现复用,减少继承带来的强耦合。
继承与组合的选择依据:
关系判断: is-a 关系(如:Student 是 Person ),优先用继承has-a 关系(如:Car 有 Engine ),优先用组合耦合与维护: 多态需求: 总结:
is-a 关系且需多态时,合理使用继承二者并非互斥,复杂类设计中常结合使用(如:继承实现接口,组合实现功能复用 )
代码案例1:STL中的stack容器适配器的实现方式
// 以下演示 stack 与 vector 的两种关系(实际标准库中 stack 通常用组合,这里对比说明)
template<class T>
class vector
{
};
// 错误示范:stack 公有继承 vector,强行让 stack "是一个" vector(is-a)
// 但 stack 语义上更适合 "有一个" vector(has-a),此写法会暴露 vector 所有接口,不符合栈的设计
template<class T>
class stack : public vector<T>
{
};
// 正确示范:stack 组合 vector,体现 has-a 关系(stack "有一个" vector 作为底层容器)
template<class T>
class stack
{
public:
vector<T> _v; // 组合 vector 对象,stack 通过 _v 实现底层存储
};代码案例2:汽车的继承和组合
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/*---------------------定义:“基类:Tire类”---------------------*/
class Tire //注:后续会被 Car 组合,体现 (has-a 关系)
{
protected:
string _brand = "Michelin"; // 轮胎品牌,默认米其林
size_t _size = 17; // 轮胎尺寸,默认 17 寸
};
/*---------------------定义:“基类:Car类”---------------------*/
class Car //注:后续被 BMW、Benz 继承,体现(is-a 关系)
{
protected:
string _colour = "白色"; // 车颜色,默认白色
string _num = "京00001"; // 车牌号,默认京00001
Tire _t1; // 第一个轮胎 ---> 组合轮胎对象,体现 Car "有一个" Tire(has-a 关系)
Tire _t2; // 第二个轮胎
Tire _t3; // 第三个轮胎
Tire _t4; // 第四个轮胎
};
/*---------------------定义:“派生类:BMW类”---------------------*/
class BMW : public Car //注:公有继承 Car 类,体现 is-a 关系(BMW "是一个" Car)
{
public:
void Drive()
{
cout << "好开-操控" << endl; // BMW 车型的驾驶体验描述
}
};
/*---------------------定义:“派生类:Benz类”---------------------*/
class Benz : public Car //注:公有继承 Car 类,体现 is-a 关系(Benz "是一个" Car)
{
public:
void Drive()
{
cout << "好坐-舒适" << endl; // Benz 车型的驾驶体验描述
}
};
int main()
{
BMW bmwCar;
bmwCar.Drive(); // 调用 BMW 的 Drive 方法
return 0;
}