【C++学习手札】多态:掌握面向对象编程的动态绑定与继承机制(初识)

news2025/1/22 8:52:34

 

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:世界上的另一个我

                                                                1:02━━━━━━️💟──────── 3:58
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

首先了解形成多态的条件

接着了解多态的“三同”规则

思考

C++11 override 和 final

抽象类


首先了解形成多态的条件

        在C++中,形成多态主要依赖于以下几个条件:

  1. 继承:多态通常发生在类与类的继承关系中。一个基类(或称为父类)可以有一个或多个派生类(或称为子类),派生类继承并可能扩展基类的行为。
  2. 虚函数:在基类中声明虚函数是实现运行时多态的关键。通过在基类中使用virtual关键字声明成员函数,可以在派生类中重写这些函数,提供不同的实现。
  3. 动态绑定(迟绑定):当通过基类指针或引用调用虚函数时,具体调用哪个版本的函数(基类还是派生类的)是在程序运行时决定的,这称为动态绑定或迟绑定。这是多态行为的核心机制。
  4. 基类指针或引用:要实现多态,通常需要使用指向基类的指针或引用来操作派生类对象。这样,可以通过相同的接口来处理不同类型的派生类对象。
  5. 派生类重写虚函数:派生类必须重写基类中的虚函数以提供自己的实现。如果派生类没有重写虚函数,它将直接继承基类中的版本。
  6. 访问权限:确保派生类有足够的访问权限来重写基类中的虚函数。例如,如果基类的虚函数是私有的,则除了友元类之外,派生类将无法重写它。
  7. 析构函数的多态性:为了使通过基类指针删除派生类对象时能够正确调用派生类的析构函数,基类的析构函数应该被声明为虚函数。
  8. 纯虚函数和抽象基类:如果基类包含纯虚函数(使用= 0声明的虚函数),那么这个基类就是一个抽象基类,不能被实例化。派生类必须提供所有纯虚函数的实现,才能成为可实例化的具体类。
  9. 类型安全:C++中的RTTI(运行时类型信息)机制,如typeiddynamic_cast,允许在运行时检查和转换对象的类型,增加了多态的安全性。
  10. 编译器和内存管理:C++编译器在后台维护虚函数表(vtable)和管理动态内存分配,以确保多态行为的正确实现。

        简简单单的先看个例子吧:

#include<iostream>
using namespace std;

class Person
{
public:
	virtual void publish()
	{
		cout << "i am a Person" << endl;
	}
	virtual ~Person() { cout << "~Person()" << endl; }
};

class Student:public Person
{
public:
	virtual void publish()
	{
		cout << "i am a Student" << endl;
	}
	virtual ~Student() { cout << "~Student()" << endl; }
};

void fun(Person& a)
{
	a.publish();
}

void fun2(Person* A)
{
	A->publish();
}

int main()
{
	Person* cc = new Person;
	Student* ccc = new Student;
	fun2(cc);
	fun2(ccc);
	delete ccc;
	delete cc;
	cout << "_________________" << endl;
	Person aa;
	Student aaa;
	fun(aa);
	fun(aaa);

	return 0;
}

接着了解多态的“三同”规则

        在C++中,多态性(Polymorphism)主要指的是运行时多态,它允许不同类的对象通过相同的接口调用适当的方法。这种特性通常归纳为“三同”原则:

  1. 同函数名(Same Function Name):基类和派生类中的虚函数必须具有相同的函数名。这样,当通过基类的指针或引用调用该函数时,可以根据对象的实际类型来调用正确的函数实现。
  2. 同参数列表(Same Parameter List):基类和派生类中的虚函数应该具有相同的参数列表。这意味着函数的参数数量和类型应该相同,以确保在调用时传递的参数是一致的。
  3. 同返回类型(Same Return Type):基类和派生类中的虚函数应具有相同的返回类型,或者派生类中的返回类型应该是基类中返回类型的子类型(C++11起支持协变返回类型)。这是为了确保在使用基类指针或引用调用虚函数时,返回值的类型是一致的,从而可以进行正确的赋值或操作。

        需要注意的是,从C++11开始,引入了返回类型协变的概念,即派生类重写的虚函数可以返回基类中虚函数返回类型的子类型。例如:

class Base {
public:
    virtual Base* clone() const {
        // ...
    }
};

class Derived : public Base {
public:
    virtual Derived* clone() const override {
        // ...
    }
};

        在这个例子中,Derived::clone 函数的返回类型是 Derived*,它是 Base::clone 函数返回类型 Base* 的子类型,满足协变的要求。

