C语言也能面向对象

C语言也能面向对象(一)——一个简单的类

面向对象不是C++, java, C#等的专利,万能的C语言一样可以面向对象编程。GObject系统已经用C语言实现了一套完整并且功能强大的面向对象系统。但GObject系统十分庞大,并依赖于众多的开发包,在项目本身很小,或者不想依赖于众多于GObject相关的开发包时,就无法使用了。
那么能不能用C语言实现一套小巧简单,易于理解,易于使用的面向对象系统呢?就让我们一起探索一下吧!
所谓类就是数据和方法的集合体。我们使用结构体定义类的数据,如下:

  

 1: struct animal

   2: {

   3:     char name[256];

   4:     int weight;

   5: };

 

我们定义了一个animal结构体,成员包括animal的name和animal的weight(重量)。
现在数据已经有了,那么方法如何定义呢?
我们知道在面向对象中有封装的概念,所以结构体中的成员我们不应该直接访问,而应该通过方法访问,我们添加访问结构体成员变量的方法:

   

  1: char* animal_get_name(animal* self);

   2: void animal_set_name(animal* self, char* name);

   3: int animal_get_weight(animal* self);

   4: void animal_set_weight(animal* self, int weight);

   5: char* animal_get_name(animal* self)

   6: {

   7:     return self->name;

   8: }

   9: 

  10: void animal_set_name(animal* self, char* name)

  11: {

  12:     strncpy(self->name, name, sizeof(self->name));

  13: }

  14: 

  15: int animal_get_weight(animal* self)

  16: {

  17:     return self->weight;

  18: }

  19: 

  20: void animal_set_weight(animal* self, int weight)

  21: {

  22:     self->weight = weight;

  23: }

  24: 

 

方法的第一个参数animal* self是对象的地址,与C++中的this指针是同一个概念,只不过C++中的this指针是由编译器帮我们传递的,无需在代码中显式的写出来。
现在类的数据、方法都有了,那么如何使用类创建、初始化、析构、销毁对象呢?
在C++中有new、delete操作符负责创建和销毁,由构造函数和析构函数负责初始化和析构。这些在我们的C语言面向对象系统中都是要实现的,但对于我们的第一个类,我们可以实现的简单一些,如下:

   

1: animal* animal_create(void);

   2: void animal_destroy(animal* self);

   3: 

   4: animal* animal_create(void)

   5: {

   6:     animal* self = malloc(sizeof(animal));

   7:     LW_ASSERT(self);

   8:     memset(self->name, 0x00, sizeof(self->name));

   9:     self->weight = 0;

  10:     return self;

  11: }

  12: 

  13: void animal_destroy(animal* self)

  14: {

  15:     free(self);

  16: }

  17: 

 

animal_create负责对象创建和初始化,animal_destroy负责对象析构和销毁。
到这里我们第一个类的定义就结束了。测试一下吧:

   

1: animal* animal1 = NULL;

   2: animal* animal2 = NULL;

   3: 

   4: animal1 = animal_create();

   5: animal_set_name(animal1, "Kitty");

   6: animal_set_weight(animal1, 30);

   7: printf("animal1, name : %s, weight : %d\n",

   8: animal_get_name(animal1),

   9: animal_get_weight(animal1));

  10: animal_destroy(animal1);

  11: 

  12: animal2 = animal_create();

  13: animal_set_name(animal2, "Bib");

  14: animal_set_weight(animal2, 10);

  15: printf("animal2, name : %s, weight : %d\n",

  16: animal_get_name(animal2),

  17: animal_get_weight(animal2));

  18: animal_destroy(animal2);

  19: 

 

看看测试代码,是不是有些感觉了,其实这里面向对象还远着呢,勉强能称得上是基于对象吧。

相关代码可在此处下载
下一篇我们将介绍如何通过new和delete操作创建和销毁对象,通过构造函数和析构函数初始化和析构对象。

C语言也能面向对象(二)——newdelete

上篇我们介绍了如何使用结构体写一个简单的类animal,并使用animal* animal_create(void)和void animal_destroy(animal* self)来创建和删除animal类的对象。

在C++中创建和删除对象时使用的是new和delete操作符,并会自动调用类的构造函数和析构函数初始化和析构对象,那么使用C语言如何实现这样的机制呢?我们可以用函数模拟new和delete,定义两个函数:lw_new和lw_delete(“lw”意为light-weight,轻量级的意思)。

使用lw_new创建类的对象时,需要为对象申请内存空间,并且需要初始化该对象,所以lw_new函数至少需要知道该类的对象需要多大的内存空间,以及该类的构造函数是谁。我们可以将这些信息封装到一个结构体中,称之为类信息结构体,如下(以上篇介绍的animal类为例):

 1 typedef struct _animal_klass_info animal_klass_info;
 2 
 3 struct _animal_klass_info
 4 
 5 {       
 6 
 7     size_t size;    /*animal's size*/
 8 
 9     animal* (*ctor)(animal* self); /*constructor*/
10 
11 };

 

