数据结构回顾 —— 从双向链表中学设计

封装的好处

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;
}

猜你喜欢

转载自blog.csdn.net/qq_36413982/article/details/108686011