## 介绍 链表是一种数据结构,其中的数据元素逻辑上连续,但在物理上可以分散存储。链表能够通过指针将多个相同类型的数据块链接成一个完整的序列,在数据结构的实现中具有重要作用。 sList模块为通用的单向链表模块,它通过一系列相互链接的节点来存储线性的数据集合。每个节点包含数据域和指向下一个节点的指针域。 ## 接口 ### 创建sList ```c sList *sList_create(void); ``` 在sList中,sList既是一个链表也是一个结点(因为结点也就是长度为1的链表)。所以此方法是创建一个为空的并且长度为1的链表(也就是创建一个空结点)。 ### 删除sList ```c void sList_delete(sList *list); ``` 此方法会删除该链表(包含其所有的结点)。 ### 设置和获取sList结点内容 ```c int sList_set(sList *list, void* data, int size); int sList_get(sList *list, void* data, int size); ``` 当一个结点被创建好之后,数据域是不具备内容的,需要通过`sList_set`方法设置其数据域内容,并且可以使用`sList_get`方法来获取结点数据域内容。 `sList_set`方法会将原来的数据覆盖掉,同时也是指定`size`为 **0** 来删除sList结点数据内容。 ```c static void test_set(void) { sList *list = NULL; int dataInt = 3; char *dataString = "Hello sList"; char outBuffer[32]; list = sList_create(); if (!list) { printf("sList_create Fail!\r\n"); return; } printf("sList_create Success!\r\n"); sList_set(list, &dataInt, sizeof(dataInt)); printf("list->data %d\r\n", sList_ref(list, int)); sList_set(list, dataString, strlen(dataString) + 1); printf("list->data %s\r\n", ((char *)(list->data))); sList_get(list, outBuffer, sizeof(outBuffer)); printf("get data %s\r\n", outBuffer); sList_delete(list); } ``` 结果: ``` sList_create Success! list->data 3 list->data Hello sList get data Hello sList ``` 示例中的`sList_ref`为数据引用,具体用法在下文 ### sList插入数据 ```c sList *sList_insert(sList **listRef, int index, void *data, int size); ``` 插入数据方法使用起来会更加简便,省去创建结点和设置数据的环节(即使是链表表头也可以省略创建,而由此方法内部完成),可以灵活的将指定数据插入到指定的位置上。 ```c static void test_insert(void) { sList *list = NULL; for (int i = 0; i < 5; i++) { if (!sList_insert(&list, -1, &i, sizeof(i))) goto FAIL; } sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); } ``` 结果: ``` data 0 data 1 data 2 data 3 data 4 ``` 示例中的`sList_forEach`为遍历方法,具体用法在下文。 对于传入sList引用为空时,会在首次创建结点并生成表头,传入`index`为负数表示插入到尾部。可以使用默认定义好的`sList_front`和`sList_back`宏定义来代表头部和尾部。 ### sList擦除数据 ```c int sList_erase(sList **listRef, int index, sList **outPrev); ``` 此方法对照`sList_insert`方法,擦除指定位置数据(会将该结点从链表中删除),同时为了更灵活使用,也支持获取被擦除的上一个结点(可以更便利高效得完成连续擦除)。 ```c static void test_erase(void) { sList *list = NULL; for (int i = 0; i < 5; i++) { if (!sList_insert(&list, -1, &i, sizeof(i))) goto FAIL; } sList_erase(&list, 0, NULL); sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); } ``` 结果: ``` data 1 data 2 data 3 data 4 ``` 对照前面插入的例子,擦除表头。 ### sList推入和弹出 ```c int sList_pushFront(sList **listRef, void *data, int size); int sList_pushBack(sList **listRef, void *data, int size); int sList_popFront(sList **listRef); int sList_popBack(sList **listRef); ``` 分别就是头插、尾插、头删、尾删方法,其实就是在`sList_insert`和`sList_erase`方法基础上针对常用场景进行封装,使用更简便。 ```c static void test_pop(void) { sList *list = NULL; for (int i = 0; i < 5; i++) { if (!sList_pushFront(&list, &i, sizeof(i))) goto FAIL; } for (int i = 0; i < 5; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; } sList_popBack(&list); sList_popBack(&list); sList_popFront(&list); sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); } ``` 结果: ``` data 3 data 2 data 1 data 0 data 0 data 1 data 2 ``` ### sList追加 ```c int sList_append(sList *list, sList **append); ``` 此方法可以将两个链表拼接成一个链表,`append`链表在拼接成功后会失效。 **注意** `append`需为表头,虽然即使不是表头也能拼接成功,但是其还属于原来的链表中,在操作时会出现一些意外。 ```c static void test_append(void) { sList *list = NULL, *ap = NULL; for (int i = 0; i < 10; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; if (!sList_pushBack(&ap, &i, sizeof(i))) goto FAIL; } if (!sList_append(list, &ap)) goto FAIL; printPoint(ap); sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); sList_delete(ap); } ``` 结果: ``` 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 ``` ### sList链接结点 ```c sList *sList_attach(sList **listRef, int index, sList *attach); ``` 这个方法是将一个结点(或者一个链表)链接到现有的一个链表当中,可以通过index来指定具体链接到哪个位置,这个方法很灵活,直接操作链表结构,**一般情况使用不上此方法**,而是使用此方法所封装的`sList_insert`等方法。此方法可以搭配其他方法灵活二次封装成其他方法。 ```c static void test_attach(void) { sList *list = NULL, *a = NULL; for (int i = 0; i < 5; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; } for (int i = 0; i < 3; i++) { if (!sList_pushBack(&a, &i, sizeof(i))) goto FAIL; } sList_attach(&list, -1, a); sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); } ``` 结果: ``` data 0 data 1 data 2 data 3 data 4 data 0 data 1 data 2 ``` ### sList断链结点 ```c sList *sList_detach(sList **listRef, int index, int count, sList **outPrev); ``` 这个方法与`sList_attach`方法为对照方法,可以从链表中断链出来若干个结点(子链表),可以通过index来指定具体断链哪个位置和count指定个数,这个方法很灵活,直接操作链表结构,**一般情况使用不上此方法**,而是使用此方法所封装的`sList_erase`等方法。此方法可以搭配其他方法灵活二次封装成其他方法。 ```c static void test_detach(void) { sList *list = NULL, *node; for (int i = 0; i < 10; i++) { if (!sList_insert(&list, -1, &i, sizeof(i))) goto FAIL; } node = sList_detach(&list, 3, -1, NULL); if (!node) { printf("sList_detach fail\r\n"); } sList_forEach(node, n) { printf("node data %d\r\n", sList_ref(n, int)); } sList_delete(node); sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); } ``` 结果: ``` node data 3 node data 4 node data 5 node data 6 node data 7 node data 8 node data 9 data 0 data 1 data 2 ``` ### sList复制 ```c sList *sList_copy(sList *list, int begin, int end); ``` 这个方法是会根据源链表的指定区间进行深拷贝一份新的链表。 ```c static void test_copy(void) { sList *list = NULL, *copy = NULL; for (int i = 0; i < 10; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; } copy = sList_copy(list, 2, -1); if (!copy) { printf("sList_copy fail\r\n"); } sList_forEach(copy, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); sList_delete(copy); } ``` 结果: ``` data 2 data 3 data 4 data 5 data 6 data 7 data 8 data 9 ``` ### sList区间翻转 ```c int sList_reverse(sList *list, int begin, int end); ``` 这个方法是会根据源链表的指定区间进行翻转。 ```c static void test_reverse(void) { sList *list = NULL; for (int i = 0; i < 10; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; } if (!sList_reverse(list, sList_front, sList_back)) { printf("sList_reverse fail\r\n"); } sList_forEach(list, n) { printf("data %d\r\n", sList_ref(n, int)); } FAIL: sList_delete(list); } ``` 结果: ``` data 9 data 8 data 7 data 6 data 5 data 4 data 3 data 2 data 1 data 0 ``` ### sList获取指定结点 ```c sList *sList_to(sList *list, int index); ``` 这个方法可以根据表头当前位置获取偏移指定位置的结点。 ```c static void test_to(void) { sList *list = NULL, *node; for (int i = 0; i < 10; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; } node = sList_to(list, 6); if (!node) { printf("sList_to fail\r\n"); goto FAIL; } printf("data %d\r\n", sList_ref(node, int)); FAIL: sList_delete(list); } ``` 结果: ``` data 6 ``` ### sList大小 ```c int sList_size(sList *list); ``` 这个方法获取链表的数据个数。 ```c static void test_size(void) { sList *list = NULL; for (int i = 0; i < 10; i++) { if (!sList_pushBack(&list, &i, sizeof(i))) goto FAIL; } printf("size %d\r\n", sList_size(list)); printf("size %d\r\n", sList_size(sList_to(list, 3))); FAIL: sList_delete(list); } ``` 结果: ``` size 10 size 7 ``` ### sList遍历 ```c #define sList_forEach(list, node) ``` 这个方法为遍历链表的方法,具体例子可以参考上文其他使用例子。 ### sList结点数据引用 ```c #define sList_ref(node, type) ``` 这个方法类似C++的引用,实则是操作指针(只是将其隐藏起来),读写更方便,但要注意的是数据操作别越界,比如本来该结点存储的是`char`型数据(分配空间也就只分配1个字节大小),如果当作`int`型使用那就越界了。具体例子可以参考上文其他使用例子。