虚函数与动态绑定的那点事

C++多态:从虚函数到动态绑定的核心原理 C++多态是面向对象编程的核心特性,通过虚函数和动态绑定实现"一个接口,多种实现"。文章系统梳理了多态从语法到底层的实现机制:虚函数作为语法基础,在继承体系中通过虚函数表(vtable)和虚指针(vptr)实现动态绑定;深入分析了单继承/多继承下的虚表变化、纯虚函数与抽象类;并探讨了多态在框架设计中的应用场景。理解这一机制能帮助开发者编写更灵活、可扩展的代码,同时掌握编译器对面向对象特性的底层支撑逻辑,为高级C++开发奠定基础。 阅读详情

面向对象编程的概念

1)多态:简单理解就是多种形态,通过继承而相关联的类型,特别在运行的情况下,对象可能是基类也可能派生类类型

1)继承:能够对类型之间的关系建模,共享公共的东西,仅仅特化本质上不同的东西。

定义为virtual的函数是基类期待派生类重新定义的,基类不希望派生类继承的则定义为非虚函数,这样类就有虚函数与非虚函数之分

2)动态绑定:使程序使用继承层次中任意类型的对象,无需关心具体的类型。

在C++,通过引用或者指针调用虚函数,发生动态绑定,引用或指针既可以指向基类对象也可以指向派生类对象,这些都是在运行时确定的,被调用的函数是引用或指针所指对象的实际类型所定义的。

如下class item_base中定义的成员:

	string book() const { return ISBN; }; //希望派生类继承的
	virtual double net_prices(size_t n) const; //virtual希望派生类重定义的

定义基类和派生类:

基类:item_base

如下基类代码:

class item_base
{
public:
	//virtual item_base(void); 构造函数不允许成为virtual
	item_base(const string &book = "", double sale_price = 0);
	virtual ~item_base(void);  //类成员可以是虚函数,非类成员不可以
		
	string book() const { return ISBN; }; //希望派生类继承的
	virtual double net_prices(size_t n=1) const; //virtual希望派生类重定义的
	virtual item_base* get_class(){ return this;}; 

	virtual void print(ostream& os) { os << "base class:" << ISBN << endl; };
	int test() const;

	static void statmem() {};

private:
	string ISBN;

protected:  //可以被派生类访问,但不能该类的普通用户访问
	double price;
};
void print_total(ostream& os, const item_base&item, size_t n);
//virtual int fun(); //error 只有类成员可以是虚函数

如下,构造函数和虚函数net_price的实现,其他暂略:

item_base::item_base(const string &book, double sale_price)
	: ISBN(book), price(sale_price) 
{
}
double item_base::net_prices(size_t n) const
{ 
	cout << __FUNCTION__ << endl;
	return n * price; 
};
1)保留字virtual的目的是启用动态绑定,除了构造函数之外,任意非static成员函数都可以是虚函数。

2)保留字virtual只能出现在类成员函数声明处,不能用在类定义体外部的函数定义上。

3)访问控制,在普通类中经常看到public和private,这两个标号的作用如下:

public:类和用户都能访问类的public成员

private:只能由基类的成员和友元访问,不能被类的用户访问

这样一来派生类是不能访问基类的private成员的,但是protected成员是可以的:

protected:可以被派生类访问但不能被该类的普通用户访问。如上定义的price成员

4)派生类对象只能通过派生类对象访问其基类的protected成员,却没有基类的protected的访问权,如下:

void bulk_item::memfcn(const bulk_item &d, const item_base &b)
{
	double ret = price;
	ret = d.price;  //注意这里为类中访问
	//ret = b.price; //error 不能访问基类的protect成员
	//b.ISBN; //无论基类还是继承类对象都不能访问private memmber
}
注意:用户只能访问public成员,类成员和友元既可以访问public也可以访问private成员。

派生类:bulk_item

派生类的定义形式如下:

