C++继承是通过派生类继承基类的成员,避免了代码冗余,提升了代码复用性。继承有public、protected和private三种方式,影响成员的访问权限。继承支持多态性,使得同一接口能表现出不同的行为。继承和组合各有优缺点,继承适用于“is-a”关系,而组合适用于“has-a”关系。在使用时需根据需求选择合适的方式,避免过度依赖继承,保持代码的可维护性。
继承机制是面向对象程序设计使代码可以复用的重要手段。它允许陈煦园在保持原有类特性的基础上进行扩展,增加功能,产生新的类,这个新的类就叫做派生类。 继承体现了面向对象程序设计的层次结构,体现了由简单到复杂的过程。以前我们所接触的复用都是函数层次的复用(函数调用,模板使用)。现在的继承是类层次设计的复用。
Person类,像是一个“通用模板”,包含所有人的共有属性,老师和学生就可以从这个类继承所有这些基本信息,轻松避免代码冗余。
Person类只负责提供基本的属性,而各自的特有属性和功能(如授课和学习)则由老师和学生各自负责,这样既不重复也能满足不同需求。
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
void Print()
{
cout << "姓名: " << _name << endl;
cout << "年龄: " << _age << endl;
}
protected:
int _age = 20;
string _name = "DaiTou";
};
class Student : public Person
{
public:
void Study()
{
cout << _name << " 正在学习中..." << endl;
}
protected:
int _stuid = 2025101010;
};
class Teacher : public Person
{
public:
void Teach()
{
cout << _name << " 正在授课中..." << endl;
}
protected:
string _title = "教授";
};
int main()
{
Student s;
s.Print();
s.Study();
Teacher t;
t.Print();
t.Teach();
return 0;
}我们来看这段代码,这其中属于老师和人的公共属性有年龄,和人名,属于他们各自的自己的属性有学生的学号和老师的职称。

上述中Person类就是父类,也叫做基类;Student类和Teacher类叫做子类,也叫做派生类。
继承方式: public继承,protected继承,private继承。 继承使用方法: class 子类名 :继承方式 父类名


类成员 / 继承方式 | public继承 | protected继承 | private继承 |
|---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
这个基类的private成员和派生类中原有的private成员不同,派生类原有的private成员可以在类里面进行访问,但是在类外面不能访问。
因为protected成员在基类中的里面可以访问,在基类的外面不可以访问(protected保护成员限定符是因为继承出现的)
public > protected > private
#include <iostream>
#include <vector>
using namespace std;
//=========================
// 自定义命名空间 dh
// 防止与标准库或其他代码命名冲突
//=========================
namespace dh
{
//=========================
// 类模板:stack<T>
// 模拟栈(stack)的实现
// 继承自 std::vector<T>,利用其底层动态数组特性
//=========================
template<class T>
class stack : public std::vector<T> // 继承标准库 vector<T>
{
public:
// 入栈操作:在 vector 尾部插入元素
void push(const T& x)
{
vector<T>::push_back(x);
}
// 出栈操作:删除 vector 尾部元素
void pop()
{
vector<T>::pop_back();
}
// 获取栈顶元素:返回 vector 的最后一个元素的引用
T& top()
{
return vector<T>::back();
}
// 判断栈是否为空
bool empty()
{
return vector<T>::empty();
}
};
}
//=========================
// 主函数入口
//=========================
int main()
{
dh::stack<int> st; // 定义一个整型栈对象
st.push(1); // 压入元素 1
st.push(2); // 压入元素 2
// 循环打印并弹出栈顶元素
while (!st.empty())
{
cout << st.top() << " "; // 输出当前栈顶元素
st.pop(); // 弹出栈顶元素
}
return 0;
}其中当派生类的对象赋值给基类的对象是,派生类中多出来的成员会被切掉,这叫做切片。派生类中基类的那一部分进行赋值。 但是当用基类指针或引用指向派生类对象时,并不会发生切片,只是“看不到”派生类新增的部分,但派生类对象仍然完整地存在内存中。这时通过虚函数可以实现多态访问。
class Base {
public:
int a;
};
class Derived : public Base {
public:
int b;
};
int main() {
Derived d;
d.a = 1;
d.b = 2;
Base b = d; // ❌ 发生对象切片
cout << b.a << endl; // 输出 1
// b 中没有 b.b 这个成员,派生部分被“切掉”了
}Derived d;
Base* pb = &d; // ✅ 没有切片
Base& rb = d; // ✅ 没有切片此时 pb 和 rb 只是“看不到”派生类新增的部分, 但派生类对象仍然完整地存在内存中。 这时通过虚函数可以实现多态访问。


