C语言OO编程

转载自
http://rubynroll.iteye.com/blog/150575

C语言面向对象编程
OO Programing in C is not only POSSIBLE but also PRACTICAL.   
经常看到关于OO编程的讨论,C++, Java, C#...还有最近很流行的动态语言Python,Ruby等,但很少看到有C的份。
在我看来,OO编程的核心是OO的思想,用什么语言倒是其次。
但是,不可否认,那些专门为OO编程设计的语言可以比较方便和自然地表达OO思想,有些语言甚至强制使用OO特性。
C,作为最贴近底层的高级语言,拥有简洁的语法和直接内存操作能力(指针),
大量运用于系统级编程,如操作系统内核,驱动程序等。而在嵌入式系统中,由于资源有限等因素,更倾向于用C编程。
C虽然在语言特性上并没有体现OO特性,但是依然可以通过各种编程技巧来体现OO的思想。
由于C的高度自由和跨平台的特点,在OO编程方面还能体现有别于其他语言的特殊韵味。

目 录 [ - ]
1.C的面向对象概念
2.用struct来仿真class
3.实现OO的继承机制
--------------------------------------------------------------------------------
1.C的面向对象概念
OO思想在Unix世界中很早就有:UNIX把设备抽象成文件,
这样就可以用一套相同的方法(open, read, write, close, ... )去访问不同的设备和文件——尽管设备之间的差异很大。
用OO的观点来看,这些“设备”对象都实现了"文件操作接口",可以想象有一个叫"文件"的基类,
定义了"文件操作接口",“设备”对象继承了“文件”对象....。在实现角度看,
在内核里面,设备驱动提供了自己的read, write等实现,并用它们去填充文件操作结构体里面的函数指针....
这和C++里面的虚函数运行时绑定的道理是一样的。(
C++虚函数是其实是运行时静态绑定,而文件操作接口可以运行时动态绑定 :-)
Linux内核(C源码)中则处处体现了OO的思想。
2.6内核的Device Driver Modal是一套层次分明又错综复杂的机制,其中体现了许多OO设计理念。
虽然可能设备驱动程序开发者觉察不到,但所有的设备驱动对象内部都隐藏了一个叫 KObject的对象。
内核把这些KObjects互相联系在一起,并通过KObject的相互关系构造了/sys文件系统。
/sys就是内核中各种设备对象的映射图,如果把/sys全部展开,我们可以清楚地看到各种对象的关系。

实践证明,C也可以很好地用于OO编程,而且可以用于构造很复杂的系统,
而且C在表达OO思想的时候并不会显得蹩脚,而是可以很简单,很自然。

--------------------------------------------------------------------------------
2.用struct来仿真class
“class“是很多OO编程语言里的关键字,它来源于OO鼻祖Smalltalk。
class(类),是对一群有相同特性的对象的抽象概括,对象称为类的实例。
在class里面可以存放有状态(变量),行为(函数/方法)....有关OO概念、方法的文章太多了,不再啰嗦。
在C里面,唯一可以实现自定义类型的是struct,struct是C的OO编程最重要的工具。
一个最常见的技巧,就是用struct来"仿真"class:
在struct里面放入变量,函数指针,嵌入其他struct等。
以下例子摘自我最近刚开发完成的一个USB Firmware项目:
C代码 
struct usb_device;  
struct usb_ctl;  
 
struct usb_iobuf {  
    int len;                  /* data length in the buffer */ 
    unsigned char buf[USBEPFIFO_SIZE];  /* data buffer itself */ 
};  
 
struct usb_endpoint { 
 int type;        /* endpoint type: BULKIN, BULKOUT, CTL, ISO ... */ 
 int qlen;        /* queue length */ 
 xQueueHandle lock;  /* semaphore lock */ 
 xQueueHandle q;     /* data queue (pointer of bulk_buf) */ 
 
