【數據結構】深入淺出理解鏈表中二級指針的應用

🦄個人主頁:修修修也

🎏所屬專欄:數據結構

??操作環境:Visual Studio 2022

(注:為方便演示本篇使用的x86系統,因此指針的大小為4個字節)


目錄

📌形參的改變不影響實參!

1.調用函數更改整型時傳值調用與傳址調用的區別

🎏傳值調用

🎏傳址調用

2.調用函數更改指針的指向時傳值調用和傳址調用的區別

🎏傳值調用

🎏傳址調用

3.調用函數更改數組和結構體成員

🎏更改數組成員

🎏更改結構體成員

📌二級指針的作用

1.鏈表的頭指針結構

2.空鏈表時的鏈表尾插

3.非空鏈表時的尾插邏輯

📌不使用二級指針操作鏈表的兩種方法

1.使用帶頭結點的鏈表

2.在外部更改頭指針的指向

結語


相信大家在初學鏈表時一定被下面這些函數的二級指針搞得暈頭轉向的,疑惑包括但不限于:

  • 什么是二級指針?
  • 為什么鏈表要用到二級指針?
  • 為什么同樣是鏈表的函數,有的要用二級指針而有的只要用一級指針?
  • 為什么同樣是鏈表,有的鏈表中使用了二級指針?而有的鏈表卻只需要使用一級指針?

要搞清上面這些問題,我們就要先搞清楚二級指針在鏈表中的作用到底是什么,接下來我將帶大家一起探究二級指針的"前世今生".


📌形參的改變不影響實參!

1.調用函數更改整型時傳值調用與傳址調用的區別

🎏傳值調用

如下代碼,我們在主函數創建了一個變量a,并給其賦值為5.然后我們通過傳值調用函數test1,在函數內部a的值改為10.并在過程中打印出a的值:

void test1(int a)
{a = 10;printf("調用函數時a=%d\n", a);
}int main()
{int a = 5;printf("沒有調用函數前a=%d\n", a);test1(a);printf("調用函數后a=%d\n", a);return 0;
}

在編譯器中查看運行結果:

可以看到,傳值調用雖然函數調用時將a的值改為了10,但是一旦出了函數之后a的值是完全沒有改變的.

因此:形參的改變不影響實參!

??????? 形參的改變不影響實參!

??????? 形參的改變不影響實參!


🎏傳址調用

如下代碼,我們在主函數創建了一個變量a,并給其賦值為5.還創建了一個整型指針pa記錄下了變量a地址.然后我們通過傳址調用函數test2,在函數內部使用指針將a的值改為10.并在過程中打印出a的值:

void test2(int *pa)
{*pa = 10;printf("調用函數時a=%d\n", *pa);
}int main()
{int a = 5;int* pa = &a;printf("沒有調用函數前a=%d\n", a);test2(pa);printf("調用函數后a=%d\n", a);return 0;
}

?在編譯器中查看運行結果:

可以看到,傳址調用的函數在內部修改a的值,出了函數依然是有效的.

這有些像快遞送貨上門時,如果按照人名派送快遞,可能在這個小區有3個人都叫"張偉",這時派送給哪個"張偉"都有可能派送錯,但是如果按照他下單時填寫的地址派送快遞,那就絕對不會出錯,名字可能出錯,但地址一定是唯一的.

傳值調用和傳址調用不同的核心原理:函數會對形參和中間變量重新分配空間?


2.調用函數更改指針的指向時傳值調用和傳址調用的區別

那么是否我們要改變形參時都傳指針就一勞永逸了呢?再來看個例子:

🎏傳值調用

如下代碼,我們在主函數創建了兩個變量a和b,并給其賦值為5和10.還創建了兩個整型指針papb分別記錄下了變量a和b的地址.然后我們通過傳值調用函數test3,在函數內部將pb的值賦給pa.并在過程中打印出pa和pb的值:

void test3(int* pa,int* pb)
{pa = pb;printf("調用函數時:\n");printf("pa指針中存儲的內容:%p\n", pa);printf("pb指針中存儲的內容:%p\n", pb);printf("\n");
}int main()
{int a = 5;int b = 10;int* pa = &a;int* pb = &b;printf("調用函數前:\n");printf("pa指針中存儲的內容:%p\n", pa);printf("pb指針中存儲的內容:%p\n", pb);printf("\n");test3(pa,pb);printf("調用函數后:\n");printf("pa指針中存儲的內容:%p\n", pa);printf("pb指針中存儲的內容:%p\n", pb);printf("\n");return 0;
}

