【數據結構 · 初階】- 單鏈表

目錄

一.相關指針知識點

二.鏈表

1.為什么學了順序表還要學鏈表

2.優點

三.實現

1.鏈表的打印 —— 理解鏈表結構

(2) 物理結構圖

2.鏈表的尾插 —— 入門

錯誤寫法:tail != NULL

總結:

正確代碼物理圖解:

(2) 尾插整體代碼?(思考對嗎?)

Bug

3.頭插

4.尾刪

Bug

5.頭刪

6.查找

7. Find 查找的功能

(1)pos 之前插入

(2)pos 位置刪除

(3)pos 之后插入

(4)pos位置后面刪除

?8.銷毀

?四.思維提升

五.總結

1.傳什么?

2.要不要斷言?

(1)打印、查找

(2)pphead

(3)*pphead

六.整體代碼

SList.h

SList.c


一.相關指針知識點

調用一個函數,就會建立一個空間,這個空間叫棧幀。局部變量是存放在棧幀里的(除了static修飾的局部變量)。函數結束,棧幀空間就銷毀,局部變量也銷毀

函數傳參,不管是傳值,還是傳地址,其實都是拷貝。就看拷貝值還是地址。

代碼1:y 的改變,不會改變 x 的值

void Func(int y)
{y = 1;
}
int main()
{int x = 0;Func(x);return 0;
}

這是兩個棧幀,Func 里面是 y,main 里面是 x。x 傳給 y 是拷貝給 y,y 的改變不會影響 x,并且 Func 會銷毀

代碼2:解決上面問題,傳地址。改變的是 int ,使用的是 int 的指針

void Func(int* p)
{*p = 1;
}
int main()
{int x = 0;Func(&x);return 0;
}

這里的 p 是 x 地址的拷貝在傳參里面,我們要改變什么,就要用它的指針。然后 * 解引用可以改變

代碼3

void Func(int* ptr)
{ptr = (int*)malloc(sizeof(int));
}
int main()
{int* px = NULL;Func(px);free(px); // 加上也沒用return 0;
}

這也是拷貝值,把 px 的值拷貝給 ptr,ptr 是空。但是我 malloc 了一塊空間,讓 ptr 指向這塊空間。
px 拷貝給 ptr,ptr 的改變不會影響 px 。并且出了作用域 Func 銷毀,malloc 的內存塊還找不到了(內存泄漏),就算 free 也 free 不到

這里我們要改變的是 int* ,不是 int 。傳 int* 不起作用。應該傳 int**(二級指針)

代碼4:改變 int* ,使用 int* 的地址,int**(二級指針)

void Func(int** pptr)
{*pptr = (int*)malloc(sizeof(int));
}
int main()
{int* px = NULL;Func(&px);free(px);return 0;
}

這里把 px 的地址傳過去,pptr 指向 px 。malloc了一塊空間,是讓 *pptr 即 px 指向這塊空間
Func 結束,棧幀銷毀。但 px 還指向這塊空間,free 可以 free 到。這里內存釋放,值也拿回來了

二.鏈表

1.為什么學了順序表還要學鏈表

順序表是有很多缺陷的:
1)中間,頭部 插入,刪除數據,需要挪動數據,效率低下。你也不可能說在中間插入一塊空間,沒有這種概念,這本來就是一塊連續的空間。
(2)空間不夠需要擴容,拷貝數據,釋放舊空間。會有不小的消耗
擴容有一定的效率消耗。原地擴還好,異地擴呢?
還可能會有一定的空間浪費。一次擴太少,會頻繁擴;一次擴太多,浪費

能不能說,我用一點給一點呢?存一塊數據,開一塊空間
可以,但怎么管理呢?
順序表里,開了整片空間,由于存放的數據是連續的,只需要記錄這塊空間最開始的地址。
現在要一塊空間,去 malloc 。多次 malloc ,他們之間的地址不能保證相鄰。

這時候,鏈表會用一個指針指向第一個內存塊(節點 Node)。
為了通過第一個能找到第二個怎么辦?上一個會存下一個的地址,上一個指向下一個。
什么時候結束?順序表是 size 。鏈表里最后一個節點的指針存?NULL 即可

2.優點

不需要擴容。存一塊,開一塊。
可以從中間插入,不需要挪動數據。

順序表,鏈表是互補,相輔相成的。很多情況是配合起來使用的