 int idx;         /* endpoint index */ 
 int epx;         /* endpoint mark bit */ 
 int cfg;         /* endpoint configure */ 
 int bank;        /* current operation bank (for ping-pong mode) */ 
 int txCount;     /* used for ping-pong mode */           
    void (*ep_process) (struct usb_device *dev,
      struct usb_endpoint *ep,
      xISRStatus *pxMessage);/* endpoint data process function */  
};  


struct usb_descriptor {  
 int type;         /* descriptor type: device, conf, string or endpoint */ 
 int idx;          /* descriptor index (for string descriptor) */ 
 int size;         /* descriptor size */ 
 void * data;         /* descriptor data */ 
 struct list_head list;  /* link list of descriptors */ 
};  

struct usb_deviceOps {  
 int (*init)(struct usb_device *dev);        /* called when framework init usb device, add device descriptors, init private data ... etc. */ 
 int (*reset)(struct usb_device *dev);       /* called when reseted by host */ 
 int (*switch_in)(struct usb_device *dev);   /* called when switch in */ 
 int (*switch_out)(struct usb_device *dev);  /* called when swithc out */    /* called when HOST request class interface data */ 
 void (*class_interface_req)(struct usb_device *dev,
        xUSB_REQUEST *pxRequest);  /* called when HOST complete the data sending stage */      int (*ctl_data_comp)(struct usb_device *dev,                     xCONTROL_MESSAGE *pxMessage);  
};  


struct usb_ctlOps {  
 void (*ctl_transmit_null)(struct usb_ctl *ctl);  
 void (*ctl_send_stall)(struct usb_ctl *ctl);  
 void (*ctl_reset_ep0)(struct usb_ctl *ctl);  
 void (*ctl_detach_usb)(struct usb_ctl *ctl);  
 void (*ctl_attach_usb)(struct usb_ctl *ctl);  
 void (*ctl_send_data)( struct usb_ctl *ctl,
       unsigned char *data,  
         int req_len,  
         int send_len,
         int is_des);
};  

 

struct usb_ctl {  
 int addr;             /* address alloced by host */ 
 int conf;             /* configuration set by host */ 
 eDRIVER_STATE state;        /* current status */ 
 xCONTROL_MESSAGE tx;        /* control transmit message */ 
 xCONTROL_MESSAGE rx;        /* control receive message */ 
 struct ubufm *bufmn;        /* 'usb_iobuf' buffer manager, shared by all usb devices */ 
 int prio;             /* the main task priority */ 
 xTaskHandle task_handle;    /* the main task handler */ 
 struct usb_ctlOps *ctlOps;  /* control endpoint operations */ 
};  


struct usb_device {  
 char name[16];           /* device name, e.g. "usbser" */ 
 struct usb_deviceOps *ops;  /* usb device callback functions */ 
 
 struct usb_ctl *ctl;        /* usb control enpoint, provided by framework */ 
 struct list_head desc_list; /* usb descriptors */ 
 struct usb_endpoint *ep[MAX_ENDPOINTS]; /* endpoints */ 
 int active;           /* whether the device is active */ 
 xQueueHandle ready;      /* notify this queue when usb device ready */ 
 void *private;           /* device private data */ 
 struct list_head list;      /* link list of usb device */ 
}; 
struct usb_device;
struct usb_ctl;
struct usb_iobuf {
 int len;          /* data length in the buffer */
 unsigned char buf[USBEPFIFO_SIZE]; /* data buffer itself */
};

struct usb_endpoint { int type;  /* endpoint type: BULKIN, BULKOUT, CTL, ISO ... */
 int qlen;    /* queue length */
 xQueueHandle lock; /* semaphore lock */
 xQueueHandle q;   /* data queue (pointer of bulk_buf) */

 int idx;     /* endpoint index */
 int epx;     /* endpoint mark bit */
 int cfg;     /* endpoint configure */
 int bank;    /* current operation bank (for ping-pong mode) */
 int txCount;    /* used for ping-pong mode */ /* endpoint data process function */ 
 void (*ep_process) (struct usb_device *dev,
     struct usb_endpoint *ep,
     xISRStatus *pxMessage);
};

