首页
社区
课程
招聘
[原创]用C实现多态
发表于: 2014-3-16 17:07 10118

[原创]用C实现多态

2014-3-16 17:07
10118
用c实现多态

(253732597@qq.com tonybo@gmail.com )

大家对C++的多态不陌生吧,但是怎么用C来实现呢?最简单的办法就是自己先用C++写个简单的多态程序然后debug这个程序再看看反汇编再用C根据C++的实现思路再写一个就行了。

下面就是用C++写的一个简单多态程序,程序就不多解释了,代码如下:

#include<stdio.h>

class Shape{
public:
    virtual double area()=0;

};

class Circle:public Shape{
public:
    Circle(double r){
        radius=r;
    };
    double area(){
        return 3.14*radius*radius;
    };
private:
    double radius;
};

class Square:public Shape{
public:
    Square(double sq){
        s=sq;
    };
    double area(){
        return s*s;
    };
private:
    double s;

};
class Rectangle:public Shape{
public:
    Rectangle(double len,double wi){
        length=len;
        width=wi;
    };
    double area(){
        return length*width;
    };
private:
    double length;
    double width;
};
int main(){

    Shape *shape[3];
    Circle circle(10);
    Square square(10);
    Rectangle rectangle(10,20);
    shape[0]=&circle;
    shape[1]=□
    shape[2]=&rectangle;
    for(int i=0;i<3;i++){
        printf("%f\n",shape[i]->area());
    }

}

然后用g++ -g -o shapecpp shape.cpp
编译连接 然后运行 shapecpp 结果是:314,100,200.

当然了如果你用VS2010那更方便啦。

回到前面的问题,怎么用C写个和这个差不多的了?
那没办法了,直接调试吧,看看别人怎么实现的。
用GDB载入,然后设置GDB的C++显示设置,来看看shape里面到底有啥:

可以清楚的看到shape里面保存了其派生类型成员以及vptr table。
既然清楚根类里面有什么了,那么我们就可以用C来实现这个多态啦。
用c写的shape如下:
struct Shape{
    struct Circle cir;
    struct Square squ;
    struct Rectangle rec;
    double (*pf_shape_area[3])(struct Shape);
};
也就是把你派生的对象嵌套在根类里面然后再加上vptr就可以啦!
这就相当于一个表了,用C来实现这个表就很容易啦!完整代码如下:

#include<stdio.h>

struct Circle{
    double radius;
};

struct Square{
    double s;
};

struct Rectangle{
    double width;
    double length;
};

struct Shape{
    struct Circle cir;

    struct Square squ;

    struct Rectangle rec;

    double (*pf_shape_area[3])(struct Shape);
};

double Circle_area(struct Shape cir){
    return 3.14*cir.cir.radius*cir.cir.radius;
}

double Square_area(struct Shape squ){
    return squ.squ.s*squ.squ.s;
}

double Rectangle_area(struct Shape rec){
    return rec.rec.length*rec.rec.width;
}

