[CPP]继承

news/2025/2/24 13:27:21/文章来源:https://www.cnblogs.com/wzhiheng/p/18566371

继承

  • 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
  • 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

什么是继承

继承是面向对象编程(OOP)中的一个重要概念,它是一种创建新类(称为派生类或子类)的机制,新类从现有的类(称为基类或父类)继承属性和行为。这使得代码可以复用,并且可以建立类之间的层次关系。

class Animal {
public:int age;void eat() {std::cout << "The animal is eating." << std::endl;}
};class Dog : public Animal {
public:void bark() {std::cout << "The dog is barking." << std::endl;}
};

在这个例子中,子类是Dog,父类是Animal,public是继承方式。继承之后,子类不仅仅有自己的成员,也有父类中的成员。

继承方式

一个类中有public,protected,private访问限定符,继承也分为三种方式,public继承,protected继承和private继承。两两组合就有了如下的表。

类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public继承 派生类的protected继承 派生类的private继承
基类的protected成员 派生类的protected继承 派生类的protected继承 派生类的private继承
基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见

总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。在派生类中不可见是指基类的私有成员还是被继承到了派生类中,但是在语法上限定了派生类对象无论是在类内还是类外都无法去访问它。
  2. 如果基类成员不想在类外直接被访问,但在派生类中可以访问,就可以定义为protected。该限定符是因继承才出现的。
  3. 关键字class默认继承方式是private,关键字struct默认继承方式是public。
  4. 除了基类的私有成员外,其它成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式)。

赋值转换

class Person 
{
public:void Print() {cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "wzh";int _age = 18;
};class Student : public Person 
{
protected:int _stuid;
};
  • 派生类对象可以赋值给基类的对象、基类的指针以及基类的引用。这种方式称为切片或切割。

    	Student s;// 子类对象可以赋值给父类对象/指针/引用Person p = s;Person* p_ptr = &s;Person& p_ref = s;
    
  • 基类对象不能赋值给派生类对象。因为派生类对象期望的内存空间比基类对象提供的要大,如果允许基类对象直接赋值给派生类对象,就可能会出现内存访问越界的问题

  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

    	//基类的指针可以通过强制类型转换赋值给派生类的指针,且基类的指针是指向派生类对象p_ptr = &s;Student* s_ptr1 = (Student*)p_ptr;s_ptr1->_stuid = 10;//基类的指针不是指向派生类对象,存在越界访问p_ptr = &p;Student* s_ptr2 = (Student*)p_ptr;s_ptr2->_stuid = 10;
    

隐藏(重定义)

在继承体系中,派生类和基类有着独立的作用域。当派生类中的函数与基类中的函数同名(函数名相同,参数列表可以不同,与返回值无关)时,派生类函数会屏蔽基类中的同名函数。成员变量同样可以被隐藏。(在派生类成员函数中,可以使用 基类::基类成员 显示访问)。

class Base {
public:void func(int a) {std::cout << "Base::func(int), a = " << a << std::endl;}
};
class Derived : public Base {
public:void func(double a) {std::cout << "Derived::func(double), a = " << a << std::endl;}
};
int main() {Derived d;d.func(3.14);   // 调用Derived::func(double)// d.func(3);   // 错误,因为Derived类中的func函数隐藏了Base类中的func(int)函数return 0;
}

在这个例子中,Derived类中的func函数隐藏了Base类中的func函数。当通过Derived类对象d调用func函数时,传入3.14会调用Derived::func(double)。但是直接传入3会产生错误,因为编译器只会看到Derived类中的func函数,它无法自动将这个调用转换为对Base::func(int)的调用。

派生类的默认成员函数

构造函数

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 派生类对象初始化先调用基类构造再调派生类构造。
class Base {
public:int baseVar;//先被调用Base(int var = 2) : baseVar(var) { std::cout << "Base part: " << baseVar << std::endl; }
};class Derived : public Base {
public:int derivedVar;//后调用Derived(int derivedVal = 1) : derivedVar(derivedVal){std::cout << "Derived part: " << derivedVar << std::endl;}
};int main() {Derived d;return 0;
}

析构函数

