跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 继承机制详解:栈实现、名称隐藏与默认成员函数

C++ 继承机制涉及私有成员可见性、模板栈实现、同名隐藏与重载区别以及派生类默认成员函数的调用顺序。核心在于理解基类成员在子类中的存在性与访问权限差异,掌握构造函数初始化列表的正确写法,明确析构函数先派生后基类的清理顺序,避免常见的作用域查找错误。

信号故障发布于 2026/3/28更新于 2026/7/1729 浏览
C++ 继承机制详解:栈实现、名称隐藏与默认成员函数

C++ 继承机制详解

面向对象编程的三大特性之一是继承。封装屏蔽了底层细节,而继承则建立了类之间的层次关系。今天我们来深入聊聊继承在 C++ 中的具体表现,特别是关于私有成员的可见性、模板栈的实现细节、同名隐藏与重载的区别,以及派生类默认成员函数的调用顺序。

继承的基本定义与访问权限

假设我们有一个 Person 基类,包含姓名、地址等共性信息;而 Student 和 Teacher 作为子类,分别拥有学号和职称。我们可以这样设计:

class Person {
public:
    // 身份认证逻辑
    void identity() { cout << "void identity()" << _name << endl; }
protected:
    string _name = "张三";
    string _address;
    string _tel;
private:
    int _age = 18;
};

class Student : public Person {
public:
    void study() { /* ... */ }
protected:
    int _stuid;
};

class Teacher : public Person {
public:
    void teaching() { /* ... */ }
protected:
    string title;
};

这里有几个关键点需要注意:

  1. 私有成员的可见性:父类的 private 成员在子类中是不可见的。但这并不意味着它们没有被继承。实际上,它们存在于子类对象内存中,只是语法上禁止直接访问。如果尝试在子类中访问 _age,编译器会报错。
  2. 保护成员的作用:如果希望成员在类外不可见,但在子类中可访问,应使用 protected。这个限定符正是为了继承场景设计的。
  3. 有效访问权限:总结来说,父类成员在子类的访问方式取决于 Min(成员在父类的访问限定符,继承方式)。即 public > protected > private。
  4. 默认继承方式:使用 class 时默认为 private 继承,使用 struct 时默认为 public 继承。实践中绝大多数情况使用 public 继承。

基于继承的类模板实现栈

之前我们用容器适配器实现了栈,现在可以用继承的方式来实现。这涉及到模板实例化和作用域解析的问题。

如果在 push 成员函数中不指定类域会发生什么?当实例化 Keda::stack<int> st 时,编译器会自动实例化 vector<int>。如果在 push_back(x) 前不加限定,编译器向上查找时会找不到标识符,因为 push_back 属于基类模板成员。

正确的做法是指定作用域:

#include <vector>
using namespace std;

namespace Keda {
template<class T>
class stack : public std::vector<T> {
public:
    void push(const T& x) {
        vector<T>::push_back(x); // 显式指定基类作用域
    }
    void pop() {
        vector<T>::pop_back();
    }
    const T& top() {
        return vector<T>::back();
    }
    bool empty() {
        return vector<T>::empty();
    }
};
}

int main() {
    Keda::stack<int> st;
    st.push(1);
    st.push(2);
    while (!st.empty()) {
        cout << st.top() << " ";
        st.pop();
    }
    return 0;
}

这里涉及模板的按需实例化。编译器只有在真正用到某个地方(如 pop())时才会实例化对应的代码。如果想通过宏来灵活切换底层容器(如 vector, list, deque),可以在预处理阶段替换 CONTAINER 宏,这样编译后就是具体的容器类型,不会存在未定义的标识符问题。

赋值兼容与对象切片

在公有继承条件下,存在一些转换规则:

  1. 子类转父类:可以将子类对象的一部分'切割'出来拷贝给父类对象,或者赋值给父类指针/引用。这被称为对象切片(Slicing)。例如,将 Student 对象传给 Person 引用,只会保留 Person 部分的数据。
  2. 父类转子类:父类对象不能直接赋值给子类对象。父类指针解引用强制转换为子类指针仅在指针实际指向子类对象时才安全。

继承中的作用域与名称隐藏

当子类与父类拥有同名成员时,会发生**隐藏(Hiding)**现象。这与函数重载不同,重载要求在同一作用域内参数不同,而隐藏发生在不同作用域。

#include <iostream>
#include <string>
using namespace std;

class Person {
protected:
    string _name = "Keda";
    int _num = 111;
};

