跳到主要内容
C++ 继承机制核心详解 | 极客日志
C++
C++ 继承机制核心详解 C++ 继承是面向对象复用的关键。文章涵盖继承概念、访问控制变化、对象赋值切片、作用域隐藏、默认成员函数调用顺序、友元与静态成员特性,以及菱形继承的二义性与虚拟继承解决方案。强调组合优于继承的设计原则,帮助开发者理解内存布局与多态基础。
FrontendX 发布于 2026/3/15 更新于 2026/4/23 0 浏览1. 继承的概念和定义
继承 是面向对象程序设计中代码复用的重要手段。它允许我们在保持原有类特性的基础上进行扩展,从而产生新的类(派生类)。继承体现了由简单到复杂的认知过程,实现了类设计层次的复用。
1.1 继承的概念
以前我们接触的复用多是函数复用,而继承则是类级别的复用。通过继承,派生类可以拥有基类的成员,并在此基础上添加新功能。
1.2 继承定义
1.2.1 继承的格式
1.2.2 继承基类成员访问方式的变化
在继承过程中,基类成员的访问权限可能会发生变化。这里有一个关键点:基类的 private 成员在派生类中无论以什么方式继承都是不可见的 。这里的不可见是指语法上限制派生类对象不能在类内或类外直接访问它,尽管这些成员实际上被继承到了派生类对象中。
如果基类成员不想在类外直接被访问,但需要在派生类中访问,应定义为 protected。保护成员限定符是因继承才出现的概念。
注意 :基类其他成员在子类的访问方式遵循 Min(成员在基类的访问限定符,继承方式) 原则,即 public > protected > private。使用关键字 class 时默认继承方式是 private,使用 struct 时默认是 public。不过最好显式写出继承方式。实际运用中一般使用 public 继承,很少使用 protected/private 继承,因为后者继承下来的成员只能在派生类内部使用,扩展维护性不强。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public :
void Print () { cout << "name: >" << _name << endl; cout << "age: >" << _age << endl; }
private :
string _name = "李华" ;
int _age = 25 ;
};
class Student : public Person {
:
_stuid;
_major;
};
{
Student s1;
s ();
s _name = ;
;
}
public
int
int
int main ()
1.
Print
1.
"张三"
return
0
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public :
void Print () { cout << "name: >" << _name << endl; cout << "age: >" << _age << endl; }
protected :
string _name = "李华" ;
private :
int _age = 25 ;
};
class Student : public Person {
public :
void Function () {
_name = "张三" ;
Print ();
}
private :
int _stuid;
int _major;
};
int main () {
Student s1;
s1.F unction();
return 0 ;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public :
string _name = "张三" ;
int _age;
};
class Student : Person {
};
int main () {
Student s1;
s1. _name = "李四" ;
return 0 ;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
struct Student : Person {
};
int main () {
Student s1;
s1. _name = "李四" ;
return 0 ;
}
2. 基类和派生类对象赋值转换
派生类对象 可以赋值给基类的对象、指针或引用 。形象地说,这是将派生类中的父类部分'切割'下来进行赋值,称为切片(Slicing) 。
基类对象不能赋值给派生类对象 。
基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用 ,但必须确保基类指针实际指向的是派生类对象,否则不安全。
2.1 派生类对象赋值给基类对象 class Person {
protected :
string _name = "李华" ;
string _sex = "男" ;
int _age = 18 ;
};
class Student : public Person {
private :
int _Number = 1 ;
};
int main () {
Student s1;
Person p1 = s1;
return 0 ;
}
2.2 派生类对象赋值给基类对象的引用 #define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public :
void Change () { _name = "李红" ; _sex = "女" ; _age = 17 ; }
protected :
string _name = "李华" ;
string _sex = "男" ;
int _age = 18 ;
};
class Student : public Person {
private :
int _Number = 1 ;
};
int main () {
Student s2;
Person& p2 = s2;
p2. Change ();
return 0 ;
}
2.3 派生类对象赋值给基类对象的指针 #define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public :
void Change () { _name = "李红" ; _sex = "女" ; _age = 17 ; }
protected :
string _name = "李华" ;
string _sex = "男" ;
int _age = 18 ;
};
class Student : public Person {
private :
int _Number = 1 ;
};
int main () {
Student s3;
Person* p3 = &s3;
p3->Change ();
return 0 ;
}
3. 继承中的作用域 在继承体系中,基类与派生类都有独立的作用域。如果子类与父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫做隐藏 (也叫重定义)。成员函数隐藏只要函数名相同即可。
建议 :在实际开发中,继承体系里最好不要定义同名的成员。
如果在子类成员函数中需要访问基类的同名成员,可以使用域作用限定符 ::。
3.1 代码示例 #define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
protected :
string _name;
string _sex;
int _age;
int _number = 125 ;
};
class Student : public Person {
public :
void Print () {
cout << "class Student: >" << _number << endl;
cout << "class Person: >" << Person::_number << endl;
}
protected :
int _number = 128 ;
};
int main () {
Student s1;
s1. Print ();
return 0 ;
}
3.2 函数隐藏面试题 #define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class A {
public :
void func () { cout << "func()" << endl; }
};
class B : public A {
public :
void func (int i) { A::func (); cout << "func(int i)->" << i << endl; }
};
int main () {
B b;
b.func (10 );
return 0 ;
}
这道题的答案选 D (构成隐藏) 。因为在继承关系中,只要基类和派生类中的成员函数名一致,就会构成隐藏,而不是重载。
3.3 如何访问基类函数 #define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class A {
public :
void func () { cout << "func()" << endl; }
};
class B : public A {
public :
void func (int i) { A::func (); cout << "func(int i)->" << i << endl; }
};
int main () {
B b;
b.func ();
b.A::func ();
return 0 ;
}
当去掉参数后,派生类会屏蔽对基类成员的访问。若要访问基类成员函数的话,则需要指定域。
4. 派生类的默认成员函数
4.1 父类和子类均有默认构造函数 当派生类没有定义构造函数时,编译器会生成默认的构造函数,并自动调用基类的默认构造函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Person {
public :
Person (const char * name = "父亲" ) :_name(name) { cout << "Person()" << endl; }
Person (const Person &p) :_name(p._name) { cout << "Person(const Person & p)" << endl; }
Person& operator =(const Person & p) {
if (this != &p) this ->_name = p._name;
return *this ;
}
~Person () { cout << "~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
protected :
int _numerber;
string s;
};
int main () {
Student s;
return 0 ;
}
4.2 子类没有默认构造函数 如果基类没有默认构造函数(例如只有带参构造函数),则派生类必须在初始化列表中显式调用基类构造函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Person {
public :
Person (const char * name = "父亲" ) :_name(name) { cout << "Person()" << endl; }
Person (const Person &p) :_name(p._name) { cout << "Person(const Person & p)" << endl; }
Person& operator =(const Person & p) {
if (this != &p) this ->_name = p._name;
return *this ;
}
~Person () { cout << "~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) {}
protected :
int _number;
string _str;
};
int main () {
Student s1 (25 , "孩子" , "父亲" ) ;
return 0 ;
}
4.3 子类有默认拷贝构造函数 如果子类没有定义拷贝构造函数,编译器会生成默认的,并调用基类的拷贝构造函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Person {
public :
Person (const char * name = "父亲" ) :_name(name) { cout << "Person()" << endl; }
Person (const Person &p) :_name(p._name) { cout << "Person(const Person & p)" << endl; }
~Person () { cout << "~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) {}
protected :
int _number;
string _str;
};
int main () {
Student s1 (25 , "孩子" , "父亲" ) ;
Student s2 (s1) ;
return 0 ;
}
4.4 子类有无默认拷贝构造函数 如果子类定义了拷贝构造函数,则不会生成默认的。此时需要注意手动调用基类的拷贝构造函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Person {
public :
Person (const char * name = "父亲" ) :_name(name) { cout << "Person()" << endl; }
Person (const Person &p) :_name(p._name) { cout << "Person(const Person & p)" << endl; }
~Person () { cout << "~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) {}
Student (const Student & s) :Person (s), _number(s._number), _str(s._str) { }
protected :
int _number;
string _str;
};
int main () {
Student s1 (25 , "孩子" , "父亲" ) ;
Student s2 (s1) ;
return 0 ;
}
4.5 子类有默认的赋值运算符重载 如果子类未定义赋值运算符,编译器会生成默认的,并调用基类的赋值运算符。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Person {
public :
Person (const char * name = "父亲" ) : _name(name) { cout << "Person()" << endl; }
Person (const Person& p) : _name(p._name) { cout << "Person(const Person& p)" << endl; }
Person& operator =(const Person& p) {
if (this != &p) _name = p._name;
return *this ;
}
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) { }
protected :
int _number;
string _str;
};
int main () {
Student s1 (25 , "孩子 1" , "父亲 1" ) ;
Student s2 (31 , "孩子 2" , "父亲 2" ) ;
s2 = s1;
return 0 ;
}
4.6 子类无默认的赋值运算符重载 如果子类定义了赋值运算符,则不会生成默认的。此时需要手动处理基类部分的赋值。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Person {
public :
Person (const char * name = "父亲" ) : _name(name) { cout << "Person()" << endl; }
Person (const Person& p) : _name(p._name) { cout << "Person(const Person& p)" << endl; }
Person& operator =(const Person& p) {
if (this != &p) _name = p._name;
return *this ;
}
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) { }
Student (const Student& s) :Person (s), _number(s._number), _str(s._str) { }
Student& operator =(const Student& s) {
if (this != &s) {
this ->Person::operator =(s);
_number = s._number;
_str = s._str;
}
return *this ;
}
protected :
int _number;
string _str;
};
int main () {
Student s1 (25 , "孩子 1" , "父亲 1" ) ;
Student s2 (31 , "孩子 2" , "父亲 2" ) ;
s2 = s1;
return 0 ;
}
4.7 子类中的析构函数 子类的析构函数也会隐藏父类的析构函数。在多态场景下,通常需要将基类析构函数声明为 virtual。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Person {
public :
Person (const char * name = "父亲" ) : _name(name) { cout << "Person()" << endl; }
virtual ~Person () { cout << " ~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) { cout << "Student()" << endl; }
~Student () { cout << "~Student()" << endl; }
protected :
int _number;
string _str;
};
int main () {
Student s (25 , "父亲" , "孩子" ) ;
return 0 ;
}
4.8 构造与析构函数的顺序
构造顺序 :先调用基类构造函数,再调用派生类构造函数。
析构顺序 :先调用派生类析构函数,再调用基类析构函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Person {
public :
Person (const char * name = "父亲" ) : _name(name) { cout << "Person()" << endl; }
virtual ~Person () { cout << " ~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
Student (int number, const char * str, const char * name) :Person (name), _number(number), _str(str) { cout << "Student()" << endl; }
~Student () { cout << "~Student()" << endl; }
protected :
int _number;
string _str;
};
int main () {
Student s (25 , "父亲" , "孩子" ) ;
return 0 ;
}
4.9 总结
派生类的构造函数必须调用基类的构造函数初始化基类部分。如果基类没有默认构造函数,则必须在初始化列表中显式调用。
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
派生类的 operator= 必须要调用基类的 operator= 完成基类的复制。
派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。保证派生类对象先清理派生类成员再清理基类成员。
派生类对象初始化先调用基类构造再调派生类构造。
派生类对象析构清理先调用派生类析构再调基类的析构。
为了实现多态,父类析构函数通常需要加 virtual,否则子类析构函数和父类析构函数构成隐藏关系。
5. 继承与友元 友元函数不受继承访问权限的限制,可以直接访问基类的私有成员。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Student ;
class Person {
friend void Display (const Person& p, const Student& s) ;
public :
protected :
string _name = "张三" ;
};
class Student : public Person {
protected :
int _number;
};
void Display (const Person& p, const Student& s) {
cout << "姓名:" << p._name << endl;
cout << "学号:" << s._number << endl;
}
6. 继承与静态成员 静态成员属于类本身,不随对象创建而重复分配内存。在继承体系中,静态成员是共享的。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Person {
public :
Person () { cout << "Person()" << endl; ++_count; }
protected :
string _name;
public :
static int _count;
};
class Student : public Person {
public :
Student () { cout << "Student()" << endl; ++_count; }
protected :
string _name;
};
class Graduate : public Student {
public :
Graduate () { cout << "Graduate()" << endl; ++_count; }
protected :
string _seminarcourse;
};
int Person::_count = 0 ;
int main () {
Person p;
Student s;
Graduate g;
cout << "Person: >" << p._count << endl;
cout << "Student: >" << s._count << endl;
cout << "Graduate: >" << g._count << endl;
return 0 ;
}
7. 菱形继承与菱形虚拟继承
7.1 单继承
7.2 多继承 一个类可以同时继承多个基类,每个基类的成员都会独立存在于派生类对象中。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Student {
public :
Student () :_number(2024 ), _name("张三" ) { cout << "Student()" << endl; }
protected :
int _number;
string _name;
};
class Teacher {
public :
Teacher () :_subject("高等数学" ) { cout << "Teacher()" << endl; }
protected :
string _subject;
};
class Assistant : public Student, public Teacher {
public :
Assistant () { cout << "Assistant()" << endl; }
};
int main () {
Assistant a;
return 0 ;
}
7.3 菱形继承
7.3.1 菱形继承造成数据冗余 当两个中间类都继承自同一个基类,而最终派生类又同时继承这两个中间类时,基类成员会被复制两份。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class School {
public :
School () :_name("清华大学" ) { cout << "School()" << endl; }
public :
string _name;
};
class Student : public School {
public :
Student () :_id(20222356 ) { cout << "Student()" << endl; }
protected :
int _id;
};
class Teacher : public School {
public :
Teacher () :_subject("高等数学" ) { cout << "Teacher()" << endl; }
protected :
string _subject;
};
class Assistant : public Student, public Teacher {
public :
Assistant () :_majorcourse("离散数学" ) { cout << "Assistant()" << endl; }
protected :
string _majorcourse;
};
int main () {
Assistant a1;
return 0 ;
}
7.3.2 菱形继承会造成二义性的问题 #define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class School {
public :
School () :_name("清华大学" ) { cout << "School" << endl; }
public :
string _name;
};
class Student : public School {
public :
Student () :_id(20222356 ) { cout << "Student()" << endl; }
protected :
int _id;
};
class Teacher : public School {
public :
Teacher () :_subject("高等数学" ) { cout << "Teacher" << endl; }
protected :
string _subject;
};
class Assistant : public Student, public Teacher {
public :
Assistant () :_majorcourse("离散数学" ) { cout << "Assistant()" << endl; }
protected :
string _majorcourse;
};
int main () {
Assistant a1;
a1. Student::_name = "北京大学" ;
a1. Teacher::_name = "兰州大学" ;
return 0 ;
}
7.3.3 解决菱形继承的方案(虚拟继承) 在中间类继承基类时使用 virtual 关键字,可以确保基类在派生类中只有一份实例。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class School {
public :
School () :_name("清华大学" ) { cout << "School" << endl; }
public :
string _name;
};
class Student : virtual public School {
public :
Student () :_id(20222356 ) { cout << "Student()" << endl; }
protected :
int _id;
};
class Teacher : virtual public School {
public :
Teacher () :_subject("高等数学" ) { cout << "Teacher" << endl; }
protected :
string _subject;
};
class Assistant : public Student, public Teacher {
public :
Assistant () :_majorcourse("离散数学" ) { cout << "Assistant()" << endl; }
protected :
string _majorcourse;
};
int main () {
Assistant a1;
a1. _name = "北京大学" ;
return 0 ;
}
7.4 虚拟继承原理 虚拟继承通过引入虚基表指针来管理共享的基类实例,确保内存布局中基类成员的唯一性。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class A {
public :
int _a;
};
class B : public A {
public :
int _b;
};
class C : public A {
public :
int _c;
};
class D : public B, public C {
public :
int _d;
};
int main () {
D d;
d.B::_a = 1 ;
d.C::_a = 2 ;
return 0 ;
}
8. 继承的反思与总结
public 继承 是 is-a 的关系,即每一个派生类对象都是一个基类对象。
组合 是一种 has-a 的关系。
优先使用对象组合,而不是类继承 。继承允许你根据基类的实现来定义派生类的实现,这被称为白箱复用。基类的改变会对派生类有很大影响,耦合度高。对象组合要求被组合的对象具有良好定义的接口,被称为黑箱复用,耦合度低,代码维护性好。
实际尽量多用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地,有些关系就适合继承,另外要实现多态也必须要继承。类之间的关系可以用继承,也可以用组合,就用组合。
相关免费在线工具 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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online