C++面向对象核心-继承

1、继承

1.1 概念

继承是面向对象的三大特性之一,体现了代码复用的思想。

继承就是在一个已存在的类的基础上建立一个新的类,并拥有其特性。

  • 已存在的类被称为"基类"或者"父类"
  • 新建立的类被称为"派生类"或者"子类"
  • 对象间没有继承关系
复制代码
#include <iostream>`

`using` `namespace` `std;`

`// 基类`
`class` `Father`
`{`
`private:`
    `string name =` `"孙";`
`public:`
    `void` `set_name(string name)`
    `{`
        `this->name = name;`
    `}`

    `string` `get_name()`
    `{`
        `return name;`
    `}`

    `void` `work()`
    `{`
`        cout <<` `"我的工作是厨师,我负责炒菜"` `<< endl;`
    `}`
`};`

`// 派生类`
`class` `Son:public Father`
`{`

`};`

`int` `main()`
`{`
    `Son son;`
`    cout << son.get_name()` `<< endl;`
`    son.work();`

    `return` `0;`
`}`

`
派生类差异化

上面的代码,Son类的功能几乎与Father类重叠,在实际的使用过程中,派生类会做出一些与基类的差异化。

  • 修改继承过来的基类内容

属性:1、公有的属性可以直接更改。2、私有的属性,需要使用基类提供的公有函数,进行更改。

成员函数:函数隐藏,通过派生类实现一个同名同参的函数,来隐藏基类的函数。

调用隐藏函数,需要指定作用域

  • 新增派生类的内容
复制代码
#include <iostream>`
`#include <array>`
`#include <vector>`
`#include <list>`
`#include <deque>`
`#include <map> // 头文件`
`using` `namespace` `std;`
`class` `Father`
`{`
`private:`
    `string name =` `"孙";`
`public:`
    `void` `set_name(string name)`
    `{`
        `this->name = name;`
    `}`
    `string` `get_name()`
    `{`
        `return name;`
    `}`
    `void` `work()`
    `{`
`        cout<<"我的工作是厨师,我负责炒菜 "<<endl;`
    `}`
`};`
`//派生类`
`class` `Son:public Father`
`{`
 `public:`
    `void` `init()`
    `{`
        `set_name("王");`
    `}`
    `//函数隐藏,隐藏父类同名函数`
    `void` `work()`
    `{`
`        cout<<"我的工作是学生"<<endl;`
    `}`
    `void` `game()`
    `{`
`        cout<<"我还玩游戏"<<endl;`
    `}`
`};`
`int` `main()`
`{`
    `Son son;//无参构造函数`
`    cout << son.get_name()<<endl;//孙`
`    son.work();//我的工作是学生`
`    son.game();//我还玩游戏`
`    son.init();`
`    cout << son.get_name()<<endl;//王`
`    son.Father::work();//"我的工作是厨师,我负责炒菜";调用隐藏函数,需要指定作用域`
    `return` `0;`
`}`
`

派生类往往是类的具象化,基类则是派生类的抽象。

基类和派生类是相对的,一个类可能又存在基类,又存在派生的情况,取决于那两个类进行比较。

1.2 构造函数

1.2.1 派生类与基类构造函数的关系

构造函数和析构函数不能被继承。

复制代码
#include <iostream>`
`using` `namespace` `std;`
`// 基类`
`class` `Father`
`{`
`private:`
    `string name;`
`public:`
    `// 有参构造函数`
    `Father(string name):name(name){}`

    `string` `get_name()`
    `{`
        `return name;`
    `}`
`};`
`// 派生类`
`class` `Son:public Father`
`{`
`public:`
    `// 编译器自动添加的构造函数,若没有该构造函数,编译器自动添加`
    `Son():Father(){}`
`};`
`int` `main()`
`{`
    `Son son;//创建对象时发现该类由基类,则找基类构造对象`
`    cout << son.get_name()` `<< endl;`
    `return` `0;`
