在《 C++ 编程思想》一书中对虚函数的实现机制有详细的描述,一般的编译器通过虚函数表,在编译时插入一段隐藏的代码,保存类型信息和虚函数地址,而在调用时,这段隐藏的代码可以找到和实际对象一致的虚函数实现。
我们在这里提供一个 C 中的实现,模仿 VTABLE 这种机制,但一切都需要我们自己在代码中装配。
之前在网上看到一篇描述 C 语言实现虚函数和多态的文章,谈到在基类中保存派生类的指针、在派生类中保存基类的指针来实现相互调用,保障基类、派生类在使用虚函数时的行为和 C++ 类似。我觉得这种方法有很大的局限性,不说继承层次的问题,单单是在基类中保存派生类指针这一做法,就已经违反了虚函数和多态的本意——多态就是要通过基类接口来使用派生类,如果基类还需要知道派生类的信息……。
我的基本思路是:
- 在“基类”中显式声明一个 void** 成员,作为数组保存基类定义的所有函数指针,同时声明一个 int 类型的成员,指明 void* 数组的长度。
- “基类”定义的每个函数指针在数组中的位置、顺序是固定的,这是约定,必须的
- 每个“派生类”都必须填充基类的函数指针数组(可能要动态增长),没有重写虚函数时,对应位置置 0
- “基类”的函数实现中,遍历函数指针数组,找到继承层次中的最后一个非 0 的函数指针,就是实际应该调用的和对象相对应的函数实现
好了,先来看一点代码:
struct base {
void ** vtable;
int vt_size;
void (*func_1)(struct base *b);
int (*func_2)(struct base *b, int x);
};
struct derived {
struct base b;
int i;
};
struct derived_2{
struct derived d;
char *name;
};
上面的代码是我们接下来要讨论的,先说一点,在 C 中,用结构体内的函数指针和 C++ 的成员函数对应, C 的这种方式,所有函数都天生是虚函数(指针可以随时修改哦)。
注意,derived 和 derived_2 并没有定义 func_1 和 func_2 。在 C 的虚函数实现中,如果派生类要重写虚函数,不需要在派生类中显式声明。要做的是,在实现文件中实现你要重写的函数,在构造函数中把重写的函数填入虚函数表。
我们面临一个问题,派生类不知道基类的函数实现在什么地方(从高内聚、低耦合的原则来看),在构造派生类实例时,如何初始化虚函数表?在 C++ 中编译器会自动调用继承层次上所有父(祖先)类的构造函数,也可以显式在派生类的构造函数的初始化列表中调用基类的构造函数。怎么办?
我们提供一个不那么优雅的解决办法:
每个类在实现时,都提供两个函数,一个构造函数,一个初始化函数,前者用户生成一个类,后者用于继承层次紧接自己的类来调用以便正确初始化虚函数表。依据这样的原则,一个派生类,只需要调用直接基类的初始化函数即可,每个派生类都保证这一点,一切都可以进行下去。
下面是要实现的两个函数:
struct derived *new_derived();
void initialize_derived(struct derived *d);
new 开头的函数作为构造函数, initialize 开头的函数作为 初始化函数。我们看一下 new_derived 这个构造函数的实现框架:
struct derived *new_derived()
{
struct derived * d = malloc(sizeof(struct derived));
initialize_base((struct base*)d);
initialize_derived(d);/* setup or modify VTABLE */
return d;
}
如果是 derived_2 的构造函数 new_derived_2,那么只需要调用 initialize_derived 即可。
说完了构造函数,对应的要说析构函数,而且析构函数要是虚函数。在删除一个对象时,需要从派生类的析构函数依次调用到继承层次最顶层的基类的析构函数。这点在 C 中也是可以保障的。做法是:给基类显式声明一个析构函数,基类的实现中查找虚函数表,从后往前调用即可。函数声明如下:
struct base {
void ** vtable;
int vt_size;
void (*func_1)(struct base *b);
int (*func_2)(struct base *b, int x);
void (*deletor)(struct base *b);
};
说完构造、析构,该说这里的虚函数表到底是怎么回事了。我们先画个图,还是以刚才的 base 、 derived 、derived_2 为例来说明,一看图就明白了:
我们假定 derived 类实现了三个虚函数, derived_2 类实现了两个,func_2 没有实现,上图就是 derived_2 的实例所拥有的最终的虚函数表,表的长度( vt_size )是 9。如果是 derived 的实例,就没有表中的最后三项,表的长度( vt_size )是 6 。
必须限制的是:基类必须实现所有的虚函数,只有这样,这套实现机制才可以运转下去。因为一切的发生是从基类的实现函数进入,通过遍历虚函数表来找到派生类的实现函数的。
当我们通过 base 类型的指针(实际指向 derived_2 的实例)来访问 func_1 时,基类实现的 func_1 会找到 VTABLE 中的 derived_2_func_1 进行调用。
好啦,到现在为止,基本说明白了实现原理,至于 初始化函数如何装配虚函数表、基类的虚函数实现,可以根据上面的思路写出代码来。按照我的这种方法实现的虚函数,通过基类指针访问,行为基本和 C++ 一致。
回顾一下:
分享到:
相关推荐
C语言实现对象编程之多态代码.rar 在C语言中还可以实现更深入的面向对象编程多态特性。例如:使用接口(interface)包含多个指向函数的指针,这样就可以实现操作的"多态性"。 在面向对象语言C++的实现上,使用了虚...
多态是面向对象编程中的另一个重要概念,它允许以统一的方式使用不同的类对象。多态性允许将派生类对象视为基类对象,这样可以在运行时选择调用哪个类的方法,从而实现不同类对象的多种行为。 在C++中,实现继承和多...
理解掌握友元函数和重载操作符,动态数组,理解掌握继承和多态性:掌握模版的使用:能够进行程序调试过程中的异常处理:进一步掌握利用C++进行类的定义和操作方法:进一步掌握类的继承和派生方法:进一步理解虚函数和多态:...
第14章 多态和虚函数 14.1向上映射 14.2问题 14.3虚函数 14.4C++如何实现晚捆绑 14.4.1存放类型信息 14.4.2对虚函数作图 14.4.3撩开面纱 14.4.4安装vpointer 14.4.5对象是不同的 14.5为什么需要虚函数 14.6抽象...
内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...
C++是在C语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言,是C语言的超集。《C++ Primer Plus(第6版)英文版(上、下册)》是根据2003年的ISO/ANSI C++标准编写的,通过大量短小精悍的程序...
面试题5:构造函数中调用虚函数能实现多态吗?为什么? c++编译器多态实现原理 6 面试题6:虚函数表指针(VPTR)被编译器初始化的过程,你是如何理解的? 6 面试题7:父类的构造函数中调用虚函数,能发生多态吗? ...
1.2 面向对象编程 1 1.3 Windows系统下搭建C++学习环境 2 二、C++基础入门 16 2.1 C++类和对象 17 2.2 C++命名空间 18 2.3 std标准命名空间 20 2.4 C++新增的标准输入输出方法(cin和cout) 22 2.5 C++规定的变量定义...
本书以流行的面试题讲解为主要内容,介绍了C、C++语言基本概念,包括保留字、字符串、指针和引用、结构体、库函数等各个方面的基础知识,介绍了面向对象编程基本概念,包括如何实现继承、多态和封装等。还介绍了排序...
C++是在C语言的基础上开发的一种集面向对象编程、通用编程和传统的过程性编程于一体的编程语言,是C语言的超集。本书是根据1998年的ISO/ANSI C++标准编写的,通过大量短小精悍的程序详细而全面地阐述了C++的基本概念...
C++是在C语言的基础上开发的一种集面向对象编程、通用编程和传统的过程性编程于一体的编程语言,是C语言的超集。本书是根据1998年的ISO/ANSI C++标准编写的,通过大量短小精悍的程序详细而全面地阐述了C++的基本概念...
《现代C++程序设计(原书第2版)》图文并茂,通俗易懂,真正做到寓教于乐,是一本难得的C++面向对象设计入门教材。 出版者的话 译者序 前言 第1章 C++概述与软件开发 1.1 什么是C语言和C++ 1.1.1 C和C++历史回顾 1.1.2...
内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...
1.3 结构化设计与面向对象设计 1.3.1 ATM——结构化设计 1.3.2 采用面向对象方法的ATM——究竟是谁的任务 1.3.3 汽车维护——结构化设计 1.3.4 采用面向对象方法的汽车维护——究竟是谁的任务 1.4 软件开发技术概述 ...
内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...
3.4 虚函数和多态 3.5 通用程序设计 3.6 作为组件的对象 . 3.7 oop和验证 第4章 tlm介绍 4.1 抽象(abstraction) 4.2 事务的定义(definilion of a transaction) 4.3 组件间的通信(communicating comf)onents) 4.4 ...
内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...
内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...
C++是在C语言基础上开发的一种集面向对象编程、通用编程和传统的过程化编程于一体的编程语言,是C语言的超集。本书是根据2003年的ISO/ANSI C++标准编写的,通过大量短小精悍的程序详细而全面地阐述了C++的基本概念和...
内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...