  • 派生类对象先调用派生类析构再调基类的析构。
class Base {
public:int baseVar;Base(int var) : baseVar(var) {}~Base() {std::cout << "Base destructor" << std::endl;}
};
class Derived : public Base {
public:int derivedVar;Derived(int baseVal, int derivedVal) : Base(baseVal), derivedVar(derivedVal) {}~Derived() {std::cout << "Derived destructor" << std::endl;}
};
int main() {Derived* d = new Derived(1, 2);delete d;return 0;
}

拷贝构造

  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

    class Base {
    public:int baseVar;Base(int var) : baseVar(var) {}Base(const Base& other) : baseVar(other.baseVar) {}
    };
    class Derived : public Base {
    public:int derivedVar;//构造Derived(int baseVal, int derivedVal) : Base(baseVal), derivedVar(derivedVal) {}//拷贝构造Derived(const Derived& other) : Base(other), derivedVar(other.derivedVar) {}
    };
    int main() {Derived d1(1, 2);Derived d2(d1);std::cout << "Base part of d2: " << d2.baseVar << ", Derived part of d2: " << d2.derivedVar << std::endl;return 0;
    }
    

赋值运算符重载

  • 派生类的operator=必须要调用基类的operator=完成基类的赋值。

    class Base {
    public:int baseVar;Base(int var) : baseVar(var) {}Base& operator=(const Base& other) {if (this != &other) baseVar = other.baseVar;return *this;}
    };
    class Derived : public Base {
    public:int derivedVar;Derived(int baseVal, int derivedVal) : Base(baseVal), derivedVar(derivedVal) {}Derived& operator=(const Derived& other) {if (this != &other) {Base::operator=(other);//调用基类的赋值derivedVar = other.derivedVar;}return *this;}
    };
    int main() {Derived d1(1, 2);Derived d2(3, 4);d2 = d1;std::cout << "Base part of d2: " << d2.baseVar << ", Derived part of d2: " << d2.derivedVar << std::endl;return 0;
    }
    

菱形继承

  • 单继承:一个子类只有一个直接父类。

  • 多继承:一个子类有两个或以上直接父类。

  • 菱形继承:一个子类有多个父类,而多个父类又继承自同一个父类。(是多继承的一种特殊情况)

    菱形继承如图:

class A
{
public:int _a;
};
class B : public A
{
public:int _b;
};
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

如上,菱形继承产生了数据冗余和二义性的问题。二义性的问题好解决,指明访问的作用域就好了,但是数据冗余如何解决呢?继续往下看。

菱形虚拟继承

为了解决菱形继承带来的问题,可以使用虚继承。虚继承可以保证在派生类中只有一份共享的基类子对象。

class A
{
public:int _a;
};
class B : virtual public A
{
public:int _b;
};
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

从上图可以看出,D对象中将A放到的了对象组成的最下 面,这个A同时属于B和C。然后在B和C中存了一个指针,这个指针叫虚基表指针,它指向虚基表,这张表里存放的是偏移量,通过偏移量可以找到A。

继承和组合

组合:

  • 组合是一种has-a的关系,即一个类包含另一个类的实例作为成员变量。
  • 组合是一种黑箱复用,也就是说对象的内部细节是不可见的。
class Engine {
public:void start() { /* 启动引擎 */ }void stop() { /* 停止引擎 */ }
};class Car {
private:Engine engine; // 组合关系public:void drive() {engine.start();// 驾驶汽车的其他操作engine.stop();}
};

继承:

  • 继承是一种is-a的关系,即一个类派生自另一个类,从而获得父类的属性和方法。
  • 继承是一种白箱复用,即基类的内部细节对子类可见,由此可见继承一定程度上破坏了基类的封装。
class Vehicle {
public:void move() { /* 移动 */ }
};class Car : public Vehicle { // 继承关系
public:void drive() { /* 驾驶汽车 */ }
};

总的来说:组合体现了代码的封装性和复用性,而继承体现了代码的多态性和扩展性。在使用上,若两者都可用那么优先使用组合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/840558.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MIT 操作系统6.S081第一章

1.1进程和内存 fork 父进程中 fork 返回 子进程的PID 子进程中 fork 返回 0 exit exit会让当前进程停止执行并释放资源(包括内存和打开的文件) 通常: 0 表示 成功 1 表示 失败 wait wait 系统调用并返回当前进程已退出或杀死的进程PID,并将子进程的状态复制到wait的地址 另…

TIA 做交通信号灯控制练习1

练习一下交通信号灯程序,从简单的功能做起。红绿黄等交替亮起。使用TIA编程,做成FB。新建一个TIA项目,选择一个1200CPU. 1. 新建一个FB,在新建的FB,先配置输入输出变量。 2. 在FB写下面的程序3. 在WINCC上建立1200?1500的变量连接,连接博图模拟器,建立变量。4.WINCC画…

Nuxt.js 应用中的 webpack:change 事件钩子

title: Nuxt.js 应用中的 webpack:change 事件钩子 date: 2024/11/24 updated: 2024/11/24 author: cmdragon excerpt: 通过webpack:change钩子,开发者可以知道哪些文件被修改,并可以进行适当的处理,比如重新加载相关模块,或更新用户界面等。 categories:前端开发tags:N…

HCIA-04 IP层及IP地址规划

详细介绍了IP地址规划的相关知识,包括IP地址的基本概念、格式、版本、头部字段、服务类型、分片机制、生存时间(TTL)以及IP地址分类等。特别强调了子网划分的重要性及其应用场景,通过实例演示了如何进行子网划分计算,包括子网数量、IP地址数量及每个子网的地址范围等。此外…

【MX-S7】梦熊 NOIP 2024 模拟赛 3 SMOI Round 2(同步赛)

【MX-S7】梦熊 NOIP 2024 模拟赛 3 & SMOI Round 2(同步赛)\(T1\) luogu P11323 【MX-S7-T1】「SMOI-R2」Happy Card \(20pts\)发现可以把「炸弹」也看做「三带一」。先使用「三带一」带走原用于出「单牌」的牌,若「三带一」还有剩余则尝试带走原用于出「对子」的牌,否…

Arthas的安装与使用

Arthas的安装与使用 简介Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常、监测方法执行耗时,类加载信息等,大大提升了线上问题排查效率。下载 …

vxe-table 设置单元格对齐方式,左对齐、右对齐

官网:https://vxeui.com/<template><div><vxe-tableborderheader-align="center"align="left":data="tableData"><vxe-column type="seq" width="70"></vxe-column><vxe-column field=&q…

高级语言程序第八次设计作业

这个作业属于哪个课程:https://edu.cnblogs.com/campus/fzu/2024C 这个作业要求在哪里: https://edu.cnblogs.com/campus/fzu/2024C/homework/13307 学号:102400128 姓名:吴俊衡 11.1 问题无11.2 问题无11.3 问题无11.6 问题无11.7 问题无12.1 问题无12.2 问题:不会怎么设…

问题待解决

model() 默认是 predict mode么,等价于 model.predict()? model 不指定 task 类型,默认是 detect task么

20222317 2024-2025-1 《网络与系统攻防技术》实验五实验报告

1.实验内容 (1)从www.besti.edu.cn、baidu.com、sina.com.cn中选择一个DNS域名进行查询,获取如下信息: ①DNS注册人及联系方式 ②该域名对应IP地址 ③IP地址注册人及联系方式 ④IP地址所在国家、城市和具体地理位置 (2)尝试获取BBS、论坛、QQ、MSN中某一好友的IP地址,并…

2024-2025-1 学号:20241301 《计算机基础与程序设计》第九周学习总结

|这个作业属于哪个课程|2024-2025-1-计算机基础与程序设计| |这个作业要求在哪里|2024-2025-1计算机基础与程序设计第一周作业| |这个作业的目标|<复习知识,巩固基础>| |作业正文|https://www.cnblogs.com/HonJo/p/18566259| 一、教材学习内容总结 (一)指针与数组 在C…

项目代码性能优化

性能优化之: 1. //减少了服务器请求次数 防抖: 防止用户在短时间内操作多次(发送多次无意义请求) 验证码 - 通过使用input验证码/滑动验证/选图片等, 验证插件, 实现先验证, 再发送请求! 节流 - 让某个函数在指定时间内, 只调用一次( 肯定会和定时器搭配使用 ) …