`}`

`

派生类的任意一个构造函数,都必须直接或者间接调用基类的任意构造函数。

1.2.2 解决方案

1.2.2.1 补充基类的无参构造函数
复制代码
#include <iostream>`
`using` `namespace` `std;`
`// 基类`
`class` `Father`
`{`
`private:`
    `string name;`
`public:`
    `Father()`
    `{`
`        name  =` `"王";`
    `}`

    `// 有参构造函数`
    `Father(string name):name(name){}`

    `string` `get_name()`
    `{`
        `return name;`
    `}`
`};`
`// 派生类`
`class` `Son:public Father`
`{`
`public:`
    `// 编译器自动添加的构造函数`
    `Son():Father(){}`
`};`
`int` `main()`
`{`
    `Son son;`
`    cout << son.get_name()` `<< endl;`

    `return` `0;`
`}`

`
1.2.2.2 手动在派生类中调用基类构造函数
1.2.2.2.1 透传构造

在派生类的构造函数中,调用基类的构造函数,实际上编译器自动添加派生类的构造函数,调用基类无参构造时,采用的就是这种方式。

复制代码
#include <iostream>`
`using` `namespace` `std;`
`// 基类`
`class` `Father`
`{`
`private:`
    `string name;`
`public:`
`//    Father()`
`//    {`
`//        name  = "王";`
`//    }`
    `// 有参构造函数`
    `Father(string name):name(name){}`
    `string` `get_name()`
    `{`
        `return name;`
    `}`
`};`
`// 派生类`
`class` `Son:public Father`
`{`
`public:`
    `// 编译器自动添加的构造函数,透传构造`
    `// Son():Father(){}`
    `// 手动添加的构造函数,透传构造`
    `Son():Father("张"){}`
    `// 手动添加派生类的有参构造函数`
    `Son(string name):Father(name){}`
`};`
`int` `main()`
`{`
    `Son` `son("王");`
`    cout << son.get_name()` `<< endl;`
    `return` `0;`
`}`

`
1.2.2.2.2 委托构造

一个类的构造函数可以调用这个类中的另一个构造函数。要避免循环委托。

委托构造的性能低于透传构造,但是代码的维护性更好。因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数。(效率低)代码重构时,只需要更改这个能力最强的构造函数即可。

复制代码
#include <iostream>`
`using` `namespace` `std;`
`// 基类`
`class` `Father`
`{`
`private:`
    `string name;`
`public:`
`//    Father()`
`//    {`
`//        name  = "王";`
`//    }`
    `// 有参构造函数`
    `Father(string name):name(name){}`
    `string` `get_name()`
    `{`
        `return name;`
    `}`
`};`
`// 派生类`
`class` `Son:public Father`
`{`
`public:`
    `// 编译器自动添加的构造函数,透传构造`
    `// Son():Father(){}`
    `// 手动添加的构造函数,透传构造`
    `Son():Son("张"){}`
    `// 手动添加派生类的有参构造函数`
    `Son(string name):Father(name){}`
`};`
`int` `main()`
`{`
    `Son` `son("王");`
`    cout << son.get_name()` `<< endl;`

    `return` `0;`
`}`

`
复制代码
#include <iostream>`

`using` `namespace` `std;`

`// 基类`
`class` `Father`
`{`
`private:`
    `string name;`
    `int i;`
`public:`

    `// 有参构造函数`

    `Father(string name,int i):name(name),i(i){}`
    `string` `get_name()`
    `{`
        `return name;`
    `}`

    `int` `get_i()`
    `{`
        `return i;`
    `}`


`};`

`// 派生类继承基类  公有继承`
`class` `Son:public Father`
`{`
`public:`
    `// 编译器自动添加的构造函数,透传构造`
    `// Son():Father(){}`

    `// 手动添加构造函数,委托构造`
    `Son():Son("张"){}`

    `Son(string name,int i =` `1):Father(name,i){}`

`};`

