(P29)继承:代码重用 ,继承 ,公有,私有,保护继承 ,默认继承保护级别 ,接口继承与实现继承 ,继承与重定义 ,继承与组合

本文主要是介绍(P29)继承:代码重用 ,继承 ,公有,私有,保护继承 ,默认继承保护级别 ,接口继承与实现继承 ,继承与重定义 ,继承与组合,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1.代码重用
    • 2.继承
    • 3.公有,私有,保护继承
    • 4.默认继承保护级别
    • 5.接口继承与实现继承
    • 6.继承与重定义
    • 7.继承与组合
    • 8.私有继承的妙用
      • 注:父类的私有虚函数

1.代码重用

  • C++很重要的一个特征就是代码重用。
    在C语言中重用代码的方式就是拷贝代码、修改代码。
    C++可以用继承或者组合的方式来重用。 通过组合或继承现有的类来创建新类,而不是重新创建它们。
  • 第一种重用方式:以组合的方式实现重用的eg
//组合的方式实现重用:就是将一个类作为另外一个类的对象成员
class A
{
public:void FunA(){....}
};
class B
{
public://要实现FunB的功能,将其委托给A来实现//调用对象a_中FunA()功能来实现重用,来实现自己的代码void FunB(){...a_.FuncA();...}
private://A类是B类的子对象A a_;
};
  • 第二种重用方式就是继承

2.继承

  • 继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补
  • 新类称为派生类或者子类,原有类称为基类或父类
  • 从形式上来看,派生类更大一些,从逻辑上,看派生类是基类的具体化
    派生类所表示的事务范围要比基类的事务范围要小的多
  • eg:派生类和基类的关系
    在这里插入图片描述

3.公有,私有,保护继承

  • 公有,私有,保护成员
    (1)在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数
    (2)在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问
    (3)在关键字protected后面声明,与private类似,其差别表现在继承与派生时,对派生类的影响不同
  • eg:P29\01.cpp
