C++ 继承:代码传承的魔法棒,开启奇幻编程之旅

C++ 继承:代码传承的魔法棒,开启奇幻编程之旅

文章目录

一.继承的概念及定义

1.1继承的概念

继承是面向对象语言特性之一,它允许一个类(派生类)从另一个类(基类)中,继承其属性和方法。这样做的好处是,提供了可以重用的代码,避免在写一个类时,它的一部分功能已经在另一个类中实现了,我们还需要在这个类中重新写一遍。

例如:目前写了一个person类,我们可以继承这个类实现,teacher类、student类、president类等等。这些类继承了person,在自己的类中就不需要花费功夫造轮子。

继承还可以这样理解,未来你总会要继承父母的家业、继承公司财产、继承百亩良田,这样继承下来的家业远远比自己白手起家好很多。

1.2继承类

定义格式:

在这里插入图片描述
class person { public: //…… protected: string _name;//姓名 int _age;//年龄 int _tel;//电环 string _address;//地址 }; //其中person称为基类,又称为父类,student称为派生类,又称为子类。冒号后边跟上的publi称为继承方法 //在student类中就不需要实现关于人的成员变量、函数,复用了person类的成员 class student : public person { public: // private: int _id;//学号 }; //还需要自己定义,自己实现相关的成员变量,成员函数,变得比较麻烦 class student { public: // private: string _name;//姓名 int _age;//年龄 int _tel;//电环 string _address;//地址 int _id;//学号 }; 

student类通过public的方式继承了person类。在student中,就不需要重定义,省去了许多麻烦

1.2.1继承方法

继承方法,是通过不同的继承方法,可以指定基类的成员继承到派生类中后的访问方式,是pbulic公共的成员、还是private私有的成员、还是protected被保护的成员。

类成员/继承方式public继承protected继承private继承
基类的public成员派生类中public成员派生类中protected成员派生类中private成员
基类的protected成员派生类中protected成员派生类中protected成员派生类中private成员
基类的private成员派生类中无法访问派生类中无法访问派生类中无法访问
  • 基类的private成员无论以何种方式继承到在派生类中是无法被访问,它呢,由于语法的限制,无论是在类中还是在类外面都无法访问。
在这里插入图片描述
  • 需要在类外无法被访问,类之间可以被访问的时候,可以使用protected访问限定符。protected修饰后的成员,在通过public或者protected方法继承到派生类中,是可以自由访问,而出了派生类的作用域就无法被访问了。
在这里插入图片描述

通过观察表格不难发现,三种继承方法在访问限制上的约束:public < protectde < private,通过public继承基类的成员到派生类中,它们访问的方式是不会发生变化的;通过protected继承基类的成员,访问方式都被限制为protected;通过private继承基类的成员,访问方式都被限制为了private。

在日常使用中,最常见的是使用public方式继承,使用protected、private继承后的成员只能在派生类中使用,无法扩展到类外,使用性并不好。


  • 若没有显示写继承方式,class中默认的继承方式为private,在struct中默认的继承方式为public

1.3继承模板

在这里插入图片描述

继承模板允许一个模板继承另一个模板

需要注意的是继承基类后,基类还没有被实例化,当调用一个成员函数时,编译器会先在自己的类域中查找,若是调用基类的成员函数没有指定类域的话,编译器将会报错,因为基类并没有实例化,编译器也就不会进入基类中查找。指定基类类域,编译器才会进入基类中查早

没有被实例化的模板是无法寻找的,在编译后,编译器提示找不到print这个标识符,原因是基类是一个类模板,模板只是声明并没有被实例化,直接调用会报错。

#include <iostream> #include <vector> using namespace std; template<class T> class Stack : public vector<T>//继承模板 { public: void push(const T& x) { //push_back(x); vector<T>::push_back(x);//指定类域 } }; 
在这里插入图片描述

指定print的类域后,正常运行。

知识补充

下列的场景中,实现了一个函数模板,试图用于对任意类型的容器进行打印

#include <iostream> #include <vector> using namespace std; template<class T> class Stack : public vector<T>//继承模板 { public: void push_back(const T& x) { //在通过继承模板实现的栈类中,当 `Stack<int>` 实例化后 `vector<int>`也会进行实例化,但模板是按需实例化的,即你需要使用那部分的函数,编译器帮你实例化那部分,当调用基类中的成员函数时,它并未实例化,编译器并不会认识它。当指定类域后,编译器就会来指定的类域中实例化这个成员函数。 push_back(x); vector<T>::push_back(x);//指定类域 } const T& top() { vector<T>::back(); } void pop() { vector<T>::pop_back(); } bool empty() { return vector<T>::empty(); } }; template<class container> void print(const container& c) { container::const_iterator it = c.begin(); typename container::const_iterator it = c.begin();//使用typename指定,container::const_iterator是一个类型 while (it != c.end()) { cout << *it << " "; it++; } cout << endl; } int main() { Stack<int> st1; st1.push_back(1); st1.push_back(2); st1.push_back(3); st1.push_back(4); st1.push_back(5); print(st1); return 0; } 

在print模板函数中,it这个类型,依赖于模板参数,在这个过程中,我们不告诉编译器,container::const_iterator是一个类型的话,编译器可能会误解它是一个成员函数,成员变量等

需要使用 typename来告诉编译器,这是一个类型,避免产生歧义。最好用的做法是使用auto关键字,自动推导类型。

二.基类和派生类的转换

