转自:https://blog.csdn.net/wwh578867817/article/details/45101033
前几天在小组无意见听到学姐说到c语言实现多态这个词,比较感兴趣,欢迎一起讨论哈。
提前说一下,c实现多态算是一个奇怪的用法吧,而且不是完全的多态,并不通用,也不推荐用。感兴趣的可以了解下
我们都知道多态性是针对OOP(面向对象语言)说的,OOP语言的三大特性:
封装,继承,多态
相对于OOP语言,c语言就比较“麻烦”且不灵活(可以查询面向对象和面向过程语言的区别),它是面向过程的。
但是可以用c来实现OOP的多态性是不是感觉很”高大上“。
多态性的表现,这里用c++来简述,其实OOP应该都差不多。
c++的多态分为两种:
1.编译时多态:重载
2.运行时多态:重写(也称为覆盖override)
重载:函数名称相同,但参数类型或参数个数不同的一组函数。在编译期就决好的。
重写:也称为覆盖,牵扯到虚函数,简单来说就是虚函数(impure virtual)为我们实现一份默认的操作,我们可以使用这个也可以自己重写(覆盖)虚函数。
虚函数可参见陈皓大牛的这篇博客http://blog.csdn.net/haoel/article/details/1948051/ 介绍的很详细。
一.重载
先说一个c中的宏,__V_ARGS__,是c99引入进来的可变参宏,一般是用来输出debug信息。
举个例子:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define Check(...) printf(__VA_ARGS__);
#define Che(x) printf(x);
int main(int argc, char *argv[])
{
int i = 1, j = 2;
char *Error = "error!!";
Check("i = %d
", j);
Check("i = %d, j = %d
", i, j);
Check("i = %d, j = %d, Error:%s
", i, j, Error);
return EXIT_SUCCESS;
}
有了这个宏__VA_ARGS__我们就可以实现简单的多态了。不太理解这个宏可以查下
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define func(...) myfunc((struct mystru) { __VA_ARGS__})
struct mystru
{
const char *name;
double d;
int number;
};
void myfunc(struct mystru ms)
{
printf("%s, %lf, %d
", ms.name, ms.d, ms.number);
}
int main(int argc, char *argv[])
{
func();
func("hello", 1.1);
func("hello");
func("hello", 1.1, 100);
func(.name = "hello");
func(.d = 1.1);
func(.number = 100);
func(.d = 1.1, .name = "hello");
return EXIT_SUCCESS;
}
给func传递了个数不同且类型不同的参数,func都可以执行,是不是感觉很神奇。
还有需要注意下在没有指定参数是结构体的哪个类型时我们必须按顺序传递参数
func("hello", 1.1);
func("hello");
func("hello", 1.1, 100);
指定结构体中的类型了就可以任意传递参数,不用在乎顺序
func(.name = "hello");
func(.d = 1.1);
func(.number = 100);
func(.d = 1.1, .name = "hello");
这个比较局限,要想实现不同的功能必须在函数里面判断传递了几个参数。然后选择不同的功能。
顺便补充下,我们都见过printf的函数原型,extern void printf(const char *format,...);
注意到"..."这个可变参东西了吧,printf的“...”内部实际上是通过三个函数来实现的,感兴趣戳这里http://zh.wikipedia.org/wiki/%E5%8F%AF%E8%AE%8A%E5%8F%83%E6%95%B8%E5%87%BD%E6%95%B8
现在来看看代码中的关键句
#define func(...) myfunc((struct mystru) { __VA_ARGS__})</span>
解释下:一般我们在使用结构体的时候都是先赋值在传参数,这里实际上是用"..."可变参数替换为了__VA_ARGS__这个宏,然后转化为(struct mystru){ __VA_ARGS__ },其实就是
(struct mystru){ ... } 。 通过(struct mystru)把参数转化结构体,这也是为什么如果我们不指定.name或者是.number时,必须按顺序传递参数,否则报错,因为在内存中结构体布局是确定的。不明确指定哪个参数只能按顺序传递。
二.重写(覆盖)
c++中的(impure virtual)虚函数我们可以使用默认提供的功能,也可以自己实现一份。
了解虚函数的都知道,c++中只要有虚函数存在,内存中就会为它维护一个虚函数表,我们在运行过程中定义父类(基类)的指针,运行时可以使用子类实际的成员函数,
也就是父类具有了多种“形态”,所谓多态。
详情可参见上面推荐的文章。
如果我们自己实现的话,一定会用到指针->函数指针,因为指针是c的精髓,函数指针是个非常强大的东西,许多功能都可以用函数指针来实现。
我以前的文章说过c++11中的std::function,产生一个函数对象,可以把任何形似函数指针的传递给它,比如函数对象,lambda表达式等,我猜想这些内部应该使用函数指针实现的。
还有一点重要的c++和c的不同,c++中struct和class本质其实没有区别,区别仅仅是默认的“权限不同”,class是private,struct是public,sizeof(class)或者sizeof(struct)在c++中结果是1(用了一个char为了保证空类和空类之间在内存中不会有相同的地址),在c中sizeof(struct)是0,从内存上来看,c++的class和struct不仅仅有数据还有成员函数,这些成员函数如果是non-inline的,那么只会在内存中产生一份实例供所有对象使用,如果是inline的,会为每一个使用这产生一个实例。而c的struct就简单多了,它不占用内存空间,每一个实例按照struct分配一份就好,但这也是问题所在,它在内存中没实例啊。
那么用c的struct实现时,我们就要想办法让它在内存中存在一份,大家都能找到它。
下面简单谈自己的想法,仅仅是想法,不严谨,不一定对,有想法会在补充的,就天马行空一回^_^。
自己实现虚函数多态的话,肯定要有虚函数表,这个在c里面实现就是结构体+函数指针
一般继承,无虚函数覆盖。
如上图建立一个结构体,里面保存着一个指针,是指向第一个virtual成员函数,然后连接第二个,第三个直到到void,然后实例该结构体,目的是让它在内存
中存在一份实例,其他成员可以访问它,从而找到所需函数的位置。
一般继承,有虚函数覆盖。
如上图,再在内存中创建一份实例,然后修改指针域,是指向被覆盖后的新的函数的指针。
多重继承与一般继承类似。
重要的问题:如何让父类指针在运行时来判断是父类还是子类从而在虚函数表中调用对应的函数呢?c++可以判断类型,而c不行,需要好好考虑。
————————————————
版权声明:本文为CSDN博主「I_myours」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wwh578867817/article/details/45101033