三.實現

上面的理解,鏈表是一個個的內存塊,再由指針鏈接起來
先來定義它的結構:從語言的角度來說,凡是有多個數據,都要存到結構體里面
為方便替換成其他類型的數據,我們將類型統一重命名為 SLTDataType

1.鏈表的打印 —— 理解鏈表結構

SList.h

上一個節點要存下一個節點的地址,每個節點都是結構體類型,所以存結構體指針 next
鏈表要有個頭指針 phead 指向第一個節點,判斷結束只需要走到空 NULL 即可。
不能斷言 phead 為空,空鏈表也可以打印

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印鏈表
void SLTPrint(SLTNode* phead);

SList.c

void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur->next != NULL) 錯誤寫法!!!//while(cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next; // 指向下一個位置// 不能寫成 ++cur;}printf("NULL\n");
}

問:為什么不能寫成 ++cur ?
答:鏈表地址不連續,++cur 不能保證它指向下一個位置。如果強行把地址弄成連續,不就成順序表了嗎?


怎么理解 cur = cur->next;
cur 是結構體指針,cur-> 就是訪問結構體成員。next 是結構體成員,是下一個節點的地址
賦值操作是把下一個節點的地址給 cur?


為什么循環判斷條件 cur->next != NULL 為錯?
cur->next 是下一節點地址。走到尾就結束了,沒有打印最后的數據

(2) 物理結構圖

上面畫的是邏輯結構圖,是為方便理解,形象畫出來的
物理結構圖:實實在在數據在內存中的變化

2.鏈表的尾插 —— 入門

依然不能斷言 phead 為空。為空(沒有數據)依然可以尾插

順序表尾插,先要判斷空間夠不夠,不夠擴容。? ? ? 鏈表不用,永遠有空間

第一步:搞個節點,并初始化。后面多次用到,分裝成函數
第二步:找尾。? 尾的特征:tail->next == NULL

// 搞節點,并初始化
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}// 初始化newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode* phead, SLTDataType x) // 思考這里對嗎?
{SLTNode* newnode = BuySLTNode(x);// 找尾SLTNode* tail = phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;
}

錯誤寫法:tail != NULL

// 找尾
SLTNode* tail = phead;
while (tail != NULL)
{tail = tail->next;
}
tail = newnode;

從邏輯結構圖角度,看似正確:

從物理結構圖理解:




tail ,newnode 都是局部變量,出了作用域銷毀

上一個節點沒有存下一個節點的地址,鏈接失敗



總結:

tail 是個局部變量。不應該賦值給 tail 。應該賦值給 tail 指向的結構體(存放下一個節點地址的)成員

不為空鏈表尾插的本質:原尾節點中要存新的尾節點的地址



正確代碼物理圖解:

// 找尾
SLTNode* tail = phead;
while (tail->next != NULL)
{tail = tail->next;
}
tail->next = newnode;




tail ,newnode 都是局部變量,出了作用域銷毀

上一個節點存儲下一個節點的地址,鏈接成功

(2) 空鏈表尾插

phead == NULL? ? ? tail = phead? ? ? ??讓 phead 指向新節點即可

(2) 尾插整體代碼?(思考對嗎?)

SList.h

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印鏈表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode* phead, SLTDataType x); // 思考這里對不對

SList.c?

void SLTPushBack(SLTNode* phead, SLTDataType x) // 對嗎?
{SLTNode* newnode = BuySLTNode(x);if (phead == NULL){phead = newnode;}else{// 找尾SLTNode* tail = phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

Test.c

void TestSList1()
{SLTNode* plist = NULL;SLTPushBack(plist, 1);SLTPushBack(plist, 2);SLTPushBack(plist, 3);SLTPushBack(plist, 4);SLTPrint(plist);
}

Bug

我們運行上面的代碼:

看下圖,phead 和 newnode 都是結構體指針類型的指針變量
phead = newnode 是賦值行為,其真正含義是讓 phead 也指向 newnode 指向的新節點

函數結束,棧幀空間銷毀。我們的目標是讓 plist 指向新節點,但最后沒有,造成了內存泄漏

改Bug

我們要改變 SLNode* plist ,傳參里要傳 SLNode* plist 的地址 ,用 SLNode** 接收

SList.h

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印鏈表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

SList.c

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

pphead 存的是 plist 的指針。*pphead 就是 plist 。
函數結束,棧幀空間銷毀。plist 指向了新節點

鏈表運行結果:

3.頭插

盲猜頭插要用二級指針,因為一定有一個情況是為空,為空肯定要用

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}

如果傳的是 phead ,改變的就是 phead ,無法改變外邊的 plist

這段代碼同樣可以解決空的情況

4.尾刪

SList.c

void SLTPopBack(SLTNode** pphead) // 這么寫對嗎?
{assert(pphead);assert(*pphead);// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}free(tail);tail = NULL;
}

Test.c

void TestSList1()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}

