C语言面向对象的实现

C语言面向对象的实现

C++语言面向对象的编程是它的基本特点,在大型C语言编程中,如:Linux内核,面向对象的编程思想也得到了广泛的应用。

C语言是面向过程的、结构化的编程;但也可应用面向对象的思想,把结构看作类,实现面向对象的编程,尽管结构与类有本质的不同。为理解方便,下面将用于面向对象编程的结构也称为类。

面向对象的编程思想

面向对象是用软件系统直接对现实世界的直接模拟,将现实中的事物直接映射到软件系统中的类,使得软件与现实中的事物一样直观。

对象是现实世界的一个实体,对应于软件的类实例,它具有名字、特征和一组操作。类是同一类型对象的抽象,具有继承性和多态性。

面向对象的编程方法是常用的方法,例如:C++和Qt是面向对象的语言,如:将DRM等协议编写成程序时,将协议描述的主体及其操作作为一个类进行编程,协议主体的层次关系,演变为类对象之间的调用关系。在窗口系统中,组成窗口的各个控件作为一个类进行编程,窗口的控件层次关系,演变为类的调用关系。

C语言是面向过程的语言,但它也可使用面向对象的思想进行编程。在Linux的系统应用编程中,面向对象编程的方法应用很多,例如:把应用程序的配置文件作为一个对象,它提供了配置文件的数据结构及其对象的各种方法函数;ACL(访问控制列表)的数据结构及各种方法函数也是面向对象编程的一个例子。

在Linux内核中面向对象的编程思想也很普遍,如:文件系统将其组成部分超级块、节点、文件等分别以数据结构进行描述,并对每个组成部分定义操作函数集、文件系统的各组成部分的层次关系,演变为各组成部分数据结构、操作函数集之间的调用关系。

在C++等面向对象的语言中,对象的抽象化是通过类的继承关系及多态性来实现的。

在C语言进行编程的应用程序和Linux内核编程中,对象的抽象化方法使用同样很普遍,对象的抽象化是通过数据结构和操作函数集结构的继承关系及多态性来实现的。如:Linux内核的文件系统将文件系统的共性抽象出虚拟文件系统,具体的文件系统(如:EXT4、FAT32等)实现虚拟文件系统的数据结构和操作函数集,同时,具体的文件系统还有自己的个性,即扩展数据结构和操作函数。还比如Linux内核的各类驱动程序,先抽象出同类驱动程序的共性,再按子类型逐步实现各子类型的驱动程序。

类的实现

在C语言中,结构的定义相当于类的定义,结构的实例化相当于类的实例化,结构的继承关系相当于类的继承关系。

(1)类的继承

下面以基类User和子类StudentUser为例说明类的继承。

基类User和子类StudentUser的定义分别列出如下:

typedef struct 
{
   char * name; 
   int age; 
} User; 
 
typedef struct 
{
    User genericUser; 
    char ** listOfCourses; 
 
    int numCourses;
    int whatYear; 
} StudentUser;

基类User抽象了用户的共性,子类StudentUser在拥有用户共性的基础上,还拥有学生用户的特殊特性。这符合面向对象的分层抽象的思想。

子类StudentUser含有基类User的实例genericUser,如果需要用到类型User*与StudentUser*指针之间的转换,则需要将genericUser作为子类StudentUser的第一个成员。例如:程序利用类工厂管理函数createUser可以创建不同的用户,这样可以统一函数接口。方法如下:

User *createUser()
{
   StudentUser *zaphod = malloc(sizeof(StudnetUser));
   ......
   return (User*)zaphod;
}
 
int main()
{
    User *user1;
    StudentUser *student1;
    user1= createUser();
    student1= (StudentUser *)user1;
    ......
}

Linux内核广泛使用了C语言类的继承关系,如:驱动程序通过类的继承关系将设备的特性进行分层抽象,例如,安全模块的设备结构platform_device从通用设备结构device继承,结构platform_device列出如下(在linux26/include/linux/platform_device.h中):
struct platform_device {

const char * name; u32 id; struct device dev;   //父类结构 u32 num_resources; struct resource * resource;

};

(2)纯虚类 纯虚类常用来抽象接口,一个简单的纯虚类样例定义如下:
typedef struct {
    void  (*Foo1)();
    char  (*Foo2)();
    char*  (*Foo3)(char* st);
}MyVirtualInterface;

Linux内核常用纯虚类抽象接口,如:文件系统使用纯虚类file_operations抽象文件的操作,各个具体文件系统再将实现这个类。 纯虚类file_operations定义部分列出如下(在linux26/include/linux/fs.h中):
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  ......
}

