在C语言中实现面向对象(2)

2022年01月15日 阅读数:5
这篇文章主要向大家介绍在C语言中实现面向对象(2),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。


   C语言是结构化和模块化的语言,它是面向过程的。但它也能够模拟C++实现面向对象的功能。那么什么是对象呢?对象就是一个包含数据以及于这些数据有关的操做的集合,也就是包含数据成员和操做代码(即成员函数)。用C语言实现面向对象功能主要就是实现拟“类”的继承,函数的重载等操做,这些主要是经过结构体和指针函数实现的。node

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

class Base {数组

        int data;数据结构

public:数据结构和算法

        Base() : data(3) {}ide

        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;

};

经过强制类型转换,就可使用通用函数了。用这种方法能够实现链表的建立、插入、删除等操做的通用函数。若是要在链表中查找指定的节点呢?运用函数指针将判别函数传入通用函数,这样查找也实现了。


root


下一篇: kissthank