C++面向对象程序设计 - 多继承,以及基类与派生类转换

        单继承是一个类是从另一个基类派生类而来的,多继承则是一个派生类是同两个或多个基类,派生类从两人或多个基类中继承所需的属性。

        声明多重继承的方法:

class D: public A, private B, protected C

{

        类D新增加的成员

}

一、多重继承派生类的构造函数

        多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。如:

派生类构造函数名 ( 总参数表列 ) : 基类1构造函数 ( 参数表列 ) ,基类2构造函数 (参数表列) ,基类3构造函数 (参数表列)

{

        派生类中新增数据成员初始化语句

}

        派生类构造函数的执行顺序:同样还是先调用基类的构造函数,再执行派生类构造函数的函数体。

示例代码:

#include <iostream>
#include <string>
using namespace std;
class Teacher{protected:string name;int age;string post;		//职称public:// 构造函数Teacher(string name, int age, string post): name(name), age(age), post(post){}
};
class Student{protected:string name;int age;int score;public:// 构造函数Student(string name, int age, int score): name(name), age(age), score(score){}
};
class Graduate: public Teacher, public Student{private:int wage;			//工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){cout <<"name:" <<Teacher::name <<endl;cout <<"age:" <<Teacher::age <<endl;cout <<"post:" <<post <<endl;cout <<"score:" <<score <<endl;cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}

        运行结果如下:

        注意的是,Teacher类和Student类中都有name和age数据成员,这样程序就不知道调用哪一个,没作指明则系统编译时会报错【[Error] reference to 'name' is ambiguous】- 对'name'的引入有歧义。但如何指明其作用域呢,上述代码中已经体现了,通过类名来指定作用域,如:

cout <<"name:" <<Teacher::name <<endl;

二、多重继承引起的二义性问题

        多重继承可以反映现实生活中的情况,能够用效地处理一些较复杂的问题,使编写程序具有灵活性,但是多重继承也引起了一些值得注意的问题,它增加了程序的复杂性,最常见的问题则是继承的成员同名而产生的二义性(ambiguous)问题。

(1)两个基类有同名成员。如下图:

        示例代码如下:

#include <iostream>
#include <string>
using namespace std;
class Teacher{protected:string name;int age;string post;		//职称public:// 构造函数Teacher(string name, int age, string post): name(name), age(age), post(post){}
};
class Student{protected:string name;int age;int score;public:// 构造函数Student(string name, int age, int score): name(name), age(age), score(score){}
};
class Graduate: public Teacher, public Student{private:int wage;			//工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);return 0;
}

(2)两个基类和派生类三者都有同名成员。如下图:

        示例代码如下:

#include <iostream>
#include <string>
using namespace std;
class Teacher{protected:string name;int age;string post;		//职称public:// 构造函数Teacher(string name, int age, string post): name(name), age(age), post(post){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"post:" <<post <<endl;}
};
class Student{protected:string name;int age;int score;public:// 构造函数Student(string name, int age, int score): name(name), age(age), score(score){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
class Graduate: public Teacher, public Student{private:int wage;			//工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){Teacher::display();Student::display();cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}

        运行结果如下:

(3)两个基类有同一基类派生的,如下图:

        示例代码如下:

#include <iostream>
#include <string>
using namespace std;
class Person{protected:string name;int age;public:Person(string name, int age): name(name), age(age){}
};
class Teacher: public Person{protected:string post;		//职称public:// 构造函数Teacher(string name, int age, string post): Person(name, age), post(post){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"post:" <<post <<endl;}
};
class Student: public Person{protected:int score;public:// 构造函数Student(string name, int age, int score): Person(name, age), score(score){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
class Graduate: public Teacher, public Student{private:int wage;			//工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){cout <<"name:" <<Teacher::name <<endl;cout <<"age:" <<Teacher::age <<endl;cout <<"post:" <<post <<endl;cout <<"score:" <<score <<endl;cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}

        运行结果如下:

三、虚基类

3.1 虚基在的作用

        如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二叉性,使用类名标识同名成员,如:Person::name。

        C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式是声明的。因为一个基类可以生成一个派一类时作为虚基类,而在生成另一个派生类时不作为虚基类。

        声明虚基类的一般形式为:

class 派生类名:virtual  继承方式  基类名 