  • 派生类的对象可以赋值给基类的指针或者引用(赋值兼容转换),可以通过切分来形容这个过程,编译器将派生类中属于基类的空间切分出来,使指针,或者引用指向基类空间的起始位置
    • 派生类赋值给基类的过程不会发生临时对象的转换。指针的指向,指向的是基类那一部分引用,引用的是基类的那一部分
class person { public: person() : _name() , _age(18) , _tel(123123) , _address() {} protected: string _name;//姓名 int _age;//年龄 int _tel;//电环 string _address;//地址 }; class student : public person { public: student() { _name = "小晨"; _age = 20; _tel = 666; _address = "ZEEKLOG"; _id = 987; } private: int _id;//学号 }; int main() { student stu; person* per1 = &stu; person& per2 = stu; person per3 = stu; return 0; } 
在这里插入图片描述

per1和stu的地址相同,per2也对stu进行了切片

main 函数中,per3 的声明可能会导致对象切片问题。因为 per3person 类型,而 stustudent 类型,当 stu 被复制给 per3 时,student 类特有的成员 _id 会被“切掉”,per3 将不会包含 _id 成员。这意味着通过 per3 访问 _id 将会导致未定义行为。

  • 基类对象不能赋值给派生类对象
    • 基类对象可以通过强制类型转焕赋值给派生类的指针或者引用,但基类的指针必须指向派生类对象时才是安全的,具体细节后续在介绍。

三.继承中的作用域

  • 继承中基类和派生类中都有独立的作用域
  • 派生类和基类中存在同名成员,派生类将隐藏基类中的同名成员,而访问派生类的成员。
    • 通过 基类::基类成员的方式进行显示访问
    • 如果时成员函数同名构成的隐藏,仅需函数名相同即可构成隐藏
    • 不可以理解为基类和派生类之间存在同名函数,可以构成函数重载。函数重载存在于同一个作用域

基类的同名成员函数被隐藏。

在这里插入图片描述

warning C4717: “student::func”: 如递归所有控件路径,函数将导致运行时堆栈溢出

编译器眼里,是func自己不断调用自己,是一个死递归的过程。

在派生类中显示调用基类的同名函数

在这里插入图片描述

四.派生类的默认成员函数

4.1默认成员函数的行为

默认成员函数的两个主要问题:

  • 不写默认成员函数,编译器默认生成的行为是什么
  • 默认生成的成员函数不符合需求,自己该如何实现?
#include <iostream> #include <string> using namespace std; class person { public: person(const char*) { cout << "constructor person" << endl; } person(const person& p1) { cout << "person(const person& p1)" << endl; } person& operator=(const person& p) { if (this != &p) { cout << "person& operator=(const person&)" << endl; } return *this; } ~person() { cout << "destructor person" << endl; } protected: string _name;//姓名 }; class student : public person { public: //student(const char*) // :_name(name) // 错误的初始化 // ,_id(2024) //{ //基类会被当作一个整体进行初始化,也就是说,编译器不会再派生类中一个一个的初始化基类成员变量 // cout << "constructor" << endl; //} student(const char*) :person(name) ,_id(2024) { cout << "constructor student" << endl; } student(const student& s)// :person(s)//派生类对象赋值给基类的引用,发生了切片行为,切分出基类那份成员变量 { cout << "student(const student& s)" << endl; } student& operator=(const student& p) { if (this != &p) { // operator=(p); //必须之类基类类域,同名函数发生了隐藏行为 person::operator=(p); // 必须显示调用基类的赋值重载函数 cout << "student& operator=(const student&)" << endl; } return *this; } ~student() { cout << "destructor student" << endl; } private: int _id = 1;//学号 }; int main() { student s1; return 0; } 

构造函数的行为

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那部分成员。
    • 如果没有在基类中实现默认构造在派生类的构造函数的初始化列表阶段显示调用需要将基类当为一个整体进行初始化
  • 在初识化列表中初始化的顺序根据声明的前后顺序,基类最先出现,先初始化基类
  • 基类没有提供构造,派生类中也得显示的调用 。基类中不存在默认构造,派生类不会自动生成
先构造基类,然后构造派生类

拷贝构造

  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
    • 想要显示调用基类的构造函数,就只能通过初始化列表完成

赋值运算符重载的行为