class derived_class: acess_label base_class{ ...};,如下派生类代码:

class bulk_item: public item_base
{
public:
	bulk_item();
	~bulk_item();
	bulk_item(const string& str, double sales_price, size_t qty = 0, double disc_rate = 0);
	string book() const { return ""; }; //
	
	/*virtual*/ double net_prices(size_t n=2) const; //虚函数不加virtual也是虚函数,永远是
	//virtual item_base* get_class() { return this; };
	virtual bulk_item* get_class() { return this; }; //虚函数的继承类成员可以返回派生类类型的指针或者引用
	
	void memfcn(const bulk_item &d, const item_base &b);

	virtual void print(ostream& os) { item_base::print(os); os <<"derived print"<< endl; };
	void print() { print(cout); };

	int test() const;

	void CallStatic(const bulk_item &);
private:
	size_t min_qty;
	double discount;
};
派生类有增加了两个成员( size_t min_qty; double discount;),加上之前的两个( string ISBN;  double price;),现在派生类拥有四个成员变量;当然函数凡是非虚函数的都被继承下来了,虚函数可能被重新定义,也可能没有,没有的话就调用基类的。
如下派生类的有些函数的实现:
bulk_item::bulk_item()
	:min_qty(0),discount(0)
{
}
double bulk_item::net_prices(size_t nNum) const
{
	cout << __FUNCTION__ << endl;

	return (nNum >= min_qty)? nNum*(1-discount)*price: nNum * price;
}

注意:

1)派生类中的虚函数声明必须与基类的定义方式完全匹配。但有一个例外,返回对基类型的引用或者指针,如下:

//base class
virtual item_base* get_class(){ return this;}; 
//derived class
virtual bulk_item* get_class() { return this; }; //虚函数的继承类成员可以返回派生类类型的指针或者引用
如上第一个虚函数,其实返回的是item_base * ,派生类中的虚函数返回的是bulk_item*
2)一旦函数在基类中声明为虚函数,它就一辈子为虚函数,派生类是无法改变它的出身的

3)派生类重定义虚函数时,可以使用虚函数virtual保留字也可以不使用,如下:

/*virtual*/ double net_prices(size_t n=2) const; //虚函数不加virtual也是虚函数,永远是
4)派生中可以使用基类的成员,要不然共享的东西也没有意义

5)基类必须是已定义的,如果只是前向声明,那么就无法使用他作为基类类定义继承类

6)派生类也可以作为基类,无穷继承下去都是可以的,子子孙孙无穷尽也。

7)如果需要一个派生类,则声明包含类名但不含派生列表

//class bulk_test1: public item_base; //error
class bulk_test2; //ok

虚函数的动态绑定:

1. 满足条件

1)只有指定为虚函数的成员函数才能进行动态绑定

2)必须通过基类的引用或者指针进行函数调用

即: 动态绑定 = 虚函数+指针/引用传递

2. 虚函数需要注意的地方:

1)从派生类到基类的转换,如下调用

//net_price使用默认实参,将由调用该函数的类型定义,与对象的动态类型无关
void print_total(ostream& os, const item_base& item, size_t n)
{
 	os << "ISBN:" << item.book() << "\tNum sold:" << n  //总是调用基类的book函数,即使继承类也定义了
 		<< "\tTotal price:" << item.net_prices() << endl;  //先调用的虚函数,然后才是普通的成员函数,与顺序无关

//	os << item.book() << item.test() << endl;
}    
如下调用:

	item_base base("hello");
	bulk_item derived;

	print_total(cout, base, 3);
	print_total(cout, derived, 4);
这里可以使用基类类型的指针或者引用来引用派生类对象,所以使用基类类型的引用或者指针时,不知道指针或者引用所绑定的对象的类型,将派生类对象当做基类对象时安全的,因为每个派生类对象都拥有基类子对象,而且派生类也继承基类的操作,任何可以在基类对象上执行的操作也可以通过派生类对象使用。

