news 2026/7/24 20:27:23

C++类细节梳理之派生和继承

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++类细节梳理之派生和继承

目录

继承派生的概念

语法与访问控制

语法

构造与析构顺序

派生与构造函数

派生与成员函数

改变访问控制

类型兼容

对象的类型转换

多重继承


继承派生的概念

当定义的一个B类拥有某个已经写好的A类的全部特点,此外还多出了A类没有的特点,就可以把A作为一个基类,B类为A类的一个派生类,这样A类派生了B类,B类继承了A类

派生类是通过基类扩充和修改得到的,基类所有成员自动成为派生类的成员,这样就不用将重复用到的地方在写一遍,提高程序的可重用性,也方便代码管理

语法与访问控制

语法
class 派生类名 : 继承访问控制 基类类名{ 成员访问控制: 成员声明列表; };

继承访问控制可为private,public,protected,若省却则默认为private

protected具有private和public双重角色,类的外部不可访问,如同private;派生类可以访问,如同public

影响继承成员访问控制方式的两个因素分别是:

  • 定义派生类时指定的继承访问控制
  • 该成员在基类中所具有的成员访问控制
基类中成员的访问控制继承访问控制派生类中继承成员的访问控制
publicpublicpublic
protectedprotected
private不可访问
publicprotectedprotected
protectedprotected
private不可访问
publicprivateprivate
protectedprivate
private不可访问

注意“私有”和“不可访问”的区别:私有可通过派生类本身访问,不可访问成员就算是派生类也不可以访问

构造与析构顺序

基类构造函数不会被继承,派生类需要声明自己的构造函数。派生类的构造函数只需初始化本类新增的成员就行了,基类的成员的初始化通过派生类构造函数初始化器中完成(自动使用无参构造行数,若有参则需要显示申明)

构造函数的调用次序

  • 基类的构造函数(多重继承,调用顺序按照声明从左到右)
  • 然后本对象成员的构造函数
  • 最后调用本类的构造函数

析构函数的调用次序

  • 本类的析构函数
  • 然后本类对象的析构函数
  • 最后调用其基类的析构函数
class baseA{ public: baseA(); ~baseA(); }; class baseB{ public: baseB(); ~baseB(); }; class C : public baseA{ public: baseB b; C(); ~C(); }; baseA::baseA() { cout << "base A" << endl; } baseA::~baseA() { cout << "~ base A" << endl; } baseB::baseB() { cout << "base B" << endl; } baseB::~baseB() { cout << "~ base B" << endl; } C::C() { cout << "C" << endl; } C::~C() { cout << "~ C" << endl; } int main() { C c; return 0; }

输出为

base A base B C ~ C ~ base B ~ base A

利用这一特性可以实现instanceof函数返回该对象的类型名字。

#include <iostream> #include <string> #include <typeinfo> using namespace std; class Object{ public: string name; Object(){ name = "Object"; } ~Object(){} string getname(){ return name; } }; class Animal : public Object{ public: Animal(){ name = "Animal"; } ~Animal(){} }; class Dog:public Animal{ public: Dog(){ name = "Dog"; } ~Dog(){} }; class Cat:public Animal{ public: Cat(){ name = "Cat"; } ~Cat(){} }; string instanceof(Object obj){ return obj.getname(); }

该类的构造函数最后调用,则name将会存入该类的名字

派生与构造函数

派生类不可以继承基类的私有成员,构造函数和析构函数(运算符重载这里暂时回避)

上面提到过,若基类构造函数带参数,则定义派生类构造函数时,仅能通过初始化列表显示调用基类构造函数

class MyPoint { private: double x, y; public: MyPoint(){ x = 0; y = 0; } MyPoint(double x, double y){ this->x = x; this->y = y; } }; class ThreeDPoint : public MyPoint { private: double z; public: ThreeDPoint(){ z = 0; } ThreeDPoint(double x, double y, double z) : MyPoint(x, y){ this->z = z; } };

同样的对于拷贝构造函数也要用初始化列表调用基类的构造函数,当然基类也要有拷贝构造函数的实现

Human(const char* _languages, const char* _sound, int _age) : Creature(_sound, _age){ ... } Human(const char* _languages, const Creature& _creature) : Creature(_creature){ ... } Human(const Human& other) : Creature(other){ ... }

当拷贝构造函数中给出的是基类的对象,那么使用基类的拷贝构造函数是理所应当的,但是若拷贝构造函数中只给出了派生类的对象,此时我们也可以将此对象直接作为基类拷贝构造函数的对象,这是因为类型兼容(见下)派生类转换为基类是安全的,因此将派生类对象直接作为基类拷贝构造函数的对象时会安全的进行隐式转换,将派生类变为基类,从而完成拷贝构造

再再再值得注意的是,“=”的运算符重载,当然前提是基类也要有=的运算符重载

Creature& Creature::operator=(const Creature& other){ ... return *this; } Human& Human::operator=(const Human& other){ if(this == &other){ return *this; } Creature::operator=(other); ... return *this; }

首先判断是否是自己赋值给自己,this和&other都是对象的地址,若相等则说明是同一个对象,然后调用基类的=重载运算符来更新基类,与拷贝构造函数类似的这里同样可以传入派生类的对象。具体调用方法见代码,首先写一个基类的命名空间,后面接=重载运算符的名称“operator=”,这一坨可看作函数的名称,最后()内写传入的参数

派生与成员函数

