C++继承和多态学习心得

2023-05-16

继承

定义:

在已有类的基础上创建新类的过程

 一个 B 类继承A类,或称从类 A 派生类 B
 类 A 称为基类(父类),类 B 称为派生类(子类)

语法形式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(注意:不论种方式继承基类,派生类都不能直接使用基类的私有成员 )

派生

派生类的生成过程经历了三个步骤:
●吸收基类成员 全部吸收(构造、析构除外),但不一定可见)
●改造基类成员
●添加派生类新成员

吸收基类成员

在C++的继承机制中,派生类吸收基类中除构造函数和析构函数之外的全部成员。

改造基类成员

通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。

添加派生类新成员

仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。

重名成员

1.派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员
2.在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员

访问静态成员

  1. 基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
  2. 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
  3. 派生类中访问静态成员,用以下形式显式说明:
    类名 :: 成员
    或通过对象访问 对象名 . 成员

基类的初始化

在这里插入图片描述

派生类构造函数和析构函数的定义规则

1.派生类构造函数和析构函数的使用原则
2.基类的构造函数和析构函数不能被继承
3.如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
4.如果基类无无参的构造函数,派生类必须定义构造函数
5.如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
6.派生类是否定义析构函数与所属的基类无关

派生类的构造函数

1.定义:派生类的数据成员既包括基类的数据成员,也包括派生类新增数据成员。
2.格式:
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
( 注意:这是基类有构造函数且含有参数时使用)

派生类析构函数

(1)当派生类中不含对象成员时
●在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
●在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。

多继承

1.定义: 一个类有多个直接基类的继承关系称为多继承
2.多继承声明语法:
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
3. 类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加自己的成员

多继承的构造函数

派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
注意:多继承方式下构造函数的执行顺序:
●先执行所有基类的构造函数
●再执行对象成员的构造函数
●最后执行派生类的构造函数

多继承的析构函数

●析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
●功能是在派生类中对新增的有关成员进行必要的清理工作。
●析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。

虚基类

1.定义:
如果一个派生类从多个基类派生,而这些基类又有一个共同
的基类,则在对该基类中声明的名字进行访问时,可能产生
二义性。
注意:
1.如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
2.如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。
3.要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
4. 虚继承声明使用关键字 virtual

赋值兼容规则

●赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
a 派生类的对象可以赋给基类对象
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针

根据赋值兼容规则, 以下几种情况是合法的:

在这里插入图片描述
在这里插入图片描述
注意:(在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。)

虚函数与多态性

1.多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
2.重载函数是多态性的一种简单形式。
3.虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。

多态的实现

1.多态性的实现和联编这一概念有关。所谓联编(Binding,绑定)就是把函数名与函数体的程序代码连接(联系)在一起的过程。
2.联编分成两大类:静态联编和动态联编。
3.静态联编优点:调用速度快,效率高,但缺乏灵活性;动态联编优点:运行效率低,但增强了程序灵活性。
4.C++为了兼容C语言仍然是编译型的,采用静态联编。为了实现多态性,利用虚函数机制,可部分地采用动态联编。
5.多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。
6.编译时的多态是通过静态联编来实现的。静态联编就是在编译阶段完成的联编。编译时多态性主要是通过函数重载和运算符重载实现的。
7.运行时的多态是用动态联编实现的。动态联编是运行阶段完成的联编。运行时多态性主要是通过虚函数来实现的。

静态联编

1.联编是指一个程序模块、代码之间互相关联的过程。
2. 静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。
3.重载函数使用静态联编。
4.动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。
5. switch 语句和 if 语句是动态联编的例子。

普通成员函数重载可表达为两种形式:
  1. 在一个类说明中重载
  2. 基类的成员函数在派生类重载。有 3 种编译区分方法:
    (1)根据参数的特征加以区分
    (2)使用“ :: ”加以区分
    (3)根据类对象加以区分

类指针的关系

基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
1.直接用基类指针引用基类对象;
2.直接用派生类指针引用派生类对象;
3.用基类指针引用一个派生类对象;
4.用派生类指针引用一个基类对象。

(注意: 派生类指针只有经过强制类型转换之后,才能引用基类对象 )

虚函数

定义 :

根据赋值兼容规则,可以将派生类的地址赋值给基类的指针。

特点:

1.根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用类(基类或派生类)的成员函数。
2.如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。
3.而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。从而实现运行过程的多态。

实现动态联编方式的前提

●先要声明虚函数
●类之间满足赋值兼容规则
●通过指针与引用来调用虚函数。

虚函数和动态联编

1.冠以关键字 virtual 的成员函数称为虚函数
2. 实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本