`int` `main()`
`{`
    `Son` `son("hello",2);`
`    cout << son.get_name()` `<< endl;`
`    cout << son.get_i()` `<< endl;`

    `return` `0;`
`}`

`
1.2.2.2.3 继承构造

只需要加这一句话,编译器就会添加上面两种构造函数(using Father::Father;)

C++11新增的写法,只需要一句话,就可以自动给派生类添加n个(n为基类构造函数的个数)构造函数。并且每个派生类的构造函数与基类的构造函数格式相同,每个 派生类的构造函数都通过透传构造调用对应格式的基类构造函数。

复制代码
#include <iostream>`
`using namespace std;`
`//基类`
`class Father`
`{`
`private:`
`    string name;`
`public:`
`    //委托构造`
`    Father():Father("王"){}`
`    //有参函数构造`
`    Father(string name):name(name){}`
`    string get_name()`
`    {`
`        return name;`
`    }`
`};`
`//派生类`
`class Son:public Father`
`{`
`public:`
`    //    //编译器自动添加的构造函数,透传构造`
`    //    //Son():Father(){}`
`    //    //手动添加的构造函数,透传构造`
`    //    Son():Father("张"){}`
`    //    //手动添加派生类的有参构造函数`
`    //    Son(string name):Father(name){}`
`    //只需要加这一句话,编译器就会添加上面两种构造函数`
`    using Father::Father;`
`};`
`int main()`
`{`
`    Son son;//无参构造函数`
`    Son son1("张");`
`    cout << son.get_name()<<endl;`
`    cout << son1.get_name()<<endl;`
`    return 0;`
`}`
`

1.3 对象的创建与销毁流程

在继承中,构造函数与析构函数的调用。

复制代码
#include <iostream>`

`using` `namespace` `std;`

`class` `Value`
`{`
`private:`
    `string str;`
`public:`
    `Value(string str):str(str)`
    `{`
`        cout << str <<"构造函数"` `<< endl;`
    `}`

    `~Value()`
    `{`
`        cout << str <<` `"析构函数"` `<< endl;`
    `}`
`};`

`class` `Father`
`{`
`public:`
    `static` `Value s_value;`
    `Value val =` `Value("Father 成员变量");`
    `Father()`
    `{`
`        cout <<` `"Father 构造函数被调用了"` `<< endl;`
    `}`
    `~Father()`
    `{`
`        cout <<` `"Father 析构函数被调用了"` `<< endl;`
    `}`
`};`
`Value Father::s_value =` `Value("静态FatherValue创建了");`

`class` `Son:public Father`
`{`
`public:`
    `static` `Value s_value;`
    `Value val =` `Value("Son 成员变量");`

    `Son()`
    `{`
`        cout <<` `"Son构造函数被调用了"` `<< endl;`
    `}`
    `~Son()`
    `{`
`        cout <<` `"Son析构函数被调用"` `<< endl;`
    `}`

`};`
`Value Son::s_value =` `Value("静态SonValue被创建了");`

`int` `main()`
`{`
`    cout <<` `"主程序开始执行"` `<< endl;`
    `// 局部代码块`
    `{`
        `Son s;`
`        cout <<` `"对象执行中"` `<< endl;`
    `}`
`    cout <<` `"主程序结束了"` `<< endl;`
    `return` `0;`
`}`
`

上面的执行结果中,可以得到下面的规律:

  • 以"对象执行中"为轴,上下对称,先创建静态后创建非静态,先创建基类,后创建派生类,先创建成员变量,后创建对象。先析构成员变量后析构函数,先析构派生类,后析构基类,先析构非静态,后析构静态。
  • 在创建的过程中,同类型的内存区域,基类先开辟。
  • 静态的创建早于非静态
  • 对象的创建,晚于类中成员变量的创建。

1.4 多重继承

1.4.1 概念

C++支持多重继承,即一个派生类可以有多个基类。派生类对于每个基类的关系仍然可以看作是一个单继承。

复制代码
#include <iostream>`
`using` `namespace` `std;`
`class` `Sofa`
`{`
`public:`
    `void` `sit()`
    `{`
