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

Linux高并发服务器实现原理:从多进程到多路IO转接的演进之路

Linux高并发服务器实现原理:从多进程到多路IO转接的演进之路

🌟 Linux高并发服务器实现原理:从多进程到多路IO转接的演进之路 🚀 引言:高并发服务器的挑战 在互联网应用爆炸式增长的今天,服务器需要同时处理成千上万的客户端连接已成为常态。想象一下,一个电商平台在"双十一"期间,每秒需要处理数十万甚至上百万的请求——这就是高并发服务器的用武之地。本文将带您深入探索Linux环境下高并发服务器的实现原理,从传统的多进程/多线程模型,到现代的多路IO转接机制。 🧩 传统实现方式回顾 1. 多进程模型:分而治之的古老智慧 在多进程模型中,每当有新客户端连接时,服务器会fork出一个子进程专门处理该连接。这种"一个客户端一个进程"的方式简单直观,就像为每位顾客配备专属服务员。 // 伪代码示例:多进程模型intmain(){int lfd =socket();// 创建监听套接字bind(lfd);// 绑定端口listen(lfd);// 开始监听while(1){int cfd =accept(lfd);// 接受新连接if(fork()==0){// 创建子进程close(

By Ne0inhk

Flutter 三方库 proper_filesize 的鸿蒙化适配指南 - 在鸿蒙系统上构建人性化、高精度的文件大小展现引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 proper_filesize 的鸿蒙化适配指南 - 在鸿蒙系统上构建人性化、高精度的文件大小展现引擎 在鸿蒙(OpenHarmony)系统开发文件管理器、下载中心或系统清理工具时,如何将枯燥的字节数(Bytes)转化为用户秒懂的“人类可读”格式?proper_filesize 为开发者提供了一个轻量且精准的转换方案。本文将带您深入实战其在鸿蒙生态中的适配细节。 前言 什么是人性化展现?它是指将 1048576 字节自动转换为 1.00 MB 或 1.00 MiB。proper_filesize 库支持国际标准的公制(Metric)与二进制(Binary/IEC)单位转换,确保在鸿蒙应用中展示的文件大小既符合技术严谨性,又具备极佳的用户体验。 一、原理分析

By Ne0inhk
Linux 读写锁深度解析:原理、应用与性能优化

Linux 读写锁深度解析:原理、应用与性能优化

🔐 Linux 读写锁深度解析:原理、应用与性能优化 * 📚 一、读写锁基础概念 * 1.1 什么是读写锁? * 1.2 读写锁 vs 互斥锁 * 🏗️ 二、Linux 读写锁的实现原理 * 2.1 数据结构解析 * 2.2 状态转换图 * 💻 三、Linux 读写锁 API 详解 * 3.1 基本操作接口 * 3.2 属性设置 * 🚀 四、实战应用案例 * 4.1 案例一:配置管理系统 * 4.2 案例二:实时数据缓存 * 📊 五、性能分析与优化 * 5.1 读写锁性能特征 * 5.

By Ne0inhk
KaiwuDB社区版 3.1.0 在 Ubuntu 22.04 部署实战:TLS 配置、踩坑复盘与轻量压测

KaiwuDB社区版 3.1.0 在 Ubuntu 22.04 部署实战:TLS 配置、踩坑复盘与轻量压测

KWDB 作为一款易用性不断优化的数据库产品,其 3.1.0 版本在运维脚本、配置管理等方面的升级为部署带来了便利,但新手在单机部署过程中仍易因环境适配、依赖缺失、配置不当等问题踩坑。为帮助开发者快速落地 KWDB 单机环境,本文以 Ubuntu 22.04 为基础环境,从实战角度出发,完整拆解 KWDB 3.1.0 单机部署的全流程:不仅明确版本选型依据和部署目标,还细化了环境核查、安装包获取、依赖配置、部署脚本执行等关键操作,针对性解决部署中的高频问题,并通过服务验证、性能基线测试完成最小化验收,最终实现 “安装即能用、问题有解法、效果可验证” 的部署目标,为 KWDB 入门者提供清晰、可复现的实操指引。 文章目录 * 1. 版本与部署路线怎么选 * 2. 目标:这篇文章读完,能带走哪些“

By Ne0inhk