虚函数和基类指针

基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员。
注意:
1.一个虚函数,在派生类层界面相同的重载函数都保持虚特性
2.虚函数必须是类的成员函数
3.不能将友元说明为虚函数,但虚函数可以是另一个类的友元
4.析构函数可以是虚函数,但构造函数不能是虚函数

虚函数的重载特性

1.在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、
参数类型和顺序完全相同
2. 如果仅仅返回类型不同,C++认为是错误重载
3. 如果函数原型不同,仅函数名相同,丢失虚特性

虚析构函数

1.构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
2.析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
注意:
1.派生类应该从它的基类公有派生。
2.必须首先在基类中定义虚函数。
3.派生类对基类中声明虚函数重新定义时,关键字virtual可以不写。
4.一般通过基类指针访问虚函数时才能体现多态性。
5.一个虚函数无论被继承多少次,保持其虚函数特性。
6.虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态函数。
7.构造函数、内联成员函数、静态成员函数不能是虚函数。
(虚函数不能以内联的方式进行处理)
8.析构函数可以是虚函数,通常声明为虚函数。

纯虚函数和抽象类

纯虚函数的作用

1.纯虚函数是一种特殊的虚函数,
2.在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。

特点

1.纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本
2.纯虚函数为各派生类提供一个公共界面
3 纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
4. 一个具有纯虚函数的基类称为抽象类。

虚函数与多态的应用

1.虚函数和多态性使成员函数根据调用对象的类型产生不同的动作
2.多态性特别适合于实现分层结构的软件系统,便于对问题抽象时
定义共性,实现时定义区别

一些实际应用

实现返回值是当前的类的程序:

#include <iostream>
using namespace std;
 
class Cfather
{
public:
	virtual Cfather & show()
	{
		cout << "From Father" << endl;
		return (*this);
	}
};
 
class Cson1 : public Cfather
{
public:
	Cson1 & show()
	{
		cout << "From Son1" << endl;
		return (*this);
	}
};
 
class Cson2 : public Cfather
{
public:
	Cson2 & show()
	{
		cout << "From Son2" << endl;
		return (*this);
	}
};
 
int main()
{
	Cfather *per = new Cson1;
	per->show();
	per = new Cson2;
	per->show();
 
	system("pause");
	return 0;
}
虚表地址与内容
#include <iostream>
using namespace std;
 
class Cfather
{
public:
	virtual void fun()
	{
		cout << "From Father---fun" << endl;
	}
 
	virtual void show()
	{
		cout << "From Father---show" << endl;
	}
 
	void real()
	{
		cout << "Form Father---real" << endl;
	}
};
 
class Cson : public Cfather
{
public:
	void show()
	{
		cout << "From Son---show" << endl;
	}
};
 
 
int main()
{
	Cfather *per = new Cson;
	int *p_cson = (int *)per;               //p_cson指向的是子类空间的首地址
	int *p_virtab = (int *)*p_cson;         //p_virtab指向的是虚表的首地址
	int *p_fun1 = (int *)*p_virtab;         //p_fun1指向虚表中存放的第一个虚函数的地址
	int *p_fun2 = (int *)*(p_virtab + 1);   //p_fun2指向虚表中存放的第二个虚函数的地址
	int *p_fun3 = (int *)*(p_virtab + 2);   //p_fun3指向虚表的结尾,为0
 
	cout << p_fun1 << endl;
	cout << p_fun2 << endl;
	cout << p_fun3 << endl;
 
	typedef void(*FUN)();
	FUN fun1 = (FUN) p_fun1;
	FUN fun2 = (FUN) p_fun2;
	fun1();
	fun2();
	return 0;
}

1.首先打印出了虚表中存放的三个地址,最后一个为0,表示虚表的结束
2.前两个地址,表示了两个函数的存放的地址,执行这个两个地址的函数,发现,第一个地址存放的是父类的虚函数,但此虚函数在子类中并没有重写,第二个地址存放的是子类的虚函数,表示子类的虚函数的确重写了父类的同名虚函数
3.从结果中也可以发现,父类中不是虚函数的地址并没有写入虚表中,只有虚函数才写入了

重名成员
class  base
  { public :
           int  a ,  b ;  
  } ;
class  derived : public  base
  { public :  
         int  b ,  c ; 
  } ;
void  f ()
{ derived  d ;
   d . a = 1 ;
   d . base :: b = 2 ;
   d . b = 3 ;
   d . c = 4 ;
};

隐藏(经典例子)
class Base
{
public:
	void show()
	{
		cout << "Base::void show()" << endl;
	}
 