`        cout <<` `"沙发可以坐着"` `<< endl;`
    `}`
`};`
`class` `Bed`
`{`
`public:`
    `void` `lay()`
    `{`
`        cout <<` `"床可以躺着"` `<< endl;`
    `}`
`};`
`class` `SofaBed:public Sofa,public Bed`
`{`
   `public:`
`};`
`int` `main()`
`{`
    `SofaBed sb;`
`    sb.sit();`
`    sb.lay();`
    `return` `0;`
`}`
`

1.4.2 可能出现的问题

1.4.2.1 问题1-重名问题

当多个基类具有重名成员时,编译器在调用的过程中会出现二义性。

解决方式:使用基类的类名::方式调用。

复制代码
#include <iostream>`
`using` `namespace` `std;`
`class` `Sofa`
`{`
`public:`
    `void` `sit()`
    `{`
`        cout <<` `"沙发可以坐着"` `<< endl;`
    `}`
    `void` `test()`
    `{`
`        cout <<` `"打扫沙发"` `<< endl;`
    `}`
`};`
`class` `Bed`
`{`
`public:`
    `void` `lay()`
    `{`
`        cout <<` `"床可以躺着"` `<< endl;`
    `}`
    `void` `test()`
    `{`
`        cout <<` `"打扫床"` `<< endl;`
    `}`
`};`
`class` `SofaBed:public Sofa,public Bed`
`{`
   `public:`
`};`
`int` `main()`
`{`
    `SofaBed sb;`
`    sb.sit();`
`    sb.lay();`
`//    sb.test(); // 错误二义性,两个类中都继承到相同的成员函数名`
`    sb.Bed::test();//作用域指定`
`    sb.Sofa::test();`
    `return` `0;`
`}`
`
1.4.2.2 问题2-菱形继承

当一个派生类有多个基类,且这些基类又有同一个基类,就会出现二义性问题,这种情况也被称为菱形继承(钻石)继承。

1)作用域加类名解决
复制代码
#include <iostream>`

`using` `namespace std;`

`class` `Furniture`
`{`
`public:`
    `void` `func()`
    `{`
`        cout <<` `"家具厂里有家具"` `<< endl;`
    `}`

`};`
`class` `Sofa:public Furniture`
`{`
`public:`
`};`

`class` `Bed:public Furniture`
`{`
`public:`
`};`

`class` `SofaBed:public Sofa,public Bed`
`{`
   `public:`
`};`


`int` `main()`
`{`
`    SofaBed sb;`
`//    sb.func();`
`//作用域加类名完成`
`    sb.Sofa::func();`
`    sb.Bed::func();`
    `return` `0;`
`}`

`
2)使用虚继承 virtual

虚继承解决二义性是通过虚基类指针和虚基类表实现的。在下面代码中Sofa和Bed类会创建一个虚基类指针和虚基类表。所有同类型对象共用一张虚基类表,每个对象内部增加一个隐藏的虚基类指针成员变量,这个虚基类指针指向虚基类表。当Sofa和Bed类作为基类有了派生类SofaBed时,SofaBed对象中也会有隐藏的虚基类指针,但是SofaBed没有自己的虚基类表。在调用Furniture的成员时,SofaBed对象会通过虚基类指针找到对应的虚基类表,通过查表避免二义性。

复制代码
#include <iostream>`

`using` `namespace std;`

`class` `Furniture`
`{`
`public:`
    `void` `func()`
    `{`
`        cout <<` `"家具厂里有家具"` `<< endl;`
    `}`

`};`
`// 虚继承`
`class` `Sofa:virtual public Furniture`
`{`
`public:`
`};`

`class` `Bed:virtual public Furniture`
`{`
`public:`
`};`

`class` `SofaBed:public Sofa,public Bed`
`{`
   `public:`