int main(){
   
    struct Circle cir={10};
    struct Square squ={10};
    struct Rectangle rec={10,20};

    struct Shape shape[3];
    shape[0].cir=cir;
    shape[0].pf_shape_area[0]=Circle_area;

    shape[1].squ=squ;
    shape[1].pf_shape_area[1]=Square_area;

    shape[2].rec=rec;
    shape[2].pf_shape_area[2]=Rectangle_area;

    int i;
    for(i=0;i<3;i++){
       printf("%f\n",shape[i].pf_shape_area[i](shape[i]));
    }

gcc -g -o shapec shape.c

这里是运行结果:

小结:
运行时多态是面向对象编程一个很重要的概念,其实和其他语言里面的接口是一样滴,java有抽象类和接口支持这种方式(不明白为什么要两种机制?),Go只有接口支持这种运行时多态。
但是C也能干净利索的实现了运行时多态,没有用什么高深的技巧和晦涩难懂的宏,一张表就搞定啦!所以我们真的要沉湎在那些设计模式当中么?在解决问题的时候要整这么复杂?
最后希望能加深你对运行时多态的理解,完。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 5
支持
分享
最新回复 (9)
雪    币: 285
活跃值: (113)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
本文的 openoffice 文档在 http://bbs.pediy.com/showthread.php?t=185650
2014-3-16 17:13
0
雪    币: 39
活跃值: (26)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
谢谢楼主,最近在看C++反汇编。嘿嘿。还没看到对象,努力中。。。
2014-3-16 22:52
0
雪    币: 132
活跃值: (40)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
//声明函数指针
typedef double (WINAPIV *CALC_AREA)(void*);

//------------基类-----------
//虚表
struct Shape_Vt
{
        //这里定义一系列的虚拟成员函数
        CALC_AREA fnArea;
}
//原型
struct Shape
{
        //基类虚表,将被子类虚表覆盖,以实现多态
        Shape_Vt*         pVt;
        //这个成员是自定义,来保存子类对象 在C++中肯定是不存在此类成员的
        void*                pObject;
}
//--------------------------

//-----------圆---------------
//每一个带虚拟成员(包括继承)都有自己的虚表

//虚表
struct Circle_Vt
{
        //首先要包含基类的虚表成员,索引和定义要和基类一致,但有不同的实现过程
        //本类的虚拟成员函数列于基类的虚拟成员函数后面
        CALC_AREA fnArea;
}
//原型
struct Circle
{
        //虚表指针 可同时被下一级的子类覆盖,来实现更丰富的多态
        Circle_Vt*                pVt;
        double                         radius;
}

//实现
double Circle_area(void* pObject)
{
        pCir        = (Circle*)pObject;
    return 3.14*pCir->radius*pCir->radius;
}
//---------------------------

//------------正方形----------
//虚表
struct Square_Vt
{
        CALC_AREA fnArea;
}
//原型
struct Square
{
        Square_Vt*                pVt;
        double                         s;
}
//实现
double Square_area(void* pObject)
{
    pS                = (Square*)pObject;
    return pS->s*pS->s;
}
//----------------------------

//------------长方形---------
//虚表
struct Rectangle_Vt
{
        CALC_AREA fnArea;
}
//原型
struct Rectangle{
        Rectangle_Vt*                pVt;
    double width;
    double length;
};
//实现
double Rectangle_area(void* pObject)
{
    pR                = (Rectangle*)pObject;
    return pR->length*pR->width;
}
//------------------------

int main()
{
    //声明3个基类
        Shape shape[3];
        //声明一个子类  里面的数据成员初始化省了 下同
        Circle        cir;
        //初始化虚表
        cir.pVt                                = &Circle_Vt;
        //初始化虚表元素,其实这个是在编译期就初始化好的应该
        Circle_Vt.fnArea        = Circle_area;
        //将基类的虚表覆盖
        shape[0].pVt                = cir.pVt;
        //设置对象
        shape[0].pObject        = (void*)○
       
        Square        square;
        square.pVt                        = &Square_Vt;
        Square_Vt.fnArea        = Square_area;
        shape[1].pVt                = square.pVt;
        shape[1].pObject        = (void*)□
       
        Rectangle rect;
        rect.pVt                        = &Rectangle_Vt;
        Rectangle_Vt.fnArea        = Rectangle_area;
        shape[2].pVt                = rect.pVt;
        shape[2].pObject        = (void*)▭
        //调用
        //虽然看起来和把shape转成Circle等对象指针调用更直接点
        //但事实上,内存确是这样操作
        //汇编代码大概是这样
        //mov eax,dword ptr ds:[ecx]   ;ecx指向Circle等对象 此时获得虚表指针 eax=shape.pVt;
        //mov edx,dword ptr ds:[eax]   ;通过这个虚表指针获取函数地址 edx=shape.pVt->fnArea
        //call edx                                           ;调用这个函数 shape.pVt->fnArea();
       
        //C的过程大概是这样
        //mov eax,dword ptr ds:[ecx]         ;eax=shape.pVt; ecx=shape
        //mov edx,dword ptr ds:[eax]         ;edx=shape.pVt->fnArea
        //push dword ptr ds:[ecx+4]                ; 即push shape.pObject  但因 为C没有对象的概念,所以此时要把对象作参数调用 这里默认
        //call edx                                                ;shape.pVt->fnArea(shape.pObject);
        //add esp,4
        //事实上C更多是这样调用  
        //push dword ptr ds:[ecx+4]
        //CALL 004XXXXXX
        //add esp,4
        for(int i=0;i<3;i++)
        {
                printf("%f\n",shape[i].pVt->fnArea(shape[i].pObject));
        }
        //以上可以看出,所谓的多态其实 就是虚表的替换,不同的虚表保存各类型的实现过程
       
        //C++在退出函数执行析构时 会把虚表写回来 然后调用相关的析构过程
        shape[0].pVt                = &Shape_Vt;
        shape[1].pVt                = &Shape_Vt;
        shape[2].pVt                = &Shape_Vt;
}
2014-3-17 03:20
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
如果你想要面向对象特性,请使用C++或者使用宏进行封装。后一种请参考http://www.codeproject.com/Articles/13601/COM-in-plain-C
2014-3-17 05:24
0
雪    币: 500
活跃值: (865)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
这个可以看看,感谢楼主
2014-3-17 10:36
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
好文,看看vfs的实现,这种写法到处都是。
2014-3-17 10:44
0
雪    币: 246
活跃值: (144)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
好厉害的样子  不懂
2014-3-17 13:23
0
雪    币: 285
活跃值: (113)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
谢谢luci的详细回复,我下次发这种帖子的时候尽量些详细点哈。
2014-3-18 12:22
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
感谢楼主开出这个题。luci的方法更详细。
2014-3-22 15:10
0
游客
登录 | 注册 方可回帖
返回
//