一前言说到面向对象,我们似乎总会在第一时间想到Java、C++、Python。对,这些都是提供了面向对象支持的语言。但事实上,面向对象就是语言本身吗?稍有点编程经验的人都会有一个否定的答案。但为什么之前所述的那些语言能称之为面向对象的语言?如果语言本身并不是答案,那用非面向对象的语言,比如C,也应该能够写出面向对象的代码了。我并不是想发表什么高见,我只是想把最近看到了一些东西做一个比较系统的记录而已。同时也希望自己能够时时记住这些方式,写更漂亮的code。二面向对象什么是面向对象?似乎我还没有能力去下这个定义。是Class?Object?还是Interface?似乎都是。在我看来,也正是因为语言本身从语法的级别开始就支持这种概念,才使之有资格成为一种面向对象的编程语言。但当然这还不足以完全地定义一个面向对象的语言,因为这只是从封装的概念来描述面向对象的概念。那然后呢?然后应该是多态。重载是一个重要的方面,它使得同样的接口在不同的对象中出现不同的行为。这样的特性其实源于封装,不单只把对象的状态封装起来,还把对象的接口也包含在内。正是因为这种封装形式的存在,才使的基于接口的程序设计方式能够在这个时代大放异彩。这样的特性带来了两个好处:代码重用和解偶。首先,把代码以类的形式封装,使得代码能够以一种更具逻辑性的方式在不同的地方得到重用。函数本身也是一种重用的概念,但它本是跟它所需要处理的数据没有显著的语法层次上的关联,类则使得方法与数据有机地组合在一起。因此,在重用的概念上,类不但重用了逻辑,还重用了逻辑所处理的数据。因此在面向对象的领域中,代码得到了更大程度的重用。关于怎么封装代码,大家可以参考“敏捷”、“测试驱动”之类的书籍。另外,现代程序设计更倾向于写“羞涩的”代码,也就是说,把内部的逻辑私有化。但同时它还有另外一层意思,不单只是类内部的逻辑不应该被外部所了解,就算是类本身的逻辑也应该分割成相互独立的部分,也就是说把代码正交化。程序的逻辑并不是永恒不变的,一旦需求发生了变化,环境出现的修改,代码本身也需要作出响应。正交的代码能使得这些改动变得容易实现,并把修改范围最小化。很大程度上,这些方法有多源于封装的概念。关于怎么写“羞涩的”代码,大家可看看“设计模式”、“重构”一类的书籍。关于面向对象,应该会有更加全面描述,但我经验不多,阅历也浅,不好再为这种模糊的东西多费笔墨了。所以,此时此刻,我想面向对象的核心可能就是一个概念:封装。什么是封装?我们可以简单回顾一下历史。Onceuponatime。计算机科学的前辈们曾经用纸带来表示一条条指令,把它放到计算机里面,然后计算机就有了一些基于二进制的动作。比如说把地址线电平变成10100110,然后从数据线把10100110这个内存字节上的内容取出来。这个时候,我们可以认为这些指令其实没有任何形式的封装。(哦,可能还是有的,比如说内存芯片自己会自动把10100110上的内容放到数据总线上。)这个时候,每个动作都是一条指令,我们要从内存中读取数据,那起码需要两个动作:指定一个地址和读取一个数据。后来,时代不同了,我们有了汇编。这个时候,我们读取一个内存数据,我们只需要用一条指令,比如:movA,#0xa6。很好,我们把机器碼封装成了更加有意义的指令。我们还可以把一堆指令划分成不同的逻辑模块,然后通过一个call指令来调用。酷,我们还把一堆指令封装成函数了!但数据呢?似乎数据本身还没有任何意义上的抽象支持。好,C用什么办法帮我们组织数据呢?类型!好,我们看到了已经不是0xa6上的一串0101010了,起码我知道它应该代表一个字符char*或者是一个数字int。我还可以帮这些数据起名字!不仅如此,我们还有struct跟tepydef来定义新的类型!太酷了!于是,我们把程序的数据变得更加有意义了。可能就只是C被成为结构化的编程语言的原因吧。但人是不会轻易满足的。C好是好,但还不够好。第一,C的数据结构细节在语法上是无法隐藏的,这意味着任何人都可以访问,人们常常会因为便利的原因用一些跟这个数据结构关系不强的代码来访问结构的字段,造成了各种各样的模块耦合。第二,每一个功能类似,操作的数据相近的函数都需要重新定义,代码可以用函数的方式重用,但接口确用重新定义,导致调用者也要做相应修改,基于接口的解偶没有办法很好地操作。这都导致了用C写程序常常有牵一发而动全身的感觉。好,到此,我已经把我想说的关于面向对象的一些背景说完了,不知道对不对,但不论怎样,我们先来看看有此得到的一些面向对象的原则:1.面向对象需要封装。数据结构内部成员不能任意访问。2.要有多态,接口不应该改变。3.要能继承,要让代码更具正交性。4.最根本的,还是封装。不但要封装数据,还要封装行为。胡言乱语了一些关于面向对象的理解,可能并不正确,大家见笑了。三C语言的面向对象方式正如之前所说,面向对象应该是一些列的编程原则,所以就算用非面向对象的语言,也一样能写出面向对象的代码。C本身并没有在语法层次上对面向对象的原则进行支持,但运用一些技巧之后,C程序也一样可以变得很OO(objectoriented)。GObject是我所见过的最完整的C语言面向对象框架,只要按照他所规定的原则编程,从封装到多态,甚至是对象初始化,这个过程都变得自动化。但事实上,这个框架引入了较多复杂性,用起来有时候相当繁琐(之所以引入这些复杂性,设计者的目的是要向其他动态语言导出API)。如果我们把对象初始化的部分忽略,这个框架就简洁多了。但下文描述的可能更加简炼,只是以小case的形式吧一些技巧(习惯)描述出来而已。1.class与structC里面没有class,而跟class最像的莫过于struct。这两个关键字都可以用来封装数据,但稍有不同。struct可以把不同的字段封装在一起,通过跟typedef一起使用,可以用来定义新的数据类型。但它的缺陷也显而易见:没有访问控制。事实上,这样一个问题可以通过命名来解决,这是Linux内核DeviceDriverModel的处理方式。用__(双下划线)开头变量代表是private的,存在protected与否似乎并不太关键,毕竟我们不是真的能够支持承继,当然你也可以用“_“(单下划线)来前缀你的protected成员。其他命名方式建立的变量则是public的:structdemo{char*name;//publicmemberint__age;//privatememberint__salary;//privatemember}还有一种更加彻底的方式:structprivate_demo{int__age;int__salary;}structdemo{structprivate_demopriv;//privatememberchar*name;//publicmember}但我总觉得第二种方式有点过火了,细节是被隐藏了,但没有语法上的支持跟编译器的检查,其实要访问这个priv里面的东西还是可能的,既然这样,何必多次一举呢?其实从命名上给予自己或者其他的开发人员以足够的警告就可以了。class跟struct另外一个不同是对方法的封装。class可以定义类的接口,而struct没有这种支持。但我们还是可以接用函数指针来完成这个任务:structdemo{int(*intf_a)(int,char*);//memberfunctionvoid(*intf_b)(void);char*name;//publicmemberint__age;//privatemember}但个人觉得运用Linuxkernel的方式更能强迫你把接口考虑清楚:structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*aio_read)(structkiocb*,char__user*,size_t,loff_t);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);ssize_t(*aio_write)(structkiocb*,constchar__user*,size_t,loff_t);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*aio_fsync)(structkiocb*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);int(*check_flags)(int);int(*dir_notify)(structfile*filp,unsignedlongarg);int(*flock)(structfile*,int,structfile_lock*);};staticstructfile_operationsskel_fops={.owner=THIS_MODULE,.read=skel_read,.write=skel_write,.open=skel_open,.release=skel_release,};//注意,这里用的是GCC的语法扩展,如果没有这种扩展,我们需要自己赋值://staticstructfile_operationsskel_fops;//skel_fops.read=skel_read;......structusb_class_driver{char*name;structfile_operations*fops;mode_tmode;intminor_base;};staticstructusb_class_driverskel_class={.name=usb/skel%d,.fops=&skel_fops,.mode=S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH,.minor_base=USB_SKEL_MINOR_BASE,};structfile_operations在这里充当了java里面interfac