跳到主要内容
C++ 继承机制详解:从概念定义到默认成员函数实现 | 极客日志
C++ 算法
C++ 继承机制详解:从概念定义到默认成员函数实现 C++ 继承机制是面向对象代码复用的核心手段。文章解析基类与派生类的定义格式,阐述公有、保护、私有三种继承方式下的成员访问权限变化。重点说明子类对象可隐式转换为基类指针或引用,而反向转换涉及切片风险。针对同名变量与函数,解释派生类如何隐藏基类成员及显式访问方法。详细拆解构造、拷贝、赋值、析构函数的调用顺序,遵循先基类后派生类、先派生后基类的原则。最后介绍利用 final 关键字或私有构造函数实现不可继承类的方案。
CloudNative 发布于 2026/3/21 更新于 2026/5/3 5 浏览C++ 继承机制详解
在面向对象开发中,经常遇到多个类存在大量重复代码的情况。例如 Student 和 Teacher 都需要姓名、地址和身份认证功能,如果各自独立编写,维护成本极高。C++ 的**继承(Inheritance)**机制正是为了解决这类问题而生,它允许子类直接复用父类的成员和方法,并在此基础上扩展专属功能。
一、继承的核心概念与定义
继承是保持原有类特性的基础上进行扩展,产生新类(派生类)的重要手段。
基类(父类) :存放公共成员的类,如 Person 类包含姓名、地址等。
派生类(子类) :继承父类并扩展专属成员的类,如 Student 增加学号。
本质 :子类是父类的'扩展',能直接使用父类的公共/保护成员,无需重复定义。
1. 继承的定义格式
关键在于'继承方式 + 父类名'。例如 class Student : public Person。
#include <iostream>
using namespace std;
class Person {
public :
void identity () { cout << "identity: " << _name << endl; }
void age () { cout << _age << endl; }
protected :
string _name = "张三" ;
string _address;
string _tel = "123456" ;
private :
int _age = 18 ;
};
class Student : public Person {
public :
void study () {
cout << << _tel << endl;
();
}
:
_stuid;
};
: Person {
:
{
cout << << _tel << endl;
}
:
string title;
};
{
Student s;
Teacher t;
s. ();
t. ();
s. ();
t. ();
;
}
"study: "
age
protected
int
class
Teacher
public
public
void teaching ()
"teaching: "
protected
int main ()
identity
identity
study
teaching
return
0
2. 继承方式与成员访问权限 父类成员在子类中的访问权限,取决于'父类的访问限定符'和'继承方式'。核心规则是:最终访问权限 = Min(成员在基类的访问限定符,继承方式) ,即 public > protected > private。
父类成员类型 public 继承(推荐) protected 继承 private 继承 父类 public 成员 子类中为 public 子类中为 protected 子类中为 private 父类 protected 成员 子类中为 protected 子类中为 protected 子类中为 private 父类 private 成员 不可见(不可访问) 不可见(不可访问) 不可见(不可访问)
基类 private 成员在派生类中无论以什么方式继承都是不可见的。虽然它们被物理继承了,但语法上限制访问。
使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认是 public 。实际开发中建议显式写出继承方式,通常使用 public 继承 。
保护成员限定符是因继承才出现的,用于解决基类成员不想在类外直接被访问,但需要在派生类中能访问的场景。
在实际运用中,一般使用 public 继承。Protected/Private 继承下来的成员只能在派生类的类里面使用,扩展维护性不强,不提倡使用。
这里可以用 Stack 的例子展示继承的实际应用:
#include <iostream>
#include <vector>
#include <list>
#include <deque>
using namespace std;
#define CONTAINER vector
namespace MyStack {
template <class T >
class stack : public std::CONTAINER<T> {
public :
void push (const T& x) {
CONTAINER<T>::push_back (x);
}
void pop () { CONTAINER<T>::pop_back (); }
const T& top () { return CONTAINER<T>::back (); }
bool empty () { return CONTAINER<T>::empty (); }
};
}
void Test2 () {
MyStack::stack<int > st;
st.push (1 ); st.push (2 ); st.push (3 );
while (!st.empty ()) {
cout << st.top () << " " ;
st.pop ();
}
}
int main () {
Test2 ();
return 0 ;
}
二、基类与派生类的转换 这是继承的核心特性之一。简单来说:子类对象能隐式转换成父类对象 / 指针 / 引用 ,反之不行。
Upcasting(向上转换) :public 继承的派生类对象可以赋值给基类的指针/引用。这被称为切片(Slicing) ,寓意把派生类中基类那部分切出来。
Downcasting(向下转换) :基类对象不能赋值给派生类对象。基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用,但必须确保基类指针确实指向派生类对象,否则不安全。
class Person {
protected :
string _name;
string _sex;
int _age;
};
class Student : public Person {
public :
int _No;
};
void Test3 () {
Student st;
Person* ppe = &st;
Person& rpe = st;
Person pe = st;
}
为什么? 子类包含'父类部分 + 自己的部分',把子类当父类用,只会用到'父类部分',不会越界;但父类没有子类的成员,强行转换成子类会访问不存在的内容(比如学号),所以禁止。
三、继承中的作用域:同名成员会冲突吗? 父类和子类有独立的作用域,如果出现同名成员 (变量或函数),子类会隐藏 父类的同名成员,这就是**'隐藏规则'**。
派生类和基类有同名成员,派生类成员将屏蔽基类对同名成员的直接访问。
如果是成员函数的隐藏,只需要函数名相同就构成隐藏 ,参数列表不影响。
避坑提醒 :继承体系中,尽量不要定义同名成员。如果必须同名,访问时一定要加父类作用域。
1. 变量隐藏 class Person {
protected :
string _name = "张三" ;
int _num = 123456 ;
};
class Student : public Person {
public :
void Print () {
cout << "子类的_num:" << _num << endl;
cout << "父类的_num:" << Person::_num << endl;
}
protected :
int _num = 111 ;
};
int main () {
Student s;
s.Print ();
}
2. 函数隐藏 比变量隐藏更坑:只要函数名相同,不管参数列表,子类就隐藏父类的函数 。
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.A::fun ();
return 0 ;
}
四、派生类的默认成员函数 子类和普通类一样,有 6 个默认成员函数,但子类的默认成员函数必须先处理父类的部分 。
核心规则 :子类的成员函数 = 父类成员的处理 + 子类成员的处理。
1. 构造函数:先调用父类构造,再初始化子类成员 派生类的构造函数必须调用基类的构造函数 初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表中显示调用。
class Person {
public :
Person (const char * name = "张三" ) :_name(name) {
cout << "Person()" << endl;
}
protected :
string _name;
};
class Student : public Person {
public :
protected :
int _num;
string _address;
};
int main () {
Student s1;
return 0 ;
}
class Person {
public :
Person (const char * name) :_name(name) {
cout << "Person()" << endl;
}
protected :
string _name;
};
class Student : public Person {
public :
Student (const char * name = "张三" , int num = 18 , const char * address = "北京" )
: Person (name), _num(num), _address(address) {
cout << "Student()" << endl;
}
protected :
int _num;
string _address;
};
int main () {
Student s1 ("李四" , 20 , "北京" ) ;
return 0 ;
}
关键顺序 :构造时 '先父后子' —— 父类先初始化,子类才能用父类的成员。
2. 拷贝构造:先拷贝父类,再拷贝子类 派生类的拷贝构造函数必须调用基类的拷贝构造 完成基类的拷贝初始化。
class Person {
public :
Person (const Person& p) :_name(p._name) {
cout << "Person(const Person& p)" << endl;
}
protected :
string _name;
};
class Student : public Person {
public :
Student (const Student& s) : Person (s), _num(s._num), _address(s._address) {
}
protected :
int _num;
string _address;
};
int main () {
Student s1 ("李四" , 20 , "北京" ) ;
Student s2 (s1) ;
return 0 ;
}
3. 赋值重载:先赋值父类,再赋值子类 派生类的 operator= 必须调用基类的 operator= 完成基类的复制 。需要注意的是派生类的 operator= 隐藏了基类的 operator= ,所以显示调用基类的 operator=,需要指定基类作用域 。
class Person {
public :
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& operator =(const Student& s) {
if (this != &s) {
Person::operator =(s);
_num = s._num;
_address = s._address;
}
return *this ;
}
protected :
int _num;
string _address;
};
int main () {
Student s1 ("李四" , 20 , "北京" ) ;
Student s3 ("王五" , 18 , "上海" ) ;
s1 = s3;
return 0 ;
}
4. 析构函数:先析构子类,再自动析构父类 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员 。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
class Person {
public :
~Person () { cout << "~Person()" << endl; }
protected :
string _name;
};
class Student : public Person {
public :
~Student () {
cout << "~Student()" << endl;
}
protected :
int _num;
string _address;
};
int main () {
Student s1 ("李四" , 20 , "北京" ) ;
return 0 ;
}
5. 实现一个不能被继承的类 方法一 :基类的构造函数私有。派生类的构造必须调用基类的构造函数,但是基类的构造函数私有化以后,派生类不可访问也就不能调用了,那么派生类就无法实例化出对象。
class Base {
public :
void func () { cout << "Base::func" << endl; }
protected :
int a = 1 ;
private :
Base () { }
};
class Derive : public Base { };
方法二 :C++11 新增了一个 final 关键字 ,final 修改基类,派生类就不能继承了。
class Base final {
public :
void func5 () { cout << "Base::func5" << endl; }
protected :
int a = 1 ;
};
class Derive : public Base { };
总结 继承的核心是'复用'与'扩展',吃透单继承的权限规则、对象转换、同名隐藏及默认函数逻辑,就能避开多数基础陷阱。
用 public 继承实现代码复用,区分 public/protected/private 的访问权限。
记住'子类能转父类,父类不能转子类',避免不安全转换。
同名成员会隐藏,访问时加父类作用域。
子类默认成员函数要先处理父类部分,尤其是构造和赋值重载。
构造顺序:先父后子,析构顺序:先子后父。
参考文档 相关免费在线工具 加密/解密文本 使用加密算法(如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