Bug

? ? ?碰上這種情況多半是野指針,調試看看

尾就是1這個節點,2這個節點存著他的地址

直接把 tail 指向的尾節點 free 了,前一個節點的 next 就是野指針了。指向已經被釋放的空間的指針是野指針

這里把 tail 置空,不會把前一個節點的 next 置空
前一個節點是結構體,想改變結構體的內容要用結構體指針


修改1

void SLTPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* prev = NULL;// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;
}

修改2:找的是倒數第2個

void SLTPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);// 找尾SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;
}

如果鏈表刪到只剩1個元素,還刪。
如果鏈表本身為空

void TestSList1()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}

? ? ? ? ? ? ??

下面用紅圈圈起來的是兩組代碼在只剩1個的情況下,分別有誤的地方

修改:只有1個節點,直接 free,plist 置空。不用找尾節點
所以尾刪如果用一級指針接收,phead 是 plist 的拷貝,對 phead 置空的改變不影響 plist,達不到置空 plist 的目的,plist 會變成野指針

void SLTPopBack(SLTNode** pphead)
{//暴力檢查assert(pphead);assert(*pphead);//溫柔檢查/*if (*pphead == NULL)return;*/if ((*pphead)->next == NULL) // 只有1個節點{free(*pphead);*pphead = NULL;}else // 多個節點{/*SLTNode* prev = NULL;// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/// 找尾SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

5.頭刪

不需要單獨處理只有1個節點的情況

void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}

6.查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

返回的是對應節點的指針,可以用 Find 實現修改

void TestSList2()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);// 值為2的節點 *2SLTNode* ret = SLTFind(plist, 2);ret->data *= 2;SLTPrint(plist);
}

Find 主要是與下面的功能相配合

7. Find 查找的功能

我們這里不傳下標,傳結構體指針,與 C++ 貼合

(1)pos 之前插入

為啥不是在 pos 位置插入? 是把 pos 及以后的數據往后移,所以邏輯上說是之前插入

單鏈表不適合 pos 之前插入,只適合在后面插入因為要找到 pos 前一個節點的地址,只能從頭找

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{if (pos == *pphead){SLTPushFront(pphead, x);}else{// 找到 pos 的前一個位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);newnode->next = pos;prev->next = newnode;}
}

如果 pos 不是鏈表里的指針,while 循環停不下來,最終出現空指針
這種情況怎么辦 (甚至 pos 就是 NULL)?? ?
說明傳錯了,斷言,起碼可以排除 NULL

(2)pos 位置刪除

這里 *pphead 可以不斷言,pos 間接斷言了
pos 不為空,有節點,一定不為空鏈表

pos 位刪除,要找到前一個位置。pos 是頭,就是頭刪,先處理這個特殊情況

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{// 找到 pos 的前一個位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);// pos = NULL;}
}

pos = NULL 沒用,形參的修改不改變實參。要不要傳二級指針呢?不。
為保持和其他的一致性,通常由用的人考慮置空

void TestSList4()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTNode* ret = SLTFind(plist, 2);SLTErase(&plist, ret);ret = NULL;SLTPrint(plist);
}

(3)pos 之后插入

錯誤寫法:會造成死循環

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);pos->next = newnode;newnode->next = pos->next;
}

正確寫法:先改后面

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}

(4)pos位置后面刪除

法1:pos->next = pos->next->next;? 這里從右往左賦值? ? ?橙圈的內容丟了,所以要引入del

void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

法2:好理解

void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

?8.銷毀

寫法1:一級指針

void SLTDestroy(SLTNode* phead); // SList.hvoid SLTDestroy(SLTNode* phead)
{SLTNode* cur = phead;while (cur){SLTNode* tmp = cur->next;free(cur);cur = tmp;}// phead = NULL;
}