  • 派生类的operator=必须要调用基类的operator=完成基类的赋值。
    • 此处存在同名函数隐藏。需要基类指定作用域显示调用基类的operator=函数。

析构函数的行为

与前几个默认成员函数的行为不同,析构函数并不需要显示调用基类的析构函数。

  • 在编译器调用派生类的析构函数时,不需要显示调用基类的析构函数,在析构过程中保证析构安全,编译器会默认调用基类的析构函数。然后析构派生类

此时析构派生类对象的时候会一起将

先析构基类,在析构派生类

4.2实现一个无法被继承的类

  1. 基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构造函数私有化后,派生类看不见无法调用,此时派生类将无法实例化出对象
  2. 使用C++11新增的关键字 final,使用它修改基类,就无法被派生类继承
#include <iostream> #include <string> using namespace std; class person final { protected: string _name;//姓名 }; class student : public person { private: int _id = 1;//学号 }; int main() { student s1; return 0; } 
在这里插入图片描述

五.继承与友元

基类的友元关系无法被继承,基类的友元无法访问派生类中收到保护的和私有的成员。

#include <iostream> #include <string> using namespace std; class student; class person { friend void Fun(const person& per, const student& stu); protected: string _name;//姓名 }; class student : public person { private: int _id;//学号 }; void Fun(const person& per, const student& stu) { cout << per._name << endl; cout << stu._id << endl;//无法被访问。 //解决:将Fun也变为student的友元即可被访问 } int main() { person per; student stu; Fun(per, stu); return 0; } 

而解决这种情况也很简单,只需要将fun函数也作为派生类的友元函数即可

六.继承与静态成员

在基类中定义了一个静态成员,则整个继承体系中都使用同一个静态成员,无论派生出多少个类。

执行以下代码,可以发现派生类进程了基类后,打印的 _name地址不相同,基类和派生类中各有一份。

_count打印的地址是相同的,印证了即使被继承,派生类和基类使用的还是同一个静态成员。

#include <iostream> #include <string> using namespace std; class person { public: string _name; static int _count; }; int person::_count = 0; class student : public person { private: int _id; }; int main() { person p1; student s1; cout << &p1._name << endl; cout << &s1._name << endl; cout << &s1._count << endl; cout << &p1._count << endl; return 0; } 
在这里插入图片描述

使用类名访问静态成员变量

使用变量名访问静态成员变量

在这里插入图片描述

七.多继承和菱形继承

7.1多继承和菱形继承

  • 单继承:一个派生类只有一个直接继承基类

这种情况往往被认为是多继承,它实际上是单继承,Assignment只有一个直接继承基类、student也只有一个直接基类

在这里插入图片描述
    • 内存关系:先继承的基类放在前面,后继承的基类在后面,派生类的成员在最后一个

多继承:一个派生类有多个直接基类时称为多继承

在这里插入图片描述

菱形继承:是一种特殊的多继承,子类继承了多个父类,而这些父类又继承了同一个基类的数据和方法。此时的派生类表现出菱形继承。

