首页 > 编程语言 >[C++ 从入门到精通] 17.基类与派生类关系的详细再探讨

[C++ 从入门到精通] 17.基类与派生类关系的详细再探讨

时间:2023-12-28 14:34:23浏览次数:45  
标签:Men 17 对象 子类 基类 C++ Human 派生类



文章预览:

  • 一. 派生类对象模型简述
  • 二. 派生类构造函数
  • 三. 既当父类又当子类(多继承)
  • 四. 不想当基类的类final
  • 五. 静态类型与动态类型
  • 六. 派生类向基类的隐式类型转换
  • 七. 父类子类之间的拷贝与赋值



一. 派生类对象模型简述

若一个类,继承自一个父类(基类),那么该类称之为子类(派生类)。

并且该子类的对象包含两种成分:

  1. 该子对象含有子类自己的对象成分(包括子类自己的成员函数以及成员变量);
  2. 该子对象也含有基类的对象成分(包括基类自己的成员函数以及成员变量);

回顾:基类的指针为什么可以new派生类的对象?

Human* phuman = new Men;

基类指针可以用来new一个子类对象本质上是因为子类对象中含有基类的成分,因此,子类对象也可以当做是一个特殊的父类对象了。实际上,编译器在我们用多态时,帮我们做了隐式的,从派生类到基类的类型转化。而这种转换的好处就是,当需要用到基类引用的地方,你可以用这个派生类对象的引用来代替or当需要用到派生类引用的地方,你可以用这个基类引用来代替。因此我们就可以用多态这种知识来实现更加复杂的代码。


二. 派生类构造函数

派生类实际上是使用基类的构造函数来初始化其基类部分的。即,基类控制基类部分的成员初始化,派生类控制派生类部分的成员初始化。

new myMen;

所以,当我们创建一个派生类的对象时,既会调用派生类的构造函数,也会调用基类的构造函数(调用顺序:先调用基类的构造函数,再调用派生类的构造函数;释放顺序:先调用派生类的析构函数,再调用基类的析构函数)。

那么,当定义派生类对象的时候,如果基类构造函数需要传递参数,该如何完成呢?

class Human
{
public:
	Human();
	Human(int);
};

可通过派生类的构造函数初始化列表中为基类构造函数传递参数。

如:

class Human {
public:
	Human (int age):m_Age(age){ 
		cout << "this is Human 的构造函数!" << endl;
	}
	
	vitrual ~Human() {}//为基类析构声明为virtual的!
private:
	int m_Age;
};
class Men: public Human {
public:
    //在子类的初始化列表中,直接调用父类的构造函数并传参进进去!
	Men(int age,int a) :Human(age), nums(a) {
		cout << "this is Son 的构造函数!" << endl;
	}
		
	virtual ~Men() {}//此时子类的析构其实本质上也是virtual的,因为你继承自Human 
private:
	int nums;
};

这时,定义子类对象时可用:

Men men(10,10);

三. 既当父类又当子类(多继承)

一个类可以既可以作为某一个类的子类,也可以作为另一个类的父类。

class GrandDad{/.../};            
class Dad: public GrandDad{/.../};//GrandDad类为Dad类的直接基类
class Son: public Dad{/.../};     //GrandDad类为Son类的间接基类

继承关系一直继承,构成了一种继承链,最终结果就是派生类Son会包含它的直接基类的成员以及每个间接基类的成员。但是,在实际开发中,尽量少用这种多继承来写代码,不然很容易造成你写的代码难维护,也不易读。


四. 不想当基类的类final

对于不想用于基类的类,C++中给出了 final

如图:这时Human类不会再被当做基类使用

[C++ 从入门到精通] 17.基类与派生类关系的详细再探讨_基类和派生类的关系

注意:若在一个类的成员函数声明后加final关键字,则该类的子类在继承该类时,不可重写该成员函数。

总结C++11中引入的final关键字的用法:

  • 对于不想被子类重写的成员函数,需要用final对基类成员函数进行声明,那么子类就不再有权限对该成员函数进行重写了。
  • 对于不想当做基类的类,用final对类进行声明后,该类就不可以给其他类用作继承时的基类了

五. 静态类型与动态类型