思考

        请你思考一下以下代码的执行结果:

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

        如下给出几个选项:

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

        答案为:B

        分析如下:

        首先使用B类型的指针p指向了新申请的一块B类型的空间,然后调用由父类A继承下来的test()函数,此时需要注意的是:由于是父类继承下来的test()函数,那么这个函数的原型是不会改变的,那其中的this指针也是不会改变的(意思就是类型为A* 的this指针)。接下来就是多态的概念的了,是一个多态调用,派生类是可以不加virtual的,因为他会继承父类的函数声明,而子类会重写父类的实现,这里调用的是B的func()需要注意的是:父类与子类func()中的参数列表是遵循“三同原则”的。因此我们使用的是A的func的声明,也就是:virtual void func(int val = 1),但是使用的是子类的实现:{ std::cout << "B->" << val << std::endl; }。这里又有一点注意事项只有构成多态子类才会继承父类的接口,如果不构成那么就是正常调用!

C++11 override 和 final

        从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

  1. final:修饰虚函数,表示该虚函数不能再被重写。修饰类,表示该类不能被继承
//原型
class Car
{
public:
	virtual void Drive() {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};
//修饰类
class Car final
{
public:
	virtual void Drive()  {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};
//修饰虚函数
class Car 
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};

        2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

抽象类

        抽象类是一种特殊的类,它不能被实例化,只能作为其他类的基类使用。

        在C++中,抽象类是一种特殊的类,它包含了至少一个纯虚函数(pure virtual function)。纯虚函数是一种没有实现的虚函数,它的声明以“= 0”结尾,表示该函数没有具体的实现,需要在派生类中被重写。

        抽象类的主要用途是为其他类提供一个公共的接口或基本的功能实现,同时强制要求派生类提供某些特定功能的实现。这样做的目的是为了保证派生类遵循某种规范或者具有某些特定的行为。由于抽象类不能被实例化,因此通常用来作为基类,通过继承机制来指导派生类的行为和属性。

        除了纯虚函数,抽象类也可以包含普通的成员函数和数据成员。这些普通成员可以在抽象类中实现,也可以留空让派生类去实现。如果一个类继承了抽象类但没有实现所有的纯虚函数,那么这个类也必须被声明为抽象类。

#include<iostream>
using namespace std;

class car
{
public:
	virtual void drive() = 0
	{}
};

class BMW : public car
{
public:
	virtual void drive()
	{
		cout << "i have a BMW" << endl;
	}
};

class Benz : public car
{
public:
	virtual void drive()
	{
		cout << "i have a Benz" << endl;
	}
};

void fun(car& aa)
{
	aa.drive();
}

int main()
{
	BMW a;
	Benz b;
	fun(a);
	fun(b);
	return 0;
}

 