然后我们创建一个该结构体的对象(称之为类信息),将animal类的内存大小、构造函数等信息存入到对象中,再使用一个全局的指针指向该对象(称之为全局类信息指针),如下:

 1 extern animal_klass_info* animal_klass;
 2 
 3 static animal_klass_info local_animal_klass =
 4 
 5 {
 6 
 7     sizeof(animal),
 8 
 9     animal_ctor,
10 
11 };
12 
13 animal_klass_info* animal_klass = &local_animal_klass;

 

这样在创建animal类的对象时,我们只需要将animal类的全局类信息指针animal_klass传入到 lw_new函数中,lw_new函数就能够申请内存空间,并自动调用构造函数初始化对象了,lw_new的实现如下:

1 animal* lw_new(animal_klass_info* klass)
2 
3 {
4 
5     animal* p = ANIMAL(malloc(klass->size));
6 
7     return klass->ctor(p);
8 
9 }

 

现在可以使用lw_new创建animal类的对象了,如下:

animal* animal1 = lw_new(animal_klass);

那么如何使用lw_delete函数删除对象,并自动调用析构函数呢?

在C++中删除对象时,传递给delete操作符的参数只有对象的地址,所以我们传递给lw_delete函数的也只能是对象的地址。使用对象地址我们可以释放对象占用的内存空间,但如何调用对象的析构函数呢?

我们可以将析构函数的地址加入到类信息结构体中,还是以animal类为例,将其类信息结构体修改为:

 1 typedef struct _animal_klass_info animal_klass_info;
 2 
 3 struct _animal_klass_info
 4 
 5 {      
 6 
 7     size_t size;    /*animal's size*/
 8 
 9     animal* (*ctor)(animal* self); /*constructor*/
10 
11     animal* (*dtor)(animal* self); /*destructor*/
12 
13 };

在创建类信息时加入析构函数的地址,如下:

 1 static animal_klass_info local_animal_klass =
 2 
 3 {
 4 
 5     sizeof(animal),
 6 
 7     animal_ctor,
 8 
 9     animal_dtor,
10 
11 };

 

然后在animal类中加入一个类信息结构体的指针,用来保存animal类信息的地址。修改animal类定义如下:

 1 typedef struct _animal animal;
 2 
 3 struct _animal
 4 
 5 {
 6 
 7 /*klass info*/
 8 
 9     animal_klass_info* klass;
10 
11  
12 
13 /*private data*/
14 
15     char name[256];
16 
17     int weight;
18 
19 };

 

在lw_new函数中初始化animal类的类信息结构体指针,如下:

 1 animal* lw_new(animal_klass_info* klass)
 2 
 3 {
 4 
 5     animal* p = ANIMAL(malloc(klass->size));
 6 
 7     p->klass = klass;
 8 
 9     return klass->ctor(p);
10 
11 }

 

这样lw_delete函数就能够根据传入的对象的地址,取得相应的类信息地址,进而取得析构函数了,lw_delete实现如下:

 1 void lw_delete(animal* self)
 2 
 3 {
 4 
 5     if(self)
 6 
 7     {
 8 
 9         free(self->klass->dtor(self));
10 
11     }
12 
13 }

 

至此,lw_new和lw_delete都已经实现了,我们测试一下吧.

 1 int main(int argc, char* argv)
 2 
 3 {
 4 
 5     argc;
 6 
 7     argv;
 8 
 9  
10 
11     animal* animal1 = NULL;
12 
13     animal* animal2 = NULL;
14 
15    
16 
17     animal1 = lw_new(animal_klass);
18 
19     animal_set_name(animal1, "Kitty");
20 
21     animal_set_weight(animal1, 30);
22 
23     printf("animal1, name : %s, weight : %d\n",
24 
25     animal_get_name(animal1),
26 
27     animal_get_weight(animal1));
28 
29     lw_delete(animal1);
30 
31  
32 
33     animal2 = lw_new(animal_klass);
34 
35     animal_set_name(animal2, "Bib");
36 
37     animal_set_weight(animal2, 10);
38 
39     printf("animal2, name : %s, weight : %d\n",
40 
41     animal_get_name(animal2),
42 
43     animal_get_weight(animal2));
44 
45     lw_delete(animal2);
46 
47     return 0;
48 
49 }

 

完整的代码可以在此处下载。

大家可以看出我们目前实现的lw_new和lw_delete只能用于创建和删除animal类的对象。如何实现通用的lw_new和lw_delete呢?这就是下篇我们将要介绍的内容。

C语言也能面向对象(三)——通用的new和delete

上篇中实现的lw_new和lw_delete函数只能创建和删除animal类的对象,这当然满足不了我们的需要,那么如何实现通用的lw_new和lw_delete函数,用来创建和删除任何类的对象呢?

