
C++ 继承进阶:友元、静态成员与菱形继承解析
C++ 继承机制涉及友元不可继承、静态成员共享及菱形继承问题。友元关系不具备传递性,派生类需重新声明。静态成员在继承体系中仅存一份,基类与派生类共享。菱形继承导致数据冗余和二义性,可通过虚继承解决,由最终派生类初始化顶层基类。组合优于继承以降低耦合。

C++ 继承机制涉及友元不可继承、静态成员共享及菱形继承问题。友元关系不具备传递性,派生类需重新声明。静态成员在继承体系中仅存一份,基类与派生类共享。菱形继承导致数据冗余和二义性,可通过虚继承解决,由最终派生类初始化顶层基类。组合优于继承以降低耦合。


在 C++ 继承的基础概念中,友元、静态成员、菱形继承等场景往往是理解难点。本文逐一讲解这些场景的底层逻辑,帮助掌握继承的隐藏规则。
C++ 中,基类的友元函数或类无法直接访问派生类的私有成员。友元关系不具有继承性。如果需要让友元访问派生类成员,必须在派生类中重新声明友元。
#include<iostream>
#include<vector>
using namespace std;
// 友元——友元不能被继承
class Person {
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name = "张三"; // 姓名
};
class Student : public Person {
protected:
int _stuid = 123; // 学号
};
void Display(const Person& p, const Student& s) {
cout << p._name << endl;
cout << s._stuid << endl;
}
void Test1() {
Person p;
Student s;
Display(p, s);
}
int main() {
Test1();
return 0;
}
编译会出现报错。第一个报错通常是因为类型未定义(缺少前置声明),编译器向上查找时找不到 Student 的定义。第二个报错说明基类的友元不能被派生类继承。
解决方法是在最开始前置声明 Student,并在派生类中也进行友元声明。
// 友元——友元不能被继承
// 前置声明 Student(为了让编译器走到友元函数时能向上查找到 Student)
class Student;
class Person {
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name = "张三"; // 姓名
};
class Student : public Person {
friend void Display(const Person& p, const Student& s); // 派生类需重新声明
protected:
int _stuid = 123; // 学号
};
void Display(const Person& p, const Student& s) {
cout << p._name << endl;
cout << s._stuid << endl;
}
void Test1() {
Person p;
Student s;
Display(p, s);
}
int main() {
Test1();
return 0;
}
核心结论:
基类的静态成员(静态变量/静态函数)在整个继承体系中仅存在一份,派生类和基类共享该成员,不会因为继承而产生多个。这与非静态成员不同 —— 非静态成员每个对象独一份。
对于基类的静态成员而言,对其进行初始化则所有派生类的该成员都会被初始化成相同值,并且一个类的静态成员进行修改也会影响其他所有相关的派生类和基类。
// 静态成员
class Person {
public:
string _name;
static int _count;
};
int Person::_count = 1;
class Student : public Person {
protected:
int _stuid;
};
void Test2() {
Person p;
Student s;
// 这里的运行结果可以看到非静态成员的_name 地址是不一样的
// 说明非静态成员派生类继承下来了,基类和派生类对象各有一份不一样的
cout << &p._name << endl;
cout << &s._name << endl;
cout << endl;
// 这里的运行结果可以看到静态成员的_count 地址是一样的
// 说明派生类和基类共用同一份静态成员,所以对其中一个修改就会影响所有相关基类和派生类的静态成员
cout << &p._count << endl;
cout << &s._count << endl;
cout << endl;
cout << p._count << endl;
cout << s._count << endl;
cout << endl;
Person::_count++; // 公有的情况下,基类派生类指定类域都可以访问静态成员
// 原因在前面的学习已经讲解过了,静态成员不属于任何一个对象,是存在静态区的
// 可以把静态成员看成全局变量只是被类域所限制而已,所以要访问就需要使用域作用限定符来突破类域访问
cout << Person::_count << endl;
cout << Student::_count << endl;
cout << endl;
}
int main() {
Test2();
return 0;
}
核心结论:
// 多继承
class Student {
protected:
string _name; // 姓名
};
class Teacher {
protected:
int _id = 123; // 职工编号
};
class Assistant : public Student, public Teacher {
protected:
string _majorCourse; // 主修课程
};
void Test3() {
Assistant a;
}
int main() {
Test3();
return 0;
}
菱形继承是指'一个派生类同时继承两个基类,而这两个基类又共同继承自一个顶层基类'的结构。这种结构会导致两个核心问题:
支持多继承就一定会有菱形继承,像 Java 就直接不支持多继承,规避掉了这里的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
// 菱形继承
// 顶层基类
class Person {
public:
string _name; // 会被 Assistant 继承两次
};
// 中间基类 1
class Student : public Person {
protected:
int _num; // 学号
};
// 中间基类 2
class Teacher : public Person {
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher {
protected:
string _majorCourse; // 主修课程
};
void Test3() {
Assistant a;
// a._name = "张三"; // error C2385: 对'_name'的访问不明确
// (到底是 Student::_name 还是 Teacher::_name 呢?)
a.Student::_name = "李四";
a.Teacher::_name = "王五";
// 只能显式指定,但数据冗余仍存在,没有解决
cout << a.Student::_name << endl; // 输出李四
cout << a.Teacher::_name << endl; // 输出王五
}
int main() {
Test3();
return 0;
}
// 虚继承
// 顶层基类
class Person {
public:
Person(const char* name) :_name(name) { }
public:
string _name; // 姓名
};
// 中间基类 1:虚继承 Person(添加 virtual)
// virtual,谁 (Person) 被继承多次就在继承谁 (Person) 的那些子类 (Student) 加
class Student : virtual public Person {
public:
Student(const char* name, int num) :Person(name)// 在虚继承下,中间基类会暂时不初始化顶层基类 , _num(num)
// 只会初始化自己的成员变量
{
}
protected:
int _num; // 学号
};
// 中间基 2:虚继承 Person(添加 virtual)
// virtual,谁 (Person) 被继承多次就在继承谁 (Person) 的那些子类 (Teacher) 加
class Teacher : virtual public Person {
public:
Teacher(const char* name, int id) :Person(name)// 在虚继承下,中间基类会暂时不初始化顶层基类 , _id(id)
// 只会初始化自己的成员变量
{
}
protected:
int _id; // 职工编号
};
// 最终派生类:菱形继承(Person 成员会被合并成仅一份)
class Assistant : public Student, public Teacher {
public:
// 关键:虚继承下,顶层基类的构造由最终派生类显式调用
Assistant(const char* name1, const char* name2, const char* name3)
:Person(name1)// 直接初始化顶层基类,下面两个初始化不会进去
, Student(name2, 1)
, Teacher(name3, 2)
, _majorCourse("计算机")
{
}
protected:
string _majorCourse; // 主修课程
};
void Test4() {
Assistant a("张三", "李四", "王五");
// 思考一下这里 a 对象中 _name 是'张三', '李四', '王五'中的哪一个?
// 上面有三次 Person(name),但其实就只有在 Assistant 里一次,其它两次会跳过。
// 所以是张三
}
int main() {
Test4();
return 0;
}
虚继承的关键细节:
virtual 仅需添加在中间基类继承顶层基类时,最终派生类继承中间基类时不需要添加。class Base1 {
public:
int _b1;
};
class Base2 {
public:
int _b2;
};
class Derive : public Base1, public Base2 {
public:
int _d;
};
int main() {
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
图解如下:

| 场景 | 核心特性 | 避坑指南 |
| 友元 | 友元关系不随继承传递,若需访问派生类私有成员,必须在派生类中重新声明友元 | 控制友元使用范围,避免因过度开放访问破坏类的封装性 |
| 静态成员 | 全继承体系共享唯一实例,需在类外初始化;静态函数仅能访问静态成员变量 | 关注静态成员的'全局共享'特性,多线程场景需加锁保护,避免并发冲突 |
| 菱形继承 | 因间接继承共同基类导致数据冗余和访问二义性,需通过虚继承解决;虚继承下顶层基类由最终派生类初始化 | 设计阶段优先规避菱形结构,确需使用时再通过虚继承处理,避免过度依赖增加代码复杂度 |
选择原则:
结束语
C++ 继承的核心价值在于实现类级别的代码复用,但友元、静态成员、菱形继承这些特殊场景,恰恰是理解继承机制'深度'的关键。从友元关系的'不可继承性',到静态成员的'全局共享特性',再到菱形继承中虚继承对数据冗余与二义性的解决,都表现出 C++ 对'封装''复用'与'安全性'的平衡设计。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online