`};`


`int` `main()`
`{`
`    SofaBed sb;`
`    sb.func();`
    `return` `0;`
`}`

`

练习多重继承:

定义学生类,有姓名,学号,性别,年龄等私有成员变量,有构造函数,有打印信息的成员函数。

要求通过构造函数可以给属性赋予初始值。

定义大学生类,继承自学生类,大学生有专业名、成绩的私有成员变量,还有是否获得奖学金的成员函数(成绩为判断依据)。隐藏基类打印信息的成员函数,新的打印信息的成员函数也要能打印姓名、学号、性别、年龄信息。

要求通过构造函数可以给属性赋予初始值。

再定义研究生类,继承自大学生类,有导师姓名和工资的私有成员变量,有打印工资这个成员函数。

要求通过构造函数可以给属性赋予初始值。

复制代码
#include <iostream>`

`using` `namespace std;`
`class` `Student`
`{`
`private:`
`    string name;`
    `int stdnum;`
`    string sex;`
    `int age;`
`public:`
    `//有参构造`
    `Student(string name,int stdnum,string sex,int age):name(name),stdnum(stdnum),sex(sex),age(age){}`
    `void` `show_student()`
    `{`
`        cout<<"姓名:"<<name<<endl<<"学号:"<<stdnum<<endl<<"性别:"<<sex<<endl<<"年龄 "<<age<<endl;`
    `}`
`    string get_name()`
    `{`
        `return name;`
    `}`
`};`
`class` `Undergraduate:public Student`
`{`
`private:`
`    string major;`
    `float grade;`
`public:`
`//透传构造`
    `Undergraduate(string name,int stdnum,string sex,int age,string major,float grade):Student(name,stdnum,sex,age),major(major),grade(grade){}`
    `//奖学金判断`
    `void` `jodge_scholarship()`
    `{`
        `if(this->grade>=95)`
        `{`
`            cout<<Student::get_name()<<"获得奖学金"<<endl;`
        `}`
        `else`
        `{`
`            cout<<Student::get_name()<<"未获得奖学金"<<endl;`
        `}`
    `}`
    `//打印大学生信息`
    `void` `show_undergraduate()`
    `{`
        `Student::show_student();`
`        cout<<"专业:"<<major<<endl<<"成绩:"<<grade<<endl;`
    `}`
`};`
`class` `Graduate:public Undergraduate`
`{`
`private:`
`    string totor;//导师`
    `int pay;`
`public:`
    `//构造`
    `Graduate(string name,int stdnum,string sex,int age,string major,float grade,string totor,int pay):Undergraduate(name,stdnum,sex,age,major,grade),totor(totor),pay(pay){}`
    `void` `get_pay()`
    `{`
`        cout<<"薪资待遇:"<<pay<<endl;`
    `}`
    `void` `show_graduate()`
    `{`
`        cout<<"导师:"<<totor<<endl;`
`        cout<<"薪资:"<<pay<<endl;`
    `}`

`};`

`int` `main()`
`{`
`    cout<<"学生类"<<endl;`
`    Student std1("张三",123,"男",18);`
`    std1.show_student();`
`    cout<<"大学生类"<<endl;`
`    Undergraduate std2("张三",123,"男",18,"嵌入式",99);`
`    std2.show_undergraduate();`
`    std2.jodge_scholarship();`
`    cout<<"研究生类"<<endl;`
`    Graduate std3("张三",123,"男",18,"嵌入式",99,"沈子",9999);`
`    std3.show_student();`
`    std3.show_graduate();`
`    std3.get_pay();`
    `return` `0;`
`}`


`
相关推荐
重生之我是数学王子5 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
Ai 编码助手7 分钟前
使用php和Xunsearch提升音乐网站的歌曲搜索效果
开发语言·php
学习前端的小z11 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
神仙别闹19 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE20 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
我们的五年29 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
zwjapple36 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five38 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省39 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang