跳到主要内容
C++ 继承机制详解:概念、访问控制与菱形继承 | 极客日志
C++
C++ 继承机制详解:概念、访问控制与菱形继承 综述由AI生成 C++ 继承是面向对象程序设计中代码复用的重要手段。文章详细讲解了继承的概念、格式及基类成员访问方式的变化。探讨了派生类对象与基类对象的赋值转换规则,包括切片现象。分析了继承中的作用域隐藏问题及默认成员函数(构造函数、析构函数、拷贝构造、赋值运算符)在继承体系中的调用顺序与行为。重点阐述了菱形继承导致的数据冗余与二义性问题,并给出了虚拟继承的解决方案。最后总结了组合优于继承的设计原则。
DevStack 发布于 2026/3/16 更新于 2026/4/25 2 浏览1. 继承的概念和定义
1.1. 继承的概念
继承 机制是面向对象程序设计使代码可以复用 的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,简称派生类。继承呈现了面向对象程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用 。
1.2. 继承定义
1.2.1. 继承的格式
1.2.2. 继承基类成员访问方式的变化
class Person {
public :
void Print () { cout << "name:>" << _name << endl; cout << "age:>" << _age << endl; }
private :
string _name = "李华" ;
int _age = 25 ;
};
class Student : public Person {
public :
int _stuid;
int _major;
};
int main () {
Student s1;
s ();
s _name = ;
;
}
1.
Print
1.
"张三"
return
0
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 = "张三" ;
_age = 25 ;
Print ();
}
private :
int _stuid;
int _major;
};
int main () {
Student s1;
s1.F unction();
}
#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 = "李四" ;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person {
public :
string _name = "张三" ;
int _age;
};
struct Student : Person { };
int main () {
Student s1;
s1. _name = "李四" ;
}
基类 private 成员 在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中 ,但是语法上限制派生类对象 不管在类里面还是类外面 都不能去访问它。基类 private 成员 在派生类 中是不能被访问 。如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected 。可以看出保护成员限定符是因继承才出现的。基类的私有成员 在子类都是不可见 。基类的其他成员 在子类的访问方式 == Min(成员在基类的访问限定符,继承方式) ,public > protected > private 。使用关键字 class 时默认的继承方式是 private ,使用struct 时默认的继承方式是 public ,不过最好显示的写出继承方式。在实际运用中一般使用都是 public 继承,几乎很少使用 protected/private 继承 ,也不提倡使用 protected/private 继承,因为 protected/private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
2. 基类和派生类对象赋值转换
派生类对象 可以赋值给基类的对象/基类的指针/基类的引用 。形象说法:切片或者切割即将派生类中的父类那一部分切割下来然后进行赋值 。
基类对象不能赋值给派生类对象 。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的 。
2.1. 代码 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;
}
2.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 ();
}
2.3. 代码 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. 继承中的作用域
在继承体系中基类与派生类 都有独立的作用域 。子类与父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,该情况叫做隐藏,也叫重定义 。成员函数隐藏,只要函数名相同即可 。注意在实际中在继承体系里 面最好不要定义同名的成员 。
PS:在子类成员函数中,可以通过使用域作用限定符在访问基类成员 .
3.1. 代码 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 ();
}
3.2. 代码 2
A:编译报错 B:运行报错 C:两个 func 函数构成函数重载 D:两个 func 函数构成隐藏.
#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. 代码 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 ();
}
当这道面试题将 b.func(10) 的中的参数去掉用了以后,那么此时将是多选,选择AD,因为由于创建了对象 b,在继承关系中,派生类与基类存在同名成员函数的话,则派生类会屏蔽对基类成员的访问。若要访问基类成员函数的话,则需要指定域 .
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) {
cout << "Person & operator=(const Person & p)" << endl;
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) {
cout << "Person & operator=(const Person & p)" << endl;
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) :_name(name),_number(number),_str(str) {}
protected :
int _number;
string _str;
};
int main () {
Student s;
return 0 ;
}
#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) {
cout << "Person & operator=(const Person & p)" << endl;
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 ;
}
#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) {
cout << "Person & operator=(const Person & p)" << endl;
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;
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& operator =(const Person & p) {
cout << "Person & operator=(const Person & p)" << endl;
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 , "孩子" , "父亲" ) ;
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& operator =(const Person & p) {
cout << "Person & operator=(const Person & p)" << endl;
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) {}
Student (const Student & s) :Person (s),_number(_number),_str(_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) {
cout << "Person operator=(const Person& p)" << endl;
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(_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) {
cout << "Person operator=(const Person& p)" << endl;
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(_number),_str(_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. 子类中的析构函数 #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 () { 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 (const Student& s) :Person (s),_number(_number),_str(_str) {}
~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; }
~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 (const Student& s) :Person (s),_number(_number),_str(_str) {}
~Student () {
cout << "~Student()" << endl;
}
protected :
int _number;
string _str;
};
int main () {
Student s (25 , "父亲" , "孩子" ) ;
return 0 ;
}
#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 () { 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 (const Student& s) :Person (s),_number(_number),_str(_str) {}
~Student () {
Person::~Person ();
cout << "~Student()" << endl;
}
protected :
int _number;
string _str;
};
int main () {
Student s (25 , "父亲" , "孩子" ) ;
return 0 ;
}
4.9. 总结
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。3. 派生类的 operator=必须要调用基类的 operator=完成基类的复制。4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。5. 派生类对象初始化先调用基类构造再调派生类构造。6. 派生类对象析构清理先调用派生类析构再调基类的析构。7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成 destructor(),所以父类析构函数不加 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. 单继承 #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;
};
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. 解决菱形继承的数据冗余与二义性问题的方案 (虚拟继承) 虚拟继承可以解决菱形继承的二义性与数据冗余问题。在 Student 和 Teacher 的继承 School 时使用虚拟继承,即可解决问题。
#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._b = 3 ;
d.C::_a = 2 ;
d._c = 4 ;
d._d = 5 ;
return 0 ;
}
8. 继承的反思与总结
public 继承是 is-a 的关系即每一个派生类对象都一个基类对象。组合是一种 has-a 的关系
优先使用对象组合,而不是类继承 。继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用 (white-box reuse)。术语'白箱'是相对可视性而言:在继承方式中,基类的内部细节对子类可见,继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用 (black-box reuse),因为对象的内部细节是不可见的。对象只以'黑箱'的形式出现,组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。实际尽量多去用组合 。组合的耦合度低,代码维护性好 。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。
相关免费在线工具 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