struct usb_descriptor {
 int type;      /* descriptor type: device, conf, string or endpoint */
 int idx;      /* descriptor index (for string descriptor) */
 int size;      /* descriptor size */
 void * data;     /* descriptor data */
 struct list_head list;  /* link list of descriptors */
};

struct usb_deviceOps {
 int (*init)(struct usb_device *dev);    /* called when framework init usb device, add device descriptors, init private data ... etc. */
 int (*reset)(struct usb_device *dev);   /* called when reseted by host */
 int (*switch_in)(struct usb_device *dev);  /* called when switch in */
 int (*switch_out)(struct usb_device *dev); /* called when swithc out */ /* called when HOST request class interface data */
 void (*class_interface_req)(struct usb_device *dev,
       xUSB_REQUEST *pxRequest); /* called when HOST complete the data sending stage */  int (*ctl_data_comp)(struct usb_device *dev,      xCONTROL_MESSAGE *pxMessage);
};

struct usb_ctlOps {
 void (*ctl_transmit_null)(struct usb_ctl *ctl);
 void (*ctl_send_stall)(struct usb_ctl *ctl);
 void (*ctl_reset_ep0)(struct usb_ctl *ctl);
 void (*ctl_detach_usb)(struct usb_ctl *ctl);
 void (*ctl_attach_usb)(struct usb_ctl *ctl);
 void (*ctl_send_data)(struct usb_ctl *ctl,
       unsigned char *data,
         int req_len,
         int send_len,
         int is_des);
};


struct usb_ctl {
 int addr;       /* address alloced by host */
 int conf;       /* configuration set by host */
 eDRIVER_STATE state;    /* current status */
 xCONTROL_MESSAGE tx;    /* control transmit message */
 xCONTROL_MESSAGE rx;    /* control receive message */
 struct ubufm *bufmn;    /* 'usb_iobuf' buffer manager, shared by all usb devices */
 int prio;       /* the main task priority */
 xTaskHandle task_handle;  /* the main task handler */
 struct usb_ctlOps *ctlOps; /* control endpoint operations */
};

struct usb_device {
 char name[16];       /* device name, e.g. "usbser" */
 struct usb_deviceOps *ops;  /* usb device callback functions */
 struct usb_ctl *ctl;     /* usb control enpoint, provided by framework */
 struct list_head desc_list;  /* usb descriptors */
 struct usb_endpoint *ep[MAX_ENDPOINTS]; /* endpoints */
 int active;        /* whether the device is active */
 xQueueHandle ready;     /* notify this queue when usb device ready */
 void *private;       /* device private data */
 struct list_head list;    /* link list of usb device */
};

在这个例子,我用struct分别描述了USB设备,USB控制通道,USB端点,USB描述符和USB缓冲区对象。
USB设备对象包含了若干个USB端点,一个USB控制通道指针,
一个USB描述符表的表头(指向若干个USB描述符),和一个USB缓冲区管理对象。
而且,USB设备对象还包含了name属性,一个由USB Framework调用的回调函数集,
还有一个用于连接其他USB设备的链表节点。
值得一提的是,USB设备对象中有一个void *private 成员,可以指向任何数据。
实际上在我的程序里,我实现了usb-serial和usb-mass-storage两个USB设备,
对于usb-serial对象,private我弃之不用,而在usb-mass-storage对象中,
private指向一个Mass storage对象,
usb-mass-storage正是通过这个Mass storage对象访问外部大容量存储的
(在我的程序里,Mass storage对象和一个MMC Card对象绑定,外部存储是SD/MMC卡)。
由于对于每一种设备的具体实现来说,它知道private指向的是何种类型的设备,因此不会引起混乱。
而外部程序根据需要在初始化USB设备对象前赋予private有意义的值——运行时动态绑定。
这一系列struct基本上如实地反映了USB DEVICE硬件逻辑和规范要求: "一个USB设备包含若干个端点,
其中有一个固定的控制端点(端点0)。在枚举阶段USB设备要根据HOST的请求应答相应的描述符..."
现在回到OO的话题,这个例子中体现了"组合类":
USB设备对象包含了USB端点对象,USB描述符对象...。还有动态绑定 (private成员)。
从严格的OO意义上来看,好像有点"怪",不过我认为这恰恰是C的特点——简洁,直接。
不信你用C++表达试试?也许会更漂亮,很OO,但是不一定会如此清爽!
P.S.:熟悉USB Firmware开发的人可能对struct usb_endpoint中的epx,cfg,bank和txCount四个成员有异议,
因为这些成员是和特定的硬件相关,并不是所有的USB硬件都支持ping-pong mode,
所以bank和txCount不一定用得上,epx, cfg也可能因硬件的不同而不同。
没错!更理想的设计是把与硬件相关的部分分离出来,
用void *private指向各自的与硬件相关的配置——就像struct usb_device所采用方法,所以更好的版本应该是:
---------------------------------------------------------------------------------
Java代码 
struct usb_endpoint {  
 int type;       /* endpoint type: BULKIN, BULKOUT, CTL, ISO ... */ 
 int qlen;       /* queue length */ 
 xQueueHandle lock;  /* semaphore lock */ 
 xQueueHandle q;    /* data queue (pointer of bulk_buf) */ 
 int idx;        /* endpoint index */ 
 