静态类型:变量声明时的类型,编译的时候是已知的。

动态类型: 指针或引用所代表的内存中的对象的类型,在运行的时候才能知道。

只有在基类指针/引用,才存在这种静态类型和动态类型不一致的情况。

Human* pHuman1 = new Men();    //静态类型是Human *,动态类型是Men *
Human& p1 = *pHuman1;          //静态类型是Human &,动态类型是Men &
Human* pHuman2 = new Woman();  //静态类型是Human *,动态类型是Woman *
Human& p2 = *pHuman2;          //静态类型是Human &,动态类型是Woman &

如果不是基类的指针/引用,那么动态类型和静态类型永远都是应该一致的:

Human* pHuman = new Human();   //静态类型是Human *,动态类型也是Human *
Human human;                   //静态类型是Human,  动态类型也是Human 
Man* pman = new Man();         //静态类型是Man*,   动态类型也是Man* 
Man man;                       //静态类型是Man,    动态类型也是Man

六. 派生类向基类的隐式类型转换

Human *phuman = new Men();  //基类指针指向一个派生类对象,编译器隐式地帮我们将Men类对象转换为了pHuman对象
Human &q = *phuman;         //基类引用绑定到派生类对象上

当我们使用多态时,编译器是隐式地帮我们执行了派生类到基类的转化工作的。这种隐式转换只所以能成功,是因为每一个派生类对象中都包含着基类的成分,所以基类的指针或者引用是可以绑定到子类对象的基类部分上的。也就是说,基类对象可以独立存在,也可以作为派生类对象的一部分存在。

但注意:并不存在从基类到派生类的自动类型转换。(因为子类是从基类中继承过来的,因此子类中含有的成分基类中不一定含有)

Men *pmen = new Human ();  //非法!不能将基类转为派生类
Human human;
Men& men = human;          //非法!不能将基类转为派生类(派生类的引用不能绑定到基类对象上去)
Men* pmen = &human;        //非法!不能将基类转为派生类(派生类指针不能指向基类地址)
Men men;
Human* phuman = &men;     //可以,编译器是通过静态类型来推断转换的合法性(派生类Men*可以转换到基类Human*上)
Men* pmen = phuman;       //非法!不能将基类转为派生类(基类Human*不可以转换到派生类Men*上)

//但是,如果基类中含有至少一个虚函数的话,就可以通过dynamic_cast<Type*>进行类型转换!
Men* pmen = dynamic_cast<Men* >(phuman);//合法!

七. 父类子类之间的拷贝与赋值

方式一

Men men;
Human human(men);// 用子类对象初始化(拷贝给)基类对象,这个会导致基类的拷贝构造函数的执行

此时调用的是基类的拷贝构造函数,将其形参const Human& thuman中的thuman动态绑定到了子类对象men上。

Human(const Human& thuman) {
	cout << "拷贝构造函数!" << endl;
}

方式二:用子类对象赋值给基类对象也是合法的

Men men;
Human human;
human = men;  //用子类对象赋值给基类对象,men对象里基类的那部分就被human拿去了

此时调用的是基类的拷贝赋值运算符的重载函数,将其形参const Human& thuman中的thuman动态绑定到了子类对象men上。

Human& operator=(const Human& thuman) {
	cout << "拷贝赋值运算符函数!" << endl;
	return *this;
}

结论:用派生类对象为一个基类对象初始化或赋值时,派生类对象只会将自己基类那部分对其进行拷贝或者赋值,派生类部分将被忽略掉。

也就是:基类只干基类自己的事,多余的部分不会去操心。


下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。


标签:Men,17,对象,子类,基类,C++,Human,派生类
From: https://blog.51cto.com/u_16436086/9014773

