LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 8459|回复: 15

C语言中的面向对象思想

 关闭 [复制链接]
发表于 2004-12-25 08:30:33 | 显示全部楼层 |阅读模式
来源:CSDN
    经常听见别人说面向对象的程序设计,以前在学校上课的时候,也有开面向对象程序设计这门课。可是不幸的是,这些都是以C++,甚至VC++为基础的。而更加不幸的是,多年以来我一直是一个C的使用者。在学校的时候,我主要做的是硬件上的驱动层,和底层功能层。

  在工作以后,又做的是手机上的软件开发,所有这些都是和C离不开的。虽然我不得不说,C++是一门很好的语言,但是它的编译速度,代码效率,编译后的代码大小都限制了它在嵌入式上的应用。(但现在的嵌入式CPU越来越快,内存容量变大。
  
  我觉得用C++也应该没有什么问题。这使我觉得似乎是嵌入式编译器的限制。虽然菲利普和TI好像都有C++的编译器,但是似乎没人用这个。难道是太贵了? 但不管怎么说,嵌入式应用中,C语言的普遍使用是肯定的)

  那么在面向过程的时代产生的C语言能否使用面向对象的思想呢?我认为是肯定可以的,C++不过是在语言级别上加入了对对象的支持,同时提供了丰富的对象库。而在C语言下,我们只好自力更生了。
  
  一、 面向对象思想的目的是框架化,手段是抽象
  
  相信很多人都明白面向对象讲了什么:类,抽象类,继承,多态。但是是什么原因促使这些概念的产生呢?
  
  打个比方说:你去买显示器,然而显示器的品牌样式是多种多样的,你在买的过程中发生的事情也是不可预测的。对于这样的事情,我们在程序语言中如何去描述呢。面向对象的思想就是为了解决这样的问题。编写一个程序(甚至说是一个工程),从无到用是困难的,从有到丰富是更加困难的。面向对象将程序的各个行为化为对象,而又用抽象的办法将这些对象归类(抽象),从而将错综复杂的事情简化为几个主要的有机组合(框架化)。
  
  其实我们的身边很多东西都是这样组成的:比如说电脑:电脑是由主板,CPU加上各种卡组成的。这就是一个框架化。而忽略不同的CPU,不同的主板,不同的声卡,网卡,显卡的区别,这就是抽象。再比如说现在的教育网:是由主核心节点:清华,北大,北邮等几个,然后是各个子节点,依次组成了整个教育网网络。
  
  所以我觉得面向对象的编程思想就是:一个大型工程是分层次结构的,每层又由抽象的结构连接为整体(框架化),各个抽象结构之间是彼此独立的,可以独立进化(继承,多态)。层次之间,结构之间各有统一的通讯方式(通常是消息,事件机制)。
  
  二、以前C语言编程中常用的“面向对象”方法
  
  其实C语言诞生以来,人们就想了很多办法来体现“面向对象”的思想。下面就来说说我所知道的方法。先说一些大家熟悉的东东,慢慢再讲诡异的。呵呵
  
  1.宏定义:
   有的人不禁要问,宏定义怎么扯到这里来了,我们可以先看一个简单的例子:
  #define MacroFunction Afunction
  然后在程序里面你调用了大量的AFunction,但是有一天,你突然发现你要用BFunction了,(不过AFunction又不能不要,很有可能你以后还要调用),这个时候,你就可以#define MacroFunction Bfunction来达到这样的目的。
   当然,不得不说这样的办法是too simple,sometime na?ve的,因为一个很滑稽的问题是如果我一般要改为BFunction,一半不变怎么办? 那就只好查找替换了。
  
2.静态的入口函数,保证函数名相同,利用标志位调用子函数:
   这样的典型应用很多,比如说网卡驱动里面有一个入口函数Nilan(int FunctionCode,Para*)。具体的参数是什么记不清楚了。不过NiLan的主体是这样的:
  Long Nilan(int FunctionCode,Para*){
  
  Switch(FunctionCode){
  
   Case SendPacket: send(….)
  
   Case ReceivePacket: receive(…)
  
   …..
  }
  
  写到这里大家明白什么意思了吧。保证相同的函数名就是说:网卡驱动是和pNA+协议栈互连的,那么如何保证pNA+协议栈和不同的驱动都兼容呢,一个简单的办法就是仅仅使用一个入口函数。通过改变如果函数的参数值,来调用内部的各个函数。这样的做法是可以进化的:如果以后想调用新的函数,增加相应的函数参数值就好了。如果我们将网卡驱动和pNA+协议栈看作两个层的话,我们可以发现:
  
  层与层之间的互连接口是很小的(这里是一个入口函数),一般是采用名字解析的办法而不是具体的函数调用(利用FunctionCode调用函数,Nilan仅仅实现名字解析的功能)――!接口限制和名字解析
  
  接口限制:层与层之间仅仅知道有限的函数
  
  名字解析:层与层之间建立共同的名字与函数的对应关系,之间利用名字调用功能。
 楼主| 发表于 2004-12-25 08:31:24 | 显示全部楼层
3.CALLBACK函数。
  
  我觉得这是C语言的一个创举,虽然它很简单,就象如何把鸡蛋竖起来一样,但是你如果没想到的话,嘿嘿。如果说静态入口函数实现了一个可管理的宏观的话, CallBack就是实现了一个可进化的微观:它使得一个函数可以在不重新编译的情况下实现功能的添加!但是在最最早期的时候,也有蛮多人持反对态度,因为它用了函数指针。

  函数指针虽然灵活,但是由于它要访问内存两次才可以调用到函数,第一次访问函数指针,第二次才是真正的函数调用。它的效率是不如普通函数的。但是在一个不太苛刻的环境下,函数调用本身就不怎么耗时,函数指针的性能又不是特别糟糕,使用函数指针其实是一个最好的选择。但是函数指针除了性能,最麻烦的地方就是会导致程序的“支离破碎”。试想:在程序中,你读到一个函数指针的时候,如果你愣是不知道这个函数指针指向的是哪个函数,那个感觉真的很糟糕。(可以看后面的文章,要使用先进的程序框架,避免这样的情况)
  
  三、Event和Message
  
  看了上面的描述,相信大家多少有些明白为什么要使用Event和Message了。具体的函数调用会带来很多的问题(虽然从效率上讲,这样做是很好的)。为了提高程序的灵活性,Event和Message的办法产生了。用名字解析的办法代替通常的函数调用,这样,如果双方对这样的解析是一致的话,就可以达到一个统一。不过Event和Message的作用还不仅仅是如此。
  
  Event和Message还有建立进程间通信的功能。进程将自己的消息发给“控制中心”(简单的就是一个消息队列,和一个while循环不断的取消息队列的内容并执行),控制程序得到消息,分发给相应的进程,这样其他进程就可以得到这个消息并进行响应。
  
  Event和Message是很灵活的,因为你可以随时添加或者关闭一个进程,(仅仅需要添加分发消息的列表就可以了)Event和Message从程序实现上将我觉得是一样的,只不过概念不同。Event多用于指一个动作,比如硬件发生了什么事情,需要调用一个什么函数等等。Message多用于指一个指示,比如什么程序发生了什么操作命令等等。
  
  四、小结
  
  其实编程序和写文章一样,都是先有一个提纲,然后慢慢的丰富。先抽象化得到程序的骨架,然后再考虑各个方面的其他内容:程序极端的时候会发生什么问题?程序的这个地方的功能现在还不完善,以后再完善会有什么问题?程序是不是可以扩展的?
  
  本系列文章是我这些阶段的一些心得,目的是抛砖引玉,希望能和大家交流,得到更多的知识。Liyuming1978@163.com (这个信箱以前发了一个文章 C优化之路,现在都快成垃圾信箱了,呵呵,网络的力量真是强大呀)
发表于 2004-12-25 09:59:28 | 显示全部楼层
我对面向对象的理解很简单:整个世界由物体(对象,Objects)组成,物体是怎么构成的?其实就是物体内部的数据结构和操作物体的方法(Method)。面向对象程序设计最重要的就是隐藏物体内部的数据结构,其他物体只能通过指定(well defined)的方法来操作这个物体。

只要明白了这个,你就会发现用C来实现面向对象程序设计的例子比比皆是。例如文件指针和文件描述符,你根本不知道文件对象的内部结构,只能通过open, read, close, fopen, fread, fclose去操作文件。编过Xlib和Win32 SDK程序的都知道窗口(Window)对象的内部结构是包装起来的,只能通过指定的函数来操作这些窗口对象。

至于抽象类,继承,多态这些东西,都是把面向对象概念再拔高而产生的东东,其核心的东西还是离不开对象和操作对象的方法。Linux内核的VFS的实现体现了这些继承,多态这些概念,核心的东西其实还是对象和操作对象的方法。

所以实现面向对象程序设计并不在乎你用什么语言,汇编语言一样可以实现。只是C++,Java可能让你写起面向对象程序程序来更方便一些而已。就我的经验而言,我倒是更喜欢用C来写面向对象程序,因为信息隐藏更彻底。例如下面的例子,internal.h定义了一个对象内部实现的细节和操作方法,这个header文件是要使用这个对象编成的外部程序员所看不见的,外部程序员看到的只是external.h。如果用C++,internal.h往往和external.h就是一样的,但是mystruct里的a, b, c都会被定义为private变量,以使外部程序员不能直接访问到它们。但是mystruct的结构外部程序员还是知道的。外部程序员其实可以使用offsetof宏来访问这些private变量,绕过C++编译器所设置的限制。

internal.h
typedef struct {
   int a, b, c;
   ...
} mystruct;
mystruct* alloc_myobj ();
int op_myobj (mystruct*);
void free_myobj (mystruct*);

external.h
typedef void*    objid;
objid alloc_myobj ();
int op_myobj (objid);
void free_myobj (objid);
 楼主| 发表于 2004-12-25 11:30:38 | 显示全部楼层
面向对象最重要的其实是信息隐藏和接口。然后在这个基础上,你能玩出什么花样就看你的能耐了。
发表于 2004-12-25 17:28:27 | 显示全部楼层
 楼主| 发表于 2004-12-26 11:09:37 | 显示全部楼层

你看过这本书啦,觉得怎么样?
发表于 2004-12-26 19:25:01 | 显示全部楼层
Post by kj501
你看过这本书啦,觉得怎么样?


我刚看了两章,感觉书上的代码无法令我满意。比如,第一章里面的 new 函数使用了一个固定数组 heap 来维护已分配的对象的地址,而且 add 中加入对这些地址的验证,这种方法我是不太喜欢(我宁愿用一个从堆中分配的可变大小的数组)。不过,书中的想法我会仔细去体会。

用 C 实现面向对象的编程实在是不如 C++ 自然,然而对于像我这样对系统编程和编译器设计比较感兴趣的人来说,用 C 的频率比 C++ 高(这也是我现在还不会 C++ 的原因),所以我觉得这本书确实是非常好的。
发表于 2005-1-4 10:46:59 | 显示全部楼层
整个gtk不就是用c语言来实现面向对象的思想吗
发表于 2006-3-11 12:08:10 | 显示全部楼层
其实用c的struct可以模拟类实现对数据的封装,只是没有构造函数/虚函数/重载吧。
模拟类和一系列对其进行操作的函数(内部和外部)组成对象库。
然后用类型定义和api函数接口为库外部使用对象提供服务。
这就是我对c中实现面向对象实质的理解,不知道对不对。。
其实也可以对函数进行封装,但是我觉得函数指针用的太多开销上不化算:P
回复 支持 反对

使用道具 举报

发表于 2006-3-17 21:45:46 | 显示全部楼层
用C来OO有点累的。。。。。。。。。。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表