        为了保证虚基类在派生类中只继承一次,应当在该基类的民有直接派生类中声明为虚基类,否则仍然会出两对基类的多次继承。

3.2 虚基类的初始化

        如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。

        示例代码如下:

#include <iostream>
#include <string>
using namespace std;
class Person{protected:string name;int age;public:Person(string name, int age): name(name), age(age){}
};
// Person 作为Teacher虚类
class Teacher: virtual public Person{protected:string post;		//职称public:// 构造函数Teacher(string name, int age, string post): Person(name, age), post(post){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"post:" <<post <<endl;}
};
// Person作为Student虚类
class Student: virtual public Person{protected:int score;public:// 构造函数Student(string name, int age, int score): Person(name, age), score(score){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
class Graduate: public Teacher, public Student{private:int wage;			//工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Person(name, age), Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){cout <<"name:" <<Teacher::name <<endl;cout <<"age:" <<Teacher::age <<endl;cout <<"post:" <<post <<endl;cout <<"score:" <<score <<endl;cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}

       运行结果如下图:

        注意,在定义Graduate类时,与以往使用的方法有所不同。以前,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。

        C++中规定,在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。否是系统编译会报错【[Error] no matching function for call to 'Person::Person()'】- 调用Person::Person()没有匹配的函数。

四、基类与派生类的转换

        公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外基类所有成员,基类的公用或保护成员的访问权限在派生类中全部都按原样保留下来,在派生类外可以调用基类的公用成员函数说基类的私有成员。因此,公用派生类具有基类的全部功能,所有基类能够实现的功能,公用派生类都能实现。而非公用派生类(私有或保护派生类)不能实现基类的全部功能。因此,只有公用派生类才是基类真正的了类型,它完整的继承了基类的功能。

        不同类型数据之间的自动转换和赋值,称为赋值兼容。

