封装、继承、多态作为C++三大特性,将数据和操作方法都集合在类中体现了类的封装,而如何体现继承特性,就由下文揭晓
一、基本语法:
继承的基本语法如下:
class 派生类名 : 访问修饰符 基类名
{
// 派生类成员
};
继承方式有三种:public、protected 和 private,它们决定了基类成员在派生类中的访问权限:
|------------|---------------|----------------|--------------|
| 继承方式\基类成员 | 父类的public成员 | 父类的protected成员 | 父类的private成员 |
| public | 在子类是public | 在子类是protected | 在子类是private |
| proteed | 在子类是protected | 在子类是protected | 在子类是private |
| private | 在子类是private | 在子类是private | 在子类是private |
本文拿自定义的people类和student类来讲述:
class People
{
public:
People(const string& name = "") :_name(name)
{
cout << "People构造" << endl;
}
People(const People& rhs)
{
cout << "People的拷贝" << endl;
}
People& operator=(const People& rhs)
{
cout << "People的赋值" << endl;
return *this;
}
~People()
{
cout << "People析构" << endl;
}
void Pfunc()
{
cout << "People func" << endl;
}
private:
string _name;
};
class Student :public People
{
public:
Student(const string& name = "", int id = 0) : _id(id)
{
cout << "Student构造" << endl;
}
Student(const Student& rhs) :People(rhs)
{
cout << "Student的拷贝" << endl;
}
Student& operator=(const Student& rhs)
{
People::operator=(rhs);
cout << "Student的赋值" << endl;
return *this;
}
~Student()
{
cout << "Student析构" << endl;
}
void Sfunc()
{
cout << "People func" << endl;
}
int _id;
};

Student类public继承了People类,所以stu1对象中成员变量不仅有student类自带的_id,还有people类的_name
二、基类和派生类对象赋值转换
- 基类指针和引用可以指向派生类,可以调用基类的函数。
- 派生类的指针和引用也可以指向基类,但是要先强制转化一下,还得小心不要访问未知地址

派生类的指针和引用也可以指向基类,一定要注意不要越界访问。也尽量少用派生类指针访问基类对象
三、重定义
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。
class A {
public:
int _n;
void func() {
cout << "A func n: " << _n << endl;
}
};
class B : public A {
public:
int _n; // 隐藏了A::_n
void func() { // 隐藏了A::func()
cout << "B func n: " << _n << endl;
}
};
int main() {
A a1;
a1._n = 5;
a1.func(); // 输出: A func n: 5
B b1;
b1._n = 10; // 访问B::_n
b1.A::_n = 15; // 显示访问A::_n
b1.func(); // 输出: B func n: 10
b1.A::func(); // 输出: A func n: 15
return 0;
}

由结果可知,对于重名成员,默认的访问是子类同名成员将父类成员隐藏了,除非像代码中显示使用才能访问到父类的成员变量和函数,这样的问题导致了数据的二义性,容易让程序员混淆,所以我们尽量不要使用同名的成员变量,而同名函数可以通过虚函数来很好解决这个问题(下篇文章将讲解多态,虚函数)
注意在实际中在继承体系里面最好不要定义同名的成员。
四、派生类的默认成员函数
构造:初始化要先调用基类的构造初始化,再初始化派生类成员。**子类的有参构造函数必须首先构造父类,**如果父类有默认构造函数,就可以不必自己显示调用了,会自动调用的
拷贝赋值:如果是自己显示调用的话,一定必须要先调用基类的拷贝或者赋值函数。如果不自己手动实现,编译器会自动生成默认的拷贝构造或赋值函数,同样也是先调用基类的拷贝或者赋值函数
析构:先调用派生类的析构再调用基类的析构,无论是手动的还是编译器默认生成都是如此