考虑新定义一个类car,其属性有车标logo和颜色color:

 1 typedef struct _car car;
 2 
 3 struct _car
 4 
 5 {
 6 
 7 /*private data*/
 8 
 9     char logo[256];
10 
11     int color;
12 
13 };

 

成员函数:

1 char* car_get_logo(car* self);
2 
3 void car_set_logo(car* self, char* logo);
4 
5 int car_get_color(car* self);
6 
7 void car_set_color(car* self, int color);

 

使用lw_new创建car类对象时,必须知道car类的对象占用内存空间大小和构造函数,以便为对象申请内存空间,并初始化该对象,而使用lw_delete函数删除car类的对象时,就必须知道car类的析构函数,以便先析构该对象,然后再释放对象占用的内存空间。可以看出,创建和删除car类的对象所需要的三个必备条件就是:类的对象占用内存空间大小,构造函数和析构函数,与创建和删除animal类时所需的条件一致。那么我们是不是可以创建一个统一的类信息结构体,然后使用结构体的不同对象,保存不同类的类信息呢?

上篇中定义的animal类的类信息结构体如下:

 1 struct _animal_klass_info
 2 
 3 {
 4 
 5     animal_klass_info* super; /*animal's super's klass info*/
 6 
 7     char* name;        /*animal klass's name*/       
 8 
 9     size_t size;    /*animal's size*/
10 
11     animal* (*ctor)(animal* self); /*constructor*/
12 
13     animal* (*dtor)(animal* self); /*destructor*/
14 
15 };

 

super为父类的类信息地址,无父类,则其值为NULL,name是该类的名称,这两个成员先不考虑,我们着重考虑size、ctor和dtor这三个成员,看看是否能够将这个animal专用的类信息结构体,改造成统一的类信息结构体。size为类对象所占内存空间大小,类型为size_t,这对任何类都是通用的,所以无需变更。ctor和dtor都是函数指针类型,传入参数为animal对象的地址,在将animal对象初始化或析构后,再将该地址返回。既然要适用于任意类,在类信息结构体中就不能使用animal类的指针了,我们将结构体中这两个函数的参数和返回值类型弱化为void*类型,如下:

1 typedef void* (*voidf)(void*);
2 
3 voidf ctor;
4 
5 voidf dtor;

 

注意修改的只是类信息结构体中的函数类型,在实现animal类的构造函数和析构函数时,仍然可以直接使用animal类的指针类型,这样在初始化类信息时,编译器会警告类型不兼容,为了避免警告,我们将类信息结构体中的构造函数和析构函数指针弱化为void*类型,最终类信息结构体的定义为:

 1 typedef struct _klass_info klass_info;
 2 
 3 struct _klass_info
 4 
 5 {
 6 
 7     klass_info* super;    /*object's klass's super klass*/
 8 
 9     char* name; /*object's klass's name*/
10 
11     size_t size; /*object's size*/
12 
13     void* ctor; /*object's constructor*/
14 
15     void* dtor; /*object's destructor*/
16 
17 };

 

lw_new和lw_delete函数的实现也要响应修改,如下:

 1 void* lw_new(void* klass)
 2 
 3 {
 4 
 5     klass_info* kls = KLASS(klass);
 6 
 7     void* p = malloc(kls->size);
 8 
 9     *((klass_info**)p) = kls;
10 
11     return ((voidf)(kls->ctor))(p);
12 
13 }
14 
15  void lw_delete(void* self)
16 
17 {
18 
19     if(self)
20 
21     {
22 
23         klass_info* kls = *((klass_info**)self);
24 
25         free(((voidf)(kls->dtor))(self));
26 
27     }
28 
29 }

 

为了避免警告,lw_new和lw_delete函数的传入参数类型也弱化为void*类型。klass_info* kls = KLASS(klass)中使用了KLASS()宏,定义如下:

#define KLASS(_klass_info_)    ((klass_info*)_klass_info_)

用来将其他指针强制转换为klass_info*类型。