phead?= NULL 沒用,形參的修改不改變實參。讓用的人置空

void TestSList4()
{SLTNode* plist = NULL;......SLTDestroy(plist);plist = NULL;
}

寫法2:二級指針? ? 自己置空

void SLTDestroy(SLTNode** pphead); // SList.hvoid SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* tmp = cur->next;free(cur);cur = tmp;}*pphead = NULL;
}SLTDestroy(&plist); // Test.c

?四.思維提升

單鏈表給了 pos 沒給頭指針

(1)插入

(2)刪除

沒有前一個位置,就刪后一個。先換值,后刪。但是不能刪尾

五.總結

1.傳什么?

我們剛開始拿到鏈表,plist?是 NULL 。要插入新節點,要讓 plist 指向新節點,會改變 plist ,所以要傳指針的地址。
刪除時,總會刪到空,這時要將 plist 置為 NULL ,也改變 plist ,所以也傳指針的地址

如果不需要修改頭指針的鏈接,就傳一級指針

2.要不要斷言?

斷言可以排出明顯的錯誤,避免調試耗時。一定不能為空,就斷言

(1)打印、查找

問:是否要 assert 指針 phead 為空? (一級指針)
答:不要。空的 (沒有數據) 的鏈表,順序表都可以打印、查找。鏈表為空時,phead == NULL,斷言直接終止程序不合適。

順序表,鏈表結構不一樣,不能一概而論。
phead 是指向第一個存有數據的節點,鏈表為空時,phead == NULL


順序表的打印


void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; ++i){printf("%d ", ps->a[i]);}printf("\n");
}

指針 ps 指向結構體 SL ,順序表的數據不是存儲在結構體上。而是存儲在結構體里的一個指針 a 指向的空間。即使順序表里沒有數據,ps 指向的結構體也是必須要有的。ps->a 是否為空也不重要,到底有沒有數據,取決于 ps->size 是否為 0
所以對順序表而言,指針就不能為空

總結:不要看到指針上來就斷言

(2)pphead

要,pphead 不能為空。為什么?
pphead 是 plist 的地址。plist 是指針變量,值有可能是空,地址一定不為空

(3)*pphead

*pphead 就是 plist ,是看是否為空 (二級指針)

要不要斷言 *pphead 取決于函數是否包容空鏈表的情況

先 assert ( pphead )? ?后 assert ( *pphead )? 如果反了,先 * ,再檢查,有啥用?

空鏈表能插入,不斷言;不能刪,要斷言。

六.整體代碼

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLTPrint(SLTNode* phead); // 打印鏈表
void SLTPushBack(SLTNode** pphead, SLTDataType x); // 尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x); // 頭插void SLTPopBack(SLTNode** pphead); // 尾刪
void SLTPopFront(SLTNode** pphead); // 頭刪SLTNode* SLTFind(SLTNode* phead, SLTDataType x); // 查找void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x); // pos之前插入
void SLTErase(SLTNode** pphead, SLTNode* pos); // pos位置刪除void SLTInsertAfter(SLTNode* pos, SLTDataType x); // pos之后插入
void SLTEraseAfter(SLTNode* pos); // pos位置后面刪除//void SLTDestroy(SLTNode* phead); // 鏈表銷毀
void SLTDestroy(SLTNode** pphead); // 鏈表銷毀

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur->next != NULL) 錯誤寫法!!!//while(cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next;//cur++; 錯誤寫法!!!}printf("NULL\n");
}// 搞新節點,并初始化
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}// 初始化newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{//暴力檢查assert(pphead);assert(*pphead);//溫柔檢查/*if (*pphead == NULL)return;*/if ((*pphead)->next == NULL) // 只有1個節點{free(*pphead);*pphead = NULL;}else // 多個節點{/*SLTNode* prev = NULL;// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/// 找尾SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{// 找到 pos 的前一個位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);newnode->next = pos;prev->next = newnode;}
}void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{// 找到 pos 的前一個位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);// pos = NULL;}
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//SLTNode* del = pos->next;//pos->next = pos->next->next;//free(del);//del = NULL;SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}/*
void SLTDestroy(SLTNode* phead)
{SLTNode* cur = phead;while (cur){SLTNode* tmp = cur->next;free(cur);cur = tmp;}
}
*/void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* tmp = cur->next;free(cur);cur = tmp;}*pphead = NULL;
}