重载

  1. 具有相同的作用域(在同一个类中)
  2. 函数名字相同
  3. 参数类型,顺序或数目不同

覆盖

  1. 基类有virtual在前面
  2. 函数名字相同

隐藏

  1. 函数名字相同,但参数列表有差异
  2. 函数名字,参数列表相同且基类函数前面没有virtual

继承

  1. 没有被覆盖或隐藏的基类函数,包括在基类中重载的函数

隐藏可以修改和赋予成员函数新的功能

改变访问控制

基类的public和protected成员,因为使用了protected或private继承访问控制而导致在派生类中访问方式发生改变,这时可以使用“访问声明”恢复为原来的访问控制

using 基类名::成员名

运用这个方法也可以直接调用基类的构造函数

类型兼容

类型兼容主要表现为向上兼容,可以将派生类的对象直接赋值给基类对象,反之则不行。值得注意的是该派生类需要public继承才适用于上述规则

class base{ public: void display(){ cout << "base" << endl; } }; class a : public base{ public: void display(){ cout << "a" << endl; } }; class b : public a{ public: void display(){ cout << "b" << endl; } }; void fun(base* p)//此处进行类型转换为base { p->display();} int main(){ base x; a y; b z; dase *ptr; ptr = &x; fun(ptr); ptr = &y; fun(ptr); ptr = &z; fun(ptr); }

输出为

base base base

对象的类型转换

static_cast用来转换类型

int a = 0; average = static_cast<float>(a);

const_cast用来转换数据是否为const

dynamic_cast用来安全的向下转换,如果出现问题,指针会为空指针,引用会报错

reinterpret_cast直接进行类型转换而不检查,使用时要慎重

多重继承

当派生类有多个基类时运用

class 派生类名 : 继承访问控制1 基类名1, 继承访问控制2 基类名2, ... { 成员声明: }

除了菱形继承外,同一个类不能被继承两次及以上

上面这两种都是不行的

而对于菱形继承,由于它们有一部分是相同的,编译器就会不知道该具体指的是谁,这时可以在前面加上他们的类名::对象名,或者在继承访问控制前面加上virtual代表虚基类。虚基类和普通基类基本一样,只有在派生类重复继承了某一基类时才表现出来(菱形继承)

virtual虚继承保证了虚基类的唯一副本只被初始化一次(类之间共享该副本)

加入虚基类后构造函数调用次序

  • 最先调用虚基类的构造函数(有参时要用初始化列表)
  • 基类的构造函数(多重继承,调用顺序按照声明从左到右)
  • 然后本对象成员的构造函数
  • 最后调用本类的构造函数

析构函数顺序相反

#include <iostream> using namespace std; class Writing { public: Writing() { cout<<"Writing constructor"<<endl; } ~Writing(){ cout<<"~Writing"<<endl; } }; class Racing { public: Racing(){ cout<<"Racing constructor"<<endl; } ~Racing(){ cout<<"~Racing"<<endl; } }; class Weekend : virtual public Writing, virtual public Racing{ public: Weekend() { cout<<"Weekend constructor"<<endl; } ~Weekend(){ cout<<"~Weekend"<<endl; } }; class Workday : virtual public Racing, virtual public Writing{ public: Workday(){ cout<<"Workday constructor"<<endl; } ~Workday(){ cout<<"~Workday"<<endl; } }; class Choseday : public Weekend, public Workday{ public: Choseday() { cout<<"Choseday constructor"<<endl; } ~Choseday(){ cout<<"~Choseday"<<endl; } };

由于虚基类构造函数是先于派生类构造函数的,所以有参数的构造函数需要用初始化列表列出调用虚基类的构造函数

class Creature { public: Creature(const int& hands, const int& legs) { _hands = hands; _legs = legs; } virtual ~Creature() {} private: int _hands; int _legs; }; class Beast : virtual public Creature { public: Beast(const int& hands, const int& legs, const string& name) : Creature(hands, legs) { _name = name; } ~Beast() {} private: string _name; };
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 20:25:23

大学哪些数字营销专业证书值得考

数字营销的世界日新月异&#xff0c;对于在校大学生或初入职场的你而言&#xff0c;考取一些高含金量的专业证书&#xff0c;无疑是提升简历竞争力、系统化构建知识体系的“快车道”。在2026年的今天&#xff0c;面对AI、大数据与营销深度融合的趋势&#xff0c;选择那些能真正…

作者头像 李华
网站建设 2026/7/24 20:23:07

连续多年展现统治力,「IM 一哥」融云的通关密码

艾瑞咨询《2023 年全球互联网通信云行业研究报告》显示&#xff0c;在月独立设备数排名前 1000 的 App 中&#xff0c;融云覆盖的 App 日活设备数合计超过 8500 万台&#xff0c;居于专业互联网通信云厂商之首&#xff0c;超过第二名一倍以上。移步【融云全球互联网通信云】回复…

作者头像 李华
网站建设 2026/7/24 20:20:54

「Qt Widget中文示例指南」如何实现一个日历?(三)

Qt是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。本文中的CalendarWidge…

作者头像 李华
网站建设 2026/7/24 20:19:28

端到端智驾算法:文章导航页

零、端到端任务概述 一、两段式端到端算法 https://blog.csdn.net/weixin_54853916/article/details/163129567?spm1011.2124.3001.6209 二、一段式端到端算法 三、SD信息应用 四、自动驾驶中强化学习算法介绍 五、端到端轨迹输出的优化 六、时空联合规划

作者头像 李华