千面之法: 释放 C++ 多态的灵活威力

千面之法: 释放 C++ 多态的灵活威力

目录

1:多态的概念

1.1:概念

2.多态的定义与实现

2.1:多态的构成条件

2.2:虚函数

2.3:虚函数的重写

2.3.1:虚函数重写的两个例外

2.3.1.1:协变(基类与派生类函数的返回值不同,基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时)

2.3.1.2:析构函数的重写

2.4:C++11 override和final

2.4.1:final关键字

2.4.2:override关键字

2.5:重载、重写、 隐藏的对比

3.抽象类

3.1:概念

3.2:接口继承与实现继承

4:多态的原理

4.1:虚函数表

4.1.1:代码1

4.1.2:代码2

4.2:多态的原理

4.3:动态绑定与静态绑定

5:单继承与多继承关系的虚函数表

5.1:单继承中的虚函数表

5.2:多继承中的虚函数表

6:多态相关的问题


1:多态的概念

1.1:概念

通俗来讲,多态就是多种形态,具体一些就是当去完成某个行为的时候,当不同的对象去完成时会产生出不同的状态.

2.多态的定义与实现

2.1:多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为.譬如Student继承了Person,Person对象买票全价,Student对象买票半价.那么在继承中要构成多态还有两个条件1.必须通过基类的指针或者引用调用虚函数2.被调用的函数必须是虚函数,且派生类对基类的虚函数进行重写.
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Adult { public: virtual void BuyTicket() { cout << "买票----全价" << endl; } }; class Child : public Adult { public: virtual void BuyTicket() { cout << "买票----半价" << endl; } }; /* * 多态的条件 * 1.必须要有继承关系 * 2.必须要有虚函数(父类的虚函数和子类的虚函数,要求三同(函数名,参数名,返回值) */ void Func(Adult& a) { a.BuyTicket(); } int main() { Adult a; Child c; Func(a); Func(c); return 0; }

2.2:虚函数

概念:被virtual修饰的类成员函数称为虚函数

class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl;} };

2.3:虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类的虚函数返回值类型,函数名,参数列表完全相同),称子类的虚函数重写了基类的虚函数.

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Adult { public: virtual void BuyTicket() { cout << "买票----全价" << endl; } }; class Child : public Adult { public: virtual void BuyTicket() { cout << "买票----半价" << endl; } }; /* * 多态的条件 * 1.必须要有继承关系 * 2.必须要有虚函数(父类的虚函数和子类的虚函数,要求三同(函数名,参数名,返回值) */ void Func(Adult& a) { a.BuyTicket(); } int main() { Adult a; Child c; Func(a); Func(c); return 0; }

2.3.1:虚函数重写的两个例外

2.3.1.1:协变(基类与派生类函数的返回值不同,基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用时)

派生类重写类虚函数时,与基类虚函数返回值类型不同,即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或者引用时.

2.3.1.2:析构函数的重写

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同.虽然函数名不相同,起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理称destructor.

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Person { public: virtual ~Person() { cout << "~Person()" << endl; } }; //公有继承 class Student : public Person { virtual ~Student() { cout << "~Student" << endl; } }; int main() { Person* p1 = new Person; Person* p2 = new Student; delete p1; delete p2; return 0; }

2.4:C++11 override和final

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

2.4.1:final关键字

修饰虚函数,表示该虚函数不能够再被重写

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Car { public: virtual void Drive() final { } }; class Benz: public Car { virtual void Drive() { cout << "Benz" << endl; } }; int main() { return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //C++11的方法: final修饰的类叫最终类,不能继承 class Car final { public: private: // C++98的方法:父类的构造函数私有 // 子类的构造无法生成和实现,导致子类对象无法实例化 Car() { } }; class Benz :public Car { public }; int main() { Benz b; return 0; }

2.4.2:override关键字

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

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Car { public: //virtual void drive() //{ //} }; class Benz : public Car { public: virtual void Drive() override { cout << "virtual void Drive()" << endl; } }; int main() { return 0; }

2.5:重载、重写、 隐藏的对比

重载两个函数在同一个作用域函数名相同/参数不同重写(覆盖)两个函数分别在基类与派生类的作用域.函数名/参数/返回值都必须相同(满足三同)协变除外.两个函数必须是虚函数.重定义(隐藏)两个函数分别在基类与派生类的作用域函数名相同两个基类和派生类的同名函数不构成重写就是重定义.

3.抽象类

3.1:概念

在虚函数的后面加上 = 0,则这个函数被成为纯虚函数.包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能够实例化出对象.派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象.纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承.

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Car { public: virtual void Drive() = 0; }; class Benz : public Car { public: }; class BMW: public Car { public: virtual void Drive() { cout << "BMW()" << endl; } }; int main() { Car* pBenz = new Benz; pBenz->Drive(); Car* pBMW = new BMW; pBMW->Drive(); return 0; }

3.2:接口继承与实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数

4:多态的原理

4.1:虚函数表

4.1.1:代码1

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Func1" << endl; } private: int _b = 1; }; int main() { Base b; cout << sizeof(b) << endl; return 0; }
通过测试可以发现b对象是8字节.除了_b成员,还多了一个_vfptr放在对象的前面(有些平台可能会放到对象的最后面,这个跟平台有关系),对象中的这个指针叫做虚函数指针表(v代表virtual,f代表function).一个含有虚函数的类中至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表.

4.1.2:代码2

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Base:Func1()" << endl; } virtual void Func2() { cout << "Base:Func2()" << endl; } virtual void Func3() { cout << "Base:Func3()" << endl; } private: int _b = 1; }; class Derive :public Base { public: virtual void Func1() { cout << "Derive:Func1()" << endl; } virtual void Func2() { cout << "Derive:Func2()" << endl; } private: int _d = 2; }; int main() { Base b; Derive d; return 0; }

4.2:多态的原理

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl; } }; class Student :public Person { public: virtual void BuyTicket() { cout << "买票-半价" << endl; } }; void Func(Person& p) { p.BuyTicket(); } int main() { Person Mike; Func(Mike); Student Johnson; Func(Johnson); return 0; }

