文档章节

C语言面向对象编程(四):面向接口编程

follitude
 follitude
发布于 2016/05/04 07:37
字数 1366
阅读 68
收藏 1

 Java 中有 interface 关键字,C++ 中有抽象类或纯虚类可以与 interface 比拟,C 语言中也可以实现类似的特性。

    接口和抽象类有什么区别?

    很多编程书籍也经常说要面向接口编程,我的理解是,接口强制派生类必须实现基类(接口)定义的契约,而抽象类则允许实现继承从而导致派生类可以不实现(重写)基类(接口)定义的契约。通常这不是问题,但在有一些特定的情况,看起来不那么合适。

    比如定义一个 Shape 基类,其中定义一个 draw() 方法,给一个什么都不做的默认实现(通常是空函数体),这实际没有任何意义。

    再比如基类改变某个方法的实现,而派生类采用实现继承并没有重写这个方法,此时可能会导致一些奇怪的问题。以鸟为例,基类为 Bird ,我们可能会定义一个 fly() 方法,一个 walk() 方法,因为有的人认为鸟既可以走又可以飞。开始时我们在 walk() 的实现里作了假定,认为双脚交叉前进才是 walk ,可是后来发现有些鸟是双脚一齐蹦的,不会交叉前进。这个时候怎么办?基类 Bird 的 walk() 方法是否要修改、如何修改?

    在 C++ 中,没有接口关键字 interface ,同时为了代码复用,经常采用实现继承。在 C 语言中,我们前面几篇文章讨论了封装、隐藏、继承、虚函数、多态等概念,虽然都可以实现,但使用起来总不如自带这些特性的语言(如 C++ 、Java )等得心应手。一旦你采用我们前面描述的方法来进行面向对象编程,就会发现,在 C 语言中正确的维护类层次是一件非常繁琐、容易出错的事情,而且要比面向对象的语言多写很多代码(这很容易理解,面向对象语言自带轮子,而 C 要自己造轮子,每实现一个类都要造一遍)。但有一点,当我们使用 C 语言作面向对象编程时,比 C++ 有明显的优势,那就是接口。

    接口强制派生类实现,这点在 C 中很容易做到。而且我们在编程中,实际上多数时候也不需要那么多的继承层次,一个接口类作为基类,一个实现类继承接口类,这基本就够了。在 C 语言中采用这种方式,可以不考虑析构函数、超过 3 层继承的上下类型转换、虚函数调用回溯、虚函数表装配等等问题,我们所要做的,就是实现基类接口,通过基类指针,就只能操作继承层次中最底层的那个类的对象;而基类接口,天生就是不能实例化的(其实是实例化了没办法使用,因为结构体的函数指针没人给它赋值)。

    一个示例如下:

[cpp] view plain copy

  1. struct base_interface {  

  2.     void (*func1)(struct base_interface* b);  

  3.     void (*func2)(struct base_interface* b);  

  4.     int (*func_3)(struct base_interface* b, char * arg);  

  5. };  

  6.   

  7. struct derived {  

  8.     struct base_interface bi;  

  9.     int x;  

  10.     char ch;  

  11.     char *name;  

  12. };  


    上面是头文件,derived 结构体通过包含 base_interface 类型的成员 bi 来达到继承的效果;而 base_interface 无法实例化,我们没有提供相应的构造函数,也没有提供与 func_1 , func_2 等函数指针对应的实现,即便有人 malloc 了一个 base_interface ,也无法使用。

    derived 类可以提供一个构造函数 new_derived ,同时在实现文件中提供 func_1 , func_2 ,func_3 的实现并将函数地址赋值给 bi 的成员,从而完成 derived 类的装配,实现 base_interface 定义的契约。

    示例实现如下:

[cpp] view plain copy

  1. static void _derived_func_1(struct base_interface *bi)  

  2. {  

  3.     struct derived * d = (struct derived*)bi;  

  4.     d->x *= 2;  

  5.     printf("d->name = %s\n", d->name);  

  6. }  

  7.   

  8. /* _derived_func_2 impl */  

  9. /* _derived_func_3 impl */  

  10.   

  11. struct derived *new_derived()  

  12. {  

  13.     struct derived *d = malloc(sizeof(struct derived));  

  14.     d->bi.func_1 = _derived_func_1;  

  15.     d->bi.func_2 = _derived_func_2;  

  16.     d->bi.func_3 = _derived_func_3;  

  17.     d->x = 0;  

  18.     d->ch = 'a';  

  19.     d->name = NULL;  

  20.   

  21.     return d;  

  22. }  


    我们可以这么使用 base_interface 接口:

[cpp] view plain copy

  1. void do_something(struct base_interface *bi)  

  2. {  

  3.     bi->func_1(bi);  

  4. }  

  5.   

  6. int main(int argc, char **argv)  

  7. {  

  8.     struct derived * d = new_derived();  

  9.     do_something((struct base_interface*)d);  

  10.   

  11.     return 0;  

  12. }  


    上面的代码中 do_something 函数完全按照接口编程,而 bi 可以实际指向任意一个实现了 base_interface 接口的类的实例,在一定程序上达到多态的效果,花费的代价相当小,却可以让我们的程序提高可扩展性,降低耦合。

    这种简单的方法也是我在自己的项目中使用的方法,效果不错。

    好啦,C 语言面向对象编程系列的基础性介绍就告一段落,下面是前几篇的链接,有兴趣的可以回头看看:

    接下来我会提供几个实作的例子,包括基本的数据结构,如单链表、树,还有一个 http server 的例子。


网友评论:

既然用到了malloc那应该也写个free吧?

谢谢提醒,实际使用时,free在别处。

本文转载自:http://blog.csdn.net/foruok/article/details/18465593

follitude
粉丝 6
博文 118
码字总数 4956
作品 0
浦东
私信 提问
2012 QCon:Go是互联网时代的C语言

【IT168 现场报道】由InfoQ主办的全球顶级技术盛会QCon 2012 于4月18日到4月20日在北京隆重召开,每年在伦敦、北京、东京、纽约、圣保罗、杭州、旧金山召开。本次大会上,上海七牛信息技术有...

it168网站
2012/04/18
0
0
OC1-OC面向对象新概念解释及其面向对象编程

一.C语言主要是面向过程,OC中开始大量使用面向对象。对于在以后的问题中有了面向对象和面向过程两种思考模式。就五子棋而言,面向过程是具体操作步骤,设计思路就是首先分析问题的步骤:1、...

小黑202
2016/07/15
7
0
为什么我们需要一门新语言——Go语言

编程语言已经非常多,偏性能敏感的编译型语言有 C、C++、Java、C#、Delphi和Objective-C等,偏快速业务开发的动态解析型语言有 PHP、Python、Perl、Ruby、JavaScript和Lua等,面向特定领域的...

生气的散人
2012/08/14
1K
6
开始学习函数式编程后对编程的一些新感受

我们绝大多数程序员都是用命令式编程,因为学校教的编程就是命令式的,什么C、Java等等(当然他们也可以用函数式编程)。函数式编程和命令式编程不是一个相对应层次的概念。理论上,函数式编...

无牙子
2014/03/31
346
1
1.7 Java类和对象的概念

Java是一门面向对象的编程语言,理解Java,首先要理解类与对象这两个概念。 Java中的类可以看做C语言中结构体的升级版。结构体是一种构造数据类型,可以包含不同的成员(变量),每个成员的数...

李序锴
2017/12/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
9分钟前
0
0
OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
737
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
40
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
26
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
79
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部