## 介绍 链表是一种数据结构,其中的数据元素逻辑上连续,但在物理上可以分散存储。链表能够通过指针将多个相同类型的数据块链接成一个完整的序列,在数据结构的实现中具有重要作用。 dList模块为通用的双向链表模块,其与sList模块非常相似,区别在于指针域由单向链表的一个指向改为两个指向(一个指向下一个,一个指向上一个),如此在存储结构上也有差异,sList为单向开环结构,dList为双向闭环结构(首尾相连,形成环形)。 在API方面,使用是和sList基本一致的(底层实现不太一样),其余性能和优缺点对比在下文展示。 ## 接口 ### 创建dList ```c dList *dList_create(void); ``` 在dList中,dList既是一个链表也是一个结点(因为结点也就是长度为1的链表)。所以此方法是创建一个为空的并且长度为1的链表(也就是创建一个空结点)。 ### 删除dList ```c void dList_delete(dList *list); ``` 此方法会删除该链表(包含其所有的结点)。 ### 设置和获取dList结点内容 ```c int dList_set(dList *list, void* data, int size); int dList_get(dList *list, void* data, int size); ``` 当一个结点被创建好之后,数据域是不具备内容的,需要通过`dList_set`方法设置其数据域内容,并且可以使用`dList_get`方法来获取结点数据域内容。 `dList_set`方法会将原来的数据覆盖掉,同时也是指定`size`为 **0** 来删除dList结点数据内容。 ```c static void test_set(void) { dList *list = NULL; int dataInt = 3; char *dataString = "Hello dList"; list = dList_create(); if (!list) { printf("dList_create Fail!\r\n"); return; } printf("dList_create Success!\r\n"); dList_set(list, &dataInt, sizeof(dataInt)); printf("list->data %d\r\n", dList_ref(list, int)); dList_set(list, dataString, strlen(dataString) + 1); printf("list->data %s\r\n", ((char *)(list->data))); dList_delete(list); } ``` 结果: ``` dList_create Success! list->data 3 list->data Hello dList ``` 示例中的`dList_ref`为数据引用,具体用法在下文 ### dList插入数据 ```c dList *dList_insert(dList **listRef, int index, void *data, int size); ``` 插入数据方法使用起来会更加简便,省去创建结点和设置数据的环节(即使是链表表头也可以省略创建,而由此方法内部完成),可以灵活的将指定数据插入到指定的位置上。 ```c static void test_insert(void) { dList *list = NULL; for (int i = 0; i < 2; i++) { if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; } int i = 100; if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } printf("------------\r\n"); dList_forEachReverse(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` data 0 data 1 data 100 ------------ data 100 data 1 data 0 ``` 示例中的`dList_forEachForward`和`dList_forEachReverse`为遍历方法,具体用法在下文。 对于传入dList引用为空时,会在首次创建结点并生成表头,传入`index`为负数表示插入到尾部。可以使用默认定义好的`dList_front`和`dList_back`宏定义来代表头部和尾部。 ### dList擦除数据 ```c int dList_erase(dList **listRef, int index, dList **outPrev); ``` 此方法对照`dList_insert`方法,擦除指定位置数据(会将该结点从链表中删除),同时为了更灵活使用,也支持获取被擦除的上一个结点(可以更便利高效得完成连续擦除)。 ```c static void test_erase(void) { dList *list = NULL; for (int i = 0; i < 5; i++) { if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; } dList_erase(&list, 0, NULL); dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` data 1 data 2 data 3 data 4 ``` 对照前面插入的例子,擦除表头。 ### dList推入和弹出 ```c int dList_pushFront(dList **listRef, void *data, int size); int dList_pushBack(dList **listRef, void *data, int size); int dList_popFront(dList **listRef); int dList_popBack(dList **listRef); ``` 分别就是头插、尾插、头删、尾删方法,其实就是在`dList_insert`和`dList_erase`方法基础上针对常用场景进行封装,使用更简便。 ```c static void test_pop(void) { dList *list = NULL; for (int i = 0; i < 5; i++) { if (!dList_pushFront(&list, &i, sizeof(i))) goto FAIL; } for (int i = 0; i < 5; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; } dList_popBack(&list); dList_popBack(&list); dList_popFront(&list); dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` data 3 data 2 data 1 data 0 data 0 data 1 data 2 ``` ### dList追加 ```c int dList_append(dList *list, dList **append); ``` 此方法可以将两个链表拼接成一个链表,`append`链表在拼接成功后会失效。 **注意** `append`需为表头,虽然即使不是表头也能拼接成功,但是其还属于原来的链表中,在操作时会出现一些意外。 ```c static void test_append(void) { dList *list = NULL, *ap = NULL; for (int i = 0; i < 10; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; if (!dList_pushBack(&ap, &i, sizeof(i))) goto FAIL; } if (!dList_append(list, &ap)) goto FAIL; printPoint(ap); dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); dList_delete(ap); } ``` 结果: ``` ap: 00000000 data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7 data 8 data 9 data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7 data 8 data 9 ``` ### dList链接结点 ```c dList *dList_attach(dList **listRef, int index, dList *attach); ``` 这个方法是将一个结点(或者一个链表)链接到现有的一个链表当中,可以通过index来指定具体链接到哪个位置,这个方法很灵活,直接操作链表结构,**一般情况使用不上此方法**,而是使用此方法所封装的`dList_insert`等方法。此方法可以搭配其他方法灵活二次封装成其他方法。 ```c static void test_attach(void) { dList *list = NULL, *a = NULL; for (int i = 0; i < 5; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; } for (int i = 0; i < 3; i++) { if (!dList_pushBack(&a, &i, sizeof(i))) goto FAIL; } dList_attach(&list, -1, a); dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` data 0 data 1 data 2 data 3 data 4 data 0 data 1 data 2 ``` ### dList断链结点 ```c dList *dList_detach(dList **listRef, int begin, int end, dList **outPrev); ``` 这个方法与`dList_attach`方法为对照方法,可以从链表中断链出来若干个结点(子链表),可以通过index来指定具体断链哪个位置和count指定个数,这个方法很灵活,直接操作链表结构,**一般情况使用不上此方法**,而是使用此方法所封装的`dList_erase`等方法。此方法可以搭配其他方法灵活二次封装成其他方法。 ```c static void test_detach(void) { dList *list = NULL, *node = NULL; for (int i = 0; i < 10; i++) { if (!dList_insert(&list, -1, &i, sizeof(i))) goto FAIL; } #if 1 node = dList_detach(&list, 0, 3, NULL); if (!node) { printf("dList_detach fail\r\n"); } #endif dList_forEachForward(node, n) { printf("node data %d\r\n", dList_ref(n, int)); } dList_delete(node); dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` node data 0 node data 1 node data 2 node data 3 data 4 data 5 data 6 data 7 data 8 data 9 ``` ### dList复制 ```c dList *dList_copy(dList *list, int begin, int end); ``` 这个方法是会根据源链表的指定区间进行深拷贝一份新的链表。 ```c static void test_copy(void) { dList *list = NULL, *copy = NULL; for (int i = 0; i < 10; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; } copy = dList_copy(list, -5, -1); if (!copy) { printf("dList_copy fail\r\n"); } dList_forEachForward(copy, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); dList_delete(copy); } ``` 结果: ``` data 5 data 6 data 7 data 8 data 9 ``` ### dList区间翻转 ```c int dList_reverse(dList *list, int begin, int end); ``` 这个方法是会根据源链表的指定区间进行翻转。 ```c static void test_reverse(void) { dList *list = NULL; for (int i = 0; i < 10; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; } if (!dList_reverse(list, 1, 5)) { printf("dList_reverse fail\r\n"); } dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` data 0 data 5 data 2 data 3 data 4 data 1 data 6 data 7 data 8 data 9 ``` ### dList获取指定结点 ```c dList *dList_to(dList *list, int index); ``` 这个方法可以根据表头当前位置获取偏移指定位置的结点,传入负数可以从末端往回找。 ```c static void test_to(void) { dList *list = NULL, *node; for (int i = 0; i < 10; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; } node = dList_to(list, -6); if (!node) { printf("dList_to fail\r\n"); goto FAIL; } printf("dList_to data %d\r\n", dList_ref(node, int)); dList_forEachForward(list, n) { printf("data %d\r\n", dList_ref(n, int)); } FAIL: dList_delete(list); } ``` 结果: ``` dList_to data 4 data 0 data 1 data 2 data 3 data 4 data 5 data 6 data 7 data 8 data 9 ``` ### dList大小 ```c int dList_size(dList *list); ``` 这个方法获取链表的数据个数。 ```c static void test_size(void) { dList *list = NULL; for (int i = 0; i < 10; i++) { if (!dList_pushBack(&list, &i, sizeof(i))) goto FAIL; } printf("size %d\r\n", dList_size(list)); printf("size %d\r\n", dList_size(dList_to(list, 3))); FAIL: dList_delete(list); } ``` 结果: ``` size 10 size 7 ``` ### dList遍历 ```c #define dList_forEach(list, node) // 从前往后遍历 #define dList_forEachForward(list, node) // 从前往后遍历 #define dList_forEachReverse(list, node) // 从后往前遍历 ``` 这个方法为遍历链表的方法,具体例子可以参考上文其他使用例子。 ### dList结点数据引用 ```c #define dList_ref(node, type) ``` 这个方法类似C++的引用,实则是操作指针(只是将其隐藏起来),读写更方便,但要注意的是数据操作别越界,比如本来该结点存储的是`char`型数据(分配空间也就只分配1个字节大小),如果当作`int`型使用那就越界了。具体例子可以参考上文其他使用例子。