千面之法: 释放 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

飞算JavaAI全链路实战:智能构建高可用电商系统核心架构

飞算JavaAI全链路实战:智能构建高可用电商系统核心架构

飞算JavaAI全链路实战:智能构建高可用电商系统核心架构 前言:AI编程新时代的电商系统开发范式变革 最近学习人工智能时遇到一个好用的网站给大家分享一下 人工智能学习 在当今数字经济时代,电商系统作为企业数字化转型的核心载体,其复杂度和技术要求与日俱增。一个完整的电商系统不仅需要处理商品、订单、用户等基础业务,还要应对高并发、分布式事务、数据一致性等复杂技术挑战。传统开发模式下,从需求分析到系统上线往往需要耗费大量人力和时间成本。 本次我通过飞算JavaAI平台,深入探索"电商系统核心功能模块"这一实战赛道,全面体验了从需求分析到代码生成的全链路开发过程。本文将完整呈现如何借助AI辅助开发工具,高效构建一个包含用户管理、商品系统、订单流程、支付集成等核心模块的电商平台,严格遵循"需求分析-开发实录-优化调试-成果总结"的四大核心框架,为开发者提供一份AI辅助全栈开发的完整实践指南。 一、需求分析与规划:构建电商系统的业务架构蓝图 在启动飞算JavaAI之前,需要进行全面的业务需求梳理和系统架构设计,这是确保AI生成代码符合预期的基础。 1.(理解需求)系统核心模块与

By Ne0inhk
【Java 开发日记】我们来说一下无锁队列 Disruptor 的原理

【Java 开发日记】我们来说一下无锁队列 Disruptor 的原理

目录 一、为什么需要 Disruptor?—— 背景与问题 二、核心设计思想 三、核心组件与原理 1. 环形缓冲区(Ring Buffer) 2. 序列(Sequence) 3. 序列屏障(Sequence Barrier) 4. 等待策略(Wait Strategy) 5. 事件处理器(EventProcessor) 6. 生产者(Producer) 四、工作流程示例(单生产者 -> 单消费者) 五、多消费者与依赖关系 六、总结:Disruptor 高性能的秘诀 一、为什么需要 Disruptor?—— 背景与问题 在高并发编程中,传统的队列(如 java.

By Ne0inhk
从 .NET 到 Java 的转型指南:详细学习路线与实践建议

从 .NET 到 Java 的转型指南:详细学习路线与实践建议

文章目录 * 第一部分:转型背景与核心差异分析 * 1.1 为什么需要从 .NET 转型到 Java * 1.2 .NET 与 Java 核心架构差异 * 1.2.1 运行时环境对比 * 1.2.2 内存管理机制 * 1.3 心态调整与学习策略 * 1.3.1 相似性利用 * 1.3.2 差异性重视 * 第二部分:Java 语言基础深入学习 * 2.1 Java 语法核心概念 * 2.1.1 基本数据类型与包装类 * 2.1.2 字符串处理 * 2.

By Ne0inhk
Java 大视界 -- Java+Flink CDC 构建实时数据同步系统:从 MySQL 到 Hive 全增量同步(443)

Java 大视界 -- Java+Flink CDC 构建实时数据同步系统:从 MySQL 到 Hive 全增量同步(443)

Java 大视界 -- Java+Flink CDC 构建实时数据同步系统:从 MySQL 到 Hive 全增量同步(443) * 引言: * 正文: * 一、 核心认知:Flink CDC 与全增量同步逻辑 * 1.1 Flink CDC 核心原理 * 1.1.1 与传统数据同步方案的对比(实战选型参考) * 1.2 全增量同步核心逻辑(MySQL→Hive) * 1.2.1 关键技术点(实战必关注,每个点都踩过坑) * 二、 环境准备:生产级环境配置(可直接复用) * 2.1 核心依赖配置(pom.xml)

By Ne0inhk