本篇的分享就到這里了,感謝觀看,如果對你有幫助,別忘了點贊+收藏+關注
小編會以自己學習過程中遇到的問題為素材,持續為您推送文章

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/900912.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/900912.shtml
英文地址,請注明出處:http://en.pswp.cn/news/900912.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

按鍵消抖(用狀態機實現)

基于狀態機的設計代碼 module key_filter(clk,rst,key,key_p_flag,key_r_flag,key_state);input clk,rst;input key;output reg key_p_flag;output reg key_r_flag;output reg key_state;reg [1:0]r_key; //后面用來判斷什么時候pedge&#xff0c;什么時候nedgealways…

大數據(7.2)Kafka萬億級數據洪流下的架構優化實戰:從參數調優到集群治理

目錄 一、海量數據場景下的性能之殤1.1 互聯網企業的數據增長曲線1.2 典型性能瓶頸分析 二、生產者端極致優化2.1 批量發送黃金法則2.1.1 分區選擇算法對比 2.2 序列化性能突破 三、消費者端并發藝術3.1 多線程消費模式演進3.1.1 消費組Rebalance優化 3.2 位移管理高階技巧 四、…

MyBatis深度解析與實戰指南:細節完整,從入門到精通

MyBatis深度解析與實戰指南&#xff1a;細節完整&#xff0c;從入門到精通 整理這份筆記&#xff0c;是因為學習 MyBatis 時發現很多教程要么只講基礎 CRUD&#xff0c;要么直接跳到 Spring 整合&#xff0c;對 MyBatis 核心特性講解不全面&#xff0c;基礎部分也不夠完整。實…

【科學技術部政務服務平臺-用戶注冊/登錄安全分析報告】

前言 由于網站注冊入口容易被黑客攻擊&#xff0c;存在如下安全問題&#xff1a; 暴力破解密碼&#xff0c;造成用戶信息泄露短信盜刷的安全問題&#xff0c;影響業務及導致用戶投訴帶來經濟損失&#xff0c;尤其是后付費客戶&#xff0c;風險巨大&#xff0c;造成虧損無底洞…

【Audio開發三】音頻audio中幀frameSize ,周期大小periodsize,緩沖區buffer原理詳解以及代碼流程分析

一、基礎概述 在分析獲取最小幀數前&#xff0c;我們先來了解幾個相關的概念。 1&#xff0c;幀 幀&#xff08;frame&#xff09;&#xff1a;表示一個完整的聲音單元&#xff0c;所謂的聲音單元是指一個采樣樣本。如果是雙聲道&#xff0c;那么一個完整的聲音單元就是 2 個樣…

K8S學習之基礎七十五:istio實現灰度發布

istio實現灰度發布 上傳鏡像到harbor 創建兩個版本的pod vi deployment-v1.yaml apiVersion: apps/v1 kind: Deployment metadata:name: appv1labels:app: v1 spec:replicas: 1selector:matchLabels:app: v1apply: canarytemplate:metadata:labels:app: v1apply: canaryspec…

C++藍橋杯填空題(攻克版)

片頭 嗨~小伙伴們&#xff0c;咱們繼續攻克填空題&#xff0c;先把5分拿到手~ 第1題 數位遞增的數 這道題&#xff0c;需要我們計算在整數 1 至 n 中有多少個數位遞增的數。 什么是數位遞增的數呢&#xff1f;一個正整數如果任何一個數位不大于右邊相鄰的數位。比如&#xf…

【Python】數據結構

【Python】數據結構&#xff1a; Series&#xff1a;1、通過列表創建Series類對象2、顯示地給數據指定標簽索引3、通過字典創建Series類對象4、獲取索引5、獲取數據 DataFrame&#xff1a;1、通過數組創建一個DataFrame類對象2、指定列索引3、指定行索引4、獲取列的數據5、查看…

Android XML布局與Compose組件對照手冊

下面我將詳細列出傳統 XML 布局中的組件與 Compose 組件的對應關系&#xff0c;幫助您更好地進行遷移或混合開發。 基礎布局對應 XML 布局Compose 組件說明LinearLayout (vertical)Column垂直排列子項LinearLayout (horizontal)Row水平排列子項FrameLayoutBox層疊子項Relativ…

云原生運維在 2025 年的發展藍圖