                     感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

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

相关文章

应用回归分析:岭回归

岭回归&#xff0c;也称为Tikhonov正则化&#xff0c;是一种专门用于处理多重共线性问题的回归分析技术。多重共线性是指模型中的自变量高度相关&#xff0c;这种高度的相关性会导致普通最小二乘法&#xff08;OLS&#xff09;估计的回归系数变得非常不稳定&#xff0c;甚至无法…

CDN缓存有什么作用?

CDN缓存是内容分发网络的核心技术之一&#xff0c;它的作用在于通过将内容缓存在边缘服务器上&#xff0c;提高内容的访问速度和可用性。以下是CDN缓存的几个主要作用&#xff1a; 加速内容的访问速度 CDN缓存通过将内容缓存在距离用户更近的边缘服务器上&#xff0c;减少了内…

【C++】C++入门—初识构造函数 , 析构函数,拷贝构造函数,赋值运算符重载

C入门 六个默认成员函数1 构造函数语法特性 2 析构函数语法特性 3 拷贝构造函数特性 4 赋值运算符重载运算符重载赋值运算符重载特例&#xff1a;前置 与 后置前置&#xff1a;返回1之后的结果后置&#xff1a; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&…

基于SSM的电影购票系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的电影购票系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

这才是大学生该做的副业,别再痴迷于游戏了!

感谢大家一直以来的支持和关注&#xff0c;尤其是在我的上一个公众号被关闭后&#xff0c;仍然选择跟随我的老粉丝们&#xff0c;你们的支持是我继续前行的动力。为了回馈大家长期以来的陪伴&#xff0c;我决定分享一些实用的干货&#xff0c;这些都是我亲身实践并且取得成功的…

【蓝桥杯】算法模板题(Floyd算法)

一.弗洛伊德算法 用途&#xff1a;用来求解多源点最短路径问题。 思想&#xff1a;Floyd算法又称为插点法&#xff0c;是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法。 主要步骤&#xff1a; 1&#xff09;初始化&#xff1a;使用邻接矩阵初始化dis…

离线数仓(三)【业务日志采集平台搭建】

前言 上一篇我们搭建完了用户行为日志数据的采集平台&#xff0c;其实也就是用两个 flume 采集数据到Kafka 中&#xff08;这种结构只有 source 和 channel 没有 sink&#xff09; 。离线数仓中的数据除了用户日志&#xff0c;还有就是业务数据了。 1、电商业务简介 1.1 电商…

Android电量相关知识

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、 查看耗电情况3.1 注册广播 ACTION…

数据结构——lesson3单链表介绍及实现

目录 1.什么是链表&#xff1f; 2.链表的分类 &#xff08;1&#xff09;无头单向非循环链表&#xff1a; &#xff08;2&#xff09;带头双向循环链表&#xff1a; 3.单链表的实现 &#xff08;1&#xff09;单链表的定义 &#xff08;2&#xff09;动态创建节点 &#…

【C++初阶】新手值得一做vector的oj题

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

SQL补充:窗口函数

SQL窗口函数 结合order by关键词和limit关键词是可以解决很多的topN问题&#xff0c;比如从二手房数据集中查询出某个地区的最贵的10套房&#xff0c;从电商交易数据集中查询出实付金额最高的5笔交易&#xff0c;从学员信息表中查询出年龄最小的3个学员等。 但是&#xff0c;…

端口号被占用怎么解决

1、快捷键"winR"打开运行&#xff0c;在其中输入"cmd"命令&#xff0c;回车键打开命令提示符。 2、进入窗口后&#xff0c;输入"netstat -ano"命令&#xff0c;可以用来查看所有窗口被占用的情况。 比如端口号为7680的端口被占用了&#xff0c…

Java+Swing+Txt实现通讯录管理系统

目录 一、系统介绍 1.开发环境 2.技术选型 3.功能模块 4.系统功能 1.系统登录 2.查看联系人 3.新增联系人 4.修改联系人 5.删除联系人 5.工程结构 二、系统展示 1.登录页面 2.主页面 3.查看联系人 4.新增联系人 5.修改联系人 三、部分代码 Login FileUtils …

c高级 函数+Makefile

一、作业 1.写一个函数&#xff0c;输出当前用户的uid和gid&#xff0c;并使用变量接收结果 #!/bin/bash function fun(){retid -uret1id -gecho $ret $ret1 } retfun echo $ret二、练习回顾 1.分文件编译&#xff08;实现冒泡排序&#xff09; 正确的&#xff1a;将数组的…

day32打卡

day32打卡 122. 买卖股票的最佳时机 II 解法&#xff0c;贪心&#xff1a;局部&#xff0c;收集每天的正利润-》整体&#xff0c;获取最大利润 从第0天到第3天&#xff0c;利润为&#xff1a;price[3] - price[0]&#xff0c;也可以是(price[3] - price[2]) (price[2] - pr…

UI设计20问(01):如何规避公说公有理婆说婆有理。

hello&#xff0c;我是大千UI工场&#xff0c;这次又开辟了一个新专题&#xff0c;回答UI设计中经常碰到问题&#xff0c;本期先回答UI设计评判标准的问题&#xff0c;欢迎关注评论点赞转发。 一、什么是公说公有理婆说婆有理 "公说公有理&#xff0c;婆说婆有理"是…

EasyUI动态加载组件

要实现如下的效果&#xff0c;在表格中显示进度条 主要是需要再次初始化组件&#xff0c;借用ChatGPT的意思是&#xff1a; 在许多 JavaScript UI 框架中&#xff0c;包括 EasyUI&#xff0c;在动态地创建或插入新的 DOM 元素后&#xff0c;通常需要手动初始化相关的组件或特性…

阿里云香港轻量应用服务器是什么线路?

阿里云香港轻量应用服务器是什么线路&#xff1f;不是cn2。 阿里云香港轻量服务器是cn2吗&#xff1f;香港轻量服务器不是cn2。阿腾云atengyun.com正好有一台阿里云轻量应用服务器&#xff0c;通过mtr traceroute测试了一下&#xff0c;最后一跳是202.97开头的ip&#xff0c;1…

【Qt学习】QIcon类 + 利用qrc机制设置图片路径(QtCreator)

文章目录 1. QIcon / windowIcon2. setIcon() 与 setwindowIcon()2.1 setIcon() 介绍与使用2.2 setWindowIcon 介绍与使用 3. 路径问题 & qrc机制的引入3.1 绝对路径 / 相对路径 的问题3.2 qrc机制3.3 在QtCreator下利用qrc机制引入图片 1. QIcon / windowIcon QIcon QIco…

dubbo源码中设计模式——负载均衡中模版模式的应用

模版模式介绍 在模板模式&#xff08;Template Pattern&#xff09;中&#xff0c;一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现&#xff0c;但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。 使用场景&#xff1a;定…