class Student : public Person {
public:
    void Print() {
        cout << _num << endl; // 打印 999,子类成员屏蔽了父类
        // cout << Person::_num << endl; // 若要访问父类需指定作用域
    }
protected:
    int _num = 999;
};

int main() {
    Student s;
    s.Print();
    return 0;
}

上面代码输出 999。子类成员 _num 直接屏蔽了父类同名的 _num。如果想访问父类的,必须使用作用域解析运算符 Person::_num。

对于函数也是如此。如果基类和派生类都有同名函数,无论参数是否相同,派生类都会隐藏基类函数。这会导致调用时只找到派生类版本,若调用被隐藏的基类函数且未指定作用域,编译器会报错。

派生类的默认成员函数

派生类通常不需要手动编写所有默认成员函数,但理解其背后的行为至关重要。

1. 构造函数

子类构造函数必须初始化父类的那部分成员。如果父类没有默认构造函数,子类必须在初始化列表中显式调用父类构造函数。

// 初始化列表顺序:先基类,再子类成员
Student(const char* name, int num, const char* address)
    : Person(name), _num(num), _address(address) {}

内置类型默认构造不初始化,自定义类型会调用其默认构造。继承自父类的成员被视为一个整体,优先调用父类构造。

2. 拷贝构造函数

对于自定义类型,拷贝构造会递归调用父类的拷贝构造。如果资源管理需要深拷贝,必须自行实现,否则默认生成的浅拷贝可能导致问题。

Student(const Student& s) : Person(s), _num(s._num) {
    cout << "Student Copy Constructor" << endl;
}

3. 赋值运算符重载

子类的 operator= 会隐藏父类的 operator=。因此,在实现时必须显式调用父类的赋值操作,否则父类成员不会被正确更新。

Student& operator=(const Student& s) {
    if (this != &s) {
        Person::operator=(s); // 显式调用基类赋值
        _num = s._num;
    }
    return *this;
}

4. 析构函数

这是最容易出错的地方。析构顺序是先子类后父类。虽然子类析构函数默认生成,但如果父类有虚析构函数或动态分配资源,必须确保父类析构函数能被正确调用。

规定如下:不需要显示调用,子类析构完成后会自动调用父类析构。这保证了清理顺序:先清理派生类成员,再清理基类成员。这与构造顺序(先基类后子类)正好相反。

核心总结

  1. 私有成员:继承了,但在子类中不可见(权限问题)。
  2. 对象大小:不一定比父类大,可能只是方法重写。
  3. 重载 vs 隐藏:重载在同作用域参数不同;隐藏在不同作用域同名即可,派生类会屏蔽基类。
  4. 构造顺序:先初始化父类,再初始化子类。
  5. 析构顺序:先析构子类,再析构父类。

理解这些机制,能帮你避免很多 C++ 继承相关的坑。

目录

  1. C++ 继承机制详解
  2. 继承的基本定义与访问权限
  3. 基于继承的类模板实现栈
  4. 赋值兼容与对象切片
  5. 继承中的作用域与名称隐藏
  6. 派生类的默认成员函数
  7. 1. 构造函数
  8. 2. 拷贝构造函数
  9. 3. 赋值运算符重载
  10. 4. 析构函数
  11. 核心总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 机器人系统架构详解:2026 年最新技术路线
  • Python FastAPI 入门实战:从环境搭建到接口开发
  • Spring Boot 集成本地 OCR 服务模块实战
  • C++ 类型转换详解:显式运算符与底层机制
  • Java 大数据在智能家居环境监测与智能调节中的应用
  • 鸿蒙电商购物车项目:用户管理、商品列表与购物车实现
  • 单链表与双向循环链表应用示例
  • Git 基本操作与版本回退原理解析
  • 程序员如何入局 AI 大模型及职业发展路径
  • Python 自动化办公与网络爬虫实战应用场景解析
  • Text2SQL 跨数据库 SQL 转换实战代码
  • 开源鸿蒙 6.1、8.1 确认为 LTS 版本:2026~2028 年发布
  • C++ 音视频工具命令行骨架:参数解析与任务回调系统
  • Dify 工作流发布为 MCP Server 实战指南
  • 五分钟理解 Rust 核心概念:所有权
  • Cursor智能生成SQL实战指南与避坑技巧
  • 前端流式输出实现详解:从原理到实践
  • Web 自动化测试实战:基于 Python+Selenium 的博客系统测试流程
  • OpenClaw 对接飞书机器人:插件安装、回调配置与配对避坑指南
  • Pandas 操作 Excel 的 9 个常用场景

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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