封装的好处
1、隔离变化 : 将内部数据结构、内部使用函数以及全局变量等最容易变化部分封装起来,让变化的部分不影响系统其它部分。
2、降低复杂度:接口最小化软件是软件设计的基本原则之一。封装内部实现细节,只暴露最小接口,让系统简单明了。—— 《系统程序员成长计划》
如何封装
1、隐藏数据结构,即结构体,C语言结构在面向对象中充当类使用,调用不能直接调用数据结构中的成员
2、提供操作该数据结构的函数。哪怕只是存取数据结构的成员,也要包装成相应的函数。
3、提供创建和销毁函数
4、隐藏内部函数
(1)在头文件中,只放最少的接口函数的声明。
(2)在C文件中,所有内部函数都加上static关键字。
5、大多数情况下禁用全局变量,除单例模式外。
以双向链表学设计
链表数据结构
//通用的双向链表
typedef struct double_link_list
{
void *data;
struct double_link_list *prev;
struct double_link_list *next;
}dlist_t, d_elem_t;
链表数据域选择void *
就可以存储任意类型数据,以此达到通用的目的。
提供对外接口
1、链表创建和销毁
//动态创建链表
dlist_t *create_dlist(void)
{
dlist_t *list = (dlist_t *)malloc(sizeof(dlist_t));
if (list != NULL)
{
list->next = NULL;
list->prev = list;
}
return list;
}
int delete_dlist(dlist_t *list)
{
DLIST_IS_VALID(!list, 0);
free(list);
return 1;
}
//静态初始化链表
int init_dlist(dlist_t *list)
{
DLIST_IS_VALID(!list, 0);
list->next = NULL;
list->prev = NULL;
return 1;
}
2、在尾部插入元素
int dlist_push_back(dlist_t *list, d_elem_t *elem)
{
DLIST_IS_VALID(!list, 0);
DLIST_IS_VALID(!elem, 0);
dlist_t *tmp;
for (tmp = list; tmp->next!=NULL && tmp->next!=elem; tmp = tmp->next);
if (tmp->next == elem) //节点存在 不插入
return;
tmp->next = elem;
elem->prev = tmp;
return 1;
}
3、删除操作
//删除元素,删除并返回删除的元素
d_elem_t *dlist_remove(dlist_t *list, d_elem_t *del_elem)
{
DLIST_IS_VALID(!list, 0);
DLIST_IS_VALID(!del_elem, 0);
dlist_t *tmp;
for (tmp = list->next; tmp != NULL && tmp != del_elem; tmp = tmp->next);
if (tmp == NULL)
{
printf("无此节点\r\n");
return NULL;
}
if (tmp->next == NULL) //删除的是最后一个节点
{
tmp->prev->next = NULL;
tmp->prev = NULL;
return tmp;
}
tmp->prev->next = tmp->next; //删除节点的上一个节点的next指向删除节点的下一个节点
tmp->next->prev = tmp->prev; //删除节点的下一个节点的prev指针指向删除节点的上一个节点
return tmp;
}
//通过编号索引删除链表元素,删除成功返回删除元素
d_elem_t *dlist_remove_by_index(dlist_t *list, int index)
{
DLIST_IS_VALID(!list, 0);
int num;
dlist_t *tmp;
for (tmp = list->next, num = 0; tmp != NULL && num != index; tmp = tmp->next, num++);
if (tmp == NULL)
{
return NULL;
}
if (tmp->next == NULL) //最后一个元素,特殊处理
{
tmp->prev->next = NULL;
tmp->prev = NULL;
return tmp;
}
tmp->prev->next = tmp->next;
tmp->next->prev = tmp->prev;
tmp->prev = NULL;
tmp->next = NULL;
return tmp;
}
//删除最后一个元素,删除成功返回删除元素
d_elem_t *dlist_pop_back(dlist_t *list)
{
DLIST_IS_VALID(!list, 0);
dlist_t *tmp;
for (tmp = list; tmp->next != NULL; tmp = tmp->next);
tmp->prev->next = NULL;
tmp->prev = NULL;
return tmp;
}
4、通过索引index获取元素的数据
void *dlist_get_by_index(dlist_t *list, int index)
{
DLIST_IS_VALID(!list, 0);
int num;
dlist_t *tmp;
void *val;
for (tmp = list->next, num = 0; tmp != NULL && num != index; tmp = tmp->next, num++);
if (tmp == NULL)
{
return NULL;
}
val = tmp->data;
return val;
}
5、遍历链表
//遍历链表
//遍历链表
int dlist_traver(dlist_t *list, void (*to_do_cb)(void *elem, void *res), void *result)
{
DLIST_IS_VALID(!list, 0);
DLIST_IS_VALID(!to_do_cb, 0);
DLIST_IS_VALID(!result, 0);
dlist_t *tmp;
for (tmp = list->next; tmp != NULL; tmp = tmp->next)
{
to_do_cb(tmp, result);
}
printf("\r\n");
return 1;
}
一般写完链表都会写个遍历去打印看是否正确,然而存储的void *类型
数据,无法具体地选择打印整形还是字符串。那如何打印呢?数据域的数据类型只有程序员知道,而遍历的操作是一样,而打印不同数据或者做其他操作这个是变化。
函数指针是实现多态的手段,多态是隔离变化的秘诀
—— 《系统程序员成长计划》
所以使用to_do_cb函数指针(回调函数)让人去决定执行什么操作
Test
使用回调函数的妙用。对于以下遍历输出、求和、求最大值等操作都需要遍历链表,变化的是所做的功能,不变的是遍历的操作,为了不写重复代码,使用回调函数来执行遍历执行的操作。
//遍历输出整型数据链表数据
void print_int_node_cb(void *node, void *res)
{
d_elem_t *tmp = (d_elem_t *)node;
int dat = *(int *)(tmp->data);
printf("int node : %d\r\n", dat);
}
void print_str_node_cb(void *node, void *res)
{
d_elem_t *tmp = (d_elem_t *)node;
char *str = tmp->data;
printf("string node : %s\r\n", str);
}
void calc_list_data_sum(void *node, void *res)
{
d_elem_t *tmp = (d_elem_t *)node;
int *sum = res;
*sum += *(int *)tmp->data;
}
void calc_list_max_data(void *node, void *res)
{
d_elem_t *tmp = (d_elem_t *)node;
int *max = (int *)res;
int prev_dat;
int next_dat;
if (tmp->next != NULL)
{
prev_dat = *(int *)tmp->data;
next_dat = *(int *)tmp->next->data;
*max = prev_dat > next_dat ? prev_dat : next_dat;
}
}
int main()
{
dlist_t *list = create_dlist(); //存放整数的链表
int arr[10];
for (int j = 0; j < 10; j++)
{
arr[j] = j;
}
d_elem_t node[10];
int i = 0;
for (i = 0; i < 10; i++)
{
init_elem(&node[i], &arr[i]);
}
for (i = 0; i < 10; i++)
{
dlist_push_back(list, &node[i]);
}
dlist_traver(list, print_int_node_cb, NULL); //遍历打印整形数据
int sum = 0;
dlist_traver(list, calc_list_data_sum, &sum); //遍历求元素和
printf("链表和为 : %d\r\n", sum);
int max = 0;
dlist_traver(list, calc_list_max_data, &max); //遍历找到元素最大值
printf("链表数据最大值为 : %d\r\n", max);
return 0;
}