  • 由于派生类继承了多份同一个基类,菱形继承存在着数据冗余和二义性问题。
在这里插入图片描述
#include <iostream> #include <string> using namespace std; class person { public: string _name; }; class student : public person { protected: int _id;//学号 }; class teacher : public person { protected: int _job_num;//工号 }; class Classroom : public student, public teacher { protected: string _course; }; int main() { Classroom Class; //Class._name = "john";// error C2385: 对“_name”的访问不明确 //指定访问解决二义性,但无法解决数据冗余 Class.student::_name = "john"; Class.teacher::_name = "sophia"; return 0; } 
在这里插入图片描述

7.2虚继承

在多继承这块就体现了C++语法的复杂。有了多继承,存在着菱形继承的问题,而为了解决菱形继承又有了菱形虚拟继承,它的底层很复杂,会丢失性能。在使用继承中应尽可能避免菱形继承的存在。

#include <iostream> using namespace std; class Animal { public: Animal() { cout << "Animal()" << endl; } protected: bool _herbivore;//食草 }; // 虚继承animal class bird : virtual public Animal { public: bird() { cout << "bird()" << endl; } }; // 虚继承 继承animal class fish : virtual public Animal { public: fish() { cout << "fish()" << endl; } }; // 使用虚继承后就存在数据的二义性、冗余的问题 class flyingfish : public bird, public fish { public: flyingfish() { cout << "flyingfish()" << endl; } }; int main() { flyingfish ffish; ffish._herbivore = true; return 0; } 

八.总结

继承与组合

  • public继承是一种 is-a的关系。每个派生类对象都是一个基类对象
  • 组合是一种 has-a的关系。例如:使用vector类,实现栈、队列。这里的栈和vector是一种组合
  • 继承允许你根据基类的实现类定义派生类的实现。通过这种生成派生类的复用常被称为白箱复用
    • 白箱,相对可见性而言,基类的内部细节对派生类可见,继承一定程度上破坏了基类的封装,基类的该变会对继承产生很大的影响
    • 派生类和基类之间依赖关系强,耦合度很高
  • 对象组合是类继承的另一种复用选择。更复杂的功能可以通过组合对象来获得。组合中要求被组合的对象要具有良好的接口。deque就组合了许多类。这种复用风格被称为黑箱复用,对象只以黑箱的形式出现。组合类之间没有强依赖关系。
    • 黑箱,对象的内部细节是不可见的。
    • 组合的耦合度低
  • 优先使用组合,而不是继承。在使用上多考虑,它们的关系是 is-a关系还是,has-a关系

耦合

耦合指的是模块或类之间的依赖程度,低耦合意味着模块与模块之间的联系和依赖低,当一个模块出现bug的时候,不会影响别的模块,在设计类时应尽可能降低它们之间的练习程度。

这就好比如还在上学的同学与父母之间的依赖关系,在生活上的依赖关系是无比紧密的一但,同学没有生活费了,或者想要买写价格比较高的东西时,都离不开父母,一但某一天自己没有生活费,也练习不上父母了,那自己不就得喝西北方了~。

  • 降低了修改模块影响的范围
  • 提高代码的复用性
  • 模块独立性强,不依赖别的模块可完成测试

内聚

内聚指一个模块只关注它特定的职责和任务,实现一个打印数组的函数,那我们不会在打印函数中再实现一个将数组排为有序的功能,这就显得的多余。

  • 低内聚的模块包含了多个不同的、关联性不强的功能,使得模块的职责不明确。
  • 模块内部功能不聚焦,会出现重复的代码来处理不同的功能部分

程度上破坏了基类的封装,基类的该变会对继承产生很大的影响

  • 派生类和基类之间依赖关系强,耦合度很高
  • 对象组合是类继承的另一种复用选择。更复杂的功能可以通过组合对象来获得。组合中要求被组合的对象要具有良好的接口。deque就组合了许多类。这种复用风格被称为黑箱复用,对象只以黑箱的形式出现。组合类之间没有强依赖关系。
    • 黑箱,对象的内部细节是不可见的。
    • 组合的耦合度低
  • 优先使用组合,而不是继承。在使用上多考虑,它们的关系是 is-a关系还是,has-a关系

耦合

耦合指的是模块或类之间的依赖程度,低耦合意味着模块与模块之间的联系和依赖低,当一个模块出现bug的时候,不会影响别的模块,在设计类时应尽可能降低它们之间的练习程度。

这就好比如还在上学的同学与父母之间的依赖关系,在生活上的依赖关系是无比紧密的一但,同学没有生活费了,或者想要买写价格比较高的东西时,都离不开父母,一但某一天自己没有生活费,也练习不上父母了,那自己不就得喝西北方了~。

  • 降低了修改模块影响的范围
  • 提高代码的复用性
  • 模块独立性强,不依赖别的模块可完成测试

内聚

内聚指一个模块只关注它特定的职责和任务,实现一个打印数组的函数,那我们不会在打印函数中再实现一个将数组排为有序的功能,这就显得的多余。

