工学1号馆

home

C语言实现多态

Wu Yudong    June 27, 2015     C   676   

原创文章,转载请注明: 转载自工学1号馆

在上篇文章《C语言结构体中的函数指针》中,介绍了函数指针在结构体中的应用,这篇文章开始进一步的研究

这篇文章的目的:

1、巩固对面向对象机制的理解

2、巩固对C语言的理解

下面的概念摘自维基:

多态Polymorphism),是指面向对象程序运行时,相同的消息可能会送给多个不同的类之对象,而系统可依据对象所属类,引发对应类的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。在面向对象程序设计中,多态一般指子类型多态(Subtype polymorphism)。

如果你已经很久没有写C代码了,为了确保你的编译器还能工作,先运行下面的一个基础程序:

#include <stdio.h>
int main(){
     printf("Hello World");
     return 0;
}

ok,我们现在开始加快步伐

我们将要创建三个继承于抽象Shape类的类,但是C语言并没有类或继承的概念,但是有struct,下面就开始吧。

struct Square{
    int width;
    int height;
};

struct Circle{
    float radius;
};

struct Triangle{
    int base;
    int height;
};

没啥特别的,让我们更进一步的研究,将下面的代码放入main函数

printf("size of square is %d\n", sizeof(struct Square));
printf("size of Circle is %d\n", sizeof(struct Circle));
printf("size of Triangle is %d\n", sizeof(struct Triangle));

输出结果如下(不能担保,取决于平台):

size of cube is 8

size of circle is 4

size of triangle is 8

涉及到内存对齐问题可以参考《深入剖析C语言内存对齐

如果用struct来模拟类,那类中的方法呢,可以采用函数指针,C语言对待函数指针就像其他成员一样,

考虑下面的函数:

void print_square( void ){
    printf("Hello Square\n");
}

这个函数接受一个空的参数,返回也为空,意味着指向这个函数的指针是一个指向接受空参数返回空类型的函数,这里你可以声明这样的一个变量:

struct Square{
    int width;
    int height;
    //now for functions
    void (* print)( void );
    float (* area)( struct Square * this );
};

关于C语言变量声明的规则,可以参考名著《C陷阱与缺陷》

print是一个指向参数为空、返回值为空的函数的指针,现在我们需要给Square一个面积函数,接受自身为参数,并且返回一个float类型来表示面积。读取的时候和print函数类似。

指针是很强大的,但是必须指向有用的东西,但是你怎么知道一个函数在内存中的地址,好消息来了,函数名就表示它在内存中的地址,下面举一个例子:

struct Square square;
square.print = print_square;

函数指针的行为就像其他的成员一样,当我们创建一个Square类型的square,它的值包含垃圾值,我们需要手动地给它们赋一些正确的值。这样的工作需要一个构造器,C语言也没有,所以我们需要创建一个自己的构造函数。

void init_square( struct Square * square, int w, int h ){
    (*square).print = print_square;
    (*square).width = w;
    (*square).height = h;
    (*square).area = calc_square_area;
}

这个构造器仅仅是另一个函数,需要传递改变square的值的参数,于是,参数必须是一个指向Square的指针以及需要传递的值

下面来测试一下我们所做的工作(以square为例):

#include<stdio.h>
struct Shape{
    void (* print)( void );
    float (* area)( struct Shape * this );    
}; 

struct Square{
    float width;
    float height;
    void (* print)( void );
    float (* area)( struct Square * this );
}; 

void print_square( void ){
    printf("Hello Square\n");
} 

float calc_square_area(struct Square * this){
    return (*this).width * (*this).height;
} 

void init_square(struct Square * square, float w, float h ){
    (*square).width = w;
    (*square).height = h;
    (*square).print = print_square;
    (*square).area = calc_square_area;
} 

int main(void)
{
    struct Square square; 
    init_square( &square, 2.5, 2.6 );
    square.print(); 
    printf("the area of the square is %.2f\n", square.area(&square));
    return 0;
}

我们必须创建一个Shape结构作为其他三种形状的父类,需要创建Shape结构的逻辑结构:

//抽象父类
struct Shape{
    void (* print)( void );
    float (* area)( struct Shape * this );    
};

既然知道了从哪开始,接着就思考我们希望什么发生,假如你希望创建一个Square多次,初始化它

struct Square square;
init_square(&square, 2, 4);

如果希望打印它,调用函数:

square.print();

如果有一个指向Shape的指针试图打印square:

struct Shape * pointer = (struct Shape *)&square;
(*pointer).print();    //?? 将会发生什么??

将得到一个中断错误,结果不是期望的那样。我们期待的是指向Shape的指针对square进行调用

现在来看看内存相关,Shape和Square对比

Square:

width hight print area

4          4         4        4

共16B

Shape:

print area

4        4

共8B

print()函数在Shape的内存模型中是前4个字节,在Square的内存模型是第三个4字节的位置

square.print();

上面的代码使用第三个字节的指针来调用print函数,下面的代码期望完成一样的功能

(*pointer).print();

但是在最前4个字节没有指针,而是int类型的值,2个字节,但是我们的指针不会意识到是2个字节,于是找到内存的位置为2的位置(可能是一些BIOS驱动\操作系统内存地址),从而导致中断错误的产生

既然我们知道了原因,那么可以找到一个解决方案(不是最好),只有当Shape结构中的print函数的指针在第三个4字节的位置,那么可以填充Shape的前8个字节,这种方法就是C语言中的“填充”技术,代码如下:

struct Shape{
    char padd[8];
    void (* print)( void );
    float (* area)( struct Shape * this );    
};

修改后测试代码如下:

#include<stdio.h>
//abstract father class
/*
struct Shape{
    void (* print)( void );
    float (* area)( struct Shape * this );    
}; 
*/
struct Shape{
    char padd[8];
    void (* print)( void );
    float (* area)( struct Shape * this );    
}; 

struct Square{
    float width;
    float height;
    
    //now for functions
    void (* print)( void );
    float (* area)( struct Square * this );
}; 

void print_square( void ){
    printf("Hello Square\n");
} 

float calc_square_area(struct Square * this){
    return (*this).width * (*this).height;
} 

void init_square(struct Square * square, float w, float h ){
    (*square).width = w;
    (*square).height = h;
    (*square).print = print_square;
    (*square).area = calc_square_area;
} 

int main(void)
{
    struct Square square; 
    struct Shape *p = (struct Shape*)&square;
    init_square( &square, 2, 2 );
    //square.print(); 
    (*p).print();
    printf("the area of the square is %.2f\n", square.area(&square));
    return 0;
}

BTW:这篇文章使用C来实现OOP的多态性质,不是最好的方法,仅仅是作为一种实现,后面将进一步优化改进

参考资料:

http://www.codeproject.com/Articles/739687/Achieving-polymorphism-in-C

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)