在編譯器中查看運行結果:

(注:為方便演示使用的x86系統,因此指針的大小為4個字節)

可以看到,傳值調用雖然在函數調用時將pa的指向改為了pb,但是一旦出了函數之后pa的指向是完全沒有改變.

因此:在改變指針變量時形參的改變同樣不影響實參!


🎏傳址調用

既然改指針的時候給函數傳指針本身沒有用,那么要傳什么呢?沒錯,要傳"指針的指針",即二級指針.

如下代碼,我們在主函數創建了兩個變量a和b,并給其賦值為5和10.還創建了兩個整型指針pa和pb分別記錄下了變量a和b的地址.又創建了一個二級整型指針ppa用來記錄指針pa的地址,然后我們通過傳址調用函數test4,在函數內部將pb的值賦給解引用的ppa.并在過程中打印出pa和pb的值:

void test4(int** ppa, int* pb)
{*ppa = pb;printf("調用函數時:\n");printf("pa指針中存儲的內容:%p\n", *ppa);printf("pb指針中存儲的內容:%p\n", pb);printf("\n");
}int main()
{int a = 5;int b = 10;int* pa = &a;int* pb = &b;int** ppa = &pa;printf("調用函數前:\n");printf("pa指針中存儲的內容:%p\n", pa);printf("pb指針中存儲的內容:%p\n", pb);printf("\n");test4(ppa, pb);printf("調用函數后:\n");printf("pa指針中存儲的內容:%p\n", pa);printf("pb指針中存儲的內容:%p\n", pb);printf("\n");return 0;
}

在編譯器中查看運行結果:

可以看到,傳址調用的函數在內部修改指針pa的值,出了函數依然是有效的.

因此當我們想要在函數內修改指針的指向時,我們應該給函數傳入二級指針.


3.調用函數更改數組和結構體成員

🎏更改數組成員

如下代碼,我們在主函數創建了一個5個成員的數組arr,并給其初始化為0.然后我們通過調用函數test5,在函數內部將arr的成員賦為0,1,2,3,4.并在過程中打印出arr數組的成員值:

void test5(int arr[])
{//修改arr數組成員的值for (int i = 0; i < 5; i++){arr[i] = i;}printf("調用函數時arr數組的成員:\n");for (int i = 0; i < 5; i++){printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[5] = { 0 };printf("調用函數前arr數組的成員:\n");for (int i = 0; i < 5; i++){printf("%d ", arr[i]);}printf("\n");test5(arr);printf("調用函數后arr數組的成員:\n");for (int i = 0; i < 5; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

在編譯器中查看運行結果:

可以看到,test5函數成功修改了arr數組的成員值,但我們好像并沒有傳給函數arr數組的地址,為什么修改成功了呢?

這是因為在C語言中,數組名就是數組首元素的地址,因此我們看似給test5函數傳入的是arr的名字,但實際上test5函數接收到的卻是arr數組的地址,因此該函數同樣可以寫為:

void test5(int* arr)
{//修改arr數組成員的值for (int i = 0; i < 5; i++){*(arr+i) = i;}printf("調用函數時arr數組的成員:\n");for (int i = 0; i < 5; i++){printf("%d ", *(arr + i));}printf("\n");
}

測試運行結果和上面沒有任何差別:


🎏更改結構體成員

如下代碼,我們在主函數中創建了一個結構體變量stu,并給其賦值"張三",20,1006.

然后我們通過傳址調用函數test6,在函數內部將stu的成員賦為"李四",30,1024.并在過程中打印出stu結構體的成員值:

typedef struct Student
{char name[5];int age;int idea;
}Stu;void test6(Stu* stu)
{strcpy(stu->name, "李四");stu->age = 30;stu->idea = 1024;printf("調用函數時stu結構體的成員:\n");printf("%s ", stu->name);printf("%d ", stu->age);printf("%d ", stu->idea);printf("\n");
}int main()
{Stu stu = { "張三",20,1006 };printf("調用函數前stu結構體的成員:\n");printf("%s ", stu.name);printf("%d ", stu.age);printf("%d ", stu.idea);printf("\n");test6(&stu);printf("調用函數后stu結構體的成員:\n");printf("%s ", stu.name);printf("%d ", stu.age);printf("%d ", stu.idea);printf("\n");return 0;
}

?在編譯器中查看運行結果:

可以看到,要更改結構體的值,需要給函數傳入結構體的指針才可以完成修改.


📌二級指針的作用

1.鏈表的頭指針結構

我們在單鏈表程序的最開始曾經寫過這樣一句代碼:

這句代碼的作用創建了一個鏈表的頭指針,其邏輯圖示如下:

在計算機的棧上的物理結構(以下簡稱物理結構)圖示如下:


2.空鏈表時的鏈表尾插

尾插操作我們已經在之前單鏈表詳解中詳細介紹過了,

因此這里只演示其邏輯圖示:(紫色線條代表操作)

物理圖示:(紫色線條代表操作)

可以看到,在空鏈表時的鏈表尾插操作中,我們更改了頭指針plist的指向,因此在函數中要使用到二級指針.


3.非空鏈表時的尾插邏輯

邏輯圖示:(紫色線條代表操作)

物理圖示:(紫色線條代表操作)

可以看到,在非空鏈表時的尾插中我們更改的是d2結點結構體的指針域的存儲內容,因此這時我們操作只需要d2結構體的地址,即一級指針.


綜上可得:

鏈表中傳入二級指針的原因是我們會遇到需要更改頭指針plist的指向的情況.

如果我們僅是在不改變頭指針plist的指向的情況下對鏈表進行操作(如非空鏈表的尾刪,尾插,對非首結點(FirstNode)的結點的插入/刪除操作等),則不需要用到二級指針.


📌不使用二級指針操作鏈表的兩種方法

那么我們在寫鏈表程序時就必須要使用二級指針嗎?答案是否定的,下面給大家提供了兩種不使用二級指針就可以完成鏈表所有操作的方法,大家可以結合自身情況選擇合適的方法完成鏈表程序.

1.使用帶頭結點的鏈表

原理:如果我們為單鏈表設置一個哨兵位的頭結點,那么plist的指向就固定了.即:

帶頭結點空鏈表示意圖:

這時我們想改變鏈表的首結點(firstNode),如頭刪,頭插等操作就只需要改變頭結點的指針域即可.而plist只需要固定存儲頭結點(headNode)的地址,既然函數不需要改變plist的指向,也就不需要用到二級指針了.

帶頭結點空鏈表頭插邏輯示意圖:(紫色線條為操作)

帶頭結點空鏈表頭插邏輯物理示意圖:(紫色線條為操作)

可以看到,在帶頭結點空鏈表的頭插操作中,plist的值沒有被改變,我們通過改變頭結點指針域的值實現了鏈表的頭插,因此使用帶頭結點的鏈表就可以不使用二級指針操作鏈表.


2.在外部更改頭指針的指向

原理:既然我們在函數內部給plist賦值不會影響到函數外的plist的指向,那么我們直接將更改指向這步操作放在函數外即可.其實類似的操作我們在獲取新結點函數中就已經應用過了:

單鏈表中的BuySLTNode()函數:

為了防止newnode指針記錄的動態開辟的空間的地址出了函數就被銷毀,我們將新結點的地址通過返回值返回到函數外并用一個指針接收,這樣雖然出了空間newnode被銷毀,但我們已經在函數外部使用指針記錄了下函數返回的它的地址,因此出了函數還可以正常使用這塊空間.

同理,函數中更改了頭指針的指向,我們將新的頭指針的地址記錄下來并返回給主函數,然后在主函數中重新使用plist指針接收這個頭即可更新頭指針的指向:

該思路代碼示例如下(僅展示頭插部分主函數與頭插函數邏輯) :

//單鏈表頭插
SLTNode* SLTPushFront(SLTNode* phead, int x)
{//創建新結點SLTNode* newnode = BuySLTNode(x);//BuySLTNode函數的實現參照上文//先將newnode的next指向首結點newnode->next = phead;//再將phead指向newnodephead = newnode;//返回新頭pheadreturn phead;
}int main()
{SLTNode* plist=NULL;printf("請輸入要頭插的數據:>");int pushfront_data = 0;scanf("%d", &pushfront_data);plist=SLTPushFront(plist, pushfront_data);//把SLTPushFront函數返回的新頭的地址賦給plist,這樣plist就重新指向新頭了return 0;
}

經過測試,這種方法同樣可以不使用二級指針就能夠完成鏈表的一系列相關操作,但缺點只要調用了有可能改變plist的函數,都必須在外面使用plist接收返回值以便更新新的頭結點.有時一旦忘了就會導致程序出錯,比較麻煩且容易出錯.


結語

希望這篇鏈表中二級指針的應用能對大家有所幫助,歡迎大佬們留言或私信與我交流.

學海漫浩浩,我亦苦作舟!關注我,大家一起學習,一起進步!

相關文章推薦

【數據結構】什么是線性表?

【數據結構】線性表的鏈式存儲結構

【數據結構】鏈表的八種形態

【數據結構】C語言實現單鏈表萬字詳解(附完整運行代碼)

【數據結構】C語言實現帶頭雙向循環鏈表萬字詳解(附完整運行代碼)

【實用編程技巧】不想改bug?初學者必須學會使用的報錯函數assert!(斷言函數詳解)



數據結構線性篇思維導圖:

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

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

相關文章

render函數舉例

在這段代碼中&#xff0c;renderButton是一個對象嗎 還有render為什么不能寫成render() {} 代碼原文鏈接 <template><div><renderButton /></div> </template><script setup> import { h, ref } from "vue"; const renderButt…

C#,簡單修改Visual Studio 2022設置以支持C#最新版本的編譯器,尊享編程之趣

1 PLS README & CHAPTER 5 用一個超簡單的例子說明各版本 C# 的差異。 使用新版本&#xff08;比如C#.11&#xff09;&#xff0c;當然有一定的好處。我們在寫程序的時候一般這樣&#xff1a; Visual Studio 2022 默認只能這樣寫&#xff1a; string imageFile Path.C…

若依框架參數驗證

文章目錄 一、前端觸發參數校驗異常1.前端頁面2.前端代碼 二、后端觸發參數校驗異常1.前端頁面2.后端報錯 三、后端自定義參數驗證1.添加注解2.觸發后端校驗 一、前端觸發參數校驗異常 1.前端頁面 輸入不符合校驗規則的值來觸發 2.前端代碼 校驗規則數組 表單的元素 修…

SQL Server數據庫備份與還原

目錄 SQL Server DataBase備份 SQL Server DataBase還原 SQL Server DataBase備份 在 SQL Server 中&#xff0c;你可以使用 SQL Server Management Studio (SSMS) 或 Transact-SQL 語句來手動備份數據庫。以下是兩種方法&#xff1a; 使用 SQL Server Management Studio (SS…

JAVA小游戲“飛翔的小鳥”

第一步是創建項目 項目名自擬 第二步創建個包名 來規范class 再創建一個包 來存儲照片 如下&#xff1a; 代碼如下&#xff1a; package game; import java.awt.*; import javax.swing.*; import javax.imageio.ImageIO;public class Bird {Image image;int x,y;int width…

Windows下安裝Anaconda3并使用JupyterNoteBook

下載安裝包 Anaconda官網 進官網&#xff0c;點擊下載 自動根據當前系統下載對應的包了&#xff0c;安裝包大約1G&#xff0c;喝杯Java耐心等待。 安裝 很多人安裝C盤&#xff0c;我這里放D盤。 注意&#xff1a;你的文件夾目錄一定要不能有空格 然后其他的直接默認install即…

不同路徑 遞歸

int dfs(int i, int j, int m, int n) { if (i > m || j > n) return 0; // 越界了 if (i m && j n) return 1; // 找到一種方法&#xff0c;相當于找到了葉子節點 return dfs(i 1, j, m, n) dfs(i, j 1, m, n); } int u…

在線視頻課程教育系統源碼/網課網校/知識付費/在線教育系統/在線課程培訓系統源碼

源碼簡介&#xff1a; 在線視頻課程教育系統源碼&#xff0c;作為網課/網校/知識付費/在線教育系統&#xff0c;它有文章付費閱讀在線點播自動發貨付費閱讀VIP會員系統等功能。它是實用的在線課程培訓系統源碼。 發貨100-在線視頻課程教育系統&#xff0c;它是一款功能實用的…

優思學院|2024年質量管理的大趨勢

2023年我們已經順利度過了整年的大部分時間&#xff0c;2024年質量管理的趨勢和問題在全球范圍內都已經引起了關注&#xff0c;或者仍然是企業導航的首要任務。 1. 通貨膨脹與質量管理 2023年&#xff0c;全球范圍內通貨膨脹和嚴峻的經濟狀況成為企業最關心的問題之一。盡管物…

Flash可更換聲音語音芯片WT588F02系列:優勢盡顯,應用廣泛

在語音技術日益普及的今天&#xff0c;唯創知音推出的Flash可更換聲音語音芯片WT588F02系列備受關注。該系列芯片憑借其強大的性能與廣泛的應用領域&#xff0c;成為市場上的一顆璀璨明星。本文將分析WT588F02系列的優勢&#xff0c;并探討其應用場景&#xff0c;以展現其在語音…

typedef 的使用

typedef 的定義 typedef 是 C 和 C 中的一個關鍵字&#xff0c;用于給已有類型定義一個新的名字&#xff0c;與 class、struct、union 和 enum 聲明不同&#xff0c;typedef 聲明不引入新類型&#xff1b;它們引入現有類型的新名稱 typedef 的語法格式 typedef existing_typ…

gitlab 12升級14(解決各種報錯問題)

1.這里是從自己公司的源下載的rpm包&#xff0c;需要換成自己的 2.從12的最后一個版本升級到14的最后一個版本 # 停服務 [rootdocker test]# gitlab-ctl stop puma && gitlab-ctl stop sidekiq && gitlab-ctl stop nginx && gitlab-ctl status# 進入…

前端如何判空

這樣判空就會報錯 loadNode(node, resolve)console.log("node")console.log(node)if (node.data ! null) {this.get(ctx /publicity/publicityType/typeTreeData?id node.data.id).then((res) > {resolve(res)})}}, 需要這樣寫&#xff0c;用typeof來做類型判…

【webrtc】ModuleRtpRtcpImpl2: RtpRtcp DEPRECATED_Create 廢棄了

基于m98 代碼。Deprecate the static RtpRtcp::Create() method. 提交記錄RtpRtcp::Create factory method 工廠方法廢棄了。std::unique_ptr<RtpRtcp> RtpRtcp::DEPRECATED_Create(const Configuration& configuration) {RTC_DCHECK

java 實現發送郵箱,復制即用,包含郵箱設置第三方登錄授權碼獲取方法

application.yml spring:profiles:active: dev # active: test#郵件附件上傳文件大小限制servlet:multipart:max-file-size: 50MB #單個文件大小限制max-request-size: 100MB #總文件大小限制&#xff08;允許存儲文件的文件夾大小&#xff09;mail:default-encoding: UTF…

【開題報告】基于SpringBoot的機車模型交流平臺的設計與實現

1.研究背景 機車模型是一種受到廣泛關注的模型制作愛好&#xff0c;它涵蓋了機車模型的收藏、展示、制作等多個方面。然而&#xff0c;由于機車模型愛好者的數量較少&#xff0c;且分散在不同的地區和社區&#xff0c;導致他們難以進行互動和資源共享。因此&#xff0c;需要一…

COBOL排序問題

*SORT排序文件文件超長&#xff0c;將主鍵保存在臨時文件里&#xff0c;超長的數據從數組里面去檢索獲取。 IDENTIFICATION DIVISION. PROGRAM-ID. TEST002. * ENVIRONMENT DIVISION. CONFIGURATION SECTION. …

YOLOv5改進: Inner-IoU基于輔助邊框的IoU損失,高效結合 GIoU, DIoU, CIoU,SIoU 等 | 2023.11

??????本文獨家改進:Inner-IoU引入尺度因子 ratio 控制輔助邊框的尺度大小用于計算損失,并與現有的基于 IoU ( GIoU, DIoU, CIoU,SIoU )損失進行有效結合 推薦指數:5顆星 新穎指數:5顆星 ??????Yolov5/Yolov7魔術師,獨家首發創新(原創),適用于…

安卓畢業設計基于安卓android微信小程序的培訓機構系統

項目介紹 本文以實際運用為開發背景&#xff0c;運用軟件工程原理和開發方法&#xff0c;它主要是采用java語言技術和mysql數據庫來完成對系統的設計。整個開發過程首先對培訓機構管理系統進行需求分析&#xff0c;得出培訓機構管理系統主要功能。接著對培訓機構管理系統 進行…

基于Python實現的一個命令行文本計數統計程序,可統計純英文txt文本中的字符數,單詞數,句子數,Python文件行數

項目簡介 這是一個用 Python 編寫的命令行文本計數統計程序。 基礎功能&#xff1a;能正確統計導入的 純英文txt文本 中的 字符數&#xff0c;單詞數&#xff0c;句子數。擴展功能&#xff1a;能正確統計導入的 Python 文件中的代碼行數&#xff0c;注釋行數&#xff0c;空白…