【C++】模板/继承/多态

  • 函数模板
  • 继承
  • 虚函数,静态绑定/动态绑定
  • 静态绑定/动态绑定
  • 如何解释多态
  • 抽象类
  • 多重继承
  • 面试题
  • 四种类型转换方式

函数模板

  • 意义:对类型进行参数化
    模板的实参推演:可以根据用户传入的实参类型,来推导出模板类型。
    函数模板 不会参与编译,在函数调用点,实例化/推导出类型,模板函数再进行编译。

  • 模板代码是不能在一个文件中定义,在另一个文件中使用
    模板代码调用之前,一定要看到模板定义的地方,这样的话,模板才能进行正常的实例化,产生能够被编译器编译的代码。

  • 模板一般都是放在头文件中的,在源文件中展开

  • 函数模板的非类型参数 必须是整数类型(整数/地址/引用)都是常量,只能使用

继承

  1. 继承的本质和原理
    继承·的·本质·:
    a.代码复用
    b.在基类中给所有派生类提供统一的虚函数接口,让派生类重写虚函数,然后就可以使用多态
  • 类和类之间的关系:组合and继承
    组合:a part of …一部分的关系
    继承:a kind of… 一种·的关系

  • 总结:
    外部只能访问对象public的成员,protected和private的成员无法直接访问。
    在继承结构中,派生类从基类可以继承过来private的成员,但是派生类却无法直接访问。

  1. protected和private的区别?
  • 在基类中·定义的成员,想被派生类访问,但是不想被外界访问,那么在基类中,把相关成员定义成protected保护的,如果派生类和外部都不打算访问,那么在基类中,就把相关成员定义成private私有的
  1. 默认继承方式:
    class定义派生类,默认继承方式就是private私有的
    struct定义派生类,默认方式就是public
  • 派生类从继承可以继承所有的成员(变量和方法),除过构造函数和析构函数
  1. 派生类怎么初始化从基类继承来的成员变量呢?
    通过调用·基类相应的构造函数来初始化
  • 派生类的构造函数和析构函数,负责初始化和清理派生类部分
  1. 派生类从基类继承来的成员的初始化和清理谁来负责?
  • 是由基类的构造和析构来负责
  1. 派生类对象构造和析构的过程是:
  • 派生类调用基类的构造函数,初始化从基类继承来的成员。
    调用派生类自己的构造函数。初始化派生类自己特有的成员
    派生类对象的作用域到期了
    1.调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
    2.调用基类的析构函数,释放派生类内存中,从基类继承来的成员可能占用的外部资源(堆内存,文件)

  • 重载:一组函数要重载,必须处在同一个作用域当中,而且函数名字相同,参数列表不同

  • 隐藏(作用域的隐藏)的关系:
    在继承结构当中,派生类的同名成员,把基类的同名成员给隐藏调用了

  • 覆盖:基类和派生类的方法,返回值,函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,他们之间成为覆盖关系。

  1. 把继承结构,也就是说成从上(基类)到下(派生类)的结构

基类对象 < -派生类对象 类型从下到上的转换(可以)
派生类对象 <- 基类对象 类型从上到下的转换(不可以)
基类指针(引用)<- 派生类对象 类型从下到上的转换(可以)
派生类指针(引用)<-基类对象 类型从上到下的转换(不可以)

  • 在继承结构中进行上下的类型转换,默认只支持从下到上的类型转换。

虚函数,静态绑定/动态绑定

总结一:
如果类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针(运行时的类型信息)和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。

二:
一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始的部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vfptable。
一个类型定义的n个对象,他们的vfptr指向的都是同一张虚函数表。

三:
一个类里面虚函数的个数,不影响对象内存大小(vfptr)影响的是虚函数白表的大小。

四:
如果派生类中的方法和基类继承来的某个方法,返回值,函数名,参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数。

静态绑定/动态绑定

  • 在编译时期的绑定(函数的调用)
    只call指令-静态绑定

  • 在运行时期的绑定(函数的调用)

      mov eax,dword ptr[pd] mov ecx,dword ptr[eax]call ecx(虚函数的地址) 动态绑定
    
  1. 那些函数不能实现成虚函数?
    虚函数能产生地址,存储在vftable当中
    对象必须存在(vfptr -》vftable -》虚函数地址)

  2. 构造函数
    virtual+构造函数 (错误)
    构造函数中(调用的任何函数,都是静态绑定的)调用虚函数,也不会发生静态绑定。派生类对象构造过程:先调用的是基类的构造函数 再调用派生类的构造函数。
    static静态成员方法 (错误)
    虚析构函数 (可以)
    析构函数调用时,对象是存在的
    基类的虚函数是虚函数,派生类的析构函数自动变成虚函数
    当基类指针(引用)指向堆上new出来的派生类对象的时候,delete 基类指针,它调用析构函数时,必须发生动态绑定,否则会导致派生类的析构函数无法调用。

  3. 虚函数和动态绑定 问题是不是虚函数的调用一定就是动态绑定?
    在类的构造函数当中,调用虚函数,也是静态绑定(构造函数中调用其他 函数(虚)不会发生动态绑定)
    如果不是通过指针或者引用变量来调用虚函数,那就是静态绑定。

如何解释多态

静态(编译时期)的多态:函数重载,模板(函数模板,类模板)

bool compare(int , int){};
bool compare(double,double){};compare(10,20); /在编译阶段就确定了调用的函数版本template<typename T>
bool compare(T a,T b){};compare<int>(10,30); int 实例化一个 compare<int>
compare(1.2,5.1);	推导出double实例化一个 compare<double>
  • 动态(运行时期)的多态:
    在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数),基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法。
    多态底层是通过动态绑定来实现的。pbase 指向谁就访问谁的vfptr,从而继续访问谁的vftable,也就调用对应的派生类对象的方法了。

抽象类

拥有纯虚函数的类,叫做抽象类
抽象类不能再实例化对象了,但是可以定义指针和引用变量。
一般情况会把基类定义成抽象类。

多重继承

代码复用 一个派生类有多个基类

virtual可以修饰继承方式,是虚继承,虚继承的类是虚基类

基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址。

#include <iostream>// 虚基类
class Base {
public:int data;
};// 第一个派生类
class Derived1 : virtual public Base {
public:void setData(int value) {data = value;}
};// 第二个派生类
class Derived2 : virtual public Base {
public:void displayData() {std::cout << "Data: " << data << std::endl;}
};// 最终派生类
class FinalDerived : public Derived1, public Derived2 {
public:// 使用虚基类的成员函数和数据void accessData() {setData(42); // 通过Derived1访问Base的成员函数displayData(); // 通过Derived2访问Base的数据}
};int main() {FinalDerived obj;obj.accessData();return 0;
}

虚基类是用于解决多重继承中的菱形继承问题的一种机制。当一个类同时继承了两个或更多个共同基类,而这些基类又继承自同一个共同的基类时,就会形成菱形继承结构。为了解决由此可能产生的二义性和数据重复的问题,可以将这些共同的基类声明为虚基类。

在声明虚基类时,需要在派生类的继承列表中使用关键字 virtual。这样做可以确保每个派生类只包含一份虚基类的实例,从而避免了数据重复和二义性。

面试题

一:

class Base
{
public:
virtual void show(){ cout<<"call Base::show"<<endl;}
};
class Derive : public Base
{
private: //编译阶段不看它
void show()
{
cout<<"call Derive::show"<<endl;
}
};
int main()
{
Base *p = new Derive();
p->show(); //最终能调用到Derive::show,是在运行时期才确定的
delete p;
return 0;
}

对于private:

void show()
{
cout<<"call Derive::show"<<endl;
}可以正常调用

成员方法能不能调用,就是说方法的访问权限是不是public的,是在编译阶段就需要确定的。

编译阶段:Base::show (call Base::show (静态绑定)/ call ecx(动态绑定))

也就是说在执行
p->show(); //最终能调用到Derive::show,是在运行时期才确定的
时看的是Base基类的访问权限,不看派生类的权限。

二:

class Base
{
public:
virtual void show(int i = 10){ cout<<"call Base::show i="<< i <<endl;}
};
class Derive : public Base
{
private: //编译阶段不看它
void show(int i = 20)
{
cout<<"call Derive::show i = "<< i <<endl; //i的值是10
}
};
int main()
{
Base *p = new Derive();
/*
因为p是Base类型
push 0Ah 参数压栈(Base里面的参数)
mov eax,dword ptr[p]
mov ecx,dword ptr[eax]
call ecx
*/
p->show(); //动态绑定 p->Derive vfptr -》Derive vftable
delete p;
return 0;
}

对于有默认值的基类和派生类发生多态时,参数压栈是在编译时期就会确定好的,所以派生类的默认参数根本不会起作用,永远用不到

三:

class Base
{
public:
Base()
{
/*
push ebp
mov ebp , esp
sub esp,...开辟空间
rep stos esp <->ebp (windows下默认初始化为0×CCCCCCCC)(Linux g++/gcc不做此步骤)
vfptr 《- &Base::vftable
*/
cout<<"Call Base()"<<endl;
clear();
}
void clear(){memset(this,0,sizeof(*this));}
virtual void show()
{
cout<<"CAll Base::show()"<<endl;
}
};class Derive : public Base
{
public:
Derive()
{
cout<<"call Derive()"<<endl;
}
void show()
{
cout<<"Call Derive::show()"<<endl;
}
};int main()
{
//错误代码
Base *pb1 = new Base();
pb1->show();
delete pb1;//正确
Base *pb2 = new Derive();
pd2 ->show();
delete pb2;return 0;
}

解释:

//错误代码
Base *pb1 = new Base();
pb1->show();
delete pb1;

因为pb1调用了clear函数,相当于把vfptr置成0地址了,vfptr已经不再指向Base::vfptable了,当再次调用pb1->show();,从而找不到,发生异常错误。

//正确
Base *pb2 = new Derive();
pd2 ->show();
delete pb2;

首先vfptr同样被置0地址,因为Base先进行构造函数,vfptr 《- &Base::vftable,再调用clear函数,vfptr被置成0地址。
然后Derive再构造,同样会执行vfptr 《- &Base::vftable,但是不会调用clear函数,所有此时vfptr 就会指向Derive::vftable,从而正常运行。

四种类型转换方式

  1. 语言级别的转换方式
    const_cast
  2. 去掉常量属性的一个类型转换
    static_cast
  3. 提供编译器认为安全的类型转换(没有任何联系的类型之间的转换不会成功)
    reinterpret_cast
  4. 类似于c风格的强制类型转换
    dynamic_cast

主要用字继承结构中,可以支持RTTI类型识别的上下类型转化

int main()
{
const int a = 10;
int *p1 = (int*)&a;int *p2 = const_cast<int*>(&a);
/*不考虑const,左右两边类型要保持一致,体现了安全性
const_cast《》里面必须是指针或引用类型
*/
int a = 10;
char b = static_cast<int>(a); //char与int有联系int*p = nullptr;
double *b = reinterpret_cast<double*>(p);//可以转换,但是不安全
}

dynamic_cast
是 C++ 中用于安全地进行基类指针或引用向派生类指针或引用的类型转换的一种运算符。它主要用于在运行时检查类型安全性,只能用于具有虚函数的类层次结构中。如果尝试转换失败,dynamic_cast 将返回一个空指针(对指针进行转换)或引发 std::bad_cast 异常(对引用进行转换)。

下面是 dynamic_cast 的一般语法:

dynamic_cast<new_type>(expression)

其中 new_type 是要转换的目标类型,expression 是要转换的指针或引用。


#include <iostream>// 基类
class Base {
public:virtual ~Base() {} // 虚析构函数确保多态性
};// 派生类
class Derived : public Base {
public:void derivedMethod() {std::cout << "Derived method called." << std::endl;}
};int main() {Base* basePtr = new Derived();// 使用 dynamic_cast 进行类型转换Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);if (derivedPtr) {// 转换成功,可以安全地调用 Derived 类的方法derivedPtr->derivedMethod();} else {// 转换失败std::cout << "Dynamic cast failed." << std::endl;}delete basePtr;return 0;
}

在这个例子中,basePtr 是一个指向基类 Base 的指针,但实际上指向了一个派生类 Derived 的对象。通过使用 dynamic_cast 将 basePtr 转换为 Derived* 类型的指针 derivedPtr,我们可以安全地调用 Derived 类的方法。如果转换失败(例如 basePtr 指向的对象不是 Derived 类型的),dynamic_cast 将返回 nullptr。

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

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

相关文章

9.11 QT ( Day 4)

一、作业 1.Widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> //定时器类 #include <QTime> #include <QtTextToSpeech> //文本转语音类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEcl…

杨氏矩阵中查找某个数字是否存在(不能使用遍历)

杨氏矩阵&#xff1a; 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的 如图所示&#xff1a; i为行&#xff0c;j为列 如果要找9&#xff0c;先从arr【0】【2】处开始找&#xff0c;3<9,i,排除第一行&#xff0c;6<9,i,排除第…

C++:sort自动排序函数

在 C 中&#xff0c;std::sort 是一个用于对容器&#xff08;如数组、std::vector、std::deque 等&#xff09;中的元素进行排序的标准库算法。std::sort 函数定义在 <algorithm> 头文件中&#xff0c;提供了多种排序方法&#xff0c;包括默认排序和自定义排序。 基本用…

上海亚商投顾:沪指探底回升 华为产业链午后爆发

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日探底回升&#xff0c;深成指、创业板指盘中跌逾1%&#xff0c;午后集体拉升翻红。华为产业链午后走强…

可解释性机器学习的目标

为了解释比如决策树、随机森林的意义&#xff0c;我们首先应该定义可解释性的目标是什么。或者 说什么才是最好的可解释性的结果呢&#xff1f;很多人对于可解释性机器学习会有一个误解&#xff0c;觉得一 个好的可解释性就是要告诉我们整个模型在做什么事。我们要了解模型的一…

cell phone teardown 手机拆卸

tweezer 镊子 screwdriver 螺丝刀 opening tool 开口工具 repair 修理 battery 电池 rear panel 后盖 front and rear cameras 前后摄像头 volume button board 音量键线路板 headphone jack 耳机孔 a cracked screen 破裂屏 otherwise non-functional screen 其它坏屏 flex c…

B-树底层原理

一、B-树介绍 定义&#xff1a; B-树&#xff08;B-Tree&#xff09;是一种自平衡的树形数据结构&#xff0c;广泛应用于数据库和操作系统中。它的设计目标是减少搜索、顺序访问、插入和删除操作中比较次数和移动次数&#xff0c;特别适合于磁盘中数据的存储和检索。 性质&a…

Linux:从入门到放弃

目录 一、基础巩固Linux&#xff1a;常用命令 二、实战应用Linux&#xff1a;CentOS7基础配置Linux&#xff1a;CentOS7安装MySQL 三、常见问题Linux&#xff1a;yum源失效问题 一、基础巩固 Linux&#xff1a;常用命令 二、实战应用 Linux&#xff1a;CentOS7基础配置 Lin…

C语言程序设计——结构体

一、结构体的定义 有时需要将不同类型的数据组合成一个有机体,以便于引用 声明一个结构体类型的一般形式为: struct 结构体名{成员表列}; 结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。 ①先声明结构体类型再定义变量 struct student # 结…

RabbitMQ练习(AMQP 0-9-1 Overview)

