C語言<數據結構-鏈表>

?

? ? ? ?鏈表是一種常見且重要的數據結構,在 C 語言中,它通過指針將一系列的節點連接起來,每個節點可以存儲不同類型的數據。相比數組,鏈表在插入和刪除元素時不需要移動大量數據,具有更好的靈活性,尤其適合處理動態變化的數據集合。

? ? ? ?當然,既然鏈表涉及到指針,而且與指針關系密切,所以只有當我們在對于指針有了一定扎實的了解后,才能更好、更輕松的了解鏈表這一新概念。然而,我認為對于鏈表這樣一類概念來說,增刪查改是最好去了解其本質的方法,所以今天,我們來看看鏈表的增刪查改

? ? ? ?首先,我們來看看比較簡單的頭插、頭刪、尾插、尾刪這四個:

一.前期準備:

對于前期準備我已經老調重彈了很多遍,這里不再過多贅述,首先最關鍵的就是鏈表節點的定義

1. 鏈表節點的定義

? ? ? ?鏈表由一個個節點組成,每個節點包含兩部分:數據域和指針域。數據域用來存儲數據,指針域指向下一個節點。所以我們選擇用結構體去定義節點:

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

代碼解釋:

1.?typedef int SLTDataType:

? ? ? ?這行代碼定義了一個類型別名?SLTDataType,它實際上是?int?的同義詞。也就說可以選擇直接在結構體內部用 int data;之所以這樣做的目的是:

  • 提高代碼可維護性:如果后續需要存儲其他類型(如?floatchar*),只需修改這一行,而不必改動整個代碼。
  • 增強可讀性SLTDataType?比直接用?int?更直觀,表示這是鏈表節點的數據類型。

2.?struct SListNode?結構體定義

這個結構體定義了單鏈表的節點結構,包含兩個成員:

SLTDataType data;

  • 數據域,用于存儲具體的數據。
  • 由于?SLTDataType?被 typedef 為?int,因此這里實際上存儲的是整數。

struct SListNode* next;

  • 指針域,指向下一個節點的地址。
  • 這是實現鏈表連接的關鍵:每個節點通過?next?指針指向下一個節點,直到最后一個節點的?next?為?NULL,表示鏈表結束。
  • 并非一個結構體,是一個結構體類型的指針

3.?SLTNode;

這行代碼將?struct SListNode?重命名為?SLTNode,目的是簡化類型名。

  • 之后可以直接用?SLTNode?聲明變量,而不需要寫?struct SListNode
  • 例如:SLTNode* node = malloc(sizeof(SLTNode));

二.打印函數:

為了更好地在測試文件里測試函數的可行性,我們首先寫一個打印函數。

void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

代碼解釋:

1. 定義遍歷指針

SLTNode* cur = phead;
  • 創建一個臨時指針?cur(current 的縮寫,意為 “當前節點”),并將其初始化為頭指針?phead
  • 作用:用?cur?遍歷鏈表,避免直接修改頭指針?phead(如果直接移動?phead,會導致鏈表 “頭節點丟失”,無法再訪問整個鏈表)。

2. 遍歷鏈表并打印數據

while (cur)
  • 循環條件?cur?等價于?cur != NULL,表示:只要當前節點?cur?不是空指針(即還沒遍歷到鏈表末尾),就繼續循環。
printf("%d->", cur->data);
  • 打印當前節點?cur?的數據域?data%d?對應?SLTDataType?為?int?的情況),并在后面加上?->?符號,模擬鏈表的 “指向” 關系。
cur = cur->next;
  • 讓?cur?指向當前節點的下一個節點(通過指針域?next),實現遍歷的 “移動”。

3. 打印鏈表末尾標識

printf("NULL\n");

?

  • 當循環結束時(cur?變為?NULL,即遍歷到鏈表末尾),打印?NULL,表示鏈表的終止。

