精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-12-24
-------------------------------------------------------------------------------- OO的一个亮点是类的"继承",通过"继承",可以重用许多代码。而且"继承"也是现实生活中非常自然的一种关系。但是很不幸,C没有class,更没有提供"继承"的表达方式。既然能用C的struct来仿真class, 那能不能继续来仿真"继承"呢?答案是:possible。就像<<Inside the C++ Object Modal>>书中所叙述的那样——你可以用C来达到所有C++能做到的事。但这种仿真显然毫无实际应用价值。 "继承"是一种表达方式,代码重用才是目的。 为了重用代码,C++可以用"继承"的方式来巧妙的达到目的,但是也必须付出代价:你必须非常仔细地设计你的类族谱,要有前瞻性,要有可扩展性,要决定分多少个层次....这些都不是容易做到的事。 C别无选择,模块化设计,函数,宏....只能通过巧妙的设计才能达到代码可重用的目的。还是举个例子来说明C是如何做到"殊途同归"的吧。 "链表"是一个非常常用的数据结构,常用于管理无序的数据(对象)集合。链表操作,特别是双向链表操作很容易出错。重用一套通用操作链表的代码可以为我们省不少事。在C++中,我们可以用经典的STL中的list类。为了适应各种数据类型,list类用模板来实现。list类实现的很巧妙,功能很强,但是,不得不说,很少人用。其实不仅list类很少用,STL都很少人用。(希望这是我的一家之言,反正我所熟悉的C++程序员都不怎么用STL :-)当然在C++中你还有另外一个选择:实现一个List基类完成链表操作,要放入链表的类从List类继承而来,就拥有了一套操作list的方法。 Linux内核中用C提供了一套非常巧妙的方法操作链表,位于.../linux/include/linux/list.h,只用一些宏和inline函数来实现双向链表。摘抄一部分出来: .... struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } ..... /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop counter. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; prefetch(pos->next), pos != (head); \ pos = pos->next) ...... 其中 container_of 宏如下: /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) 这里使用了GCC特有的 "typeof" 关键字,如果想用其他编译器也想编译通过的话,可以修改成: #define container_of(ptr, type, member) ( \ (type *)( (char *)ptr - offsetof(type,member) ) ) 为了便于说明,prefetch定义成: static inline void prefetch(const void *x) {;} offsetof的一个简单版本: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 好了,让我们看看怎么用: struct my_data { int x; int y; struct list_head list; } /* 链表头 */ LIST_HEAD(my_listhead); void my_function() { ... /* 节点对象 */ struct my_data *node_1 = (struct my_data *) malloc(sizeof(struct my_data)); struct my_data *node_2 = (struct my_data *) malloc(sizeof(struct my_data)); ... /* 加入链表 */ list_add (node_1->list, &my_listhead); list_add (node_2->list, &my_listhead); ... /* 遍历链表 */ struct my_data * node; struct list_head *pos; list_for_each (pos, &my_listhead) { node = list_entry (pos, struct my_data, list); ... } 其中最精彩的部分是遍历链表的表达方式: list_for_each (...) { ... } 这种表达方式另我想起了Ruby,C++ STL中的到处出现的iterator,和VB中的for each...in...next语句。 从内部结构角度来看,Linux的list实现方式有点类似C++中的"组合类"——在需要放入链表的对象内部放入list类(struct list_head)。但是从遍历链表的时候,可以根据list指针得到包含list节点的对象指针来看,又有点超出了"组合类"的范畴。能否把 struct my_data看成继承了struct list_head呢?从内存映像来看倒有点像(C++子类对象的内存映像是父类对象的超级)。当然,这种强行联系完全没有必要,C就是C,何必去往C+ +套呢?C自有C的表达方式 --THE END-- 注:这几篇是我原来在MSN SPACE的老blog,实在受不了MSN SPACE的速度,于是搬到了javaeye。写这些东西,只是想和使用C的同志分享自己的体验,也想告诉新学习C的同志,C语言也是很有魅力的:) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2007-12-25
啊 我一直用源自bsd家的queue.h
queue.h貌似更通用并且更强大呢 |
|
返回顶楼 | |
发表时间:2007-12-25
c当中还有个地方也用到很多oo的思想-网络编程
|
|
返回顶楼 | |
发表时间:2007-12-31
引用 啊 我一直用源自bsd家的queue.h
queue.h貌似更通用并且更强大呢 初步看了一下bsd的queue.h, 确实该有的操作都有了,虽然没实际使用,不过貌似挺不错的。不过,Linux的版本有一个优点,那就是通常使用时把struct list_head嵌入到数据节点中,这样无需为list_head结构本身分配内存,而且利用container_of宏可以方便的根据list_head来获得数据节点的指针,这样使得list_for_each变得非常的自然和优雅。 不知是否可以用queue.h实现个例子我们来看看? 引用 c当中还有个地方也用到很多oo的思想-网络编程
举个例子来看看? |
|
返回顶楼 | |
发表时间:2007-12-31
rubynroll 写道 引用 啊 我一直用源自bsd家的queue.h
queue.h貌似更通用并且更强大呢 初步看了一下bsd的queue.h, 确实该有的操作都有了,虽然没实际使用,不过貌似挺不错的。不过,Linux的版本有一个优点,那就是通常使用时把struct list_head嵌入到数据节点中,这样无需为list_head结构本身分配内存,而且利用container_of宏可以方便的根据list_head来获得数据节点的指针,这样使得list_for_each变得非常的自然和优雅。 不知是否可以用queue.h实现个例子我们来看看? 引用 c当中还有个地方也用到很多oo的思想-网络编程
举个例子来看看? man 3 queue 里面有几个很简单的例子 不过足以说明大概用法了 在内存管理,文件系统,这些地方很多用到queue.h来管理pageing和inode的,可以刨出来看看做参考 网络编程里面最常用的就是cast mbuf, ip, tcp, udp之间的转换都是靠cast |
|
返回顶楼 | |
发表时间:2008-01-08
看宏看得有点懵。
我一般只用宏来定义一些常量,做其他的事情程序可读性就不太好了,使用inline是一个不错的选择 |
|
返回顶楼 | |
发表时间:2008-01-08
使用文件来封装类,而不是struct更合理一点,因为c中有这些关键字:staic,extern可以实现类的访问控制功能
继承机制可以使用组合来代替 http://xombat.iteye.com/blog/101300 这里是学数据结构的时候写的一些ADT,绞尽脑汁采用了oo的思想,.h文件中定义接口,而且巧妙的使用void *(可以将void *看成是所有type *的父类),比如: typedef void * dlist_t; typedef const void *const_dlist_t; 这样对用户看,封装了.c中实际的数据结构 如此既实现了数据的封装,也实现了方法的封装。 自我感觉不好的一点是: /////////////////////////////////////////////////// //用户可以任意修改结构的定义,除了结构名称。 /////////////////////////////////////////////////// typedef struct dllElem{ int n; }dllElem; int dllElem_equal(const dllElem *,const dllElem*); /////////////////////////////////////////////////// 我的目的是提供一个像c++ 中template的功能,使这个ADT中的数据可以是任何对象,但代码好像开始变得晦涩起来 |
|
返回顶楼 | |
发表时间:2008-01-08
xombat 写道 看宏看得有点懵。
我一般只用宏来定义一些常量,做其他的事情程序可读性就不太好了,使用inline是一个不错的选择 恰恰相反,使用宏来避免“魔数”当然是一个好处不用说了,大多数情况下,宏的使用是为了提高代码可读性的。例如: struct my_point { int x; int y; }; struct my_point *point; #define WIDTH 640 #define HEIGHT 480 ... if (point->x >= 0 && point->y >= 0 && point->x < WIDTH && point->y < HEIGHT) { ... } 换成如下代码: #define IS_VALID_POINT(s) (s->x >= 0 && s->y >= 0 && s->x < WIDTH && s->y < HEIGHT) if (IS_VALID_POINT(point)) { ... } 可读性不是大大提高了么? 另外一个方面,可能使用宏有时侯会使某一部分代码看起来难懂一些,但是其目的是为了使用它的人的代码的可读性更强,我上面提到的linux的linklist.h就是个例子。linklist.h本身可能不容易一下子看透,但是使用linklist.h的代码的可读性就变得非常高,看看那个list_for_each,能使你遍历链表的操作变得如此自然,可以和有些语言内置的foreach语句相媲美! |
|
返回顶楼 | |
发表时间:2008-01-08
为了进一步说明问题,我写一个稍微完整的例子来说明Linux内核那个链表操作宏是如何好用:
//定义你的数据结构 struct my_point { int x; int y; struct list_head list; //嵌入链表管理节点 }; //在这个函数里,产生30个my_point,并且加入链表 void create_my_points(struct list_head *head) { struct my_point *p; int x, y; for (x = 0; x < 10; x++) { for (y = 0; y < 3; y++) { p = (struct my_point *)malloc(sizeof(struct my_point)); p->x = x; p->y = y; list_add(&p->list, head); } } } //遍历链表,打印信息 void print_list(struct list_head *head) { struct list_head *node; struct my_point *p; list_for_each(node, head) { p = list_entry(node, struct my_list, list); printf("x = %d, y = %d\n", p->x, p->y); } } //使用: LIST_HEAD(points_list); //定义链表头 create_my_points(&points_list); print_list(&points_list); Linux内核的那个链表操作的实现,淋漓尽致地体现了C语言之美,是我所看到的最好的C链表操作实现,C++实现?估计很难有这种优美。 另外,我们可以比较一下xombat的实现(http://xombat.iteye.com/blog/101300),就可以看出Linux内核的那个实现在使用的时候是有明显的优势: 1. 链表管理节点内嵌入数据节点,这样不用单独的管理节点,好处是:
2. 用宏来实现迭代遍历链表,给予使用者最大的灵活性,代码可读性非常好,同时大大减少使用者的代码 我以前也经常在程序中实现自己的链表操作代码,现在我开始项目写代码的第一件事,就是把Linux下的include/linux/list.h拷贝一份出来到项目的头文件目录! 所以在这里我也劝大家不要再去发明轮子了,因为Linux的list.h这个轮子已经足够圆了 |
|
返回顶楼 | |
发表时间:2008-01-08
谢谢,我认真去看了看那个list.h文件,领悟了他的思想,感觉确实挺好的。
他的实现跟我的正好相反,我的实现是将数据放在被管理的节点里面,而他的节点是和数据放在同一个container里,然后使用container_of获得他们的container,而管理的时候只需要管理那些节点就可以了,实现起来简单多了,而且扩展性也非常强。 另外如果lz对/usr/include/linux中的头文件熟悉的话,期待楼主写一写对那些文件的介绍,我稍微看了看,感觉那些头文件中的函数还是很实用的,而且封装的也比较好。 贴一下感觉挺nb的代码: 获得member在type(一个struct)中的偏移: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 可读性强,而且省些好多代码的for_each #define list_for_each(pos, head) \ for (pos = (head)->next; prefetch(pos->next), pos != (head); \ pos = pos->next) |
|
返回顶楼 | |