用C语言模拟面向对象编程(转)

来源:互联网 发布:孙鑫的java视频教程 编辑:程序博客网 时间:2024/06/03 10:04

用C语言模拟面向对象编程

  虽然我接触计算机已经有将近一年了,但一直以来我不愿意写技术方面的文档,因为所谓的“技术”就是照着做得东西, 即使可能遇到一些难题,到网上搜索一下就可以解决,不值得把网上长篇累牍的文档复制粘贴到这里来。即使是自己写,也是写些别人已经解决过的东西。所以不论是数学,还是计算机,在这里我没有写过那种简单搬运知识的文章。
  但即使再纯粹的技术,弄得时间长了都会产生一些独特的想法和感受,记录这些想法可能不光对自己是有意义的。今天来写第一篇偏向技术的东西。

  如何在C语言中用面向对象的思路写程序?这已经是很多人考虑过的问题了,而且实际上已经有人在用C语言实现一些面向对象的项目了,比如,在Linux下大名鼎鼎的图形界面GNOME,就是通过C语言模拟的面向对象特性来实现的。
  在一本名叫《面向对象编程,C++和Java比较教程》的书中有一个抛砖引玉的例子,代码如下(见该书中文版第67页,其中注释块中是自己的评注):

/* SimulatedOO.c */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* 基类型: */
typedef struct
{
        char * name;
        int age;
} User;

/* 子类型: */
typedef struct
{
        User genericUser;
        char ** listOfCourses;
        int numCourses;
        int whatYear;
} StudentUser;
/********************************************************************
* 这样, 在子类型 StudentUser 中就有一个基类 User 的结构体
* 变量, 并且在内存中 User 结构体占据 StudentUser 的开头部分,
* 这样,把一个指向 StudentUser 类型的指针强制类型转换到 User*
* 类型,就可以访问到 User 的相应区域,这样就实现了继承机制。
********************************************************************/

/* 为基类型User定义的函数: */
void setUserName( User* user,char aName[] ) {
        user -> name = malloc(strlen(aName)+1);/* 原文为 malloc( sizeof(strlen(aName)+1) ), 这是不正确的。*/
        strcpy(user->name,aName);
}
/* 为基类型User定义的函数: */
char * getUserName( User * user) {
        printf("/nName of user: %s/n",user->name);
        return user->name;
}
/* 为基类型User定义的函数: */
void setUserAge(User * user,int yy) {
        user -> age=yy;
}
/* 为基类型User定义的函数: */
int getUserAge( User * user ) {
        printf("%s's age: %d/n",user->name,user->age);
        return user-> age;
}

/* 为基类型User定义的函数: */
/***************************************************************
* 这个函数在书中是用来展示多态行为的,不过这种实现多态的
* 方式并不是很好,并不能实现类似于C++或Java中多态的动态连
* 接机制。具体用法见主程序中注释为“多态行为” 那两行。
***************************************************************/
int isSenior( User * user ) {
        if ( user->age > 70 ) return 1;
        else return 0;
}

/* 为子类型 StudentUser 定义的函数: */
void setListOfCourses( StudentUser* student,char* listCrs[],int nCourses) {
        int i;
        char ** temp;
        student->numCourses = nCourses;
        temp = malloc( nCourses * sizeof( char* ));
        student->listOfCourses=temp;
        for(i=0;i < student->numCourses;i++) {
                *temp = malloc( strlen( *listCrs )+1);
                strcpy(*temp,*listCrs);
                *temp++;
                listCrs++;
        }
}

/* 为子类型 StudentUser 定义的函数: */
void printListOfCourses( StudentUser * student ) {
        int i;
        char ** temp;
        temp = student->listOfCourses;

        printf("/n%s's courses: /n",student->genericUser.name);
        for(i=0;i < student->numCourses;i++)
                printf("%s/n",*(temp++));
}

/* 为子类型 StudentUser 定义的函数: */
void setYear( StudentUser* student,int yy) {
        student->whatYear=yy;
}

/* 为子类型 StudentUser 定义的函数: */
int getYear( StudentUser* student) {
        return student->whatYear;
}

int main()
{
        User * zaphod;
        StudentUser* trillian;
        char * listCourses[]={"Physics","Chemistry","algebra"};
        int numCrs = sizeof(listCourses)/sizeof(listCourses[0]);
        zaphod = malloc(sizeof(User));
        setUserName(zaphod,"Zaphod");
        setUserAge(zaphod,89);
        getUserName(zaphod);
        getUserAge(zaphod);
        trillian=malloc(sizeof(StudentUser));
        setUserName((User*)trillian,"Trillian" );
        setUserAge((User*)trillian,18);
        getUserName((User*)trillian);
        getUserAge((User*)trillian);
        setListOfCourses(trillian,listCourses,numCrs);
        printListOfCourses(trillian);

        /*  多态行为 */
        printf("/nZaphod is senior is %s/n",isSenior(zaphod)? "true":"false");
        printf("/nTrillian is senior is %s/n", isSenior((User*)trillian)? "true":"false");
        /*****************************************************
         * 和setUserAge 、setUserName之类的函数一样,isSenior
         * 需要一个基类指针参数,通过将 trillian 转换为 User*
         * 类型,就会把指向trillian 中User域的指针传进函数。
         * ***************************************************/

        printf("name field of trillian is at address: %p/n", &( trillian->genericUser.name ));
        printf("trillian when cast to User* is at address: %p/n",(User*)trillian );
        /***************************************************
         * 这两行输出的地址值是一样的。
         * *************************************************/
}

  在C++和Java中,多态行为是由一种动态连接机实现的,比如,在C++中定义如下的类 Base 和它的子类 Sub:

class Base {
        int data;
public:
        Base() : data(3) {}
        virtual int getData() const {
                return data;
        }
};

class Sub:public Base {
        int data;
public:
        Sub() : data(5) {}
        int getData() const {
                return data;
        }
};

  那么如果有一个Base 类型的指针指向了一个Sub类,通过这个指针调用getData()时将返回子类Sub中的data:初始值5。这样,如果有一个储存基类型指针的数组,但这些指针有的指向基类,有的指向子类,那么我就可以通过指针统一地调用 getData() 函数,依然能够得到正确的值。
  怎么在C中也实现类似的功能呢?

 要想根据基类的指针正确地选择应该调用的函数,一个合适的备选方案是用函数指针,即在基类的结构中定义一个函数指针,这个函数指针的值将根据具体对象的类别设置,比如上面的C++代码可以用C写成这样:

struct Base {
        int data;
        int (*getData)( struct Base * );
};

struct Sub {
        struct Base base;
        int data;
};

  这样,如果有一个struct Base 型的指针 base,通过 base->getData(base) 就可以得到正确的值,这样就实现了我们刚才的目的。但是如果有一个真正的 struct Sub 型的指针 sub,要想通过 sub 来调用正确的 getData,则至少要经过两次强制类型转换(如果不想让编译器发出警告的话)。这在写代码时是比较麻烦的。我们可以在sub中也添加一个函数指针,它 指向专门为 struct Sub 写的函数,这样就可以解决这种不便之处:

struct Base {
        int data;
        int (*getData)( struct Base * );
};

struct Sub {
        struct Base base;
        int (*getData)( struct Sub * );
        int data;
};

  这样一来,我们需要适当地初始化这些指针,让它们指向合适的值。那么这种初始化的工作由谁来做呢?我们可以分别为两个类写初始化函数,类似于C++和Java中的构造函数,同时,在必要的时候我们也可以写出它们的析构函数用来释放内存空间。完整的例子如下:

#include<stdio.h>
#include<stdlib.h>
struct Base {
        int data;
        int (*getData)( struct Base * );
};

struct Sub {
        struct Base base;
        int (*getData)( struct Sub * );
        int data;
};

int getDataForBase( struct Base * base ) {
        return base->data;
}

int getDataForSubBase( struct Base * base ) {
        return ((struct Sub *)base)->data;
}
int getDataForSub( struct Sub * sub ) {
        /*这个函数和上面的函数只有参数类型不同。
         * 如果代码太长我们可以直接调用上面的函数。
         * 我们也可以省略这个函数而把 sub 中的函数指针
         * 设成和 Base 类相同,这样在调用时如果传递 sub
         * 指针,那么编译器会发出警告。*/
        return sub->data;
}

/* Base 的“构造函数” */
void Base_init( struct Base * base ) {
        base->data = 3;
        base->getData = getDataForBase;
}

/* Sub 的“构造函数” */
void Sub_init( struct Sub * sub ) {
        Base_init( (struct Base*)sub );        /* 在C++中,子类的构造方法默认将调用父类的无参数构造方法。*/
        ((struct Base*)sub)->getData = getDataForSubBase;/* 设置函数指针 */
        sub->getData = getDataForSub;        /* 设置函数指针 */
        sub->data = 5;
}

/* Base 和 Sub 的析构函数: */
void Base_destroy( struct Base * base) {}
void Sub_destroy( struct Sub * sub) {}

int main()
{
        struct Base * base = (struct Base*)malloc(sizeof(struct Base));
        Base_init(base);
        struct Sub * sub = (struct Sub*)malloc(sizeof(struct Sub));
        Sub_init(sub);
        struct Base * subbase = (struct Base*)sub;
        /*从下面的语句可以看出,不论是 Base 型的指针指向 Base 型,Base 型指针指向 Sub 型,还是 Sub 型指针指向 Sub 型,调用函数的格式都是统一的。*/
        printf( "%d/n%d/n%d/n", base->getData(base), sub->getData(sub), subbase->getData(subbase) );
        free(base);        /*适当地换成析构函数*/
        free(sub);        /*适当地换成析构函数*/
}

  这样实现动态连接的类显然就不能通过切割内存来实现类型转换了,如果试图把一个Sub类强行切割成Base类,那么得到的Base类中的函数指针就可能指向错误的函数。我们必须在切割之后重新设置Base中函数指针的值。

  讨论过这些之后,我们设想一下在C语言中可不可以实现数据结构和算法的通用函数。在以前学习C语言的时候,即使是简单的单链表操作,在一个程序中实现的操作函数也不能直接拿到另一个程序中使用,因为链表的节点结构不同。而现在,只要定义一个基本的节点模板:
struct listNode {
        struct listNode * next;
};
  我们就可以写出针对它的操作函数。而在使用时,我们定义一个继承它的类:
struct myListNode {
        struct listNode node;
        int data;
};
  通过强制类型转换,就可以使用通用函数了。用这种方法可以实现链表的创建、插入、删除等操作的通用函数。如果要在链表中查找指定的节点呢?运用函数指针将判别函数传入通用函数,这样查找也实现了。

原创粉丝点击