      /* endpoint data process function */ 
 void (*ep_process)(struct usb_device *dev,    struct usb_endpoint *ep, xISRStatus *pxMessage);  
 void *private;     /* endpoint private data (hardware relevant) */ 
}; 
tips: 用C表达的一个关键处就是要很好地应用struct来描述模型。
实现OO的继承机制 

--------------------------------------------------------------------------------

OO的一个亮点是类的"继承",通过"继承",可以重用许多代码。
而且"继承"也是现实生活中非常自然的一种关系。
但是很不幸,C没有class,更没有提供"继承"的表达方式。
既然能用C的struct来仿真class, 那能不能继续来仿真"继承"呢?
答案是:possible。就像<<Inside the C++ Object Modal>>书中所叙述的那样——你可以用C来达到所有C++能做到的事。
但这种仿真显然毫无实际应用价值。
"继承"是一种表达方式,代码重用才是目的。
为了重用代码,C++可以用"继承"的方式来巧妙的达到目的,但是也必须付出代价:
你必须非常仔细地设计你的类族谱,要有前瞻性,要有可扩展性,要决定分多少个层次....这些都不是容易做到的事。
C别无选择,模块化设计,函数,宏....只能通过巧妙的设计才能达到代码可重用的目的。
还是举个例子来说明C是如何做到"殊途同归"的吧。
"链表"是一个非常常用的数据结构,常用于管理无序的数据(对象)集合。
链表操作,特别是双向链表操作很容易出错。
重用一套通用操作链表的代码可以为我们省不少事。
在C++中,我们可以用经典的STL中的list类。
为了适应各种数据类型,list类用模板来实现。
list类实现的很巧妙,功能很强,。
当然在C++中你还有另外一个选择:实现一个List基类完成链表操作,
要放入链表的类从List类继承而来,就拥有了一套操作list的方法。

Linux内核中用C提供了一套非常巧妙的方法操作链表,
位于.../linux/include/linux/list.h,只用一些宏和inline函数来实现双向链表。
摘抄一部分出来:

C代码 
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)  
...

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 宏如下:

C代码 
/** 
* 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) );}) 
/**
 * 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" 关键字,如果想用其他编译器也想编译通过的话,可以修改成:


C代码 
#define container_of(ptr, type, member) (            \  
        (type *)( (char *)ptr - offsetof(type,member) ) ) 
#define container_of(ptr, type, member) (            \
        (type *)( (char *)ptr - offsetof(type,member) ) )

为了便于说明,prefetch定义成:
Java代码 
1.static inline void prefetch(const void *x) {;} 
static inline void prefetch(const void *x) {;}

offsetof的一个简单版本:
C代码 
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
好了,让我们看看怎么用:
Java代码 
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);  
       ...  
}
 
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的表达方式 !

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值