跳到主要内容吃透 C++ 继承:作用域隐藏、对象转换与默认函数 | 极客日志C++
吃透 C++ 继承:作用域隐藏、对象转换与默认函数
C++ 继承是代码复用的核心机制,理解权限规则、对象转换与隐藏对避坑至关重要。派生类的构造、拷贝、赋值和析构都必须先处理基类部分,顺序上构造先父后子、析构先子后父。此外,私有构造函数或 final 关键字可创建不可继承的类。
吃透 C++ 继承:作用域隐藏、对象转换与默认函数
在写 Student 和 Teacher 类时,如果各自定义姓名、地址、身份认证,改了就得同步两处。C++ 的继承正是用来解决这种重复的。
继承的概念与定义
继承允许在保持原有类特性的基础上扩展,新类称为派生类。
简单说,把公共部分抽成父类(基类),子类(派生类)复用它们,再添加自己独有的成员。
核心概念
- 基类:存放公共成员的类,比如
Person(姓名、地址、身份认证函数)。
- 派生类:继承基类并扩展,比如
Student 加学号,Teacher 加职称。
- 本质:派生类是基类的'扩展',能直接使用基类的 public/protected 成员。

定义格式
关键写法:class Student : public Person。继承方式放在冒号后面。

看一个实际例子:
#include <iostream>
using namespace std;
class Person {
public:
void identity() { cout << "void identity() " << _name << endl; }
void age() { cout << _age << endl; }
protected:
string _name = "张三";
string _address;
string _tel = "123456";
private:
int _age = 18;
};
: Person {
:
{ cout << << _tel << endl; }
:
_stuid;
};
: Person {
:
{ cout << << _tel << endl; }
:
string title;
};
{
Student s;
Teacher t;
s.();
t.();
s.();
t.();
;
}
class
Student
public
public
void study()
"void study() "
protected
int
class
Teacher
public
public
void teaching()
"void teaching() "
protected
int main()
identity
identity
study
teaching
return
0
继承方式与访问权限
派生类中成员的访问权限由基类访问限定符和继承方式共同决定,取更严格的那个(public > protected > private)。
| 基类成员类型 | public 继承 | protected 继承 | private 继承 |
|---|
| public 成员 | 子类中为 public | 子类中为 protected | 子类中为 private |
| protected 成员 | 子类中为 protected | 子类中为 protected | 子类中为 private |
| private 成员 | 不可见 | 不可见 | 不可见 |
- 基类的 private 成员在派生类中无论以什么方式继承都不可访问,但它确实被继承到了派生类对象中,只是语法上限制访问。
- 如果想让成员在类外不可直接访问,但在派生类中可访问,应定义为 protected。
- class 默认继承方式是 private,struct 默认是 public;最好显式写出继承方式。
- 实际开发中几乎只用 public 继承,protected/private 继承极少用,且维护性差。
#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;
}
基类是类模板时,调用成员需要指定类域(如 CONTAINER<T>::push_back),因为模板按需实例化,直接写 push_back 可能找不到。
基类与派生类的对象转换
派生类对象可以隐式转换成基类对象/指针/引用,反过来则不行。
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;
}
这种转换被称为'切片':把派生类对象中基类部分切出来,基类指针或引用就指向这部分。它和普通的隐式类型转换不一样——不会产生临时对象,因此引用可以直接绑定而不需要 const。
反过来,如果把基类指针强制转成派生类指针,前提是这个基类指针本来就指向一个派生类对象,否则不安全。后面讲 dynamic_cast 会涉及。
继承中的作用域:同名隐藏
基类和派生类有独立作用域。如果出现同名的成员变量或函数,派生类的成员会隐藏基类的同名成员,这就是隐藏规则。
变量隐藏
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;
};
要访问被隐藏的基类成员,必须加上类域限定符 基类::成员名。
函数隐藏
更常见也更坑的情况:只要函数名相同,哪怕参数不同,派生类函数也会隐藏基类的所有同名函数。
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;
}
这道题选 B 和 A,原因就是函数隐藏:因为 B 中有 fun(int),基类的 fun() 就被隐藏了,所以 b.fun() 会报错。
实际开发中最好避免定义同名成员。如果不可避免地同名,记得显式加上作用域。
派生类的默认成员函数
派生类默认生成的构造函数、拷贝构造、赋值重载、析构,行为都遵循同一个原则:先处理基类部分,再处理派生类自己的成员。
构造函数
派生类的构造函数必须调用基类的构造函数初始化基类部分。如果基类有默认构造函数,派生类构造函数可以不用显式调用;如果没有,则必须在初始化列表中显式调用。
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;
};
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()" << _name << endl;
}
protected:
int _num;
string _address;
};
初始化顺序是先执行基类构造,再执行派生类自己的初始化列表,最后是构造体。
拷贝构造
派生类拷贝构造同样要调用基类的拷贝构造,且基类拷贝构造的参数通常是 const 基类&,而派生类对象可以隐式转换过去(切片)。
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;
};
赋值运算符重载
派生类的 operator= 会隐藏基类的同名函数,因此必须在内部显式调用基类的 operator=,否则会死循环。
class Person {
public:
Person& operator=(const Person& p) {
cout << "Person::operator=" << 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;
};
析构函数
派生类析构函数执行完后,编译器会自动调用基类析构函数。我们不需要显式调用,否则可能导致重复释放。
析构的调用顺序和构造相反:先子后父。这是因为如果先析构了基类,派生类析构时无法访问基类成员,可能出错。
class Person {
public:
~Person() { cout << "~Person()" << endl; }
protected:
string _name;
};
class Student : public Person {
public:
~Student() {
cout << "~Student()" << endl;
}
protected:
int _num;
string _address;
};
析构函数名会被编译器处理成 destructor,所以派生类析构和基类析构也构成隐藏。这也是多态中析构函数要声明为 virtual 的原因之一,但那是后面的话题。
实现一个不能被继承的类
方法一:私有构造函数(C++98)
将基类的构造函数设为 private,派生类无法调用它,也就无法实例化。
class Base {
public:
void func() { cout << "Base::func" << endl; }
private:
Base() {}
};
class Derive : public Base {};
方法二:final 关键字(C++11)
class Base final {
public:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
};
class Derive : public Base {};
围绕继承,核心就是复用与扩展。吃透 public 继承的权限规则、对象转换、同名隐藏以及默认成员函数的处理顺序,就能避开大多数基础陷阱。总结一下:
- 用 public 继承实现复用,清楚 public/protected/private 的不同访问权限。
- 派生类对象可以安全转为基类引用/指针,反之不安全。
- 同名成员会隐藏,需要时通过
基类::成员 显式访问。
- 派生类默认成员函数必须处理基类部分,初始化顺序构造先父后子,析构先子后父。
- 私有构造函数或 final 都可以阻止类被继承。
相关免费在线工具
- 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