我们以car类为例,看看如何使用klass_info类信息结构体定义类。

  1 car.h
  2 
  3 extern klass_info* car_klass;
  4 
  5  
  6 
  7 #define CAR(_object_)    ((car*)_object_)
  8 
  9  
 10 
 11 typedef struct _car car;
 12 
 13 struct _car
 14 
 15 {
 16 
 17 /*class info*/
 18 
 19     klass_info* klass;
 20 
 21  
 22 
 23 /*private data*/
 24 
 25     char logo[256];
 26 
 27     int color;
 28 
 29 };
 30 
 31  
 32 
 33 char* car_get_logo(car* self);
 34 
 35 void car_set_logo(car* self, char* logo);
 36 
 37 int car_get_color(car* self);
 38 
 39 void car_set_color(car* self, int color);
 40 
 41 car.c
 42 
 43 #include "car.h"
 44 
 45  
 46 
 47 static car* car_ctor(car* self);
 48 
 49 static car* car_dtor(car* self);
 50 
 51  
 52 
 53 static klass_info local_car_klass =
 54 
 55 {
 56 
 57     NULL,
 58 
 59     "car_klass",
 60 
 61     sizeof(car),
 62 
 63     car_ctor,
 64 
 65     car_dtor,
 66 
 67 };
 68 
 69 klass_info* car_klass = &local_car_klass;
 70 
 71  
 72 
 73 static car* car_ctor(car* self)
 74 
 75 {
 76 
 77     memset(self->logo, 0x00, sizeof(self->logo));
 78 
 79     self->color = 0;
 80 
 81 }
 82 
 83  
 84 
 85 static car* car_dtor(car* self)
 86 
 87 {
 88 
 89     return self;
 90 
 91 }
 92 
 93  
 94 
 95 char* car_get_logo(car* self)
 96 
 97 {
 98 
 99     return self->logo;
100 
101 }
102 
103  
104 
105 void car_set_logo(car* self, char* logo)
106 
107 {
108 
109     strncpy(self->logo, logo, sizeof(self->logo));
110 
111 }
112 
113  
114 
115 int car_get_color(car* self)
116 
117 {
118 
119     return self->color;
120 
121 }
122 
123  
124 
125 void car_set_color(car* self, int color)
126 
127 {
128 
129     self->color = color;
130 
131 }

 

下面测试一下:

 1 int main(int argc, char* argv)
 2 
 3 {
 4 
 5     argc;
 6 
 7     argv;
 8 
 9  
10 
11     animal* animal1 = NULL;
12 
13     animal* animal2 = NULL;
14 
15     car* car1 = NULL;
16 
17     car* car2 = NULL;
18 
19    
20 
21     animal1 = lw_new(animal_klass);
22 
23     animal_set_name(animal1, "Kitty");
24 
25     animal_set_weight(animal1, 30);
26 
27     printf("animal1, name : %s, weight : %d\n",
28 
29     animal_get_name(animal1),
30 
31     animal_get_weight(animal1));
32 
33     lw_delete(animal1);
34 
35  
36 
37     animal2 = lw_new(animal_klass);
38 
39     animal_set_name(animal2, "Bib");
40 
41     animal_set_weight(animal2, 10);
42 
43     printf("animal2, name : %s, weight : %d\n",
44 
45     animal_get_name(animal2),
46 
47     animal_get_weight(animal2));
48 
49     lw_delete(animal2);
50 
51  
52 
53     car1 = lw_new(car_klass);
54 
55     car_set_logo(car1, "Honda");
56 
57     car_set_color(car1, 0);
58 
59     printf("car1, logo : %s, color : %d\n",
60 
61     car_get_logo(car1),
62 
63     car_get_color(car1));
64 
65     lw_delete(car1);
66 
67  
68 
69     car2 = lw_new(car_klass);
70 
71     car_set_logo(car2, "BMW");
72 
73     car_set_color(car2, 1);
74 
75     printf("car2, logo : %s, color : %d\n",
76 
77     car_get_logo(car2),
78 
79     car_get_color(car2));
80 
81     lw_delete(car2);
82 
83  
84 
85     return 0;
86 
87 }

 

到这里我们已经能够使用lw_new和lw_delete函数创建和删除任何类的对象,并且自动调用其构造函数和析构函数了,本篇文章相关代码可以到这里下载

下篇文章我们将讨论继承。

C语言也能面向对象(四)(继承)

在C++中如果一个类有父类,那么这个类的对象中就包含了父类中定义的数据,并且可以使用父类的函数访问或操作该这些数据。在C中如何实现这样的机制呢?

animal类的定义如下:

 1 typedef struct _animal animal;
 2 
 3 struct _animal
 4 
 5 {
 6 
 7 /*class info*/
 8 
 9     klass_info* klass;
10 
11    
12 
13 /*private data*/
14 
15     char name[256];
16 
17     int weight;
18 
19 };

 

现在我们再定义一个dog类,除了包含animal类的属性外,还包括一个age属性,即年龄。如下:

 1 typedef struct _dog dog ;
 2 
 3 struct _dog
 4 
 5 {
 6 
 7 /*class info*/
 8 
 9     klass_info* klass;
10 
11    
12 
13 /*private data*/
14 
15     char name[256];
16 
17     int weight;
18 
19     int age;
20 
21 };

 

在保持类的对象的内存布局不变的情况下,我们可以将dog类的定义变换为:

 1 typedef struct _dog dog;
 2 
 3 struct _dog
 4 
 5 {
 6 
 7 /*base*/
 8 
 9     animal base;
10 
11  
12 
13 /*private data*/
14 
15     int age;
16 
17 };

 

两个dog类的定义在内存布局上是完全一致的。因此,只要将dog类对象的指针强制转型为animal类的指针,那么animal类的成员函数就可以访问或操作 dog类对象了,如下:

1 dog* my_dog = lw_new(dog_klass);
2 
3 animal_set_weight(ANIMAL(my_dog), 40);
4 
5 int weight = animal_get_weight(ANIMAL(my_dog));
6 
7 lw_delete(my_dog);

 

在C++中创建子类的对象时,要先调用父类的构造函数,然后再调用子类的函数;删除子类对象时,要先调用子类的析构函数,然后再调用父类的析构函数。

animal类是dog类的父类,在定义dog类信息时,要将dog类信息中的super属性初始化为animal类信息的地址,如下:

 1 static klass_info local_dog_klass =
 2 
 3 {
 4 
 5     animal_klass,
 6 
 7     "dog_klass",
 8 
 9     sizeof(dog),
10 
11     dog_ctor,
12 
13     dog_dtor,
14 
15 };

 

在实现dog的构造函数时,就可以使用类信息的super成员获取其父类的构造函数地址,如下:

 1 static dog* dog_ctor(dog* self)
 2 
 3 {
 4 
 5     ((voidf)(klass_of(self)->super->ctor))(self);
 6 
 7     self->age = 0;
 8 
 9     return self;
10 
11 }

 

klass_of函数的功能是获取对象的类信息地址。可以看出dog类的构造函数是先调用父类的构造函数,然后再初始化子类的属性。类似,析构函数的实现如下:

1 static dog* dog_dtor(dog* self)
2 
3 {
4 
5     ((voidf)(klass_of(self)->super->dtor))(self);
6 
7     return self;
8 
9 }

 

目前为止,我们都是使用结构体初始化的方式来初始化类信息,这种方式有代码重复、容易犯错,难于维护的缺点,例如,animal类信息的初始化在animal.c中已经实现过,定义dog类时在dog.c中又要再次实现,如果再定义其他继承自animal类的子类,则还要实现。按顺序初始化结构体这种做法本身就很容易犯错,如果这样的代码到处都是,那么维护难度就可想而知了。

所以我们考虑使用类信息初始化函数来初始化类信息,即专门定义一个函数用来初始化类信息,该函数在程序运行后,第一次创建该类的对象时由lw_new函数调用。在类信息结构体中新加入一个属性,即类信息初始化函数的地址,如下:

 1 typedef struct _klass_info klass_info;
 2 
 3 struct _klass_info
 4 
 5 {
 6 
 7     void* init; /*initialize function*/
 8 
 9     klass_info* super;    /*object's klass's super klass*/
10 
11     char* name; /*object's klass's name*/
12 
13     size_t size; /*object's size*/
14 
15     void* ctor; /*object's constructor*/
16 
17     void* dtor; /*object's destructor*/
18 
19 };

 

init属性在定义静态类信息结构体对象时初始化,其他属性在init指向的函数中初始化,以animal类为例:

 1 static animal_klass_info local_animal_klass = {animal_init};
 2 
 3 animal_klass_info* animal_klass = &local_animal_klass;
 4 
 5  
 6 
 7 void animal_init(void)
 8 
 9 {
10 
11     if(animal_klass->init)
12 
13     {
14 
15         animal_klass->init = NULL;
16 
17         animal_klass->super = NULL;
18 
19         animal_klass->name = "animal_klass";
20 
21         animal_klass->size = sizeof(animal);
22 
23         animal_klass->ctor = animal_ctor;
24 
25         animal_klass->dtor = animal_dtor;
26 
27     }
28 
29 }

 

lw_new函数修改如下:

 1 void* lw_new(void* klass)
 2 
 3 {
 4 
 5     klass_info* kls = KLASS(klass);
 6 
 7     if(kls->init)
 8 
 9     {
10 
11         ((init_fun)kls->init)();
12 
13     }
14 
15    
16 
17     void* p = malloc(kls->size);
18 
19     *((klass_info**)p) = kls;
20 
21     return ((voidf)(kls->ctor))(p);
22 
23 }

 

类信息初始化函数中会将类信息结构体的init属性置空,所以该函数只会被调用一次,即第一次创建该类的对象时调用。

子类的类信息初始化函数可以直接调用父类的类信息初始化函数,以dog类为例:

 1 void dog_init(void)
 2 
 3 {
 4 
 5     if(dog_klass->init)
 6 
 7     {
 8 
 9         animal_init();
10 
11         memcpy(dog_klass, animal_klass, sizeof(animal_klass_info));
12 
13         dog_klass->super = animal_klass;
14 
15         dog_klass->name = "dog_klass";
16 
17         dog_klass->size = sizeof(dog);
18 
19         dog_klass->ctor = dog_ctor;
20 
21     }
22 
23 }

 

至此我们实现了类继承,并使用类信息初始化函数来初始化类信息。

这里是相关代码。

下篇文章我们将讨论多态。

 

