文章目录
- 一、面向对象的三个基本特征
- 1.1 封装
- 1.2 继承
- 1.3 多态
- 二、C语言实现封装
- 2.1 成员变量定义和访问控制
- 2.2 对象的创建和删除
- 2.3 成员函数的访问控制
- 三、C语言实现继承
- 3.1 子类继承父类成员变量
- 3.2 子类使用父类成员函数
- 四、C语言实现多态
- 4.1 基类中增加虚表指针
- 4.2 虚表的构建和初始化
- 4.3 利用虚表实现多态
- 4.4 代码仓库
一、面向对象的三个基本特征
1.1 封装
封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据和行为相结合,形成一个有机的整体,形成“类”,其中数据和函数都是类的成员。
1.2 继承
继承即派生类继承了基类的成员变量和成员函数,使子类拥有和父类相同的行为。
1.3 多态
多态同一个行为具有多个不同表现形式或形态的能力。在C++中具有继承关系的子类对象和父类对象对外提供一系列统一的接口,在外部调用时会根据对象类型产生不同的结果。
二、C语言实现封装
在一个类里面包含了成员变量和成员函数,成员变量代表类的属性,成员函数代表类的行为,C语言本身是一门结构化的语言,不直接直接面向对象的编程,但是面向对象只是一种编程手法,通过使用结构体和函数也可以实现同样的功能。
2.1 成员变量定义和访问控制
以顺序表为例,首先是顺序表数据成员,里面包含了长度信息和一个头指针,在C语言中并不支持模板类型,如果要存储任意类型的数据,只能存储对应数据的首地址,当需要存储的时候把地址放入到表中,拿出来后做强制转换,然后进行数据操作。
Vector是void类型,这样的作用是隐藏内部成员细节,相当于C++中的private成员。
typedef void* VectorNode;
typedef void Vector;
typedef struct
{
int length;
VectorNode* head;
}vector_def;
在C++中如果要构建一个对象,这个对象首先会使用构造函数对内部数据进行初始化,并且对象可以构建在堆空间和栈空间上,堆空间的对象可以通过智能指针或者手动释放,栈空间在生命周期结束后就不存在了。
而C语言里面并没有构造函数和析构函数,需要手动构建对象和析构对象,否则就会造成内存泄漏。
在顺序表这个实验里面表的空间是需要动态申请的,因此构造函数也从堆空间申请内存进行对象构建。
2.2 对象的创建和删除
Vector* vector_create(int length)
{
vector_def* ret = NULL;
if(length > 0)
{
ret = malloc(sizeof(vector_def));
ret->head = malloc(sizeof(VectorNode) * length);
if(ret && ret->head)
{
ret->length = length;
for(int i = 0; i < length; i++)
{
ret->head[i] = NULL;
}
}
}
return (Vector*)ret;
}
void vector_clear(Vector* list)
{
if(list)
{
free(((vector_def*)list)->head);
free(list);
}
}
在一个类中除了成员变量还有对应的成员函数,C++在调用时候会传递对象地址用于在成员函数中访问成员变量,并且隐藏了实现细节,C语言里面需要显式传递对象地址,这样才能访问到成员变量,并且由于函数参数中包含了对应类型的变量,因此该函数只能被该类使用。
2.3 成员函数的访问控制
下面使用顺序表的基本操作来进行演示,这三个函数是需要提供给外部使用的,相当于公有成员函数:
bool vector_insert(Vector* list, int i, const VectorNode node)
{
bool ret = true;
vector_def* obj = (vector_def*)list;
if(obj && (i >= 0) && (i < obj->length) && node)
{
for(int j = obj->length - 1; j > i; j--)
{
obj->head[j] = obj->head[j - 1];
}
obj->head[i] = node;
}
else
{
ret = false;
}
return ret;
}
VectorNode vector_remove(Vector* list, int i)
{
VectorNode ret = NULL;
vector_def* obj = (vector_def*)list;
if(obj && (i >= 0) && (i < obj->length))
{
for(int j = i + 1; j < obj->length; j++)
{
obj->head[j - 1] = obj->head[j];
}
ret = obj->head[i];
}
return ret;
}
bool vector_get(Vector* list, int i, VectorNode* node)
{
bool ret = true;
vector_def* obj = (vector_def*)list;
if(obj && (i >= 0) && (i < obj->length) && node)
{
*node = obj->head[i];
}
else
{
ret = false;
}
return ret;
}
int vector_length(Vector* list)
{
int ret = 0;
ret = (list) ? ((vector_def*)list)->length : (0);
return ret;
}
与公有成员函数相对应的还有私有成员函数,不能被外部进行访问,这时可以使用static修饰对应的成员函数,让该函数只能被内部使用。
三、C语言实现继承
3.1 子类继承父类成员变量
子类继承父类之后,在数据成员上面表现为叠加,并且子类可以使用父类的函数。
此处使用链表来进行演示。
typedef void LinkList;
typedef void* LinkListNode;
// 继承父类vector_def
typedef struct
{
vector_def base;
struct link_list_node head;
}link_list_def;
// 链表节点定义
struct link_list_node
{
struct link_list_node* next;
};
创建和销毁链表
LinkList* link_list_create()
{
link_list_def* list = malloc(sizeof(link_list_def));
if(list)
{
list->base.length = 0;
list->base.head = NULL;
list->head.next = NULL;
}
return (LinkList*)list;
}
void link_list_clear(LinkList* list)
{
free(list);
}
3.2 子类使用父类成员函数
使用父类的成员函数获取链表长度:
int main()
{
LinkList* list = link_list_create();
printf("%d\n", vector_length(list));
link_list_clear(list);
}
$> ./a.out
0
由于链表的数据插入、移除和获取操作和顺序表不一样,因此需要重新编写相对应的函数:
bool link_list_insert(LinkList* list, int i, const LinkListNode node)
{
bool ret = true;
link_list_def* obj = (link_list_def*)list;
if(obj && (i >= 0) && (i <= obj->base.length) && node)
{
struct link_list_node* current = position(obj, i);
if(current)
{
((struct link_list_node*)node)->next = current->next;
current->next = node;
++obj->base.length;
}
}
else
{
ret = false;
}
return ret;
}
LinkListNode link_list_remove(LinkList* list, int i)
{
LinkListNode ret = NULL;
link_list_def* obj = (link_list_def*)list;
if(obj && (i >= 0) && (i < obj->length))
{
struct link_list_node* current = link_list_position(obj, i);
struct link_list_node* next = (current) ? (current->next) : (NULL);
if(current && next)
{
ret = next;
current->next = next->next;
--obj->length;
}
}
return ret;
}
bool link_list_get(LinkList* list, int i, LinkListNode* node)
{
bool ret = true;
link_list_def* obj = (link_list_def*)list;
if(obj && (i >= 0) && (i < obj->length) && node)
{
struct link_list_node* current = position(obj, i);
*((struct link_list_node**)node) = current->next;
}
else
{
ret = false;
}
return ret;
}
四、C语言实现多态
C++中如果一个类中存在虚函数,那么产生的对象都会有一个虚表指针,虚表指针位于对象的最前面,这个虚表指针指向虚函数表,虚函数表在构造函数中初始化,在析构函数中销毁,所以在这两个函数中都不会发生多态行为。
4.1 基类中增加虚表指针
上面的链表继承自顺序表,显然这种继承方式并不好,因此可以将其进行重构,首先定义一个接口 list_vtable_def,里面包含了顺序表和链表的方法,但是并没有具体实现,相当于C++中的纯虚类。
除了相同的接口之外,他们还有一些相同的属性,这些属性用变量来进行表示,在顺序表和链表中,表的长度就是一种属性,由此在派生出一个抽象类 list_def。
C语言如果要实现多态也可以参考C++的内部实现,在基类中增加一个成员虚表指针。
而刚才所说的 list_vtable_def就是一个虚函数表的定义,派生出的每一个对象都会有一个虚表指针,指向对应类的虚函数表,虚表在构造函数中初始化,在析构函数中销毁。
typedef struct
{
bool (*insert)(List* list, int i, const ListNode node);
ListNode (*remove)(List* list, int i);
bool (*get)(List* list, int i, ListNode* node);
int (*length)(List* list);
}list_vtable_def;
typedef struct
{
list_vtable_def* vtable;
int length;
}list_def;
定义好了抽象类之后,前面两个类的定义也需要进行修改:
typedef struct
{
list_def base;
VectorNode* head;
}vector_def;
typedef struct
{
list_def base;
struct link_list_node head;
}link_list_def;
4.2 虚表的构建和初始化
除了需要定义虚函数表之外,每一个不同的类都需要定义一个不同的虚表,将虚表指针指向它,下面来看一下顺序表和链表中的虚表以及他们的构造:
// 定义在vector.c中的虚表
static vector_vtable s_vector_vtable = {
.insert = vector_insert,
.remove = vector_remove,
.get = vector_get,
.length = vector_length
};
// 构造函数
Vector* vector_create(int length)
{
vector_def* ret = NULL;
if(length > 0)
{
ret = malloc(sizeof(vector_def));
ret->head = malloc(sizeof(VectorNode) * length);
if(ret && ret->head)
{
ret->base.vtable = &s_vector_vtable;
ret->base.length = length;
for(int i = 0; i < length; i++)
{
ret->head[i] = NULL;
}
}
}
return (Vector*)ret;
}
// 定义在link_list.c中的虚表
static vector_vtable s_link_list_vtable =
{
.insert = link_list_insert,
.remove = link_list_remove,
.get = link_list_get,
.length = vector_length
};
// 构造函数
LinkList* link_list_create()
{
link_list_def* list = malloc(sizeof(link_list_def));
if(list)
{
list->base.length = 0;
list->base.vtable = &s_link_list_vtable;
list->head.next = NULL;
}
return (LinkList*)list;
}
4.3 利用虚表实现多态
前面定义了虚函数,利用虚函数可以实现多态,对外提供一个统一的接口供应用层调用:
typedef void List;
typedef void* ListNode;
void list_insert(List* list, int i, ListNode node)
{
if((*((list_vtable_def**)list))->insert)
{
(*((list_vtable_def**)list))->insert(list, i, node);
}
else
{
LOG(ERR_CONSTRUCT(NullPointer), "insert function not exist in vtable");
}
}
ListNode list_remove(List* list, int i)
{
ListNode ret = NULL;
if((*((list_vtable_def**)list))->remove)
{
ret = (*((list_vtable_def**)list))->remove(list, i);
}
else
{
LOG(ERR_CONSTRUCT(NullPointer), "remove function not exist in vtable");
}
return ret;
}
ListNode list_get(List* list, int i)
{
ListNode ret = NULL;
if((*((list_vtable_def**)list))->get)
{
(*((list_vtable_def**)list))->get(list, i, &ret);
}
else
{
LOG(ERR_CONSTRUCT(NullPointer), "get function not exist in vtable");
}
return ret;
}
int list_length(List* list)
{
if((*((list_vtable_def**)list))->length)
{
return (*((list_vtable_def**)list))->length(list);
}
else
{
LOG(ERR_CONSTRUCT(NullPointer), "length function not exist in vtable");
return 0;
}
}
实现完成之后分别创建一个链表和一个顺序表,然后调用上面的四个函数:
#include "err.h"
#include "Vector.h"
#include "LinkList.h"
#define MAX_LEN 10
struct list_node
{
struct link_list_node next;
int data;
};
// 100个链表节点
struct list_node array1[MAX_LEN] = {0};
// 100个顺序表节点
int array2[MAX_LEN];
int main()
{
LinkList* list = link_list_create();
Vector* vector = vector_create(MAX_LEN);
// 将数据插入表中
for(int i = 0; i < MAX_LEN; i++)
{
array1[i].data = MAX_LEN - i;
array2[i] = i;
list_insert(list, i, &array1[i]);
list_insert(vector, i, &array2[i]);
}
printf("list length:%d\n", list_length(list));
printf("vector length:%d\n", list_length(vector));
// 输出链表中的值
printf("list:");
for(int i = 0; i < MAX_LEN; i++)
{
struct list_node* ret = list_get(list, i);
printf("%d ", ret->data);
}
printf("\n");
// 输出顺序表中的值
printf("vector:");
for(int i = 0; i < MAX_LEN; i++)
{
int* ret = list_get(vector, i);
printf("%d ", *ret);
}
printf("\n");
//移除链表中的数据
while(list_length(list))
{
list_remove(list, 0);
}
printf("list length:%d\n", list_length(list));
printf("vector length:%d\n", list_length(vector));
vector_clear(vector);
link_list_clear(list);
}
最终的结果
$> ./a.out
list length:10
vector length:10
list:10 9 8 7 6 5 4 3 2 1
vector:0 1 2 3 4 5 6 7 8 9
list length:0
vector length:10
4.4 代码仓库