#include <iostream>
#include <string>
using namespace std;
//基类
class Person
{
public:
void Print()
{
cout << "姓名: " << _name << endl;
cout << "年龄: " << _age << endl;
}
protected:
int _age = 20;
string _name = "DaiTou";
};
//派生类
class Student : public Person
{
public:
void Study()
{
cout << _name << " 正在学习中..." << endl;
}
protected:
int _stuid = 2025101010;
};
class Teacher : public Person
{
public:
void Teach()
{
cout << _name << " 正在授课中..." << endl;
}
protected:
string _title = "教授";
};
int main()
{
Student s;
Teacher t;
// ======= 基类指针指向派生类对象 =======
Person* p1 = &s; // 基类指针指向 Student 对象
Person* p2 = &t; // 基类指针指向 Teacher 对象
cout << "基类指针访问派生类对象(Student):" << endl;
p1->Print(); // ✅ 可以访问基类成员函数
// p1->Study(); // ❌ 错误:基类指针不能访问派生类独有成员
cout << endl << "基类指针访问派生类对象(Teacher):" << endl;
p2->Print();
// p2->Teach(); // ❌ 同理,不可访问派生类特有函数
cout << endl;
// ======= 基类引用指向派生类对象 =======
Person& rp1 = s; // 基类引用绑定到 Student
Person& rp2 = t; // 基类引用绑定到 Teacher
cout << "基类引用访问派生类对象(Student):" << endl;
rp1.Print(); // ✅ 可以调用基类成员
cout << endl << "基类引用访问派生类对象(Teacher):" << endl;
rp2.Print(); // ✅ 依然可以访问基类成员
return 0;
}
#include <iostream>
#include <string>
using namespace std;
// 基类
class Person
{
public:
void Print()
{
cout << "基类:年龄:" << _age << endl;
cout << "基类:姓名:" << _name << endl;
}
protected:
int _age = 20;
string _name = "DaiTou";
};
// 派生类
class Student : public Person
{
public:
void Print()
{
cout << "派生类:年龄:" << _age << endl;
cout << "派生类:姓名:" << _name << endl;
cout << "--- 调用基类 Print() ---" << endl;
Person::Print();
}
protected:
int _age = 666; // 派生类自己的年龄,隐藏基类 _age
int _stuid = 2025101010;
};
int main()
{
Student s;
cout << "--- 调用派生类 Print() ---" << endl;
s.Print(); // 调用 Student::Print
cout << "--- 直接调用基类 Print() ---" << endl;
s.Person::Print(); // 调用基类 Print
return 0;
}
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;
}A::fun() 和 B::fun(int),B 类中定义了一个与基类同名的函数 fun。在 C++ 中,如果派生类中定义了一个与基类同名的函数,会隐藏基类所有同名函数(不管参数列表是否相同)。 注意:这不是重载(overload),因为重载是在同一个类中根据参数类型或个数区分函数。
b.fun(10); ✅ 可以正常调用 B::fun(int) 但是b.fun(); ❌因为编译器找不到匹配的函数因为基类 A::fun() 被 B::fun(int) 隐藏了。
class B : public A
{
public:
using A::fun; // 引入基类 fun() 到当前作用域
void fun(int i)
{
cout << "func(int i)" << i << endl;
}
};此时就能够同时调用b.fun(10); b.fun();

