对象是 C 语言中最重要的概念。有基础的小伙伴可能会反问:“C 语言不是面向过程的吗?怎么会有对象呢?好像C++、Java之类的语言才有对象!”说的没错,但是这样问显然是未曾对 C 的灵魂——指针进行过探索。
你可能听过“指针是 C 语言的灵魂所在”、“利用指针可以实现高级语言提供的操作”之类的话。
那么接下就让我们乘坐“指针”的快车,在 C 语言中构造一个真正的对象。
既然是构造对象,那就免不了描述对象的内部结构,即对象本身的数据结构,很显然,struct 结构体就很适合,但是 struct 的短板在于无法直接存储函数。而这一点我们可以使用指针来解决。
下面我们尝试构造一个 Student 学生的对象,定义一个对象的前提是要有对应的类。类是对对象的抽象。
那么,请问在开发过程中,是现有对象还是现有类呢?
从教科书中来,那自然是现有类,再有对象。
但在实际的项目开发中,一般应该是先有对象,然后再有类。原因就在于那句“类是对对象的抽象。”
除非这个对象是别人已经抽象好了的,那你当然不用再额外搞一个对象了,因为已经有一个与这个对象相对应的类了......
一、对象/类的结构
学生的一般包含姓名、年级、年纪等很多信息,为方便起见,这里只使用这三个基本信息做演示。
为简便使用 Student 类型,在此将该结构体指针直接声明为 Student 类型
typedef struct struct_Student * Student;
struct struct_Student {
char * name; // 姓名
int year; // 入学年
int age; // 年纪
};
二、针对属性的操作
一般情况下,对象是动态分配空间的,对象本身不属于函数来管辖,由用户手动控制对象本身所占内存空间的释放与分配。
在 C 语言中,一般使用下表中的函数进行内存空间的操作
void *malloc(size_t size)
分配 size 字节的内存,并返回该内存的指针,若失 败返回 NULL。
void *calloc(size_t nitems, size_t size)
以 size 字节为单位,分配 ntimes 个连续的内存,并置零,并返回该内存的指针,若失败返回 NULL。
void *realloc(void *ptr, size_t size)
该内存是通过 malloc、calloc、realloc 进行分配的。若 ptr 指向 NULL,则分配新内存,并返回指向新的内存的指针,若失败返回 NULL。
void free(void *ptr)
释放 ptr 指向的内存。该内存是通过 malloc、calloc、realloc 进行分配的。
2.1 分配对象空间
分配空间时,对 malloc 的返回值进行断言,如果返回值为 NULL,说明空间分配失败,则中断程序执行。
Student Student_new () {
Student self = (Student) malloc (sizeof (struct struct_Student));
assert (self != NULL);
self->name = NULL;
self-> age = 0;
self->year = 0;
return self;
}
2.2 释放对象空间
释放空间之前,对目标对象进行判断,如果已经为 NULL,则不需要再释放。
void Student_del (Student self) {
if (self != NULL)
free (self);
}
2.3 其他一般方法
一般方法如:introduce 介绍方法 对象使用之前,应当进行判断是否为 NULL,如果为 NULL,则中断程序执行。
加上类名作为前缀是为了防止出现重名函数。
void Student_introduce (Student self) {
assert (self != NULL);
printf("My name is %s. I'm %d years old.\n", self->name, self->age);
}
三、类的继承
针对 Student 类,可以通过 Person 类来继承一些属性。一般情况,仅允许单继承,如果要实现多继承,则需要使用动态数组或列表来实现。
以下为 Person 类的实现:
typedef struct struct_Person * Person;
struct struct_Person {
char * name;
int age;
};
3.1 实现 Person
对象的构造
Person Person_new () {
Person self = (Person) malloc (sizeof (struct struct_Person));
assert (self != NULL);
self->name = NULL;
self-> age = 0;
return self;
}
3.2 通过继承设计 Student
对象
在通过继承设计 Student 对象时,Person parent 必须放置在最前面,只有这样,才能够为“多态”的实现提供便利。另外,如此安排也符合类之间继承的合理性、顺序性。
typedef struct struct_Student * Student;
struct struct_Student {
Person parent; // 父类
int year; // 独有的属性
};
3.3 实现 Student
对象的构造
Student Student_new () {
Student self = (Student) malloc (sizeof (struct struct_Student));
assert (self != NULL);
self->parent = Person_new ();
return self;
}
通过这种实现方式,我们发现基本上无法实现函数的重写和重载。因为在 C 语言中,不允许出现重名函数。
别忘了,我们还有指针。我们可以利用指针来为函数的调用进行“重定向”。
仍然以 Person 和 Student 为例。
四、基于指针的函数重载与重写
struct struct_Person {
char * name;
int age;
void (* introduce) (Person self)
};
// 为了给类的introduce变量赋值,该函数必须写在构造之前
void Person_introduce (Person self) {
assert (self != NULL);
printf("My name is %s. I'm %d years old.\n", self->name, self->age);
}
Person Person_new () {
Person self = (Person) malloc (sizeof (struct struct_Person));
assert (self != NULL);
self->name = NULL;
self-> age = 0;
self->introduce = Person_introduce;
return self;
}
利用指针实现函数的重载
typedef struct struct_Student * Student;
struct struct_Student {
Person parent; // 父类
int year; // 独有的属性
};
void Student_introduce (Student self) {
assert (self != NULL);
printf("My name is %s. I'm %d years old.\nI am a student in the Class of %d\n", self->parent->name, self->parent->age, self->year);
}
Student Student_new () {
Student self = (Student) malloc (sizeof (struct struct_Student));
assert (self != NULL);
self->parent = Person_new ();
self->parent->introduce = Student_introduce;
return self;
}