- 一、面向对象的三个基本特征
- 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.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;
}
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;
}
与公有成员函数相对应的还有私有成员函数,不能被外部进行访问,这时可以使用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("%dn", 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;
}
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->base.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 基类中增加虚表指针C语言如果要实现多态也可以模拟C++的内部实现,在基类中增加一个成员虚表指针。
上面已经介绍了顺序表和链表中的插入和获取的不同实现,因此这两个函数应该是虚函数表中的成员:
typedef struct
{
bool (*insert)(Vector* list, int i, const VectorNode node);
bool (*get)(Vector* list, int i, VectorNode* node);
}vector_vtable;
typedef struct
{
vector_vtable* vtable;
int length;
VectorNode* head;
}vector_def;
4.2 虚表的构建和初始化
除了需要定义虚函数表的结构体之外,每一个不同的类型都需要定义一个不同的虚表,将虚表指针指向它,下面来看一下顺序表和链表中的虚表以及他们的构造:
// 定义在vector.c中的虚表
static vector_vtable s_vector_vtable = {
.insert = vector_insert,
.get = vector_get
};
// 构造函数
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->vtable = &s_vector_vtable;
ret->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,
.get = link_list_get
};
// 构造函数
linkList* link_list_create()
{
link_list_def* list = malloc(sizeof(link_list_def));
if(list)
{
list->base.length = 0;
list->base.head = NULL;
// 初始化虚表
list->base.vtable = &s_link_list_vtable;
list->head.next = NULL;
}
return (linkList*)list;
}
4.3 利用虚表实现多态
#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];
// 读取数据,最前面四个字节是虚表指针,通过虚表实现多态行为
VectorNode read_data(Vector* list, int i)
{
VectorNode ret = NULL;
// 使用各自的函数将数据进行获取
(*((vector_vtable**)list))->get(list, i, &ret);
return ret;
}
// 写入数据
void write_data(Vector* list, int i, VectorNode node)
{
(*((vector_vtable**)list))->insert(list, i, node);
}
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;
write_data(list, i, &array1[i]);
write_data(vector, i, &array2[i]);
}
printf("list length:%dn", vector_length(list));
printf("vector length:%dn", vector_length(vector));
// 输出链表中的值
printf("list:");
for(int i = 0; i < MAX_LEN; i++)
{
struct list_node* ret = read_data(list, i);
printf("%d ", ret->data);
}
printf("n");
// 输出顺序表中的值
printf("vector:");
for(int i = 0; i < MAX_LEN; i++)
{
int* ret = read_data(vector, i);
printf("%d ", *ret);
}
printf("n");
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 94.4 代码仓库
仓库