隨著云計算技術的不斷發展和普及&#xff0c;云原生已經成為了現代應用開發和運維的主流趨勢。云原生運維是指在云原生環境下&#xff0c;對應用進行部署、監控、管理和優化的過程。在 2025 年&#xff0c;云原生運維將迎來更加廣闊的發展前景&#xff0c;同時也將面臨著一系列…

js day5

復習模板字符串&#xff1a; 在輸出語句里面 document.write(我今年${a}歲了)中間是反引號&#xff1b;里面是${變量}&#xff1b; 復習基本類型 number String null undefined boolean 檢測數據類型輸出typedf 變量則可&#xff1b; 添加鏈接描述 復習樣式變量table什么的邊…

SmolVLM2: The Smollest Video Model Ever(三)

這是對《SmolLM2: When Smol Goes Big — Data-Centric Training of a Small Language Model》的翻譯閱讀 摘要 雖然大語言模型在人工智能的許多應用中取得了突破&#xff0c;但其固有的大規模特性使得它們在計算上成本高昂&#xff0c;并且在資源受限的環境中部署具有挑戰性。…

汽車軟件開發常用的需求管理工具匯總

目錄 往期推薦 DOORS&#xff08;IBM &#xff09; 行業應用企業&#xff1a; 應用背景&#xff1a; 主要特點&#xff1a; Polarion ALM&#xff08;Siemens&#xff09; 行業應用企業&#xff1a; 應用背景&#xff1a; 主要特點&#xff1a; Codebeamer ALM&#x…

爬蟲工程師雜活工具人

30歲的年齡;這個年齡大家都是成年人;都是做父母的年齡了;你再工位上的心態會發生很大變化的; 爬蟲工程師基本都是如此;社會最low的一幫連銷售都做不了的;單子都開不出來的然后轉行做爬蟲工程師的;這樣的人基本不太和社會接觸; 你作為爬蟲初級工程師就敲著鍵盤然后解析著html;…

如何使用Tomcat

1 簡介 Tomcat是Apache 軟件基金會&#xff08;Apache Software Foundation&#xff09;的Jakarta 項目中的一個核心項目&#xff0c;由Apache、Sun 和其他一些公司及個人共同開發而成。因為Tomcat 技術先進、性能穩定&#xff0c;而且免費&#xff0c;成為目前比較流行的Web 應…

【AI工具】FastGPT:開啟高效智能問答新征程

前言 在人工智能飛速發展的當下&#xff0c;各類 AI 工具如雨后春筍般涌現。FastGPT 作為一款基于大語言模型&#xff08;LLM&#xff09;的知識圖譜問答系統&#xff0c;憑借其強大的數據處理和模型調校能力&#xff0c;為用戶帶來了便捷的使用體驗。今天&#xff0c;就讓我們…

14. git remote

基本概述 git remote 的作用是&#xff1a;查看、添加、修改和刪除與本地倉庫關聯的遠程倉庫。 基本用法 1.查看遠程倉庫 git remote # 顯示所有關聯的遠程倉庫&#xff08;名稱&#xff09; git remote -v # 顯示所有關聯的遠程倉庫&a…

【spark-submit】--提交任務

Spark-submit spark-submit 是 Apache Spark 提供的用于提交 Spark 應用程序到集群的命令行工具。 基本語法 spark-submit [options] <app-jar> [app-arguments]常用參數說明 應用程序配置 --class <class-name>: 指定應用程序的主類&#xff08;對于 Java/Sc…

2025.4.10總結

今日記錄&#xff1a;今天提了兩個問題單&#xff0c;最近要關注一下產出了&#xff0c;上半年的考核如今還剩兩個月了&#xff0c;然后發現一同入職的同事&#xff0c;有的人進步得很快&#xff0c;得向優秀得同事看齊了&#xff0c;不然幾年過去&#xff0c;別人連升好幾年&a…

SvelteKit 最新中文文檔教程(18)—— 淺層路由和 Packaging

前言 Svelte&#xff0c;一個語法簡潔、入門容易&#xff0c;面向未來的前端框架。 從 Svelte 誕生之初&#xff0c;就備受開發者的喜愛&#xff0c;根據統計&#xff0c;從 2019 年到 2024 年&#xff0c;連續 6 年一直是開發者最感興趣的前端框架 No.1&#xff1a; Svelte …