#include <iostream>
using namespace std;class Base
{
public:int x_;
protected:int y_;
private:int z_;
};class PublicInherit : public Base
{
public:void Test(){x_  = 10;//在派生类中可以访问基类的public成员y_ = 20;//在派生类中可以访问基类的protected成员z_ = 30;//在派生类中不能访问基类的private成员,虽然基类的private成员是派生类的一部分}
private:
};int main(void)
{Base b;b.x_ = 20;//类外部可以访问public成员b.z_ = 30;//类外部不能访问protected成员return 0;
}
  • 公有、私有、保护继承的关系
    公有继承:公有和保护成员不变;私有成员任何一种继承都变成了不可直接访问;
    私有继承:公有和保护成员都变成了私有的;
    保护继承:公有成员降级为保护,保护的不变
    在这里插入图片描述

4.默认继承保护级别

class Base {};
struct D1 : Base {};//对于结构体而言,默认是公有继承
class D2 : Base {};//对于类而言,默认是私有继承

5.接口继承与实现继承

  • 我们将类的公有成员函数称为接口
  • 公有继承,基类的公有成员函数在派生类中仍然是公有的。换句话说就是基类的接口成为了派生类的接口,因而将它称为接口继承
  • 实现继承,对于私有、保护继承,派生类不继承基类的接口。
    派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承

6.继承与重定义

  • 对基类的数据成员的重定义

  • 对基类成员函数的重定义分为2种
    (1)overwrite:称为重定义,重定义会隐藏基类的成员
    与基类完全相同;
    与基类成员函数名相同,参数不同;
    (2)override
    称为:覆盖,要求是虚函数

  • overload:称为重载,发生在:作用域相同,需要在同一个类当中

  • eg1:

#include <iostream>
using namespace std;class Base
{
public:Base() : _x(0){}int GetBaseX() const { return _x; }int x_;
};class Derived : public Base
{
public:// Derived() : _x(0)// {// }// int GetDerivedX() const // { //     return _x; // }// int x_;
};int main(void)
{Derived d;d.x_ = 10;//重定义会隐藏基类的成员,基类的x_=0,派生类的x_=10cout<<d.GetBaseX()<<endl;// cout<<d.GetDerivedX()<<endl;return 0;
}
  • 测试:
    没有重定义的话,改变的就是基类的x_
    在这里插入图片描述

  • eg2:
    在派生类中定义与基类同名的函数,但是带参数

#include <iostream>
using namespace std;class Base
{
public:Base() : _x(0){}int GetBaseX() const { return _x; }int x_;void Show(){cout<<"Base::show..."<<endl;}
};class Derived : public Base
{
public:Derived() : _x(0){}int GetDerivedX() const { return _x; }int x_;void Show(int n){cout<<"Derived::show"<<endl;}};int main(void)
{Derived d;d.x_ = 10;//重定义会隐藏基类的成员,基类的x_=0,派生类的x_=10cout<<d.GetBaseX()<<endl;cout<<d.GetDerivedX()<<endl;d.show();return 0;
}
  • 测试:
    不能直接调用基类的show函数了,基类的show()在派生类中被隐藏了;
    在这里插入图片描述

  • eg3:
    在派生类中定义与基类同名的函数
    调用的是,派生类的不带参数的show方法

#include <iostream>
using namespace std;class Base
{
public:Base() : _x(0){}int GetBaseX() const { return _x; }int x_;void Show(){cout<<"Base::show..."<<endl;}
};class Derived : public Base
{
public:Derived() : _x(0){}int GetDerivedX() const { return _x; }int x_;void Show(int n){cout<<"Derived::show"<<endl;}void Show(){cout<<"Derived::show..."<<endl;}};int main(void)
{Derived d;d.x_ = 10;d.Base::x_ = 20;cout<<d.GetBaseX()<<endl;cout<<d.GetDerivedX()<<endl;//调用的是,派生类的不带参数的show方法d.show();//访问基类的show方法d.Base::show();return 0;
}
  • 测试:
    在这里插入图片描述

7.继承与组合

  • 继承与组合的内存模型,基本是一样的,都是把基类作为派生类的子对象用子成员描述更加合适)

  • 无论是继承与组合,其本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象

  • 组合通常是希望:新类内部具有已存在的类的功能时能使用,而不是希望已存在类作为它的接口。
    组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口,而不是来自老类的接口。(has-a)
    组合通常表现为has-a特点:将一个类作为另外一个类的子对象

  • 如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也称为子类型化。(is-a)
    凡是能够接收基类参数,都能用派生类来替换,因为派生类也是一种基类对象,子类型化,子类拥有基类所有的方法,子类继承基类的所有的接口,能够使用基类的地方就能使用子类

  • 里氏代换原则能够检验继承的质量,继承更关注的是行为

  • eg:P29\02.cpp

#include <iostream>
using namespace std;class Base
{
public:Base() : _x(0){}int GetBaseX() const { return _x; }int x_;void Show(){cout<<"Base::show..."<<endl;}
};class Derived : public Base
{
public:Derived() : _x(0){}int GetDerivedX() const { return _x; }int x_;void Show(int n){cout<<"Derived::show"<<endl;}void Show(){cout<<"Derived::show..."<<endl;}};class Test
{
public:Base b_;int x_;
};int main(void)
{Derived d;d.x_ = 10;d.Base::x_ = 20;cout<<d.GetBaseX()<<endl;cout<<d.GetDerivedX()<<endl;//调用的是,派生类的不带参数的show方法d.show();//访问基类的show方法d.Base::show();//Derived继承了Base,相当于把base当成了子对象一样的//Derived包含了2个数据成员:基类的_x,派生类的_xcout<<sizeof(Derived)<<endl;cout<<sizeof(Test)<<endl;return 0;
}
  • 测试:
    在这里插入图片描述

8.私有继承的妙用

private继承导致的结果

  • 基类中的所有成员都会变成private;

  • 如果是private继承,则不会自动将派生类类型转换为基类类型(不会自动转换,但是可以手动显式进行转换),不能隐式转换

  • 最大的用处是通过“继承”的纵向逻辑建立了一种“has-a”逻辑(物理上还是“is-a”)。更直白点说,就是从基类继承来的成员,具有对内可用但是对外不可见的特点,这和组合的逻辑很像。所以说,在私有继承的语境下,可以把基类看成派生类的数据成员对象。