C语言中的面向对象(1)-类模拟和多态,继续

  在面向对象的语言里面,出现了类的概念。这是编程思想的一种进化。所谓类:是对特定数据的特定操作的集合体。所以说类包含了两个范畴:数据和操作。而视频教程'>c语言中的struct仅仅是数据的集合。(liyuming1978@163.com)

 

  1.实例:下面先从一个小例子看起

 

  1 #ifndef C_Class
  2 
  3        #define C_Class struct
  4 
  5 #endif
  6 
  7  
  8 
  9 C_Class A {
 10 
 11        C_Class A *A_this;
 12 
 13        void (*Foo)(C_Class A *A_this);
 14 
 15        int a;
 16 
 17        int b;
 18 
 19 };
 20 
 21  
 22 
 23 C_Class B{               //B继续了A
 24 
 25        C_Class B *B_this;  //顺序很重要
 26 
 27        void (*Foo)(C_Class B *Bthis);              //虚函数
 28 
 29        int a;
 30 
 31        int b;
 32 
 33  
 34 
 35        int c;
 36 
 37 };
 38 
 39  
 40 
 41  
 42 
 43 void B_F2(C_Class B *Bthis)
 44 
 45 {
 46 
 47        printf("It is B_Fun\n");
 48 
 49 }
 50 
 51  
 52 
 53 void A_Foo(C_Class A *Athis)
 54 
 55 {
 56 
 57        printf("It is A.a=%d\n",Athis->a);//或者这里
 58 
 59 //     exit(1);
 60 
 61 //     printf("纯虚 不答应执行\n");//或者这里
 62 
 63 }
 64 
 65  
 66 
 67 void B_Foo(C_Class B *Bthis)
 68 
 69 {
 70 
 71        printf("It is B.c=%d\n",Bthis->c);
 72 
 73 }
 74 
 75  
 76 
 77 void A_Creat(struct A* p)
 78 
 79 {
 80 
 81        p->Foo=A_Foo;
 82 
 83        p->a=1;
 84 
 85        p->b=2;
 86 
 87        p->A_this=p;
 88 
 89 }
 90 
 91  
 92 
 93  
 94 
 95 void B_Creat(struct B* p)
 96 
 97 {
 98 
 99        p->Foo=B_Foo;
100 
101        p->a=11;
102 
103        p->b=12;     
104 
105        p->c=13;
106 
107        p->B_this=p;
108 
109 }
110 
111  
112 
113  
114 
115 int main(int argc, char* argv[])
116 
117 {
118 
119        C_Class A *ma,a;
120 
121        C_Class B *mb,b;
122 
123  
124 
125        A_Creat(&a);//实例化
126 
127        B_Creat(&b);
128 
129  
130 
131        mb=&b;
132 
133        ma=&a;
134 
135  
136 
137        ma=(C_Class A*)mb;//引入多态指针
138 
139        printf("%d\n",ma->a);//可惜的就是 函数变量没有private
140 
141        ma->Foo(ma);//多态
142 
143        a.Foo(&a);//不是多态了
144 
145        B_F2(&b);//成员函数,因为效率问题不使用函数指针
146 
147        return 0;
148 
149 }
150 
151  

 

  输出结果:

 

1 11
2 
3 It is B.c=13
4 
5 It is A.a=1
6 
7 It is B_Fun

 

C语言中的面向对象(2)-C语言的多态实现

  相信很多人都看过设计模式方面的书,大家有什么体会呢?Bridge,Proxy,Factory这些设计模式都是基于抽象类的。使用抽象对象是这里的一个核心。

      

  其实我觉得框架化编程的一个核心问题是抽象,用抽象的对象构建程序的主体框架,这是面向对象编程的普遍思想。用抽象构建骨架,再加上多态就形成了一个完整的程序。由于C++语言本身实现了继续和多态,使用这样的编程理念(理念啥意思?跟个风,嘿嘿)在C++中是十分普遍的现象,可以说Virtual(多态)是VC的灵魂。

 

  但是,使用视频教程'>c语言的我们都快把这个多态忘光光了。我常听见前辈说,类?多态?我们用的是C,把这些忘了吧。很不幸的是,我是一个固执的人。这么好的东西,为啥不用呢。很兴奋的,在最近的一些纯C代码中,我看见了C中的多态!下面且听我慢慢道来。

 

  1. VC中的Interface是什么

 

  Interface:中文解释是接口,其实它表示的是一个纯虚类。不过我所要说的是,在VC中的Interface其实就是struct,查找Interface的定义,你可以发现有这样的宏定义:

 

      