ecryptfs文件系统在ecryptfs_main_fops中实现了类file_operations中的函数,ecryptfs_main_fops部分列出如下(在linux26/fs/ecryptfs/file.c中):
const struct file_operations ecryptfs_main_fops = {

.llseek = ecryptfs_llseek, .read = do_sync_read, .aio_read = ecryptfs_read_update_atime, .write = do_sync_write, .aio_write = generic_file_aio_write,    ......

}

(3)类的多态性 类通过虚函数可实现继承与多态性,例如,类Base和Sub是父类与子类的关系,两个类可以有同名的虚函数。它们列出如下:
struct Base {
        int data;
        int (*getData)( struct Base * );
};
 
struct Sub {
        struct Base base;
        int (*getData)( struct Sub * );
        int data;
};

(4)类的私有函数和公有函数

类的私有函数只在类的内部使用,类的公有函数是对外公开的接口函数。C语言中公有函数和私有函数的处理与C++中不同。C语言的类是基于文件的类,即以文件为单位区分类的外部与内部,C++则严格以类封装类的外部与内部。

C语言中,文件用“static”关键字定义该类的私有数据成员。公有数据成员必须定义到头文件,其他文件引用这个头文件后才可使用公有数据成员。或者其他文件使用“extern”关键字使用公用数据成员,但这种方法引用不清晰,不建议使用。

面向对象的C文件的一般原则说明如下:

1)一个C文件和h文件对封装一个类。

2)公有成员需要在.h文件中有他们的原型声明。

3)私有成员在C文件的前面中有他们的原型声明,原型前加“static”关键字,表示函数名在本文件中有效。

(5)类的实现与应用

类的实现包括类的构造函数、析构函数和类成员函数的实现。类对象的应用包括类对象的创建、类对象函数的应用、类对象的析构几个步骤。下面以类Base和Sub说明类的应用。

#include<stdio.h>
#include<stdlib.h>
 
/*类Base和Sub的定义*/
struct Base {
        int data;
        int (*getData)( struct Base * );
};
 
struct Sub {
        struct Base base;
        int (*getData)( struct Sub * );
        int data;
};
 
/*类成员函数getData的实现*/
int getDataForBase( struct Base * base ) {
        return base->data;
}
 
int getDataForSubBase( struct Base * base ) {
        return ((struct Sub *)base)->data;
}
int getDataForSub( struct Sub * sub ) {       
        return sub->data;
}
 
/* 类Base 的构造函数*/
void Base_init( struct Base * base ) {
        base->data = 1;
        base->getData = getDataForBase;
}
 
/*类Sub的构造函数*/
void Sub_init( struct Sub * sub ) {
        Base_init( (struct Base*)sub );        
        ((struct Base*)sub)->getData = getDataForSubBase; /* 设置函数*/
        sub->getData = getDataForSub;        /* 设置函数*/
        sub->data = 2;
}
 
/*类Base和Sub的析构函数*/
void Base_destroy( struct Base * base) {
    if(base!=0) 
       free(base);
}
void Sub_destroy( struct Sub * sub) {
    if(sub!=0) 
       free(sub);
}
 
/*类Base和Sub的应用*/
int main()
{
        struct Base * base = (struct Base*)malloc(sizeof(struct Base));
        Base_init(base);
        struct Sub * sub = (struct Sub*)malloc(sizeof(struct Sub));
        Sub_init(sub);
        struct Base * subbase = (struct Base*)sub;
       printf( "%d/n%d/n%d/n", base->getData(base), sub->getData(sub), subbase->getData(subbase) );
        Base_destroy (base);       
        Sub_destroy (sub);       
}

Linux内核广泛使用了类,它将某一对象抽象化实现为类,通过类的继承关系反映实际对象的继承关系。

例如,Linux内核通过链表将各类对象链接在一起。由于具体对象不同,形成的实际链表也不同。Linux内核用类list_head抽象链表的操作,具体对象通过从类list_head继承而具有了链表操作功能。Linux内核在linux26/include/linux/list.h中实现了类list_head的公有函数。这样,其他需要链表操作的具体对象,只要从类list_head继承,就可以使用它的链表操作函数。如:类ecryptfs_auth_tok_list_item从类list_head继承,其列出如下:
struct ecryptfs_auth_tok_list_item {
	unsigned char encrypted_session_key[ECRYPTFS_MAX_KEY_BYTES];
	struct list_head list;    //表示从类list_head继承
	struct ecryptfs_auth_tok auth_tok;
};