三.尾插函數:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;if (*pphead == NULL){*pphead = newnode;}else{//找尾:SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

代碼解釋:

函數定義解析:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
  • 參數
    • SLTNode** pphead:指向頭指針的指針(二級指針)。因為尾插可能改變頭指針(當鏈表為空時),必須通過二級指針修改原頭指針的值。
    • SLTDataType x:要插入的數據(類型為?int,因為?SLTDataType?被 typedef 為?int)。
  • 返回值void,只執行插入操作,不返回數據。

函數體詳解

1. 斷言檢查

assert(pphead);
  • 確保傳入的二級指針?pphead?不為空(即頭指針的地址必須有效)。
  • 若?pphead?為空,程序會觸發斷言錯誤并終止(防止野指針)。

2. 創建新節點

SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{perror("malloc fail");return;
}
newnode->data = x;
newnode->next = NULL;
  • 內存分配:用?malloc?動態分配一個節點大小的內存空間。
  • 錯誤處理:若?malloc?失敗(返回?NULL),打印錯誤信息并提前返回。
  • 初始化節點:將數據?x?存入新節點的數據域?data,并將指針域?next?置為?NULL(因為新節點將成為尾節點)。

3. 處理鏈表為空的情況

if (*pphead == NULL)
{*pphead = newnode;
}
  • 若鏈表為空(頭指針?*pphead?為?NULL),直接讓頭指針指向新節點。
  • 關鍵點:通過?*pphead?修改原頭指針的值,使新節點成為鏈表的第一個節點。

4. 鏈表不為空時的尾插操作

else
{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;
}

?

  • 遍歷找尾:從鏈表頭開始,用?tail?指針遍歷到最后一個節點(即?tail->next == NULL?的節點)。
  • 插入新節點:將尾節點的?next?指針指向新節點?newnode,使新節點成為新的尾節點。

函數定義進階解析:

到這里相信很多人仍然對于二級指針那個點感到糊涂:為什么前面打印函數定義時就是一級指針,而到了插入函數開始就用二級指針了呢?下面我先順著原始思路去解釋,認為和打印函數一樣用的是一級指針的代碼和測試代碼會如下:

void SLTPushBack(SLTNode* phead, SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;if (phead == NULL){phead = newnode;}else{//找尾:SLTNode* tail = phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

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

然而這樣最終編譯結果為:

我們可以看到,結果好像不是我們想要的,這是因為:

問題核心:形參與實參的區別

void SLTPushBack(SLTNode* phead, SLTDataType x) 
  • 參數?phead?是實參的副本:當調用?SLTPushBack(plist, 1)?時,函數內部的?phead?是?plist?的拷貝,它們指向同一塊內存,但本身是兩個不同的變量。
  • 修改?phead?不影響實參?plist:在函數內部,當鏈表為空時執行?phead = newnode,只是修改了副本?phead?的值,原頭指針?plist?仍為?NULL

示例分析:

SLTNode* plist = NULL;  // plist 指向 NULL

第一次調用?SLTPushBack(plist, 1)

  1. 傳值調用phead?是?plist?的副本,初始值為?NULL
  2. 創建新節點newnode?指向新分配的節點(數據為 1,next?為?NULL)。
  3. 執行?phead = newnodephead?指向新節點,但?plist?仍為?NULL因為?phead?是副本)。
  4. 函數返回phead?被銷毀,原?plist?未被修改,仍為?NULL

后續調用?SLTPushBack(plist, 2)SLTPushBack(plist, 3)?等:

  • 每次都重復上述過程,plist?始終為?NULL,所有新節點都無法連接到鏈表中。
  • 最終結果:鏈表為空,SLTPrint(plist)?輸出?NULL

正確做法:使用二級指針

void SLTPushBack(SLTNode** pphead, SLTDataType x)

?

  • 傳址調用pphead?是指向頭指針?plist?的指針,通過?*pphead?可以直接修改原頭指針。
  • 修改?*pphead?影響實參:當鏈表為空時,*pphead = newnode?直接將原頭指針?plist?指向新節點。

?明白了這點之后,后續函數該用一級指針還是二級指針我們就清楚了。

四.新增節點函數:

完成尾插函數以后,我們發現需要開辟新的節點,我們也能預想到,后續函數也會用到新增節點,所以我們不妨封裝這個函數,簡化代碼量:

SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;return newnode;
}

之后在開辟時,直接調用即可;

五.頭插函數:

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

代碼解釋:?

1. 斷言檢查

assert(pphead);
  • 確保傳入的二級指針?pphead?不為空(即頭指針的地址必須有效)。
  • 若?pphead?為空,程序會觸發斷言錯誤并終止(防止野指針)。

2. 創建新節點

SLTNode* newnode = BuySLTNode(x);
  • 調用?BuySLTNode?函數,創建一個新節點并初始化為數據?xnext?指針為?NULL

3. 插入新節點到頭部

newnode->next = *pphead;
*pphead = newnode;

?

  • 關鍵點

    1. 將新節點的?next?指針指向原頭節點(*pphead)。
    2. 將原頭指針?*pphead?更新為新節點?newnode,使新節點成為鏈表的新頭。
  • 無論鏈表是否為空,這兩行代碼都能正確工作

    • 若鏈表為空(*pphead == NULL),新節點的?next?為?NULL,并成為唯一節點。
    • 若鏈表非空,新節點的?next?指向原頭節點,實現頭插。

頭插函數很簡約,理解起來也沒什么大問題;

六.尾刪函數:

void SLTPopBack(SLTNode** pphead)
{//暴力檢查:assert(pphead);assert(*pphead);//只有一個節點;// 有多個節點;if ((*pphead)->next == NULL){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;}
}

代碼解釋:?

1. 斷言檢查

assert(pphead);
assert(*pphead);
  • 第一個斷言:確保傳入的二級指針?pphead?不為空(防止野指針)。
  • 第二個斷言:確保鏈表不為空(*pphead != NULL)。若鏈表為空時調用此函數,會觸發斷言錯誤,終止程序。

2. 處理只有一個節點的情況

if ((*pphead)->next == NULL)
{free(*pphead);*pphead = NULL;
}
  • 若鏈表只有一個節點(即頭節點的?next?為?NULL):
    1. 釋放頭節點的內存(free(*pphead))。
    2. 將頭指針置為?NULL*pphead = NULL),表示鏈表已空。