2)运行时确定virtual虚函数的调用

将基类类型的引用或者指针绑定到派生类对象对及基对象没有影响,基对象本身也没有改变,仍为派生类对象。对象的实际类型可能不同于该对象的引用或者指针的静态类型。

如上,第一个item形参绑定到item_base对象上,因此调用基类的net_price,第二个形参绑定到bulk_item,所以调用派生类的net_price函数。

注意:引用和指针的动态类型与静态类型可以不同。如果调用非虚函数,无论实际对象时什么类型,都会调用基类类型所定义的函数。只有通过引用或者指针调用的虚函数,才会在运行时确定对象的实际类型。

3)虚函数的覆盖机制

在默认情况下,继承类调用虚函数,一般是使用继承类的版本,如何使用基类版本呢??如下调用就可以使用基类版本:

	item_base base("hello");
	bulk_item derived;
	derived.item_base::test();  //调用基类版本
	derived.test();   //调用继承类

如果派生类需要调用基类中定义的虚函数,就需要使用覆盖虚函数机制。派生类虚函数调用基类版本时,必须显式使用作用域操作符。

4) 虚函数的形参使用默认实参

如果一个调用省略了具体有默认默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。如下调用

	base.net_prices(); //调用基类版本
	derived.net_prices(); //调用继承类版本
	derived.item_base::net_prices(); //调用基类版本

这里还不出端倪,再看下面的调用

	print_total(cout, base, 3);
	print_total(cout, derived, 4);

上面调用中我传入了实参,但是为了测试默认实参的作用,我修改了下面函数的调用方式,这里面传过来的实参就没有用了,默认使用函数声明的实参,使用如下:

//base class
	virtual double net_prices(size_t n=1) const; //virtual希望派生类重定义的
//derived class
/*virtual*/ double net_prices(size_t n=2) const; //虚函数不加virtual也是虚函数

//net_price使用默认实参,将由调用该函数的类型定义,与对象的动态类型无关
void print_total(ostream& os, const item_base& item, size_t n)
{
	os << "ISBN:" << item.book() << "\tNum sold:" << n  //总是调用基类的book函数,即使继承类也定义了
		<< "\tTotal price:" << item.net_prices() << endl;  //先调用的虚函数,然后才是普通的成员函数
}
另外一个需要注意的就是我在调试这个非类成员函数的时候,发现默认先是进入虚函数net_price,然后才是非虚函数book,这个是<<操作符先调用右边的表达式或者函数!这里两次调用虽然进入的函数不同,一个是基类版本,一个是继承类版本,但是传入的参数都是1,也就是基类的默认实参,看出端倪没?!通俗点讲就是谁离他最近他就使用谁,他不需要关注间接人后面的真家伙,但是动态绑定正好跟这不一样或者说是相反的,动态绑定要知道真正的家伙是谁!!。

5)函数调用的延伸

看如下的定义

	base.print(cout);
	derived.print(cout);  //会导致无穷递归自己 ,这里进行了修改
	derived.print(); //会调用继承类的print然后无穷递归
	item_base *bp1 = &base;
	item_base &br1 = base;
	item_base *bp2 = &derived;  //动态绑定 与形参使用基类类类型传递是一致的
	item_base &br2 = derived;

这里print的定义在类中已经声明,主要说一下,在继承类中有实参的虚函数继承中,如果不指定基类就会导致无穷递归,也就说使用了如下的方式:

virtual void print(ostream& os) { print(os); os <<"derived print"<< endl; };
无实参的非虚函数调用的是继承类中的虚函数版本,因为当前this指针指向的是bulk_item*。

调用方式如下:

	bp1->print(cout); //调用基类
	br1.print(cout); //调用基类
	bp2->print(cout); //调用继承类 
	br2.print(cout); //调用继承类