类ecryptfs_auth_tok_list_item没有将成员list作为第一个成员,因此,必须通过特殊的函数container_of才能从父类得到子类对象的指针。这样还可以实现子类从多个父类继承。例如:类avc_node从类avc_entry、list_head和rcu_head继承,函数avc_node_free调用函数container_of通过父类rhead获取子类avc_node的指针。类avc_node列出如下(在linux26/security/selinux/avc.c中):
struct avc_node {

struct avc_entry ae; struct list_head list; struct rcu_head rhead; };   static void avc_node_free(struct rcu_head *rhead) { struct avc_node *node = container_of(rhead, struct avc_node, rhead); ......

}

其他对象化方法

(1)宏定义

将多次使用的常量或函数使用宏定义,以便于以后程序的修改。例如:定义了宏MacroFunction后,源代码使用宏MacroFunction进行操作,而不是使用函数FunctionA本身进行操作,这样,以后修改时,只需要改变宏定义,就可以更改所有对函数FunctionA的调用了。宏MacroFunction的定义列出如下:
#define MacroFunction FunctionA

(2)函数功能分发

一个函数可通过功能分发来调用各个功能子函数,这样,可以统一或减少程序的接口。Linux内核的各种控制函数常用这种方式来定义各个子系统的特殊功能。例如,密钥环子系统的密钥控制系统调用函数sys_keyctl,它根据参数option的命令定义来调用不同的功能函数。函数sys_keyctl部分列出如下(在linux26/security/keys/keyctl.c中):
asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
			   unsigned long arg4, unsigned long arg5)
{
	switch (option) {
	case KEYCTL_GET_KEYRING_ID:
		return keyctl_get_keyring_ID((key_serial_t) arg2,
					     (int) arg3);
 
	case KEYCTL_JOIN_SESSION_KEYRING:
		return keyctl_join_session_keyring((const char __user *) arg2);
 
	case KEYCTL_UPDATE:
		return keyctl_update_key((key_serial_t) arg2,
					 (const void __user *) arg3,
					 (size_t) arg4);
	......
	case KEYCTL_ASSUME_AUTHORITY:
		return keyctl_assume_authority((key_serial_t) arg2);
 
	default:
		return -EOPNOTSUPP;
	}

上述这种方法,还常用于事件或消息分发功能函数中,函数根据接收到的消息调用不同的功能函数。

(3)函数参数含有回调函数指针

在函数参数中的函数指针提高了接口的灵活性,函数实现同类对象通用的功能,函数指针实现具体对象的特殊功能。如果参数对应的函数在动态库中实现,那么,更换参数对应函数时,函数所在应用程序不需要重新编译。

Linux内核广泛地使用了函数参数回调函数,用来提高接口的通用性和灵活性。例如:文件系统的超级块创建函数sget,它查找或创建一个超级块。它是基类super_block的公有操作函数,但不同文件系统超级块的比较函数和设置函数可能不同,为了适应具体文件系统的需要,函数sget用参数指针test和set来调用不同文件系统超级块的比较函数和设置函数。

函数sget的定义列出如下(在linux26/fs/super.c中):
struct super_block *sget(struct file_system_type *type,
			int (*test)(struct super_block *,void *),	//比较回调函数
			int (*set)(struct super_block *,void *),	//设置回调函数
			void *data)                       			//回调函数参数的值

文件系统的函数get_sb_single调用函数sget时,它使用了比较回调函数compare_single和设置回调函数set_anon_super。函数get_sb_single部分列出如下:
int get_sb_single(struct file_system_type *fs_type,

int flags, void *data, int (*fill_super)(struct super_block *, void *, int), struct vfsmount *mnt) { struct super_block *s; int error;   s = sget(fs_type, compare_single, set_anon_super, NULL);

    ......
}

(4)上下文结构

尽量避免少使用全局变量,对于程序运行期间使用的各种特征数据应尽量放入上下文结构中,通过函数参数来传递上下文结构指针,这样,让特征数据只暴露给需要使用的函数。

Linux内核中进程上下文结构task_struct是最典型的应用,它将进程的各种特征数据传递给所需要的函数。例如:函数sched_migrate_task通过指针p将进程上下文传递给它调用的各个函数。函数sched_migrate_task列出如下(在linux26/kernel/sched.c中):


static void sched_migrate_task(struct task_struct *p, int dest_cpu)
{
	......
	if (migrate_task(p, dest_cpu, &req)) {
		/* Need to wait for migration thread (might exit: take ref). */
		struct task_struct *mt = rq->migration_thread;
 
		get_task_struct(mt);
		task_rq_unlock(rq, &flags);
		wake_up_process(mt);
		put_task_struct(mt);
		wait_for_completion(&req.done);
 
		return;
	}
out:
	task_rq_unlock(rq, &flags);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值