3. 處理多個節點的情況

else
{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;
}
  • 遍歷找尾
    • tail?指針遍歷到最后一個節點,prev?始終指向?tail?的前一個節點。
  • 釋放尾節點
    1. 釋放?tail?指向的尾節點(free(tail))。
    2. 將?tail?置為?NULL(這一步是多余的,后面會解釋)。
  • 更新鏈表
    將?prev?的?next?置為?NULL,使?prev?成為新的尾節點。
  • 注意對于這段代碼,while內的代碼邏輯極其重要,不能找到尾以后就直接刪去,這樣將導致刪完以后,尾部出現隨機值的情況,需要格外注意!!!

但其實這段代碼還存在一點小問題:

存在的問題:

free(tail);
tail = NULL;  // 這一步無效!
  • 問題tail = NULL?僅將局部變量?tail?置為?NULL,無法影響原鏈表。
  • 原因tail?是局部指針,free(tail)?后內存已釋放,但?tail?本身仍保存著被釋放內存的地址。將?tail?置為?NULL?只是修改了局部變量,對鏈表無影響。
  • 修正:刪除?tail = NULL?這一行,因為它不影響鏈表結構,且是多余操作。

七.頭刪函數:

void SLTPopFront(SLTNode** pphead)
{//暴力檢查:assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}

代碼解釋:?

1. 斷言檢查

assert(pphead);
assert(*pphead);
  • 第一個斷言:確保傳入的二級指針?pphead?不為空(防止野指針)。
  • 第二個斷言:確保鏈表不為空(*pphead != NULL)。若鏈表為空時調用此函數,會觸發斷言錯誤,終止程序。

2. 保存原頭節點并更新頭指針

SLTNode* first = *pphead;
*pphead = first->next;

?

  • 關鍵點
    1. 用臨時指針?first?保存原頭節點的地址。
    2. 將頭指針?*pphead?更新為原頭節點的下一個節點(first->next)。
  • 處理單節點情況:若鏈表只有一個節點,first->next?為?NULL,更新后頭指針?*pphead?變為?NULL,鏈表為空。

3. 釋放原頭節點的內存

free(first);
first = NULL;

?

  • 釋放內存:調用?free(first)?釋放原頭節點占用的內存。
  • 置空局部指針:將?first?置為?NULL(這一步是可選的,因為?first?是局部變量,函數返回后會被銷毀)。

? ? ? ?這樣一來,我們基礎的尾插、尾刪、頭插、頭刪都已經講完了,除了上面我們所關注到的細節點以外,還有一個點就是斷言我們發現有的地方加入了斷言,有的地方沒有,有的地方只有一個,而有的地方卻有兩個,我們來談談斷言:

斷言:

在 C 語言鏈表操作中,斷言(assert)?是一種強大的調試工具,用于確保程序運行時的某些條件始終為真。

一、斷言的本質與作用

斷言是 C 標準庫?<assert.h>?提供的宏,定義為:

void assert(int expression);

?

  • 功能:若?expression?為假(0),程序會立即終止,并打印錯誤信息(包括斷言失敗的位置、表達式內容)。
  • 目的:在開發和測試階段盡早發現邏輯錯誤,避免程序在錯誤狀態下繼續運行導致更嚴重的問題。

二、鏈表操作中常見的斷言場景

1.?檢查指針有效性

assert(pphead);  // 確保二級指針(頭指針的地址)不為NULL
  • 場景:在需要修改頭指針的函數中(如?SLTPushBackSLTPopFront),若傳入的?pphead?為?NULL,后續解引用?*pphead?會導致野指針錯誤。
  • 錯誤示例
    SLTNode* plist = NULL;
    SLTPushBack(NULL, 1);  // 錯誤:傳入NULL作為pphead
    

2.?檢查鏈表非空

assert(*pphead);  // 確保鏈表不為空(頭指針不為NULL)
  • 場景:在刪除操作(如?SLTPopBackSLTPopFront)中,若鏈表為空,刪除操作無意義且可能導致崩潰。
  • 錯誤示例
    SLTNode* plist = NULL;
    SLTPopFront(&plist);  // 錯誤:對空鏈表執行頭刪
    

三、斷言的優缺點

優點:

  1. 快速定位錯誤:斷言失敗時會直接打印錯誤位置和表達式,無需調試器即可快速發現問題。
  2. 強制約束條件:明確函數的前置條件(如 “鏈表必須非空”),提高代碼安全性。
  3. 零運行時開銷:在發布版本中可通過?#define NDEBUG?禁用斷言,消除性能影響。

缺點:

  1. 僅在調試階段有效:發布版本中斷言被禁用,無法檢查運行時錯誤。
  2. 可能掩蓋真實問題:若斷言條件過于嚴格,可能導致程序頻繁崩潰,需謹慎設置。

所以,鏈表細節滿滿,需要大家多花時間去了解,剩下的一些函數,將會放到下一篇博客中,敬請期待......

?

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

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

相關文章

基于Matlab多特征融合的可視化指紋識別系統

針對中小規模&#xff08;百級&#xff09;指紋模板庫中常見的脊線斷裂、噪聲干擾以及結果缺乏可解釋性等難點&#xff0c;本文提出并實現了一種基于多特征融合的可視化指紋識別系統。系統整體采用模塊化設計&#xff1a;在預處理階段&#xff0c;先通過改進的灰度歸一化與局部…

50天50個小項目 (Vue3 + Tailwindcss V4) ? | DoubleVerticalSlider(雙垂直滑塊)

&#x1f4c5; 我們繼續 50 個小項目挑戰&#xff01;—— DoubleVerticalSlider組件 倉庫地址&#xff1a;https://github.com/SunACong/50-vue-projects 項目預覽地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API&#xff08;<scrip…

mysql join語句、全表掃描 執行優化與訪問冷數據對內存命中率的影響

文章目錄join執行邏輯Index Nested_Loop Join&#xff08;NLJ&#xff09;MMR(Mutli-Range Read) 優化BKA(Batched Key Access)算法Simple Nested_Loop JoinBlock Nested-Loop Join&#xff08;BLJ&#xff09;join buffer 一次放不下 驅動表join buffer優化的影響&#xff1a;…

【LeetCode100】--- 1.兩數之和【復習回滾】

題目傳送門 解法一&#xff1a;暴力枚舉&#xff08;也是最容易想到的&#xff09; class Solution {public int[] twoSum(int[] nums, int target) {int n nums.length;for(int i 0; i < n; i){for(int j i1; j<n; j){if(nums[i] nums[j] target){return new int…

opencv提取png線段

import cv2 import matplotlib.pyplot as plt import numpy as np# 讀取圖像 image cv2.imread(./data/1.png) if image is None:print("無法讀取圖像文件") else:# 轉換為灰度圖像gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用Canny邊緣檢測edges cv2.Can…

計算機網絡:概述層---計算機網絡概念解析

計算機網絡的概念詳解 &#x1f4c5; 更新時間&#xff1a;2025年07月6日 &#x1f3f7;? 標簽&#xff1a;計算機網絡 | 網絡基礎 | 互聯網 | TCP/IP | 路由器 文章目錄前言一、計算機網絡的發展歷程二、什么是計算機網絡&#xff1f;1. 計算機網絡的基本功能2. 計算機網絡的…

springMVC04-Filter過濾器與攔截器

一、Filter&#xff08;過濾器&#xff09;和 Interceptor&#xff08;攔截器&#xff09;在 SpringMVC 中&#xff0c;Filter&#xff08;過濾器&#xff09;和 Interceptor&#xff08;攔截器&#xff09;都是對請求和響應進行預處理和后處理的重要工具&#xff0c;但它們存在…

STM32第十九天 ESP8266-01S和電腦實現串口通信(2)

1&#xff1a;UDP 傳輸UDP 傳輸不不區分 server 或者 client &#xff0c;由指令 ATCIPSTART 建?立傳輸。 1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 響應 : OK 2. 連接路路由器? ATCWJAP"SSID","password" // SSID and password of router 響…

大健康IP如何用合規運營打破“信任危機”|創客匠人

一、行業亂象下的信任裂痕當前大健康領域私域直播亂象頻發&#xff0c;部分機構利用“假專家義診”“限量搶購”等話術&#xff0c;將低成本保健品高價賣給老人&#xff0c;甚至有技術公司提供“全鏈路坑老方案”&#xff0c;加劇行業信任危機。這種短視行為不僅損害消費者權益…

MySQL(122)如何解決慢查詢問題?

解決慢查詢問題通常涉及到多種技術和方法&#xff0c;以確保數據庫查詢的高效性和響應速度。以下是詳細步驟和示例代碼&#xff0c;闡述如何解決慢查詢問題。 一. 慢查詢的常見原因 缺少索引&#xff1a;查詢未使用索引或索引未優化。查詢不當&#xff1a;查詢語句本身書寫不合…

esp32在vscode中仿真調試

此方法可以用在具有usb serial jtag功能的esp32芯片用&#xff0c;支持型號&#xff1a; ESP32-C3 ESP32-S3 ESP32-C6 ESP32-H2 ESP32-C5 USB Serial JTAG功能介紹&#xff1a; 從硬件角度&#xff1a; 它是ESP32芯片內置的硬件功能 不是一個獨立的物理接口 是通過USB接口實…

藍橋云課 矩形切割-Java

目錄 題目鏈接 題目 解題思路 代碼 題目鏈接 競賽中心 - 藍橋云課 題目 解題思路 找最大的正方形就是大邊-n個小邊&#xff0c;直至相等或者小于1 代碼 import java.util.Scanner; // 1:無需package // 2: 類名必須Main, 不可修改public class Main {public static voi…

PostgreSQL 鎖等待監控,查找等待中的鎖

直接貼SQLWITH RECURSIVE l AS (SELECT pid, locktype, mode, granted, ROW(locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid) objFROM pg_locks ), pairs AS (SELECT w.pid waiter, l.pid locker, l.obj, l.modeFROM l wJOIN l ON l.…

Elasticsearch 字符串包含子字符串:高級查詢技巧

作者&#xff1a;來自 Elastic Justin Castilla 想要獲得 Elastic 認證&#xff1f;看看下一次 Elasticsearch Engineer 培訓什么時候開始吧&#xff01; Elasticsearch 擁有大量新功能&#xff0c;可以幫助你為你的使用場景構建最佳的搜索解決方案。深入了解我們的示例 noteb…

Vue、Laravel 項目初始化命令對比 / curl 命令/ CORS 機制總結與案例

前言一個疑問衍生出另一個疑問再衍生出又一個疑問&#xff0c;于是有了這篇文章。一、Vue 項目初始化命令 基于 Vite 創建 Vue 項目 命令&#xff1a;npm create vitelatest my-project -- --template vue適用場景&#xff1a;需輕量級、高速開發環境關鍵點&#xff1a;使用 Vi…

Jenkins 流水線配置

Jenkinsfile dsl文件:pipeline {// 指定任務在哪個集群節點執行agent any// 聲明全局變量environment {keyvalueAPPLICATION_NAMEspringboot-demo // 項目名稱HOST_PORT7777 // 宿主機暴露服務端口CONTAINER_PORT8080 // 容器內部服務端口…

服務器重裝后如何“復活”舊硬盤上的 Anaconda 環境?—— 一次完整的排錯與恢復記錄

目錄 摘要 一、 背景&#xff1a;熟悉的陌生人 二、 問題浮現&#xff1a;一次次失敗的嘗試 問題一&#xff1a;source activate 失效&#xff0c;被寫死的舊路徑 問題二&#xff1a;官方安裝器修復失敗&#xff0c;神秘的“進程池損壞” 問題三&#xff1a;核心腳本也“背…

Redis的多并發實際業務場景下的使用分析:布隆過濾器

文章目錄前言什么是布隆過濾器項目中引入布隆過濾器與緩存結合的最佳實踐場景&#xff1a;高并發用戶訪問商品詳情頁&#xff08;防止緩存穿透&#xff09;總結&#xff1a;前言 okok 我們已經學完了 所有的redis中的常用的數據結構 下面就是進階 我會用一系列的例子 去講解 如…

【AI】人工智能領域關鍵術語全解析

一、前言 人工智能&#xff08;AI&#xff09;作為當今最熱門的技術領域之一&#xff0c;正在深刻改變著我們的生活和工作方式。然而&#xff0c;對于初學者或非技術背景的人士來說&#xff0c;理解AI領域的專業術語可能是一項挑戰。本文旨在全面解析人工智能領域的關鍵術語&a…

【Linux基礎知識系列】第四十三篇 - 基礎正則表達式與 grep/sed

在Linux系統中&#xff0c;正則表達式是一種強大的文本處理工具&#xff0c;廣泛用于文本搜索、替換和批量處理。通過掌握基礎正則表達式的語法&#xff0c;結合grep和sed命令&#xff0c;用戶可以高效地完成復雜的文本處理任務。無論是數據分析師、軟件開發者還是系統管理員&a…