4.3:动态绑定与静态绑定

5:单继承与多继承关系的虚函数表

5.1:单继承中的虚函数表

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } private: int _a = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "Derive::Func1()" << endl; } virtual void Func3() { cout << "Derive::Func3()" << endl; } virtual void Func4() { cout << "Derive::Func4()" << endl; } private: int _b = 2; }; //打印对象虚基表,对象虚基表本质是一个函数指针数组 typedef void(*Vfptr)(); void PrintVfptr(Vfptr * vft) { for(size_t i = 0; i < 4; i++) { cout << vft[i] << "->"; vft[i](); } } int main() { Base b; Derive d; Vfptr* ptr = (Vfptr*)(*(int*)(&d)); PrintVfptr(ptr); return 0; }

5.2:多继承中的虚函数表

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //定义函数指针 typedef void (*Vfptr)(); class Base1 { public: virtual void func1() { cout << "Base1::func1()" << endl; } virtual void func2() { cout << "Base1::func2()" << endl; } private: int _b1; }; class Base2 { public: virtual void func1() { cout << "Derive:func1()" << endl; } virtual void func3() { cout << "Derive:func3()" << endl; } private: int _d1; }; class Derive : public Base1, public Base2 { virtual void func1() { cout << "Derive:func1()" << endl; } virtual void func3() { cout << "Derive:func3()" << endl; } private: int _d1; }; void PrintTable(Vfptr Vtable[]) { cout << "虚表地址>" << Vtable << endl; for (size_t i = 0; Vtable[i] != nullptr; i++) { cout << "第" << i << "个虚函数地址: 0X" << Vtable[i] << endl; Vtable[i](); } cout << endl; } int main() { Derive d; /* * (*(int*)(&d))强制类型转换为指针类型并且解引用,那么每次在访问的时候只访问四个字节的数据 * 取出d对象的头4个字节,就是虚表的指针,虚表的本质是存了一个虚函数的指针数组,这个数组最后面放了一个Nullptr */ Vfptr* vTableb1 = (Vfptr*)(*(int*)&d); PrintTable(vTableb1); /* * 强转成char *,char*类型的指针每次解引用跳过一个字节, */ Vfptr* vTableb2 = (Vfptr*)(*(int*)((char*)&d + sizeof(Base1))); PrintTable(vTableb2); return 0; } 

6:多态相关的问题

什么是多态.
  • 通俗来说,就是多种形态,具体一些就是去完成某个行为,当不同的对象去完成的时候会产生出不同的状态.
什么是重载、重写(覆盖)、重定义(隐藏)
  • 重载

(1):两个函数在同一个作用域.

(2):函数名相同/参数不同.

  • 重写(覆盖)

(1):两个函数分别在基类与派生类的作用域.

(2):要满足三同(函数名/参数/返回值都必须相同) PS:协变除外.

(3):两个函数都必须是虚函数.

  • 重定义(隐藏)

(1):两个函数分别在基类和派生类的作用域

(2):函数名相同

(3):两个基类和派生类的同名函数(不构成重写就是重定义).

inline(内联函数)可以是虚函数吗
  • 可以,不过编译器如果忽略inline属性,那么这个函数就不再是inline函数,而是会把虚函数表放到虚函数中.
静态成员函数可以是虚函数吗
  • 不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表
构造函数可以是虚函数吗
  • 不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的.
析构函数可以是虚函数吗
  • 可以,并且最好把基类的析构函数定义成虚函数.
什么场景下析构函数是虚函数
  • 如果基类的析构函数为虚函数,此时派生类的析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写.
对象是访问普通函数快还是虚函数快
  1. 首先如果是普通对象,是一样快的.、
  2. 如果是指针对象或者引用对象,则调用普通函数快一些,因为构成了多态,运行时调用虚函数需要到虚函数表中去查找.