private继承的意义

  • 不同于public继承是表示子类与基类之间的’is-a’关系,private并不象征着类之间的’is-a’关系。

  • private继承意味着“子类根据基类的实现而得到”。(implemented-in-terms-of,根据基类实现出子类)

  • 由于private继承将基类的所有public都改为private,因此,可以将private继承视为继承子类的实现而略去子类的接口(因为子类的接口由于private的原因不能再被调用者调用,相当于接口被取消),接口指一个class内的public方法。

什么情况下该/不该使用private继承

  • 由于private就是将一个类(基类)作为另一个类(子类)的一部分实现内容,即用基类来实现子类,它与对象之间的复合/包含关系很像,因此需要明确它们的异同点并考虑替代。
  • 复合/聚合关系:即一个类包含另一个类,如在class Foo中定义一个成员,其类型是另一个类,这两个类之间就是复合关系。

使用private继承来代替复合的情况

  • 存在protected成员的时候,使用private继承和使用复合的结果是不同的:复合后一个类仍然不能使用另一个类的protected成员;而private继承可以。
  • 存在virtual成员的时候:复合与private继承的结果也不同。

总结:什么时候使用private继承比复合更好

  • 当你希望访问protected接口的时候,使用private继承比复合更好,因为private继承能够提供访问权限;
  • 当你希望override它的virtual函数的时候,使用private继承更好,因为继承能够提供override。
  • 除此之外一般来说,使用复合比使用private继承更好。

eg:

class father1;
class father2
{public:void func1(){}
protected:void func2(){}
}//使用继承的逻辑初始化。
class son:private father1, private father2{
public:
//p1是father1构造函数需要的参数,p2同理son(p1,p2):father1(p1),father2(p2){}private:void func3(){func1();func2();}
}

访问基类的方法

  • 可以访问访问基类的public和protected的方法;

访问基类public对象(or 方法):

  • 如果直接要使用整个基类对象本身怎么做呢?方法是使用强制转换。因为是继承,所以可以这么做。
  • 如:(const father1&) this,这个this指的是派生类。
#include <iostream>class Test {protected:Test() { std::cout << "Test()" << std::endl; }~Test() = default;public:void configure() { std::cout << "configure()" << std::endl; }protected:void func() { std::cout << "func()" << std::endl; }
};class SubTest : private Test {
public:SubTest() : Test() {((Test *)this)->configure();configure();func();}
};
class EmptyClass {};int main() {auto test = SubTest{};std::cout << sizeof(EmptyClass) << std::endl;return 0;
}

访问基类友元函数

  • 跟访问基类对象的方法一样,即强制转换

注:父类的私有虚函数

一个成员函数被定义为private属性,标志着其只能被当前类的其他成员函数(或友元函数)所访问。

而virtual修饰符则强调父类的成员函数可以在子类中被重写,因为重写之时并没有与父类发生任何的调用关系,故而重写是被允许的。

被virtual修饰的成员函数,不论他们是private、protect或是public的,都会被统一的放置到虚函数表中。

对父类进行派生时,子类会继承到拥有相同偏移地址的虚函数表(相同偏移地址指,各虚函数相对于VPTR指针的偏移),则子类就会被允许对这些虚函数进行重载。且重载时可以给重载函数定义新的属性;

  • 例如public,其只标志着该重载函数在该子类中的访问属性为public,和父类的private属性没有任何关系!

  • eg:

class WeatherBase
{
privatevirtual void init();
}
class Rain : public WeatherBase
{
private:virtual void init();
}

参考:
private继承的特点和意义以及何时使用;
C++私有继承有什么用、怎么用

这篇关于(P29)继承:代码重用 ,继承 ,公有,私有,保护继承 ,默认继承保护级别 ,接口继承与实现继承 ,继承与重定义 ,继承与组合的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1041382

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

hdu4869(逆元+求组合数)

//输入n,m,n表示翻牌的次数,m表示牌的数目,求经过n次操作后共有几种状态#include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdlib.h>#includ

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n