1、What is AMQP 0-9-1 AMQP 0-9-1&#xff08;高级消息队列协议&#xff09;是一种网络协议&#xff0c;它允许遵从该协议的客户端&#xff08;Publisher或者Consumer&#xff09;应用程序与遵从该协议的消息中间件代理&#xff08;Broker&#xff0c;如RabbitMQ&#xff09;…

欺诈文本分类检测(十四):GPTQ量化模型

1. 引言 量化的本质&#xff1a;通过将模型参数从高精度&#xff08;例如32位&#xff09;降低到低精度&#xff08;例如8位&#xff09;&#xff0c;来缩小模型体积。 本文将采用一种训练后量化方法GPTQ&#xff0c;对前文已经训练并合并过的模型文件进行量化&#xff0c;通…

Unity3D Android多渠道极速打包方案详解

在移动应用开发过程中&#xff0c;特别是在使用Unity3D进行Android游戏或应用开发时&#xff0c;多渠道打包是一个常见且重要的需求。不同的渠道&#xff08;如Google Play、华为应用市场、小米应用商店等&#xff09;可能需要不同的配置和包名&#xff0c;手动进行这些操作既耗…

【电子通识】规格书上的%FS和%RD具体指什么?

在仪器仪表类的手册上&#xff0c;常见的精度表达规格显示方式&#xff1a;%FS 和%RD 究竟如何解读呢&#xff1f; 术语解说 %RD(Reading)&#xff1a;用于表示对比显示值(读值)存在多少(%)的误差 %FS(Full Scale)&#xff1a;用于表示对比全量程存在多少(%)的误差 %SP(Set Poi…

基于ssm+vue+uniapp的电影交流平台小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

多文件编程实现链表创建,插入,输出(上)

linklist.c #include "linklist.h" //创建空的链表&#xff0c;为头结点在堆区分配空间 linklist_t *creat_empty_linklist() {linklist_t *head NULL;head (linklist_t *) malloc(sizeof(linknode_t));if(NULL head){printf("malloc is fail!\n");ret…

泛型及其使用

1. 为什么要用泛型 我们来看下面这个场景&#xff1a; 有一个Dog类和Cat类&#xff0c;创建一个ArrayList用来存放dog对象&#xff0c;程序员不小心往里边添加了一个Cat对象&#xff0c;此时不会有问题&#xff0c;但是当我们遍历这个ArrayList时&#xff0c;强行转为Dog类就会…

项目小结二()

一.个人信息的界面 这里可以进行用户信息的修改&#xff0c;并渲染数据上去 二.这两天&#xff0c;出现的问题&#xff1a; 1.mybatis中 字段取别名 &#xff08;还没验证&#xff0c;是否正确&#xff09; 问题描述&#xff1a;由于实体类中的变量名&#xff0c;与数据库中…

CTF—杂项题目

1.ctfshow-Misc入门-misc17 1 用010editer打开图片后没有直接搜到ctf&#xff1b; 2 用binwalk分析文件发现有一个bzip2的隐藏文件并将其分离&#xff1b; 3 得到一个压缩文件D86.bz2&#xff1b; 4 但使用解压命令进行解压时&#xff0c;显示文件受损&#xff1b; 5 参考别人…

SpringBoot学习(8)RabbitMQ详解

RabbitMQ 即一个消息队列&#xff0c;主要是用来实现应用程序的异步和解耦&#xff0c;同时也能起到消息缓冲&#xff0c;消息分发的作用。 消息中间件最主要的作用是解耦&#xff0c;中间件最标准的用法是生产者生产消息传送到队列&#xff0c;消费者从队列中拿取消息并处理&…

Docker高级管理--Compose容器编排与私有仓库(Docker技术集群与应用)

本文介绍了Docker的三大工具&#xff1a;Docker Machine用于创建和管理Docker主机&#xff0c;Docker Compose用于单引擎模式下的多容器应用部署和管理&#xff0c;而Docker Swarm则是一个集群管理工具&#xff0c;提供微服务应用编排功能。Docker Machine支持在不同环境配置Do…