这里可以看出引用于指针并无任何区别,关键的是对象本身是什么,这就需要动态运行期间才能确定,由于bp2 br2默认是指向或者绑定于继承类对象,所以在运行期间使用的是继承类版本。

PuTTY使用方法 PuTTY是一款开源SSH/Telnet客户端,主要用于Windows系统连接Linux服务器。使用步骤包括:下载安装PuTTY,输入服务器IP和端口(默认22),可选择保存会话配置。支持密码和SSH密钥认证(需转换.pem为.ppk格式)。首次连接需确认主机密钥,成功登录后可执行Linux命令。其他功能包括会话日志记录、X11转发和端口隧道配置。操作完成后输入exit断开连接。 阅读详情

相关推荐

【Unity游戏开发】SDK接入与集成——小白入门篇

一、简介   通常一款游戏开发到后期,一般都会涉及到第三方SDK的接入与集成,对于不熟悉SDK接入的同学来说,接SDK每次都是云里雾里,而熟悉SDK接入的同学又觉得不断地重复做接入SDK工作这样没有成就感,太尼玛无聊了(Android渠道一弄就十几个,直接吐血)。其实通常情况下接入SDK都是很简单的一个过程,本系列博客就让马三和大家从小白开始,从零基础开始学习如何接入SDK以及一些常见的SDK的...

weixin_34195142的博客 5764

C++_虚函数调用的工作原理(基于虚函数多态的机制)

在运行状态下进行称之为动态。 1.虚函数表 (1)包含虚函数的类 class B { virtual int f1 (void); virtual void f2 (int); virtual int f3 (int); }; 编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的下标。 ...

qq_28090573的博客 746

PCIe链路训练Recovery状态机:从RcvrLock到链路稳定的关键路径

本文深入解析PCIe链路训练中的Recovery状态机,特别是RcvrLock状态的关键作用。从Block对齐、均衡预设值应用到状态转移条件,详细介绍了确保链路稳定的技术要点,并提供了高速率下的特殊处理方法和常见问题排查指南,帮助工程师解决PCIe链路不稳定问题。

weixin_28739079的博客 176

C++ 虚函数详解(动态绑定)

虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。我们在使用基类的引用(指针)调用虚函数时,就会发生动态绑定。所谓动态绑定,就是在运行时,虚函数会根据绑定对象的实际类型,选择调用函数的版本。

qq_41897304的博客 1216

【测试】echo发送和接收TCP/UDP数据包|shell 发送TCP/UDP数据包

