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

在面试 Java 程序员时我经常问的一个问题是:接口和抽象类有什么区别。

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

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

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

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

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

一个示例如下:

[cpp] view plaincopy print?
  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. struct derived {
  7. struct base_interface bi;
  8. int x;
  9. char ch;
  10. char *name;
  11. };
struct base_interface {void (*func1)(struct base_interface* b);void (*func2)(struct base_interface* b);int (*func_3)(struct base_interface* b, char * arg);
};struct derived {struct base_interface bi;int x;char ch;char *name;
};

上面是头文件,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 plaincopy print?
  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. /* _derived_func_2 impl */
  8. /* _derived_func_3 impl */
  9. struct derived *new_derived()
  10. {
  11. struct derived *d = malloc(sizeof(struct derived));
  12. d->bi.func_1 = _derived_func_1;
  13. d->bi.func_2 = _derived_func_2;
  14. d->bi.func_3 = _derived_func_3;
  15. d->x = 0;
  16. d->ch = 'a';
  17. d->name = NULL;
  18. return d;
  19. }
static void _derived_func_1(struct base_interface *bi)
{struct derived * d = (struct derived*)bi;d->x *= 2;printf("d->name = %s\n", d->name);
}/* _derived_func_2 impl */
/* _derived_func_3 impl */struct derived *new_derived()
{struct derived *d = malloc(sizeof(struct derived));d->bi.func_1 = _derived_func_1;d->bi.func_2 = _derived_func_2;d->bi.func_3 = _derived_func_3;d->x = 0;d->ch = 'a';d->name = NULL;return d;
}

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

[cpp] view plaincopy print?
  1. void do_something(struct base_interface *bi)
  2. {
  3. bi->func_1(bi);
  4. }
  5. int main(int argc, char **argv)
  6. {
  7. struct derived * d = new_derived();
  8. do_something((struct base_interface*)d);
  9. return 0;
  10. }
void do_something(struct base_interface *bi)
{bi->func_1(bi);
}int main(int argc, char **argv)
{struct derived * d = new_derived();do_something((struct base_interface*)d);return 0;
}

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

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

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

  • C语言面向对象编程(一):封装与继承
  • C语言面向对象编程(二):继承详解
  • C语言面向对象编程(三):虚函数与多态

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

C语言面向对象编程(四):面向接口编程相关推荐

  1. 为什么有人说面向对象编程就是面向接口编程?

    "面向对象编程就是面向接口编程" 这句话相信, 很多人都在网上见过, 装b利器. 我一开始也是这么想的, 那些装b者丢下这一句, 就没下文了. 首先, 我认为这句话是1个假命题. ...

  2. 从面向对象编程转为面向接口编程

    大家写过C++或者Java,或者其他语言,基本上都会接触到面向对象这个概念. 面向对象,本身是软件编程发展过程中的产物,当然相比于面向过程,是一种突破性的设计.但是,如果只是停留在面向对象编程,而不是 ...

  3. java 接口编程_JAVA面向接口编程

    一.什么是面向接口编程 要正确地使用Java语言进行面向对象的编程,从而提高程序的复用性,增加程序的可维护性.可扩展性,就必须是面向接口的编程.面向接口的编程就意味着:开发系统时,主体构架使用接口,接 ...

  4. Java 面向抽象编程和面向接口编程

    以下内容来自<Java 2实用教程>,主编:耿祥义.张跃平 鉴于面向抽象编程和面向接口编程思维培养的重要性,写此博客巩固. 面向抽象编程: 在设计程序时,经常会使用到abstract类,其 ...

  5. 【转】工厂模式面向接口编程

    为了实现更好的灵活性     应改面向接口编程.因此,应该面向接口提供工场.         比如,Cat,   Dog,   Mouse,都是4条腿会跑的动物.     因此,我们建立一个接口叫做F ...

  6. python面向接口编程_Python 中的面向接口编程

    前言 "面向接口编程"写 Java 的朋友耳朵已经可以听出干茧了吧,当然这个思想在 Java 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...

  7. python 接口编程_Python 中的面向接口编程

    前言 "面向接口编程"写 Java 的朋友耳朵已经可以听出干茧了吧,当然这个思想在 Java 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...

  8. 软件设计模式—面向接口编程

    原文作者:laoer2009 原文地址:设计模式之面向接口编程 01第一次需求 玩家有很多属性,例如:身高,性别 blalalala ,玩家可以攻击其他玩家.产品狗YY妹子写程序也是很利索,一天就把程 ...

  9. java继续_Java中消除实现继续和面向接口编程

    在匆忙之际理清消除实现继续和面向接口编程这样两个大题目可不是一件轻易的事情,尤其考虑到自身的熟悉水平.坦白的说,这又是一篇"炒冷饭"的文章,但这"冷饭"又确实不 ...

最新文章

  1. 关于自定义程序的效能优化
  2. [jQuery] jQuery函数
  3. 深入浅出 消息队列 ActiveMQ
  4. python os.environ windows_python 获取系统环境变量 os.environ and os.putenv
  5. Spring、SpringMVC和SpringBoot之间的关系
  6. 迷恋猫爆红,彰显区块链 NFT 巨大魔力!
  7. 面向 Android* Jelly Bean 的英特尔® 凌动™ x86 映像安装说明 — 推荐
  8. git的丰富实用经验
  9. 包邮送72本R语言和Python的书籍
  10. SEO—搜索引擎优化初探
  11. [学科总结] 《线性系统理论》
  12. EBS开发_创建销售人员API
  13. java 正则 中文_Java使用正则表达式(regex)匹配中文实例代码
  14. HuaWei ❀ Virtual Firewalld 虚拟防火墙
  15. sas入门-笔记2 SAS语言
  16. 爱因斯坦的宇宙(纪念爱因斯坦)
  17. Python 中 selenium 模块
  18. vue mixin传参
  19. 传统农业插上数字翅膀,种地变得更智慧
  20. 怎样把html成mp3,如何将OGG文件转换成MP3?

热门文章

  1. invalidate(true) 图形不显示_2D图像界面-ATI 图形界面卡
  2. 博图wincc连接数据块_西门子博途WINCC 可通过创建画面模板提高编程效率
  3. 内存分段分页机制理解_深入理解虚拟机,JVM高级特性-自动内存管理机制
  4. (私人收藏)[开发必备]HTML5最全快速查找离线手册(可查询可学习,带实例)...
  5. USB摄像头无法正常读取问题
  6. POJ--2488 A Knight's Journeyb
  7. js跨浏览器事件处理
  8. C++求解最大子序列和问题
  9. 浅入深出Vue:发布项目
  10. Normalization,Regularization 和 standardization