	void show(int a)
	{
		cout << "Base::void show(int a)" << endl;
	}
 
};
 
class Derive:public Base
{
public:
	void show()
	{
		cout << "Derive::void show()" << endl;
	}
 
};
 
int main()
{
	int a = 10;
	Derive derive;
	derive.show();  
	//derive.show(a);          //这句语句报错
	derive.Base::show();
	derive.Base::show(a);
}

这里子类的成员方法隐藏了基类的成员方法,因为Derive是基类Base的子类,但在我们引用了基类中有而子类中没有的成员方法时,却出现了错误,这就是我们所讲的第二个关系——隐藏。这里子类隐藏了基类中的成员方法,那如果我们想要调用基类中的成员方法,我们就加作用域

多继承实例

#include <iostream>
Worker
{
public:
    Worker(string name)
{
m_strName = name;
cout << "Worker" << endl;
}
virtual ~Worker()
{
cout << "~Worker" << endl;
}
void work()
{
cout << m_strName << endl;
cout << "work" << endl;
}
protected:
string m_strName;
};

class Children
{
public:
Children(int age)
{
m_iAge = age;
cout << "Children" << endl;
}
~Children()
{
cout << "~Children" << endl;
}
void play()
{
cout << m_iAge << endl;
cout << "play" << endl;
}
protected:
int m_iAge;
};
class ChildLabourer :public Worker,public Children
{
public:
ChildLabourer(string name, int age):Worker(name),Children(age)
{
cout << "ChildLabourer" << endl;
}


~ChildLabourer()
{
cout << "~ChildLabourer" << endl;
}
};


int main()
{
Worker * p = new ChildLabourer("jack",14);
p->work();
  delete p;
    p = NULL;
return 0;
}

注意:由输出结果可以看出,在多继承中,任何父类的指针都可以指向子类的对象,在实例化子类时,先根据继承的顺序依次调用父类的构造函数,然后再调用该子类自己的构造函数;

继承的例子:

#include <iostream>
 
using namespace std;
 
// 基类
class Shape 
{
   public:
      void setWidth(int w)
      {
         width = w;
      }
      void setHeight(int h)
      {
         height = h;
      }
   protected:
      int width;
      int height;
};
 
// 派生类
class Rectangle: public Shape
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
};
 
int main(void)
{
   Rectangle Rect;
 
   Rect.setWidth(5);
   Rect.setHeight(7);
 
   // 输出对象的面积
   cout << "Total area: " << Rect.getArea() << endl;
 
   return 0;
}

输出结果:
Total area: 35

学习心得

这段时间的学习状态还可以,作息基本调整过来了,这几次的作业对我来说可能难度比较大,成绩比较差,我真的是个菜鸡,那个图书馆的程序,我调试了好几天,不是报错就是结果不对,把我给搞疯了,最后自暴自弃。我可能太容易放弃了,总是想着自己做不出来,已经无能为力了,可能只差一段时间的坚持,希望自己能够乐观起来,积极去面对,不要半途而废,坚持走下去。虽然课程结束了,但是并不意味着我们就不需要努力了,每天事物都在变,我们还有许多值得学习的东西,抱着一个学徒的心态,砥砺前行,坚持下去!坚持就是胜利!
奥利给!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++继承和多态学习心得 的相关文章