        基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。具体表现有以下几个方面:

(1)派生类对象可以向基类对象赋值,实际上是舍弃派生类自己的数据成员,对基类继承过来数据成员进行赋值。示例如下:

Person p;        // 定义基类Person类对象p
Student s;       // 定义Person类的公用派生类Student的对象s
p = s;           // 用派生类Student对象s对基类对象p赋值

(2)派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。示例如下:

Person p;        // 定义基类Person对象p
Student s;       // 定义公用派生类Student对象s
Person& p2 = p;  // 定义基类Person对象的引用变量p2,并用p对其实始化

这里也可以用子对象初始化引用p2,不过此时p2不是与对象s共享同一段存储单元,而是与对象s部分共享同一段单元。示例如下:

Person& p2 = s;    // 定义基类Person对象的引用变量p2,并用派生类Student对象s对其初始化

(3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。示例如下:

// 定义基类Person
class Person{//...
}
// 定义派生类Student
class Student: public Person{//...
}
// 定义一个函数其形参为Person类引用
void display(Person& p){//...
}
int main(){Person p;        // 定义基类Person的对象pStudent s;       // 定义派生类Student的对象s// 可以用子类对象display(s);return 0;
}

(4)派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。示例代码如下:

#include <iostream>
#include <string>
using namespace std;
// Student类
class Student{protected:string name;int age;public:// 构造函数Student(string name, int age): name(name), age(age){}void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;}
};
class Graduate: public Student{private:int score;			//成绩public:// 多继承构造函数Graduate(string name, int age, int score):Student(name, age), score(score){}// 显示信息void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
int main(){// 实例Student对象Student student("Tom", 20);// 实例Graduate对象Graduate graduate("John", 30, 98.5);Student* p = &student;			// 定义指向Student类对象的指针并指向studentp->display();					// 调用student对象的成员函数p = &graduate;					// 指针指向graduatep->display();					// 调用graduate对象的成员函数return 0;
}

        运行结果可以看出,当指针指向对象graduate后调用display(),只输出了派生类继承基类的数据成员,而自身自增的数据成员并未输出。这是因为指向基类对象的指针,只能访问派生类的基类成员,而不能访问派生类增加的成员,所以p->display()调用的是基类 的display()函数,所以没有输出Graduate对象自增部分数据成员。如下图:

五、继承与组合

        在一个类中可以用类对象作为数据成员,即子对象。在一个类中以另一个类的对象作为数据成员的,称为类的组合(composition)。

        类的组合和继承一样,是软件重用的重要方式。组合和继承都是有效地利用已有类的资源。这里定义一个Birthday类,并将Birthday作为Student数据成员,用存储该学生的生日信息。示例代码如下:

#include <iostream>
#include <string>
using namespace std;
class Person{protected:string name;int age;public:Person(string name, int age): name(name), age(age){}
};
// 定义生日类
class Birthday{private:int year;int month;int day;public:Birthday(int year, int month, int day): year(year), month(month), day(day){}// 显示日期void show(){cout <<year <<'/' <<month <<'/' <<day <<endl;}
};
// Person作为Student基类
class Student: public Person{protected:Birthday birth;public:// 构造函数Student(string name, int age, Birthday birth): Person(name, age), birth(birth){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"birth:"; birth.show();}
};
int main(){Student s("Tom", 20, Birthday(2000, 1, 15));// 显示信息s.display();return 0;
}

        运行结果如下图:

        由于C++提供了继承的机制,用户将它们作为基类去建立适合于自己的类(即派生类),并在此基础设计自己的应用程序,类库的出现使得软件的重用更加方便。

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

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

相关文章

UI图中的opacity效果和代码效果不一样

有时UI图中使用了opacity属性&#xff0c;这个和前端代码效果不一致&#xff0c;修改方法&#xff1a; 不要用opacity属性&#xff0c;而是使用background:rgba( )的效果接客。

shell 局域网IP探活脚本

Linux Shell脚本是一种编程方式&#xff0c;它允许用户通过编写一系列命令和控制结构来自动化系统任务。Shell脚本通常以.sh为扩展名&#xff0c;使用诸如Bash、Zsh、Ksh等Shell解释器来执行。以下是一个简单的Shell脚本示例&#xff0c;该脚本用于展示如何遍历局域网的一个子网…

Vue---组件

Vue—组件 目录 Vue---组件定义组件全局组件局部组件 组件通讯***重点***父子通信之父传子&#xff08;props&#xff09;父子通信之子传父&#xff08;$emit&#xff09;ref属性&#xff08;$refs&#xff09; 动态组件插槽命名插槽 定义组件 全局组件 vue2中template只能传…

设置消息边界的方法有哪几种?

1. 特定字符或字符串 使用一些不会在正常消息内容中出现的特殊字符或字符串作为消息的分隔符。例如&#xff1a; 行分隔符&#xff1a;在类似于HTTP头部的文本协议中&#xff0c;可以使用换行符&#xff08;如\r\n&#xff09;作为每一行的结束标记。 特殊字符串&#xff1a;…

浏览器渲染机制:重排(Reflow)与重绘(Repaint)以及Vue优化策略

浏览器渲染机制是一个复杂但有序的过程&#xff0c;其目的是将HTML、CSS和JavaScript代码转化为用户可以看到和交互的视觉界面。重排&#xff08;Reflow&#xff09;与重绘&#xff08;Repaint&#xff09;是浏览器渲染过程中对页面元素进行更新的两个重要步骤&#xff0c;理解…

ubuntu22.04安装TensorRT(过程记录)

重要说明&#xff1a;此贴经过多次修改。第一次安装的的为trt8.6.1版本。第二次安装的10.0.0.6版本。有些地方可能没改过来&#xff0c;比如链接向导&#xff0c;我懒得改了&#xff0c;但是流程是对的。 cuda和cudnn版本对应关系 tensorRT历史发行版本 CUDA历史发行版本 cudn…

electron退出时添加一个包含“不再提示”选项的确认对话框

在 Electron 中添加一个包含“不再提示”选项的确认对话框可以通过 checkboxLabel 和 checkboxChecked 属性在 dialog.showMessageBox 函数中实现。这两个属性分别用于设置复选框的标签和初始状态。您可以根据用户的选择来决定是否在将来再显示此对话框。 以下是一个更新的示例…

13 内核开发-任务调度-Work queues工作队列

13 内核开发-任务调度-Work queues工作队列 目录 13 内核开发-任务调度-Work queues工作队列 1.定义 2.内涵 3.使用示例 4.具体代码使用实践 5.注意事项 6.最佳实践 7.总结 9.比较 workqueue_struct 与 tasklet 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助…

ENVI不同版本个人使用对比

ENVI不同版本个人使用对比 文章目录 ENVI不同版本个人使用对比前言对比5.3学习版5.6学习版6.0试用版 总结 前言 目前来看&#xff0c;流传较广的可供大家免费获取的ENVI版本主要是5.3学习版 5.6学习版 6.0学习版这三个版本&#xff0c;不同的版本有不同特色&#xff0c;在此做…

关于面向对象与面向过程的基本概念

什么是面向对象与面向过程编程 面向对象编程&#xff08;OOP&#xff09;和面向过程编程&#xff08;POP&#xff09;是两种主要的编程范式&#xff0c;它们在解决问题的方法和组织代码的方式上有所不同。C#和Java都是支持这两种编程范式的语言&#xff0c;但它们在实现细节上…

C#基础|StringBuilder字符串如何高效处理。

哈喽&#xff0c;你好&#xff0c;我是雷工。 字符串处理在C#程序开发中是使用频率比较高的&#xff0c;但常规的字符串处理方式对内存占用比较多&#xff0c;为了优化内存&#xff0c;减少不必要的内存浪费&#xff0c;引入了StringBuilder类。 下面学习下StringBuilder类的使…

PC-3000 Flash:NAND 闪存设备(包括一体式U盘)数据恢复的重量级工具(一)

天津鸿萌科贸发展有限公司从事数据安全业务20余年&#xff0c;在数据恢复、数据取证、数据备份等领域有丰富的案例经验、前沿专业技术及良好的行业口碑。同时&#xff0c;公司面向取证机构及数据恢复公司&#xff0c;提供数据恢复实验室建设方案&#xff0c;包含 PC-3000 系列数…

LeetCode 热题 100 Day05

矩阵相关题型 Leetcode 73. 矩阵置零【中等】 题意理解&#xff1a; 将矩阵中0所在位置&#xff0c;行|列置换为全0 其中可以通过记录0元素所在的行、列号&#xff0c;来标记要置换的行|列 将对应位置置换为0 解题思路&#xff1a; 第一个思路&#xff1a; 可以…

React | classnames

classnames 这个库在我们的项目中有大量的使用到&#xff0c;它不仅很实用&#xff0c;还非常好用&#xff0c;但还有人不知道这个库&#xff0c;我真的是十分心痛。 通过 classnames&#xff0c;我们可以给组件设置多个 className&#xff0c;还可以根据需要动态设置 classNa…

模块四:前缀和——DP35 【模板】二维前缀和

文章目录 题目描述算法原理解法一&#xff1a;暴力模拟&#xff08;时间复杂度为O(n*m*q)&#xff09;解法二&#xff1a;二维前缀和&#xff08;时间复杂度为O(m*n)O(q)) 代码实现解法二&#xff1a;前缀和&#xff08;C)Java 题目描述 题目链接&#xff1a;DP35 【模板】二维…

三星电脑文件夹误删了怎么办?恢复方案在此

在使用三星电脑的过程中&#xff0c;我们可能会不小心删除了某个重要的文件夹&#xff0c;其中可能包含了工作文件、家庭照片、视频或其他珍贵的数据。面对这种突发情况&#xff0c;不必过于焦虑。本文将为您提供几种有效的恢复方案&#xff0c;希望能帮助您找回误删的文件夹及…

openEuler-22.03安装 mysql8.0.32

一、下载解压 下载地址&#xff1a; MySQL :: Download MySQL Community Server (Archived Versions) tar -xvf mysql-8.0.32-1.el7.x86_64.rpm-bundle.tar -C /opt/mysql-8.0.32 二、安装 最开始安装一直报错 缺少 libcrypto.so.10库文件,安装openssl可以解决 wget http://…

kafka 线上消费积压问题

背景 线上kafka 流量大&#xff0c;消费小于生产&#xff0c;如何处理&#xff1f; 方案 增加consumer数量 可以增加consumer的消费者&#xff0c;不过这个只能在一定程序上缓解&#xff0c;如果consumer 数量超过partition 数&#xff0c;那有的就会空转&#xff0c;解决不…

rabbitmq报错

文章目录 Applying plugin configuration to rabbitDESKTOP-C3HQ9BK... Plugin configuration unchanged.rabbitmq-service.bat start和rabbitmq-plugins enable rabbitmq_management这两个命令在RabbitMQ中具有不同的功能和用途。以下是两者之间的主要区别&#xff1a; Applyi…

国家开放大学2024春学期《社会调查研究与方法-邮政学院》形成性考核一参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 某市城区有 18 个社区&#xff0c;常住居民 10000 户&a…