虚函数表是在什么阶段生成的
  • 虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的.
C++菱形继承的问题,虚继承的原理
  • 菱形继承造成了数据冗余和二义性

Read more

Python入门:Python3 PyMySQL 驱动操作数据库全面学习教程

Python入门:Python3 PyMySQL 驱动操作数据库全面学习教程

Python入门:Python3 PyMySQL 驱动操作数据库全面学习教程 Python入门:Python3 PyMySQL 驱动操作数据库全面学习教程,本文是关于 Python3 使用 PyMySQL 驱动操作 MySQL 数据库的教程,首先介绍了 PyMySQL 的定义,它是 Python3 连接 MySQL 的库,遵循特定规范。接着讲解了多种安装方法及常见问题解决,然后说明连接数据库的准备工作,还详细演示了连接数据库、创建表、增删改查等核心操作,同时介绍了事务处理的方法和特性,以及各类错误异常的相关内容,帮助初学者快速掌握相关技能。 前言     Python作为一门简洁、易读、功能强大的编程语言,其基础语法是入门学习的核心。掌握好基础语法,能为后续的编程实践打下坚实的基础。本文将全面讲解Python3的基础语法知识,适合编程初学者系统学习。Python以其简洁优雅的语法和强大的通用性,成为当今最受欢迎的编程语言。本专栏旨在系统性地带你从零基础入门到精通Python核心。无论你是零基础小白还是希望进阶的专业开发者,都将通过清晰的讲解、丰富的实例和实战项目,逐步掌握语法基

By Ne0inhk
从海量时序数据到无人值守:数据库在新能源集控系统中的架构实践

从海量时序数据到无人值守:数据库在新能源集控系统中的架构实践

文章目录 * 引言 * 关于金仓数据库 * 金仓数据库在新能源行业的技术解读 * 1. 应对海量时序数据:分区存储与高效查询 * 2. 支撑高并发访问:读写分离与自治调优 * 3. 保障业务连续性:跨地域高可用与容灾 * 4. 实现平滑迁移:高度兼容与自动化工具 * 案例分析:金仓数据库赋能新能源智慧运维 * 案例一:中广核新能源生产运维系统——应对“整合、高并发、高可用”三大挑战 * 案例二:国家能源集团龙源电力——186个新能源场站集控系统国产化替代 * 案例三:国家电投集团甘肃新能源——“无人值守”风电场集控系统 * 结语 引言 谈到“双碳”与能源革命,风电,光伏这些新能源产业显然是当下最为炙手可热的风口,若想在该赛道跑得更远,更快,数字化和智能化转型并非可选,而是必备功课,要知道,从远程操控成千上万台风电机组,到及时分析大量的设备数据,直至把整个生产运维流程管理得井井有条,哪一步能离开稳定,高效且安全的数据“大后方”

By Ne0inhk
Flutter for OpenHarmony:postgres 直连 PostgreSQL 数据库,实现 Dart 原生的高效读写(数据库驱动) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:postgres 直连 PostgreSQL 数据库,实现 Dart 原生的高效读写(数据库驱动) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 虽然移动应用大多使用本地数据库(如 SQLite),但在某些特定场景,比如内部企业应用、数据看板,或者 Serverless 架构中,客户端直接连接远程数据库进行即时查询是非常便捷的。 postgres 是一个纯 Dart 实现的 PostgreSQL 驱动,拥有完整的协议支持,不需要任何原生库绑定(JNI/FFI)。这意味着它不仅能在服务器端(Dart VM)运行完美,同样也能在 Flutter 移动端及 Web 端流畅运行,当然也包括 OpenHarmony。 一、核心特性 * 纯 Dart 实现:零原生依赖,全平台通用。 * SSL/TLS 安全连接:保障数据传输安全。 * 流式查询:支持大结果集的流式读取,

By Ne0inhk
Flutter 三方库 health_connector_core 的鸿蒙化适配指南 - 实现具备跨平台标准的数据采集与同步架构、支持端侧健康指标建模与设备总线协同实战

Flutter 三方库 health_connector_core 的鸿蒙化适配指南 - 实现具备跨平台标准的数据采集与同步架构、支持端侧健康指标建模与设备总线协同实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 health_connector_core 的鸿蒙化适配指南 - 实现具备跨平台标准的数据采集与同步架构、支持端侧健康指标建模与设备总线协同实战 前言 在进行 Flutter for OpenHarmony 的运动健身、个人健康管理或数字疗法类应用开发时,如何统一管理来自不同传感器(如心率计、血糖仪、计步器)的异构数据?health_connector_core 是一款专注于健康数据标准化处理的底层库。它提供了从指标建模到数据聚合的完整逻辑框架。本文将探讨如何在鸿蒙端构建极致、严谨的健康数据中枢。 一、原直观解析 / 概念介绍 1.1 基础原理 health_connector_core 建立在“标准指标(Standard Metrics)”系统之上。它将杂乱无章的原生生理信号抽象为统一的 Record 模型。

By Ne0inhk