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,一经查实,立即删除!

相关文章

Vue---组件

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

浏览器渲染机制:重排(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…

ENVI不同版本个人使用对比

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

Java客户端如何直接调用es的API

Java客户端如何直接调用es的API 一. 问题二. withJson 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一. 问题 今天做项目的时候&#xff0c;想要直接通过java客户端调用es的api…

揭秘工业大模型:从人工智能小白到技术先锋

工业大模型的五个基本问题 信息化时代&#xff0c;数字化转型成为企业提升营运效率、应对经营风险和提升核心竞争力的重要途径。在此过程中&#xff0c;数据作为一种客观存在的资源&#xff0c;所产生的价值日益凸显。党的十九届四中全会从国家治理体系和治理能力现代化的高度将…

【万字长文】看完这篇yolov4详解,那算是真会了

前言 目标检测作为计算机视觉领域的一个核心任务&#xff0c;其目的是识别出图像中所有感兴趣的目标&#xff0c;并给出它们的类别和位置。YOLO&#xff08;You Only Look Once&#xff09;系列模型因其检测速度快、性能优异而成为该领域的明星。随着YOLOv4的推出&#xff0c;…

双塔模型在召回和粗排的区别

答案参考&#xff1a;推荐系统中&#xff0c;双塔模型用于粗排和用于召回的区别有哪些? - 知乎 召回和粗排在不同阶段面临样本不一样&#xff0c;对双塔来说样本分布差异会使召回和粗排采取不一样的方式。召回打分空间是全部item空间&#xff0c;曝光只有很少一部分&#xff0…

【机器学习】集成学习---Bagging之随机森林(RF)

【机器学习】集成学习---Bagging之随机森林&#xff08;RF&#xff09; 一、引言1. 简要介绍集成学习的概念及其在机器学习领域的重要性。2. 引出随机森林作为Bagging算法的一个典型应用。 二、随机森林原理1. Bagging算法的基本思想2. 随机森林的构造3. 随机森林的工作机制 三…

ClickHouse 如何实现数据一致性

文章目录 ReplacingMegreTree 引擎数据一致性实现方式1.ReplacingMegreTree 引擎2.ReplacingMegreTree 引擎 手动合并3.ReplacingMegreTree 引擎 FINAL 查询4.ReplacingMegreTree 引擎 标记 GroupBy5.允许偏差 前言&#xff1a;在大数据中&#xff0c;基本上所有组件都要求…

Docker创建镜像之--------------基于Dockerfile创建

目录 一、在编写 Dockerfile 时&#xff0c;有严格的格式需要遵循 二、Dockerfile 操作常用的指令 2.1ENTRYPOINT和CMD共存的情形 2.2ENTRYPOINT和CMD的区别 2.3ADD 与COPY的区别 三、Dockerfile案例 3.1构建apache镜像 3.1.1 创建镜像目录方便管理 3.1.2创建编写dock…

函数递归与迭代

目录 1.递归 1.1递归的思想 1.2递归的限制条件 2.递归与迭代 1.递归 函数递归是什么&#xff1f; 递归是学习C语⾔函数绕不开的⼀个话题&#xff0c;那什么是递归呢? 递归其实是⼀种解决问题的⽅法&#xff0c;在C语⾔中&#xff0c;递归就是函数⾃⼰调⽤⾃⼰。 写⼀个史…

大模型对数字营销的驱动赋能

一、大模型驱动的营销数智化个信未来发展趋势 1.模型算法能力全面升级 大模型凭借智能化的用户洞察&#xff0c;个性化的需求预测、系统化的数据分析、效率化的营销决策以及实实化的全域检测支持&#xff0c;为营销行业更加准确地把握市场动态和消费者需求提供了强大支持。可以…