目录 通过/dev/tcp、/dev/udp shell给tcp或udp服务发送16进制报文指令 通过/dev/tcp、/dev/udp (https://blog.youkuaiyun.com/u010039418/article/details/86251470) 通过/dev/tcp、/dev/udp可以直接在shell脚本中发起tcp、udp连接,方便又高效,平时用于测试啥的还是挺方便的。 直接发送 格式 /dev/udp/ip/port 比如要向本地10001端口发送数据,可以使用 ec.

我的笔记本 1万+

VS Code配置MASM32汇编环境:从安装到调试的完整指南

本文提供了一份详尽的指南,教你如何在VS Code中配置MASM32汇编开发环境,实现从安装、环境变量设置到编写、编译和调试32位Windows汇编窗口程序的完整工作流。通过集成现代编辑器的便利性与经典汇编工具链,旨在降低学习门槛,提升底层开发效率。

nn3456的博客 487

C++三大特性—多态 “虚函数与动态绑定”

多态的定义及实现、虚函数重写、动态绑定、override和final (C++11)、重载、覆盖(重写)、隐藏(重定义)的对比、成员函数与继承、派生类中的虚函数虚函数与默认实参、如何回避虚函数的机制

DEXTERFUTIAN的博客 2927

Centos 7 Linux 离线安装单机 Kafka:下载、安装、配置、使用

本文详细介绍了在 CentOS 7 系统上离线安装单机版 Apache Kafka 的完整流程,包括环境准备(JDK 安装)、Kafka 安装包上传与解压、关键配置修改(如监听地址)、ZooKeeper 与 Kafka 服务的启动,以及 Topic 的创建、消息的生产和消费等基础操作。适用于无网络环境下的 Kafka 快速部署与学习,为后续开发或测试提供本地消息队列支持。

寒水馨 · 轻量级技术文档 · hanshuixin.org 1320

动态绑定与静态绑定 虚函数(以及纯虚函数)与多态

动态绑定与静态绑定 虚函数(以及纯虚函数)与多态

qq_61971570的博客 939

3.4 ISIS和OSPF之间路由引入

通过以上输出可以看到OSPF的路由引入到了IS-IS中。通过以上输出可以看到ISIS的路由没有传递给OSPF。通过以上输出可以看到OSPF的路由没有传递给ISIS。通过以上输出可以看到ISIS的路由被引入进了OSPF。掌握在IS-IS中引入OSPF路由的方法。掌握在OSPF中引入IS-IS路由的方法。图3-3:ISIS和OSPF之间路由引入。在R3上把ISIS的路由引入到OSPF。OSPF引入外部路由的缺省度量值为1。在R3上把OSPF的路由引入到ISIS。引入的外部路由类型为Type2。

lwljh134的博客 1803

python爬虫案例——腾讯网新闻标题(异步加载网站数据抓取,post请求)(6)

本篇案例主要讲解异步加载网站如何分析网页接口,以及如何观察post请求URL的参数,网站数据并不难抓取,主要是将要抓取的数据接口分析清楚,才能根据需求编写想要的代码。

@优快云盲敲代码的阿豪的博客 946

C++ 虚函数与动态绑定原理剖析

C++ 虚函数与动态绑定 1. 虚函数基本概念     基类(Base)我们记作 B ,派生类类(Derive)我们记作 D 。有时候会出现这么一种状况:对于某些函数,基类 B 希望它的派生类 D 各自定义适合自身的版本,此时基类 B 就将这些函数声明成虚函数(virtual function)。 将一个成员函数声明成虚函数,只需要在函数前添加 virtual 关键字。 Tips: 派生类必须在其内部对所有重新定义的虚函数进行声明。 2. 虚函数的几点说明 所有

annjeff的博客 2665

推理篇第3节:TensorRT基础——Builder、Engine、ExecutionContext生命周期

TensorRT模型优化与推理流程摘要 TensorRT是NVIDIA的深度学习推理优化器,通过Builder、Engine和ExecutionContext三阶段实现高效推理。Builder负责编译模型,执行图优化和kernel选择;Engine是优化后的推理计划;ExecutionContext管理运行时执行。编译期进行重量级优化(如算子融合、kernel调优),运行时快速执行。TensorRT 10.x引入新API,推荐使用buildSerialized方法。构建流程包括创建Builder、网络定义、

PeterPan的博客 503

【C++】多态 - 从虚函数到动态绑定的核心原理

本篇文章主要介绍了多态的概念以及多态的定义及实现,虚函数和抽象类,多态的原理等内容。

2301_81290732的博客 2224

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C++虚函数与纯虚函数:实现动态绑定

摘要:C++中的虚函数和纯虚函数是实现多态性的关键工具。虚函数通过virtual关键字声明,允许派生类重写,实现运行时动态绑定;纯虚函数以=0表示,使基类成为抽象类,强制派生类实现接口。文章通过形状(Shape)和动物(Animal)等示例,展示了如何使用虚函数实现基类指针调用不同派生类方法,以及纯虚函数定义统一接口的方式。这些特性使代码更具灵活性和扩展性,是面向对象编程的重要概念。

fykam的博客 1932

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【C++ | 虚函数】虚函数详解 及 例子代码演示(包含虚函数使用、动态绑定、虚函数表、虚表指针)

👉本文介绍了C++的虚函数,包括虚函数的定义、重写,动态绑定、虚函数表、虚表指针等内容。

wkd_007的博客 7099

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

C++中的动态类型与动态绑定、虚函数、运行时多态的实现

C++中的动态类型与动态绑定、虚函数、运行时多态的实现,认真捉摸。

IIcyZhao's Road 1万+

虚函数与动态绑定

在定义基类时,我们希望基类中的有些函数可以在派生类中重新定义。比如,我们定义了基类记录的书,可以求出买了多少书花了多少钱;而在派生类中,我们定义的是打折的书,还是要计算买了多少书花了多少钱。这时,就需要重新定义计算钱数的函数了。注意,这里的重新定义,与之前讲过的函数重载或者操作符重载不同:后面两类,是通过不同的形参,返回值类型来让编译器判断到底使用的是哪个函数,在程序执行前就能判断;而这里的重新定

thefutureisour的专栏 5916

C++ 虚函数、虚函数表、静态绑定与动态绑定笔记

虚函数表是 C++ 编译器实现动态绑定的核心机制。

FHKHH的博客 1092

C++虚函数详解:动态绑定机制深度解析

深入对比动态绑定与静态绑定的区别,通过具体案例帮助读者理解虚函数的工作原理

筏的博客 1564

C++ 多态的原理,虚函数,虚函数的重写,虚函数表,静态绑定,动态绑定

文章目录:

HanMeng的博客 2086

C++的静态绑定和动态绑定、虚函数表的理解

"绑定"指的是函数调用与函数实现之间的关联,在C++中,这种绑定可以是静态的(编译时决定)或动态的(运行时决定)。静态绑定发生在一个函数调用与一个函数实现之间。这里,函数的调用是基于调用对象的静态类型(即声明时的类型)来决定的。也就是说,编译器在编译时就决定了会调用哪个函数;动态绑定发生在一个虚函数调用与多个可能的函数实现之间。在这种情况下,函数的调用是基于调用对象的实际类型(即运行时对象的类型)来决定的。也就是说,运行时系统(而不是编译器)在运行时决定了会调用哪个函数。

KK_2018的技术博客_程序员不止是程序员 2624

虚函数以及静态绑定与动态绑定简单总结

一个类添加了虚函数,对这个类有什么影响? 一个类里面定义了虚函数,那么编译阶段,编译器会给这个类类型产生一个唯一的虚函数表(vftable),虚函数表里面储存的内容主要是RTTI指针和虚函数的地址。当程序运行时,每张虚函数表都会加载到内存的.rodata区。 一个类里定义了虚函数,那么这个类定义的对象,其运行时,内存中开始的部分会多储存一个vfptr虚函数指针,指向对应类型的虚函数表。一个类型定义的n个对象,vfptr指向的都是同一张虚函数表。 一个类中定义多个虚函数,不会影响对象内存的大小,只会影响虚函数

mlc741295821的博客 1520

虚函数:虚函数是如何实现动态绑定的

在复习c++的过程中,重点看了虚函数的内容,以前学习的时候不求甚解,如今重新学习,才更加深刻地理解了其中缘由,所谓“温故而知新”。 首先虚函数是在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。是实现动态多态的一种方法。 那么虚函数是如何实现动态的呢? 首先要了解虚函数是如何实现的,简单的说,虚函数是通过虚函数表来实现的。每一个含有虚函数的类,都会有一个虚函数...

weixin_43742894的博客 2729

C++ 多态(二) : 虚函数、静态绑定、动态绑定、单/多继承下的虚函数表

虚函数、静态绑定、动态绑定、单/多继承下的虚函数表

林慢慢的博客 2374

C++ 中的虚函数、虚函数表、虚函数表指针、运行时多态(动态多态)、动态绑定

a. 定义:在 C++ 中,被。

m0_53601985的博客 2238
上一篇: 调用操作符和函数对象
下一篇: STL之关联容器(pair、map、set的使用)
comwise
博客等级 码龄15年 58粉丝 85原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值