相关文章

  • [C++ 从入门到精通] 18.左值、右值,左值引用、右值引用、move
    文章预览:一.左值和右值二.引用分类三.左值引用(1个地址符&)四.右值引用(2个地址符&)五.std::move函数一.左值和右值inti;//赋值语句i=20;//左值:i(int类型的对象,代表一块内存区域),右值:20(代表一个值)左值(左值表达式):能用在赋值语句等号左侧的东西,就称之为左值。它能够代表一个......
  • [C++ 从入门到精通] 16.RTTI、dynamic_cast、typeid、虚函数表
    文章预览:一.RTTI是什么二.dynamic_cast类型(指针/引用)转换2.1C风格的强制类型转换2.2指针转换(常见用法)2.3引用转换三.typeid运算符四.type_info类五.RTTI与虚函数表一.RTTI是什么RTTI(Run-TimeTypeIdentification):通过运行时类型信息,程序能够使用基类的指针或引用来检查......
  • [C++ 从入门到精通] 15.友元函数、友元类、友元成员函数
    文章预览:一.前言二.友元函数三.友元类四.友元成员函数一.前言众所周知,C++控制对类对象私有成员的访问。通常,公有类方法(public)提供唯一的访问途径,但是有时候这种限制太严格,以至于不适合特定的编程问题。在这种情况下,C++提供了另外一种形式的访问权限:友元,友元有3种:友元函数;友......
  • Unity3D 基类脚本怎么分别获取多个子类脚本的组件详解
    Unity3D是一款非常流行的游戏开发引擎,它提供了丰富的功能和工具,使得开发者可以轻松地创建高质量的游戏。在Unity3D中,脚本是游戏对象的一部分,它们通过附加到游戏对象上的组件来实现特定的功能。本文将详细介绍在Unity3D中如何分别获取多个子类脚本的组件,并提供相应的代码实现。对......
  • 【CF1917F】Construct Tree
    题目题目链接:https://codeforces.com/contest/1917/problem/F给出\(n\)条边的边权,询问是否可以构造出一棵树,使得所有边都被用上恰好一次且直径为\(d\)。\(n,d\leq2000\)。思路首先肯定是找出一条长度为\(d\)的链,然后判断可不可以把剩下的所有边都挂在这条链的带权重心......
  • 人体关键点检测4:C/C++实现人体关键点检测(人体姿势估计)含源码 可实时检测OpenCV库使
    人体关键点检测4:C/C++实现人体关键点检测(人体姿势估计)含源码可实时检测目录人体关键点检测4:C/C++实现人体关键点检测(人体姿势估计)含源码可实时检测1.项目介绍2.人体关键点检测方法(1)Top-Down(自上而下)方法(2)Bottom-Up(自下而上)方法:3.人体关键点检测模型(1)人体关键点检测......
  • 一键抠图2:C/C++实现人像抠图 (Portrait Matting)OpenCV库使用opencv-4.3.0版本,opencv_
    一键抠图2:C/C++实现人像抠图(PortraitMatting)目录一键抠图2:C/C++实现人像抠图(PortraitMatting)1.前言2.抠图算法3.人像抠图算法MODNet(1)模型训练(2)将Pytorch模型转换ONNX模型(3)将ONNX模型转换为TNN模型4.模型C++部署(1)项目结构(2)配置开发环境(OpenCV+OpenCL+base-utils+TNN)(3)......
  • 手部关键点检测5:C++实现手部关键点检测(手部姿势估计)含源码 可实时检测OpenCV库使用o
    手部关键点检测5:C++实现手部关键点检测(手部姿势估计)含源码可实时检测目录手部关键点检测4:C++实现手部关键点检测(手部姿势估计)含源码可实时检测1.项目介绍2.手部关键点检测(手部姿势估计)方法(1)Top-Down(自上而下)方法(2)Bottom-Up(自下而上)方法:3.手部关键点检测模型(1)手部......
  • 2017 《Java 2实用教程(第5版)》是由耿祥义、张跃平编著
    我的研究生同学河南老乡河南工业大学Jackso_hao大学期间学习的Java教材  《Java2实用教程(第5版)》是由耿祥义、张跃平编著,2017年清华大学出版社出版的高等学校Java课程系列教材、普通高等教育“十一五”国家级规划教材。该教材既可作为高等院校相关专业Java程序设计的教材......
  • C++ --- 函数模板
    函数模板C++的一种编程思想称为泛型编程,主要利用的技术就是模板。编写与类型无关的调用代码,是代码复用的一种手段。 模板是泛型编程的基础。C++提供两种模板机制:函数模板和类模板。函数模板:建立一个通用的函数,它用到的参数类型可以不确定,用一个虚拟类型替代。等到函数调用的时......