随机推荐

  • 嵌入式FreeRTOS学习十一,深入理解任务调度机制

    一 任务调度机制 可抢占 xff1a 高优先级的任务先运行时间片轮转 xff1a 同优先级的任务轮流执行空闲任务礼让 xff1a 如果有同是优先级为0的其他就绪任务 xff0c 空闲任务主动放弃一次运行机会函数调用vTaskDelay xD
  • SOAP传输协议

    一 HTTP传输协议 超文本传输协议 xff08 HyperText Transfer Protocol xff0c 缩写 xff1a HTTP xff09 xff0c 它是基于请求 响应的模式协议 xff0c 客户端发出请求 xff0c
  • ONVIF简介

    一 什么是ONVIF ONVIF规范描述了网络视频的模型 接口 数据类型以及数据交互的模式 并复用了一些现有的标准 xff0c 如WS系列标准等 ONVIF规范的目标是实现一个网络视频框架协议 xff0c 使不同厂商所生产的网络视频产品 x
  • gsoap工具生成onvif设备搜索(remotediscovery)代码框架

    什么是gsoap工具 xff1f gSOAP 提供了两个工具来方便开发人员使用 C C 43 43 语言快速开发Web 服务应用 xff0c 通过 gSOAP 提供的这两个工具 xff0c 开发人员可以快速生成服务端与客户端代码框架 xff
  • 001_初识_飞航科技光标飞控

    这两天老潘给我一块飞控 xff0c 让我练手 xff0c 为电赛做准备 xff0c 拿到控挺开心的 xff0c 毕竟省了一笔RMB 本来想着买块正点原子的飞控 资料 xff1a 说起资料简单看了一下发现还蛮全的 xff0c 但是这个资料我居
  • 写出C语言的第一个程序“Hello World”

    这里写自定义目录标题 写出C语言的第一个程序 Hello World 写出C语言的第一个程序 Hello World 下面展示一些 内联代码片 span class token comment A code block span span
  • Eigen库的安装攻略

    Eigen库的安装攻略 转载 xff1a Eigen库安装 xff08 两种方式 xff09 转载 xff1a Eigen库安装 xff08 两种方式 xff09 链接 link
  • 【ROS2基础学习】

    入门篇 前言一 创建一个功能包二 编译三 source总结 前言 提示 xff1a 这里是记录的大概内容 xff1a 随着机器人技术的不断发展 xff0c ROS也越来越重要 xff0c 很多人都开启了学习ROS xff0c 本文就介绍了R
  • Arduino 外部中断重置内部定时器

    Arduino 外部中断重置内部定时器 文章目录 Arduino 外部中断重置内部定时器前言一 准备工作二 代码三 实验效果1 设置1Hz的方波 xff08 外部中断触发 xff09 xff1a 2 观察示波器各个波形 xff1a 3 延迟
  • ALUBI LPMS-IG1 RS232 IMU ROS2驱动安装

    文章目录 前言一 下载官方系列文档二 windows上的上位机程序安装2 Ubuntu上的ROS2驱动安装Offset Mode 2 总结 前言 IMU在自动驾驶领域广泛应用 xff0c 本文主要记录了在ROS2中使用ALUBI LPMS
  • ovn-northd 源码分析

    ovn northd是ovn中的核心后台进程 xff0c 主要负责将ovn的高层配置转换成供ovn controller后台进程使用的逻辑配置 xff0c 更详细的说就是它将ovn northbound数据库中传统意义上的逻辑网络配置转换成
  • 镭神CH128x1系列激光雷达使用记录

    镭神CH128x1系列激光雷达使用记录 文章目录 镭神CH128x1系列激光雷达使用记录前言一 操作步骤1 PC连接雷达 二 实验1 雷达控制器上的接口 xff1a 2 接口定义3 Rviz中显示效果 总结致谢 前言 本条博客的需求来源于自
  • c语言中char数组的结束位

    因为是半路出家学习cpp的 xff0c 所以经常会对c语言里面的字符数组感到困惑 xff0c 这次一次性做个总结 首先 xff0c 结束位 0 只针对字符数组 xff0c 不针对整型或者其他数组 其次 xff0c 字符数组的大小只针对里面的
  • SUMO 使用netconvert报错解决办法

    SUMO 使用netconvert报错 问题描述正确解决方法不适用的解决方法 问题描述 刚开始学习使用sumo xff0c 版本是sumo1 8 0 第一次使用netconvert转换地图时出现报错 xff0c 提示没有PROJ Libra
  • IoTDB基础 初识IoTDB 安装及基本使用(个人学习记录)

    官方文档 http iotdb incubator apache org zh UserGuide V0 13 x API Programming Java Native API html 参考博客 时序数据库IoTDB安装及基本使用htt
  • 树莓派+ L298N 控制二相四线步进电机

    树莓派 43 L298N 控制二相四线步进电机 1 步进电机 步进电机是一种将电脉冲信号转换成相应角位移或线位移的电动机 在非超载的情况下 xff0c 电机的转速 停止的位置只取决于脉冲信号的频率和脉冲数 xff0c 而不受负载变化的影响
  • VTK移动立方体法处理CT图像建立三维模型

    span class token comment 移动立方体 span span class token macro property span class token directive keyword include span span
  • 使用tcp工具调试socket服务端通信

    背景介绍 xff1a 客户端是用tcp连接调试 xff0c 工具链接 https pan baidu com s 1kfjv1jdS8KJgb7YVSu9Wtw 提取码 z34a 服务端是Java代码写的逻辑 xff0c 用byte 流接收
  • Fast Planner 轨迹规划

    文章目录 0 概览1 关键问题2 相关工作3 F a s t P
  • C++继承和多态学习心得

    继承 定义 xff1a 在已有类的基础上创建新类的过程 一个 B 类继承A类 xff0c 或称从类 A 派生类 B 类 A 称为基类 xff08 父类 xff09 xff0c 类 B 称为派生类 xff08 子类 xff09 语法形式 xf