跳到主要内容
C++ 继承机制详解:从基础概念到多继承实战 | 极客日志
C++ 算法
C++ 继承机制详解:从基础概念到多继承实战 C++ 继承是面向对象的核心特性,允许派生类复用基类代码并扩展功能。详细讲解了继承的定义与访问控制规则,包括 public/protected/private 继承对成员可见性的影响。内容涵盖作用域隐藏、默认成员函数的生成与调用顺序、虚继承解决菱形继承的数据冗余问题,以及多继承中的指针偏移现象。此外,还对比了继承与组合的区别,强调优先使用组合以降低耦合度。通过代码示例演示了模板继承、友元关系及静态成员在继承体系中的行为,帮助开发者构建可维护的类体系。
abccba 发布于 2026/3/16 更新于 2026/7/22 49 浏览C++ 继承机制详解
一、继承的概念及定义
1.1 继承的概念
继承(Inheritance)是面向对象编程中代码复用的核心手段。它允许我们在已有类的基础上创建新类,在保持原有特性的同时扩展功能。通过继承构建的层次结构,能让代码更清晰、易维护。
举个例子,如果没有继承,Student 和 Teacher 类都需要重复定义姓名、年龄等成员变量。引入继承后,我们可以将公共部分提取为 Person 基类,让子类复用这些逻辑。
class Person {
public :
void identity () {
cout << "void identity()" << _name << endl;
}
protected :
string _name = "张三" ;
string _address;
string _tel;
int _age = 18 ;
};
class Student : public Person {
public :
void study () {
}
protected :
int _stuid;
};
class Teacher : public Person {
public :
void teaching () {
}
protected :
string title;
};
{
Student s;
Teacher t;
s. ();
t. ();
;
}
int
main
()
identity
identity
return
0
1.2 继承的定义
1.2.1 定义格式 基类(父类)与派生类(子类)的关系通过继承方式建立。C++ 支持三种继承方式:public、protected 和 private。它们决定了基类成员在派生类中的访问权限变化。
注意 :private 和 protected 在同一个类内作用相似,但在继承时效果截然不同,直接影响派生类的访问能力。
1.2.2 继承基类成员访问方式的变化 类成员 / 继承方式 public 继承 protected 继承 private 继承 基类的 public 成员 派生类的 public 成员 派生类的 protected 成员 派生类的 private 成员 基类的 protected 成员 派生类的 protected 成员 派生类的 protected 成员 派生类的 private 成员 基类的 private 成员 在派生类中不可见 在派生类中不可见 在派生类中不可见
基类 private 成员在派生类中始终不可见,但物理上仍存在于对象内存中。
若希望基类成员不被外部直接访问,但允许派生类访问,应使用 protected。
实际开发中,绝大多数情况使用 public 继承。protected/private 继承会限制成员的可用性,降低扩展性。
默认情况下,class 关键字对应 private 继承,struct 对应 public 继承,建议显式声明。
1.3 继承类模板 容器适配器如 stack 底层通常基于 vector。我们可以尝试用 stack 继承 vector,但这涉及模板实例化的细节。
namespace sxn {
template <class T >
class stack : public std::vector<T> {
public :
void push (const T& x) {
vector<T>::push_back (x);
}
void pop () {
vector<T>::pop_back ();
}
const T& top () {
return vector<T>::back ();
}
bool empty () {
return vector<T>::empty ();
}
};
}
int main () {
sxn::stack<int > st;
st.push (1 );
st.push (2 );
st.push (3 );
while (!st.empty ()) {
cout << st.top () << " " ;
st.pop ();
}
return 0 ;
}
为什么需要指定类域?
类模板是按需实例化的。当定义 stack 时,编译器只检查语法。只有当实际使用 stack<int> 并调用 push_back 时,才会实例化 vector<int> 的相关代码。如果未指定作用域,编译器可能找不到 push_back 标识符,导致编译错误。
二、基类和派生类间的转化
派生类对象 -> 基类指针/引用 :这是安全的,称为'切片'或'切割'。基类指针指向的是派生类对象中属于基类的那部分数据。
基类对象 -> 派生类对象 :不允许直接赋值,因为派生类包含更多成员。
强制转换 :基类指针可转为派生类指针,但必须确保该指针实际指向的是派生类对象。对于多态类型,推荐使用 RTTI 的 dynamic_cast 进行安全转换。
class Person {
protected :
string _name;
string _sex;
int _age;
};
class Student : public Person {
public :
int _No;
};
int main () {
Student sobj;
Person* pp = &sobj;
Person& rp = sobj;
return 0 ;
}
三、继承中的作用域
3.1 隐藏规则
基类和派生类拥有独立的作用域。
若两者存在同名成员,派生类成员将屏蔽基类同名成员的直接访问,这叫隐藏 。
函数名相同即构成隐藏(区别于重载)。在派生类中可通过 基类::成员名 显式访问被隐藏的基类成员。
实际开发中,尽量避免在继承体系中定义同名成员,以免混淆。
class Person {
protected :
string _name = "小雷子" ;
int _num = 072 ;
};
class Student : public Person {
public :
void Print () {
cout << "姓名:" << _name << endl;
cout << "身份证号:" << Person::_num << endl;
cout << "学号:" << _num << endl;
}
protected :
int _num = 520 ;
};
int main () {
Student s1;
s1. Print ();
return 0 ;
}
3.2 考察继承作用域相关选择题 问题: 下面 A 和 B 类中的两个 func 构成什么关系?
A. 重载 B. 隐藏 C. 没关系
解析: 重载发生在同一作用域内。基类和派生类是两个不同作用域,且函数名相同,因此是隐藏 关系。
class A {
public :
void fun () { cout << "func()" << endl; }
};
class B : public A {
public :
void fun (int i) { cout << "func(int i)" << i << endl; }
};
int main () {
B b;
b.fun (10 );
b.fun ();
return 0 ;
};
答案: 编译报错。虽然 b.fun() 语义上想调用基类函数,但由于派生类定义了同名的 fun,基类版本被隐藏。编译器在语义分析时发现调用不匹配,直接报错。
四、派生类的默认成员函数 派生类会自动生成 6 个默认成员函数,其行为如下:
构造函数 :必须调用基类构造函数初始化基类部分。若基类无默认构造,需在初始化列表中显式调用。
拷贝构造函数 :需调用基类拷贝构造完成基类部分的拷贝。
赋值运算符 :需调用基类赋值运算符。注意派生类 operator= 可能隐藏基类版本,需指定作用域调用。
析构函数 :先执行派生类析构,再自动调用基类析构,确保清理顺序正确。
初始化顺序 :先基类构造,后派生类构造;析构则相反。
虚析构 :在多态场景下,基类析构函数建议加 virtual,否则派生类析构可能与基类构成隐藏而非重写。
class Person {
public :
Person (const char * name = "joke" ) : _name(name) {
cout << "Person()" << endl;
}
Person (const Person& p) : _name(p._name) {
cout << "Person(const Person& p)" << endl;
}
Person& operator =(const Person& p) {
cout << "Person operator=(const Person& p)" << endl;
if (this != &p) _name = p._name;
return *this ;
}
~Person () {
cout << "~Person()" << endl;
}
protected :
string _name;
};
class Student : public Person {
public :
Student (const char * name, int num) : Person (name), _num(num) {
cout << "Student()" << endl;
}
Student (const Student& s) : Person (s), _num(s._num) {
cout << "Student(const Student& s)" << endl;
}
Student& operator =(const Student& s) {
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s) {
Person::operator =(s);
_num = s._num;
}
return *this ;
}
~Student () {
cout << "~Student()" << endl;
}
protected :
int _num;
};
int main () {
Student s1 ("jack" , 18 ) ;
Student s2 (s1) ;
Student s3 ("rose" , 17 ) ;
s1 = s3;
return 0 ;
}
4.2 实现一个不能被继承的类 方法 1:将基类构造函数设为私有,派生类无法访问。
方法 2:C++11 引入 final 关键字修饰基类。
class Base final {
public :
void func5 () { cout << "Base::func5" << endl; }
protected :
int a = 1 ;
};
class Derive : public Base {
void func4 () { cout << "Derive::func4" << endl; }
protected :
int b = 2 ;
};
五、继承与友元 友元关系不能继承 。基类的友元无法访问派生类的私有和保护成员。
#include <iostream>
#include <string>
using namespace std;
class Student ;
class Person {
public :
friend void Display (const Person& p, const Student& s) ;
protected :
string _name;
};
class Student : public Person {
protected :
int _stuNum;
};
void Display (const Person& p, const Student& s) {
cout << p._name << endl;
cout << s._stuNum << endl;
}
int main () {
Person p;
Student s;
Display (p, s);
return 0 ;
}
六、继承与静态成员 基类定义的 static 静态成员在整个继承体系中只有一个实例。无论派生出多少个派生类,都共享同一个静态成员。
#include <iostream>
#include <string>
using namespace std;
class Person {
public :
string _name;
static int _count;
};
int Person::_count = 0 ;
class Student : public Person {
protected :
int _stuNum;
};
int main () {
Person p;
Student s;
cout << &p._name << endl;
cout << &s._name << endl;
cout << &p._count << endl;
cout << &s._count << endl;
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0 ;
}
七、多继承及菱形继承问题
7.1 继承模型
单继承 :一个派生类只有一个直接基类。
多继承 :一个派生类有两个或以上直接基类。内存布局通常是先继承的基类在前,派生类成员在最后。
菱形继承 :多继承的特殊情况,会导致数据冗余 和二义性 。
例如 Assistant 同时继承 Student 和 Teacher,而两者都继承自 Person。此时 Assistant 对象中会有两份 Person 的成员。
class Person {
public :
string _name;
};
class Student : public Person {
protected :
int _num;
};
class Teacher : public Person {
protected :
int _id;
};
class Assistant : public Student, public Teacher {
protected :
string _majorCourse;
};
int main () {
Assistant a;
a._name = "peter" ;
return 0 ;
}
解决二义性需显式指定:a.Student::_name = "xxx";,但这无法解决数据冗余。
7.2 虚继承 引入 virtual 关键字可解决菱形继承问题,确保基类在派生类中只有一份副本。
class Person {
public :
string _name;
};
class Student : virtual public Person {
protected :
int _num;
};
class Teacher : virtual public Person {
protected :
int _id;
};
class Assistant : public Student, public Teacher {
protected :
string _majorCourse;
};
int main () {
Assistant a;
a._name = "bob" ;
return 0 ;
}
注意 :虚继承会增加底层实现的复杂度及性能开销,除非必要,不建议随意设计菱形继承结构。
7.3 多继承中指针偏移问题 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 ;
}
八、继承和组合
继承 是 is-a 关系,派生类对象也是基类对象。属于白箱复用,耦合度高。
组合 是 has-a 关系,对象中包含另一个对象。属于黑箱复用,耦合度低。
原则 :优先使用组合,而非继承。组合能更好地封装内部细节,降低依赖。当然,若确实需要多态或明确的 is-a 关系,继承仍是必要的。
就像 stack 继承 vector 一样,标准库也提供了组合的实现方式供选择。
总结 继承为后续学习多态打下了坚实基础,是深入理解 C++ 面向对象编程的关键一步。合理运用继承机制,能够让我们设计出更加优雅、可扩展的代码结构。在实际开发中,要注意避免过度继承带来的复杂性和维护成本,优先考虑组合模式。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online