1  #Ifndef Interface
2 
3  #define Interface struct
4 
5  #endif

 

 

  而且,实际上在VC中,假如一个类有Virtual的函数,则类里面会有vtable,它实际上是一个虚函数列表。实际上C++是从C发展而来的,它不过是在语言级别上支持了很多新功能,在C语言中,我们也可以使用这样的功能,前提是我们不得不自己实现。

 

  2.C中如何实现纯虚类(我称它为纯虚结构)

 

  比较前面,相信大家已经豁然开朗了。使用struct组合函数指针就可以实现纯虚类。

 

  例子:

 

    

 1  typedef struct {
 2 
 3         void  (*Foo1)();
 4 
 5         char  (*Foo2)();
 6 
 7         char*  (*Foo3)(char* st);
 8 
 9     }
10 
11   MyVirtualInterface;

 

      

  这样假设我们在主体框架中要使用桥模式。(我们的主类是DoMyAct,接口具体实现类是Act1,Act2)下面我将依次介绍这些“类”。(C中的“类”在前面有说明,这里换了一个,是使用早期的数组的办法)

 

  主类DoMyAct: 主类中含有MyVirtualInterface* m_pInterface; 主类有下函数:

 

   

 1  DoMyAct_SetInterface(MyVirtualInterface* pInterface)
 2 
 3     {
 4 
 5         m_pInterface= pInterface;
 6 
 7     }
 8 
 9     DoMyAct_Do()
10 
11     {
12 
13         if(m_pInterface==NULL) return;
14 
15         m_pInterface->Foo1();
16 
17         c=m_pInterface->Foo2();
18 
19     }
20 
21  

 

  子类Act1:实现虚结构,含有MyVirtualInterface  st[MAX]; 有以下函数:

 

   

 1  MyVirtualInterface* Act1_CreatInterface()
 2 
 3     {
 4 
 5         index=FindValid() //对象池或者使用Malloc !应该留在外面申请,实例化
 6 
 7         if(index==-1) return NULL;
 8 
 9         St[index].Foo1=Act1_Foo1; // Act1_Foo1要在下面具体实现
10 
11         St[index].Foo2=Act1_Foo2;
12 
13         St[index].Foo3=Act1_Foo3;
14 
15         Return &st [index];
16 
17     }

 

 

  子类Act2同上。

 

  在main中,假设有一个对象List。List中存贮的是MyVirtualInterface指针,则有:

 

   

 1  if( (p= Act1_CreatInterface()) != NULL)
 2 
 3     List_AddObject(&List, p); //Add All
 4 
 5  
 6 
 7     While(p=List_GetObject()){
 8 
 9         DoMyAct_SetInterface(p);//使用Interface代替了原来大篇幅的Switch Case
10 
11         DoMyAct_Do();//不要理会具体的什么样的动作,just do it
12 
13     }
14 
15  
16 
17     FREE ALL

C语言中的面向对象(3)-类模拟的性能分析

    类模拟中使用了大量的函数指针,结构体等等,有必须对此进行性能分析,以便观察这样的结构对程序的整体性能有什么程度的影响。

 

  1.函数调用的开销

 

 1 #define COUNTER XX
 2 
 3 void testfunc()
 4 
 5 {
 6 
 7     int i,k=0;
 8 
 9     for(i=0;i<YY;i++)
10 
11 }

 

 

  在测试程序里面,我们使用的是一个测试函数,函数体内部可以通过改变YY的值来改变函数的耗时。测试对比是 循环调用XX次函数,和循环XX次函数内部的YY循环。

 

  结果发现,在YY足够小,X足够大的情况下,函数调用耗时成为了主要原因。所以当一个“简单”功能需要“反复”调用的时候,将它编写为函数将会对性能有影响。这个时候可以使用宏,或者inline要害字。

 

  但是,实际上我设置XX=10000000(1千万)的时候,才出现ms级别的耗时,对于非实时操作(UI等等),即使是很慢的cpu(嵌入式10M级别的),也只会在XX=10万的时候出现短暂的函数调用耗时,所以实际上这个是可以忽略的。

 

  2.普通函数调用和函数指针调用的开销

 

1 void (*tf)();
2 
3 tf=testfunc;

 

 

  测试程序修改为一个使用函数调用,一个使用函数指针调用。测试发现对时间基本没有什么影响。(在第一次编写的时候,发现在函数调用出现耗时的情况下(XX=1亿),函数指针的调用要慢(release版本),调用耗时350:500。后来才发现这个影响是由于将变量申请为全局的原因,全局变量的访问要比局部变量慢很多)。

 

  3.函数指针和指针结构访问的开销

 

1 struct a {
2 
3     void (*tf)();
4 
5 }

 

 

  测试程序修改为使用结构的函数指针,测试发现对时间基本没有什么影响。其实使用结构并不会产生影响,因为结构的访问是固定偏移量的。所以结构变量的访问和普通变量的访问对于机器码来说是一样的。

 C语言中的面向对象(4)-面向对象思想