五、菱形继承问题
问题描述
当我们创建如下继承结构时,会出现菱形继承问题:
#include <iostream>
#include <string>
using namespace std;
class People
{
public:
People(const string& name = "") :_name(name)
{
cout << "People构造: " << _name << endl;
}
string _name;
};
class Student : public People // 普通继承
{
public:
Student(const string& name = "") : People(name)
{
cout << "Student构造" << endl;
}
int _id;
};
class Teacher : public People // 普通继承
{
public:
Teacher(const string& name = "") : People(name)
{
cout << "Teacher构造" << endl;
}
int _tid;
};
class TeachingAssistant : public Teacher, public Student
{
public:
TeachingAssistant(const string& tname = "", const string& sname = "")
: Teacher(tname), Student(sname)
{
cout << "TeachingAssistant构造" << endl;
}
};
问题分析
这种继承结构会导致:
-
数据冗余 :
TeachingAssistant
对象中包含两份People
的成员 -
二义性 :无法直接访问
_name
,需要指定访问路径int main()
{
TeachingAssistant ta("Dr. Smith", "Tom");// 错误:对成员 '_name' 的请求不明确 // cout << ta._name << endl; // 必须指定访问路径 cout << "Teacher name: " << ta.Teacher::_name << endl; cout << "Student name: " << ta.Student::_name << endl; return 0;
}
解决方案:虚拟继承
使用虚拟继承
class Student : virtual public People // 虚拟继承
{
public:
Student(const string& name = "") : People(name)
{
cout << "Student构造" << endl;
}
int _id;
};
class Teacher : virtual public People // 虚拟继承
{
public:
Teacher(const string& name = "") : People(name)
{
cout << "Teacher构造" << endl;
}
int _tid;
};
class TeachingAssistant : public Teacher, public Student
{
public:
TeachingAssistant(const string& name = "")
: People(name), Teacher(name), Student(name)
{
cout << "TeachingAssistant构造" << endl;
}
void printName()
{
cout << "Name: " << _name << endl; // 现在可以直接访问,没有二义性
}
};
虚拟继承的优势
-
消除数据冗余 :
People
子对象在TeachingAssistant
中只有一份 -
消除二义性 :可以直接访问
_name
,无需指定路径int main()
{
TeachingAssistant ta("Dr. Tom Smith");
ta.printName(); // 输出: Name: Dr. Tom Smith// 也可以直接访问 cout << "Direct access: " << ta._name << endl; return 0;
}
底层实现原理
虚基表指针机制
虚拟继承通过以下机制实现:
- 虚基表指针:每个虚拟继承的类都有一个指向虚基表的指针
- 虚基表:存储了到虚基类子对象的偏移量
- 共享访问:通过偏移量找到共享的虚基类子对象


在student类中和teacher类中加入指针,指向虚基表,而虚基表的第二行开始就是想访问People类中成员的偏移量(这里的0x00000010=16字节),从0x001CFEA4到0x001CFEB的差值正好相等。

六、继承和组合:
继承:
优势:
- 可以复用基类代码,减少代码量,并在此基础上易增加新功能
- 支持多态,针对不同的对象调用不同的虚函数
劣势:
- 在一定程度上破坏了类的封装性
- 强耦合,子类依靠基类,改变一个基类可能会影响多个子类
- 有多继承、菱形虚拟继承这些复杂的关系
组合:
优势:
- 不破坏类的封装性,灵活性高
- 低耦合,更易于维护
劣势:
- 代码较多、需要提供更多接口供外部使用
优先使用组合,只在真正需要多态时才使用继承。对于接口使用继承,对于实现使用组合。
七、总结
继承是 C++ 中强大的代码复用机制,但也带来了复杂性。合理使用继承需要注意:
- 谨慎选择继承方式,大多都是public继承
- 避免同名成员引发的隐藏问题
- 正确处理派生类的构造、析构和赋值
- 使用虚拟继承解决菱形继承问题
- 优先考虑组合而非继承
通过合理使用继承,可以构建出清晰、可维护的类层次结构,实现代码的高效复用。
下图是我做的思维导图,供参考复习

结语:
以上就是我分享的C++继承的全部内容了,希望对大家有些帮助,也希望与一样喜欢编程的朋友们共进步
谢谢观看
如果觉得还阔以的话,三连一下,以后会持续更新的,我会加油的
祝大家早安午安晚安