6个默认成员函数,意思就是我们不写,编译器也会自动生成,那么他们分别是如何生成的呢?
// 基类
class Person
{
public:
// 构造函数:如果没有传递 name 参数,则使用默认值 "peter"
Person(const string& name = "peter")
: _name(name) // 使用初始化列表初始化 _name
{
cout << "Person()" << endl; // 输出提示,表示基类构造函数被调用
}
private:
string _name; // 姓名属性,用于存储人的名字
};
// 派生类
class Student : public Person
{
public:
// 构造函数:接受 name 和 id 参数来初始化对象
Student(const string& name, int id)
: _id(id) // 初始化派生类的成员 _id
{
cout << "Student()" << endl; // 输出提示,表示派生类构造函数被调用
}
private:
int _id; // 学号属性,用于存储学生的学号
};Person(const string& name = "peter");然后使用初始化列表来初始化成员变量_name,即_name会被赋予传入name参数值,如果没用传递参数,默认为peter。Student(const string& name, int id)会接受两个参数,name和id用来初始化对象,值得注意的是在这个构造函数里面,基类的构造函数没用显示调用,但是由于基类的构造函数有默认参数,编译器会自动调用基类构造函数来初始化基类的_name。
//基类
class Person
{
public:
// 构造函数
// 如果没有传递 name 参数,则默认使用 "peter"
Person(const string& name)
: _name(name) // 使用初始化列表初始化 _name
{
cout << "Person()" << endl; // 构造函数的输出,表示对象创建
}
private:
string _name; // 姓名属性,存储人的名字
};
//派生类
class Student : public Person
{
public:
//构造函数
Student(const string& name, int id)
:Person(name) //调用基类的构造函数初始化基类的那一部分成员
, _id(id) //初始化派生类的成员
{
cout << "Student()" << endl;
}
private:
int _id; //学号
};
拷贝构造函数也属于构造函数,如果我们在进行派生类的拷贝构造的时候没有进行显式调用,编译器就会去调用基类的默认构造函数去完成对基类部分的初始化,这时候基类中的数据就不是我们想要进行拷贝的数据,而是初始化的数据。
class Person
{
public:
// 构造函数
// 如果没有传递 name 参数,则默认使用 "peter"
Person(const string& name)
: _name(name) // 使用初始化列表初始化 _name
{
cout << "Person()" << endl; // 构造函数的输出,表示对象创建
}
//拷贝构造函数
Person(const Person& p)
:_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
private:
string _name; // 姓名属性,存储人的名字
};
class Student : public Person
{
public:
//构造函数
Student(const string& name, int id)
:Person(name)
,_id(id) //初始化派生类的成员
{
cout << "Student()" << endl;
}
//拷贝构造函数
Student(const Student& s)
:Person(s) //显示调用基类的拷贝构造函数完成基类成员的拷贝构造
, _id(s._id) //拷贝构造派生类的成员
{
cout << "Student(const Student& s)" << endl;
}
private:
int _id; //学号
};
int main()
{
Student s("DaiTou", 20231010);
Student s1(s);
return 0;
}

