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

在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;
};
通过强制类型转换,就可以使用通用函数了。用这种方法可以实现链表的创建、插入、删除等操作的通用函数。如果要在链表中查找指定的节点呢?运用函数指针将判别函数传入通用函数,这样查找也实现了。

在C语言中实现面向对象(2)的更多相关文章

  1. Java语言中的面向对象特性总结

    Java语言中的面向对象特性 (总结得不错) [课前思考]  1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类?  2. 面向对象编程的特性有哪三个?它们各自又有哪些特性?  3. 你知 ...

  2. Golang 入门系列(五)GO语言中的面向对象

    前面讲了很多Go 语言的基础知识,包括go环境的安装,go语言的语法等,感兴趣的朋友可以先看看之前的文章.https://www.cnblogs.com/zhangweizhong/category/ ...

  3. Java语言中的面向对象特性:封装、继承、多态,面向对象的基本思想(总结得不错)

    Java语言中的面向对象特性(总结得不错) [课前思考] 1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类? 2. 面向对象编程的特性有哪三个?它们各自又有哪些特性? 3. 你知道jav ...

  4. Go语言中的面向对象

    前言 如果说最纯粹的面向对象语言,我觉得是Java无疑.而且Java语言的面向对象也是很直观,很容易理解的.class是基础,其他都是要写在class里的. 最近学习了Go语言,有了一些对比和思考.虽 ...

  5. Java语言中的面向对象特性

    面向对象的基本特征 1.封装性 封装性就是把对象的属性和服务结合成一个独立的相同单位,并尽可能隐蔽对象的内部细节,包含两个含义: ◇ 把对象的全部属性和全部服务结合在一起,形成一个不可分割的独立单位( ...

  6. 怎样在C语言里实现“面向对象编程”

    有人觉得面向对象是C++/Java这样的高级语言的专利,实际不是这样.面向对象作为一种设计方法.是不限制语言的.仅仅能说,用C++/Java这样的语法来实现面向对象会更easy.更自然一些. 在本节中 ...

  7. Golang 中的 面向对象: 方法, 类, 方法继承, 接口, 多态的简单描述与实现

    前言: Golang 相似与C语言, 基础语法与C基本一致,除了广受争议的 左花括号 必须与代码同行的问题, 别的基本差不多; 学会了C, 基本上万变不离其宗, 现在的高级语言身上都能看到C的影子; ...

  8. Cocos2d-x 脚本语言Lua中的面向对象

    Cocos2d-x 脚本语言Lua中的面向对象 面向对象不是针对某一门语言,而是一种思想.在面向过程的语言也能够使用面向对象的思想来进行编程. 在Lua中,并没有面向对象的概念存在,没有类的定义和子类 ...

  9. 简单分析JavaScript中的面向对象

    初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...

随机推荐

  1. python标准库介绍——4 string模块详解

    ==string 模块== ``string`` 模块提供了一些用于处理字符串类型的函数, 如 [Example 1-51 #eg-1-51] 所示. ====Example 1-51. 使用 str ...

  2. jenkins 执行远程linux命令

    在Jenkins中进行构建时,可能需要首先SSH登录到一个远程服务器以执行必要的脚本,然后再执行构建.这时,需要安装SSH Plugin,并进行如下配置.1.在Jenkins界面,系统管理->管 ...

  3. 免费申请 Github 私有仓库--学生和教育人士的福利

    免费申请 Github 私有仓库 -学生和教育人士的福利 Github 是全球知名的软件项目托管网站.在 Github 创建私有仓库是需要收费的,收费方案有多种,费用最小的方案是每月 7 美元的“微型 ...

  4. ThreadLocal 类说明

    1 ThreadLocal 不是一个线程,而是保存线程本地化对象的容器.当运行于多线程环境的某个对象使用 ThreadLocal 维护变量时,ThreadLocal 为每一个使用该变量的线程分配一个独 ...

  5. 网络编程之TCP/UDP及其流程比较(转)

    TCP与UDP的区别 基于连接与无连接 对系统资源的要求(TCP较多,UDP少) UDP程序结构较简单 流模式与数据报模式TCP保证数据正确性,UDP可能丢包TCP保证数据顺序,UDP不保证 具体编程 ...

  6. 大数问题:求n的阶乘

    题目:求100! 这看起来是一个非常简答的问题,递归解之毫无压力 int func(int n){ if(n <= 1) return 1; else return n*func(n-1); } ...

  7. 给openvpn客户分配固定ip地址

    虽然openvpn提供dhcp服务,但是dhcp是有租约的,到期后会重新分配ip,造成连接中断的问题,所以最好还是给客户端固定一个ip. 思路:开启客户端配置目录,然后为每一个客户建一个配置文件,里面 ...

  8. lattice diamond 3.7安装破解

    第一步安装:执行.EXE文件,一直下一步,最后license选择没有USB什么的那个(具体记不清了). 第二步破解:安装完成后在环境变量中将license路径指定到license文件即可(LM_LIC ...

  9. Progressive Web App

    下一代 Web 应用? 近年来,Web 应用在整个软件与互联网行业承载的责任越来越重,软件复杂度和维护成本越来越高,Web 技术,尤其是 Web 客户端技术,迎来了爆发式的发展. 包括但不限于基于 N ...

  10. 用一个for循环实现打印乘法口诀表

    本博客不再更新,很多其它精彩内容请訪问我的独立博客 循环体内定义两个变量.一个控制列,一个控制行. for(int i=0,j=0;j<9;i++){ System.out.println(j+ ...