C语言实现通用链表初步(四)----双向链表
在前面的文章中,我們討論了如何實現通用類型的鏈表,方法是用void *類型的指針,指向數據。那么還有其他的方法嗎(不考慮內核鏈表)?
答案是肯定的。用零長數組也可以實現。
這里的最后一個元素,是元素個數為0的數組。 其不占用任何空間,甚至是一個指針的空間都不占!
注意:在標準C和C++中,長度為0的數組是被禁止使用的。不過在GNU C中,存在一個非常奇怪的用法,那就是長度為0的數組。
在一個結構體的最后?,定義一個長度為0的數組,就可以使得這個結構體是可變長的。對于編譯器來說,這個長度為0的數組并不占用空間,因為數組名本身不占空間,它只是一個偏移量,?數組名這個符號本身代 表了一個不可修改的地址常量?
先來看看整個代碼的頭文件吧
#pragma once struct node_info {struct node_info *next;struct node_info *prev;char data[0]; };struct student {char name[20];unsigned char age;};//for test//有頭雙向循環鏈表 struct dlist_info {struct node_info *head;void (*add_head)(struct dlist_info *info,const void *data, size_t size);void (*add_tail)(struct dlist_info *info,const void *data, size_t size);void (*del)(struct node_info *node);struct node_info* (*find)(struct dlist_info *info,int (*compare)(void *dest_data, void *key_data), void *key_data);void (*for_each_safe)(struct dlist_info *info,void (*todo)(struct node_info *));};int dlist_init(struct dlist_info *info); void dlist_destroy(struct dlist_info *info);#define node_init(node) \do\{\(node)->next = (node);\(node)->prev = (node);\}while(0)#define dlist_is_empty(info) \((info)->head->next == (info)->head) 接下來我們實現一些方法
1.頭插
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "dlist.h"/* 有頭循環雙鏈表*/static void dlist_add_head(struct dlist_info *info,const void *my_data, size_t size) {assert(info != NULL && my_data != NULL);if (size == 0) {return ;}struct node_info *new_node = (struct node_info *)malloc(sizeof(struct node_info) + size);if (new_node == NULL) {fprintf(stderr, "out of memory\n");return ;} //數據域,內存拷貝memmove(new_node->data, my_data, size);//指針域修改new_node->next = info->head->next;new_node->prev = info->head;info->head->next = new_node;new_node->next->prev = new_node; }size 表示數據域占用了多少個字節。memmove(new_node->data, my_data, size); 這句話把用戶的數據拷貝到了結構體的最后。關于指針域的修改,是不是有點繞呢?沒有關系,畫圖就明白了。
2.尾插
static void dlist_add_tail(struct dlist_info *info,const void *my_data, size_t size) {assert(info != NULL && my_data != NULL);if (size == 0) {return ;}struct node_info *new_node = (struct node_info *)malloc(sizeof(struct node_info) + size);if (new_node == NULL) {fprintf(stderr, "out of memory\n");return ;} //數據域,內存拷貝memmove(new_node->data, my_data, size);//指針域修改new_node->next = info->head;new_node->prev = info->head->prev;info->head->prev->next = new_node;info->head->prev = new_node;}
3.刪除
static void dlist_del(struct node_info *node) {assert(node != NULL);node->next->prev = node->prev;node->prev->next = node->next;node_init(node); free(node); }因為申請空間的時候是帶著size一起申請的,所以這里的釋放就全部釋放了,不存在內存泄漏。
4.查找
static struct node_info *dlist_find(struct dlist_info *info,int (*key)(void *dest_data, void *key_data), void *key_data) {assert(info != NULL && key != NULL);if (dlist_is_empty(info)) {fprintf(stderr, "dlist is empty\n");return NULL;}struct node_info *cur = NULL;for (cur = info->head->next; cur != info->head; cur = cur->next) {if (key(cur->data, key_data) != 0) {return cur;}}return NULL; } 回調函數需要用戶自己實現,不用多說。5.安全遍歷
static void dlist_for_each_safe(struct dlist_info *info,void (*todo)(struct node_info *)) {assert(info != NULL && todo != NULL);struct node_info *cur = NULL;struct node_info *Next = NULL;for (cur = info->head->next; cur != info->head;cur = Next) {Next = cur->next;todo(cur);} }6.構造和析構 int dlist_init(struct dlist_info *info) {info->head = (struct node_info *)malloc(sizeof(struct node_info));if (info->head == NULL) {fprintf(stderr, "Error:Out of memory\n");return -1;}/*頭節點空間的初始化*/node_init(info->head);/*函數指針的掛接*/info->add_head = dlist_add_head;info->add_tail = dlist_add_tail;info->del = dlist_del;info->find = dlist_find;info->for_each_safe = dlist_for_each_safe;return 0; }void dlist_destroy(struct dlist_info *info) {// 依次刪除,直到為空while (!dlist_is_empty(info)) {dlist_del(info->head->next);} free(info->head); }
接下來是單元測試。
測試一下頭插和尾插吧。
#include "uni_test.h"#include "dlist.h"#include <stdio.h>void setup (void) {// will excute in every case }void teardown (void) {}void print_student(struct node_info *node) {struct student *p = (struct student *)(node->data);printf("Name: %15s Age:%d\n",p->name,p->age);}int compare_student(void *dest,void *src) {struct student *p1 = dest;struct student *p2 = src;if(strcmp(p1->name,p2->name)==0)return 1;elsereturn 0; }START_TEST(my_dlist_1) {struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};struct dlist_info list;dlist_init(&list);int i = 0;for(;i<sizeof(students)/sizeof(students[0]);++i)list.add_head(&list,students+i,sizeof(students[0]));list.for_each_safe(&list,print_student);printf("===========\n");dlist_destroy(&list);} END_TESTSTART_TEST(my_dlist_2) {struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};struct dlist_info list;dlist_init(&list);int i = 0;for(;i<sizeof(students)/sizeof(students[0]);++i)list.add_tail(&list,students+i,sizeof(students[0]));list.for_each_safe(&list,print_student);printf("===========\n");dlist_destroy(&list);} END_TEST運行結果如圖
Running suite(s): two_way_list_with_head
Name: ? ? WangGuozhen? Age:48
Name:? ? ? ? LiuDehua? Age:53
Name:? ? ZhangGuorong? Age:47
Name: ? ? ? LiuXuewei? Age:28
Name:? ? ? ? ? ChenYu? Age:27
Name: ? ? ? SunYazhou? Age:21
Name: ? ? ? ? LiuMing? Age:19
Name:? ? ? ? WangDong? Age:18
===========
Name:? ? ? ? WangDong? Age:18
Name: ? ? ? ? LiuMing? Age:19
Name: ? ? ? SunYazhou? Age:21
Name:? ? ? ? ? ChenYu? Age:27
Name: ? ? ? LiuXuewei? Age:28
Name:? ? ZhangGuorong? Age:47
Name:? ? ? ? LiuDehua? Age:53
Name: ? ? WangGuozhen? Age:48
===========
測試一下遍歷,在遍歷的過程中,我們把節點給刪除了。這可以體現出安全遍歷的好處。 START_TEST(my_dlist_3)//遍歷刪除 {struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};struct dlist_info list;dlist_init(&list);int i = 0;for(;i<sizeof(students)/sizeof(students[0]);++i)list.add_tail(&list,students+i,sizeof(students[0]));list.for_each_safe(&list,list.del);list.for_each_safe(&list,print_student);printf("===========\n");dlist_destroy(&list);} END_TEST
運行結果是:
===========
果然沒有節點了。
查找并刪除。
START_TEST(my_dlist_4)//查找并刪除 {struct student students[8] = {{"WangDong",18},{"LiuMing",19},{"SunYazhou",21},{"ChenYu",27},{"LiuXuewei",28},\{"ZhangGuorong",47},{"LiuDehua",53},{"WangGuozhen",48}};struct dlist_info list;dlist_init(&list);int i = 0;for(;i<sizeof(students)/sizeof(students[0]);++i)list.add_tail(&list,students+i,sizeof(students[0]));list.del(list.find(&list,compare_student,"ChenYu"));list.for_each_safe(&list,print_student);printf("===========\n");dlist_destroy(&list);} END_TESTName:? ? ? ? WangDong? Age:18
Name: ? ? ? ? LiuMing? Age:19
Name: ? ? ? SunYazhou? Age:21
Name: ? ? ? LiuXuewei? Age:28
Name:? ? ZhangGuorong? Age:47
Name:? ? ? ? LiuDehua? Age:53
Name: ? ? WangGuozhen? Age:48
===========
(完)
總結
以上是生活随笔為你收集整理的C语言实现通用链表初步(四)----双向链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 智慧城市产业图谱研究报告(2020年)
- 下一篇: 宝藏新品牌成长白皮书:新品牌心智与营销增