如果你在派生类的 operator= 中不显示调用基类的 operator=,基类的 _name 就不会被正确赋值,导致 s2 的基类部分依然保持原来的值(可能是默认值或不正确的值)。值得注意的是派生类的赋值运算符隐藏了基类的赋值运算符,所以显示调用基类的operator=,需要指定基类作用域
//基类
class Person
{
public:
// 构造函数
// 如果没有传递 name 参数,则默认使用 "peter"
Person(const string& name = "Peter")
: _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;
}
private:
string _name; // 姓名属性,存储人的名字
};
//派生类
class Student : public Person
{
public:
//构造函数
Student(const string& name, int id)
:Person(name)
,_id(id) //初始化派生类的成员
{
cout << "Student()" << endl;
}
//拷贝构造函数
Student(const Student& s)
:Person(s) //显示调用基类的拷贝构造函数完成基类成员的拷贝构造
, _id(s._id) //拷贝构造派生类的成员
{
cout << "Student(const Student& s)" << endl;
}
//赋值运算符重载函数
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s); //显示调用基类的operator=完成基类成员的赋值
_id = s._id; //完成派生类成员的赋值
}
return *this;
}
private:
int _id; //学号
};
int main()
{
Student s("DaiTou", 20231010);
Student s1(s);
Student s2("BuDai", 20231212);
s1 = s2;
return 0;
}
因为派生类对象可能管理一些资源或成员,在基类析构之前,派生类需要完成自己的资源清理。如果先调用基类的析构函数,派生类的析构函数可能会尝试访问已经销毁的基类成员,这样会导致未定义的行为或错误。C++ 会自动处理析构函数的调用顺序,基类的析构函数不需要在派生类析构函数中显式调用。
//基类
class Person
{
public:
// 构造函数
// 如果没有传递 name 参数,则默认使用 "peter"
Person(const string& name = "Peter")
: _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;
}
//析构函数
~Person()
{
cout << "~Person()" << endl;
}
private:
string _name; // 姓名属性,存储人的名字
};
//派生类
class Student : public Person
{
public:
//构造函数
Student(const string& name, int id)
:Person(name)
,_id(id) //初始化派生类的成员
{
cout << "Student()" << endl;
}
//拷贝构造函数
Student(const Student& s)
:Person(s) //显示调用基类的拷贝构造函数完成基类成员的拷贝构造
, _id(s._id) //拷贝构造派生类的成员
{
cout << "Student(const Student& s)" << endl;
}
//赋值运算符重载函数
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s); //显示调用基类的operator=完成基类成员的赋值
_id = s._id; //完成派生类成员的赋值
}
return *this;
}
//析构函数
~Student()
{
cout << "~Student()" << endl;
//派生类的析构函数会在被调用完成后自动调用基类的析构函数
}
private:
int _id; //学号
};
int main()
{
Student s("DaiTou", 20231010);
Student s1(s);
Student s2("BuDai", 20231212);
s1 = s2;
return 0;
}
定义派生类成员的时候,先调用派生类中基类的那一部分进行初始化,然后在生命周期结束后,由于派生类自己那部分的成员是后定义的,所以先调用派生类的析构函数进行析构,派生类中的在基类先定义那一部分就会调用基类的析构函数后析构。
// 友元函数示例
// 基类 Person
class Person
{
// 友元函数声明:Print 允许访问 Person 类的私有成员
friend void Print(const Person& p); // 友元函数 Print 被声明为 Person 类的友元函数
public:
protected:
// 保护成员:年龄(_age),默认为 20
int _age = 20;
private:
// 私有成员:name 是私有的,只有友元函数或者该类内部可以访问
// 私有成员无法直接通过外部对象访问
string _name = "DaiTou";
};
// 友元函数定义:该函数可以访问 Person 类的私有成员
// 注意:Print 函数是一个全局函数,可以访问 Person 的私有成员(因为它是 Person 的友元)
void Print(const Person& p)
{
cout << p._age << endl; // 访问 Person 类的 _age 成员
cout << p._name << endl; // 访问 Person 类的 _name 成员
}
// 派生类 Student
class Student : public Person
{
// 友元函数声明:Print 也可以访问 Student 类及其基类 Person 的私有成员
friend void Print(const Person& p); // 友元函数 Print 被声明为 Student 类的友元函数
public:
// 公共成员:学号(_stuid),默认为 20251010
int _stuid = 20251010;
protected:
// 保护成员:暂无
};
// 主函数:创建 Person 和 Student 类型的对象,调用 Print 函数
int main()
{
Student s; // 创建一个 Student 类型的对象
Person p; // 创建一个 Person 类型的对象
Print(p); // 调用 Print 函数打印 Person 类型对象 p 的成员
Print(s); // 调用 Print 函数打印 Student 类型对象 s 的成员
return 0;
}
基类定义了static的静态成员,则在整个继承体系中都只有一个这样的成员。无论派生出多少个派生类整个继承体系都只有一个static静态成员。 当我们定义一个_count基类的静态成员变量的时候,我们让它在基类的构造函数和拷贝构造函数进行自增,这样我们就能够在该时刻实时获取已经实例化的基类和派生类的总个数(因为派生类在进行构造的时候会优先调用基类的默认构造完成派生类中在基类的那一部分成员的初始化)
#include <iostream>
using namespace std;
// 基类 Person
class Person
{
public:
// 构造函数,每次创建一个 Person 对象时,_count 自增
Person()
{
_count++;
}
// 静态成员变量:计数器,用于统计创建了多少个 Person 或其派生类对象
static int _count;
};
// 静态成员变量的定义与初始化
int Person::_count = 0; // 该静态变量仅在 Person 类中定义和初始化
// 派生类 Student
class Student : public Person
{
public:
// 默认构造函数
Student()
{
// 每当创建一个 Student 对象时,_count 会在基类 Person 中自增
}
};
int main()
{
Person p; // 创建一个 Person 对象,_count 会自增至 1
Student s1; // 创建一个 Student 对象,_count 会自增至 2
Student s2; // 创建另一个 Student 对象,_count 会自增至 3
Student s3; // 再创建一个 Student 对象,_count 会自增至 4
// 输出 _count,显示创建的 Person 和 Student 对象的总数
cout << "Total number of objects created: " << Person::_count << endl;
return 0;
}
特性 | 继承(is-a) | 组合(has-a) |
|---|---|---|
关系类型 | is-a 关系,即派生类是基类的一种类型 | has-a 关系,即对象包含另一个对象 |
复用类型 | 白箱复用:派生类可以访问基类的实现细节 | 黑箱复用:对象细节不可见,只通过接口访问 |
耦合度 | 高,基类变化会影响派生类 | 低,改变一个对象不会影响另一个对象 |
优点 | 代码复用,继承可以拓展基类功能 | 类之间的关系松散,低耦合,易于维护 |
缺点 | 破坏封装,依赖性强,基类变动可能影响派生类 | 需要定义清晰的接口 |
使用场景 | 类之间是 is-a 关系,且需要多态支持 | 类之间是 has-a 关系,且不需要多态 |
C++中的继承是面向对象编程的核心机制之一,它允许通过父类复用代码并扩展子类的功能。通过继承,子类可以继承父类的属性和方法,并根据需要进行扩展或重写。继承分为三种方式:public、protected和private继承,不同方式会影响访问权限。继承不仅可以带来代码复用,还能够实现多态和重写,使得程序更加灵活和可扩展。然而,继承也有其缺点,如破坏封装性、耦合度较高等,因此在实际设计时需要合理权衡,选择继承还是组合。