经常听见别人说面向对象的程序设计,以前在学校上课的时候,也有开面向对象程序设计这门课。可是不幸的是,这些都是以C++,甚至VC++为基础的。而更加不幸的是,多年以来我一直是一个C的使用者。在学校的时候,我主要做的是硬件上的驱动层,和底层功能层。在工作以后,又做的是手机上的软件开发,所有这些都是和C离不开的。虽然我不得不说,C++是一门很好的语言,但是它的编译速度,代码效率,编译后的代码大小都限制了它在嵌入式上的应用。(但现在的嵌入式CPU越来越快,内存容量变大。我觉得用C++也应该没有什么问题。这使我觉得似乎是嵌入式编译器的限制。虽然菲利普和TI似乎都有C++的编译器,但是似乎没人用这个。难道是太贵了? 但不管怎么说,嵌入式应用中,视频教程'>c语言的普遍使用是肯定的)

那么在面向过程的时代产生的C语言能否使用面向对象的思想呢?我认为是肯定可以的,C++不过是在语言级别上加入了对对象的支持,同时提供了丰富的对象库。而在C语言下,我们只好自力更生了。

 

一、 面向对象思想的目的是框架化,手段是抽象

 

相信很多人都明白面向对象讲了什么:类,抽象类,继续,多态。但是是什么原因促使这些概念的产生呢?

 

打个比方说:你去买显示器,然而显示器的品牌样式是多种多样的,你在买的过程中发生的事情也是不可猜测的。对于这样的事情,我们在程序语言中如何去描述呢。面向对象的思想就是为了解决这样的问题。编写一个程序(甚至说是一个工程),从无到用是困难的,从有到丰富是更加困难的。面向对象将程序的各个行为化为对象,而又用抽象的办法将这些对象归类(抽象),从而将错综复杂的事情简化为几个主要的有机组合(框架化)。

 

其实我们的身边很多东西都是这样组成的:比如说电脑:电脑是由主板,CPU加上各种卡组成的。这就是一个框架化。而忽略不同的CPU,不同的主板,不同的声卡,网卡,显卡的区别,这就是抽象。再比如说现在的教育网:是由主核心节点:清华,北大,北邮等几个,然后是各个子节点,依次组成了整个教育网网络。

 

所以我觉得面向对象的编程思想就是:一个大型工程是分层次结构的,每层又由抽象的结构连接为整体(框架化),各个抽象结构之间是彼此独立的,可以独立进化(继续,多态)。层次之间,结构之间各有统一的通讯方式(通常是消息,事件机制)。

 

二、 以前 C 语言编程中常用的“面向对象”方法

 

其实C语言诞生以来,人们就想了很多办法来体现“面向对象”的思想。下面就来说说我所知道的方法。先说一些大家熟悉的东东,慢慢再讲诡异的。呵呵

 

1. 宏定义:

 

有的人不禁要问,宏定义怎么扯到这里来了,我们可以先看一个简单的例子:

 

1 #define MacroFunction Afunction

 

 

然后在程序里面你调用了大量的AFunction,但是有一天,你忽然发现你要用BFunction了,(不过AFunction又不能不要,很有可能你以后还要调用),这个时候,你就可以#define MacroFunction Bfunction来达到这样的目的。

 

当然,不得不说这样的办法是too simple,sometime naïve的,因为一个很滑稽的问题是假如我一般要改为BFunction,一半不变怎么办? 那就只好查找替换了。

 

2. 静态的入口函数,保证函数名相同,利用标志位调用子函数:

 

这样的典型应用很多,比如说网卡驱动里面有一个入口函数Nilan(int FunctionCode,Para*)。具体的参数是什么记不清楚了。不过NiLan的主体是这样的:

 

 1 Long Nilan(int FunctionCode,Para*){
 2 
 3  
 4 
 5 Switch(FunctionCode){
 6 
 7  
 8 
 9 Case SendPacket: send(….)
10 
11  
12 
13 Case ReceivePacket: receive(…)
14 
15  
16 
17 …..
18 
19  
20 
21 }

 

 

写到这里大家明白什么意思了吧。保证相同的函数名就是说:网卡驱动是和pNA+协议栈互连的,那么如何保证pNA+协议栈和不同的驱动都兼容呢,一个简单的办法就是仅仅使用一个入口函数。通过改变假如函数的参数值,来调用内部的各个函数。这样的做法是可以进化的:假如以后想调用新的函数,增加相应的函数参数值就好了。假如我们将网卡驱动和pNA+协议栈看作两个层的话,我们可以发现:

 

层与层之间的互连接口是很小的(这里是一个入口函数),一般是采用名字解析的办法而不是具体的函数调用(利用FunctionCode调用函数,Nilan仅仅实现名字解析的功能) ――!接口限制和名字解析

 

接口限制:层与层之间仅仅知道有限的函数

 

名字解析:层与层之间建立共同的名字与函数的对应关系,之间利用名字调用功能。

 

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优化之路,现在都快成垃圾信箱了,呵呵,网络的力量真是强大呀)

 

 

posted @ 2012-08-02 10:30  软件小书童  阅读(662)  评论(0)    收藏  举报