  • 低内聚的模块包含了多个不同的、关联性不强的功能,使得模块的职责不明确。
  • 模块内部功能不聚焦,会出现重复的代码来处理不同的功能部分

Read more

当OpenClaw引爆全网,谁来解决企业AI Agent的“落地焦虑”?

当OpenClaw引爆全网,谁来解决企业AI Agent的“落地焦虑”?

2026 年 3 月,开源 AI Agent 框架 OpenClaw 在 GitHub 上的星标突破28万,并一度超越 React,成为 GitHub 最受关注的软件项目之一。短时间内,开发者利用它构建了大量实验性应用:从全栈开发辅助,到自动化营销脚本,再到桌面操作自动化,AI Agent 的能力边界正在迅速被拓展。 这股热潮也带动了另一个趋势——本地部署与算力硬件需求的快速增长。越来越多开发者尝试在个人设备或企业服务器上运行 Agent 系统,以获得更高的控制权和数据安全性。 从表面上看,AI Agent 似乎正从“概念验证”走向更广泛的开发实践。但在企业环境中,情况却没有想象中乐观。当企业负责人开始追问—— “它能直接解决我的业务问题吗?” 很多演示级产品仍难以给出令人满意的答案。 如何让 Agent 真正融入企业既有系统、适配复杂业务流程,正成为大模型产业落地必须跨越的一道门槛。 与此同时,中国不同城市的产业结构差异明显:互联网、

By Ne0inhk
二手平台出现OpenClaw卸载服务,299元可上门“帮卸”;2026年春招AI人才身价暴涨:平均月薪超6万;Meta辟谣亚历山大·王离职 | 极客头条

二手平台出现OpenClaw卸载服务,299元可上门“帮卸”;2026年春招AI人才身价暴涨:平均月薪超6万;Meta辟谣亚历山大·王离职 | 极客头条

「极客头条」—— 技术人员的新闻圈! ZEEKLOG 的读者朋友们好,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧。(投稿或寻求报道:[email protected]) 整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 一分钟速览新闻点! * 微信员工辟谣“小龙虾可自动发红包”:不要以讹传讹 * 蚂蚁集团启动春招,超 70% 为 AI 相关岗位 * 受贿 208 万!拼多多一员工被抓 * 2026 年春招 AI 人才身价暴涨: 平均月薪超 6 万元 * 二手平台出现 OpenClaw 上门卸载服务 * 权限太高,国家互联网应急中心发布 OpenClaw 安全应用的风险提示 * 字节豆包内测 AI 电商功能:无需跳转抖音,日活用户数超

By Ne0inhk
遭“美国政府封杀”后,Anthropic正式提起诉讼!

遭“美国政府封杀”后,Anthropic正式提起诉讼!

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 据路透社报道,当地时间周一,AI 初创公司 Anthropic 正式对美国国防部及特朗普政府提起诉讼,抗议五角大楼将其列为“国家安全供应链风险”主体的决定。 Anthropic 在向美国加州北区地方法院提交的诉讼文件中表示,这一认定“史无前例且非法”,已对公司造成“不可挽回的损害”。公司希望法院撤销该决定,并指示联邦机构停止执行相关认定。 划定 AI 应用红线,双方观点不一 正如我们此前报道,这场争端的核心在于 Anthropic 为其核心 AI 模型 Claude 设定的两条技术使用红线,与美国国防部的使用需求发生根本冲突。 此前,Anthropic 曾与五角大楼签署一份价值最高可达 2 亿美元的合作合同,Claude 也成为少数被纳入美国机密网络环境进行测试的 AI 系统之一。 对此,Anthropic 一直坚持两条底线: * Claude 等技术不得被用于对美国民众的大规模国内监控;

By Ne0inhk
星标超 28 万,OpenClaw 两天两次大更!适配GPT 5.4,告别“抽卡式 Prompt”

星标超 28 万,OpenClaw 两天两次大更!适配GPT 5.4,告别“抽卡式 Prompt”

整理 | 梦依丹 出品 | ZEEKLOG(ID:ZEEKLOGnews) “We don’t do small releases.” 这是 OpenClaw 在发布 2026.3.7 版本时写下的一句话。 刚刚过去的周六与周日,这个 GitHub 星标已超 28 万 的 AI Agent 开源项目再次迎来两轮重量级更新。 两天两次更新:OpenClaw 做了一次“真正的大版本升级” 打开 OpenClaw 的 GitHub 更新日志,你会发现这次版本更新的规模确实不小。在 3 月 7 日发布更新后,第二天又迅速推出 2026.3.8-beta.1 和

By Ne0inhk