【數據結構】鏈表中二級指針的應用

🦄個人主頁:修修修也

🎏所屬專欄:數據結構

??操作環境: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/160578.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/160578.shtml
英文地址,請注明出處:http://en.pswp.cn/news/160578.shtml

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

相關文章

微服務學習|初識Docker、使用Docker、自定義鏡像、DockerCompose、Docker鏡像倉庫

初識Docker 項目部署的問題 大型項目組件較多&#xff0c;運行環境也較為復雜&#xff0c;部署時會碰到一些問題 依賴關系復雜&#xff0c;容易出現兼容性問題 開發、測試、生產環境有差異 Docker如何解決依賴的兼容問題的? 將應用的Libs (函數庫)、Deps (依賴)配置與應用…

線性回歸的正則方法:嶺回歸和Lasso

線性回歸的正則方法包括嶺回歸&#xff08;Ridge Regression&#xff09;和Lasso回歸&#xff08;Least Absolute Shrinkage and Selection Operator Regression&#xff09;。這兩種方法都是為了解決線性回歸中可能存在的過擬合問題而提出的。 選擇使用嶺回歸還是Lasso回歸通常…

使用 goland 開發 golang 項目環境配置

方式1&#xff1a;使用 GOPATH 和 GOROOT 在 goland 中打開&#xff1a;Settings - Go&#xff0c;會看到 GOROOT、GOPATH&#xff0c;其相關解釋與配置如下&#xff1a; GOROOT&#xff1a;對應 go 的安裝路徑&#xff0c;例如&#xff1a;D:\go\binGOPATH&#xff1a;是我們…

JavaScript中的事件循環 為什么是微任務先運行

無意中看到這個問題&#xff0c;以下是個人的看法 1、性能和響應性&#xff1a; 微任務通常比宏任務執行得更快&#xff0c;因為微任務通常涉及更少的工作量。將微任務放在宏任務之前可以盡早執行那些需要快速響應的任務&#xff0c;提高系統的響應性能。 2、Promise 的異步特…

3d標簽云實現過程(tagcloud.js)同步原生和 vue

寫在前面 本來是沒有準備寫這個知識點&#xff0c;但是下載這個 js 的時候發現很多都是要錢或者是積分的&#xff0c;我就不明白了一個開源了這么久的 js 怎么還有人拿來掙錢的&#xff0c;同時還有一些只有原生 html 的例子&#xff0c;但是現在都是 框架主導的一些項目&#…

【Exception】Error: Dynamic require of “path“ is not supported

Talk is cheap, show me the code. 環境 | Environment kversionOSwindows 11Node.jsv18.14.2npm9.5.0vite5.0.0vue3.3.8 報錯日志 | Error log >npm run dev> app10.0.0 dev > viteERROR failed to load config from C:\code\frontend\app1\vite.config.js …

【LeetCode二叉樹進階題目】606,102,107

二叉樹進階題目 606. 根據二叉樹創建字符串解題思路及實現 102. 二叉樹的層序遍歷解題思路及實現 107. 二叉樹的層序遍歷 II解題思路及實現 606. 根據二叉樹創建字符串 描述 給你二叉樹的根節點 root &#xff0c;請你采用前序遍歷的方式&#xff0c;將二叉樹轉化為一個由括號…

從零開始學習typescript——運算符(算術運算符、賦值運算符、比較運算符)

算術運算符 算術運算符主要是針對數值類型和長整型&#xff1b;包括有加法、減法、乘法、除法、自增、自減等運算 加法&#xff08;&#xff09; let x:number1let y:number 2console.log(xy)減法&#xff08;-&#xff09; let x:number1let y:number 2console.log(y-x)乘法…

晶振有哪幾種?晶振旁邊的兩個電容起什么作用?

晶振可以分為普通晶振、溫補晶振、壓控晶振、恒溫晶振、差分晶振。 普通晶振通常用作微處理器的時鐘器件&#xff0c;主要應用于那些穩定度要求不要的設備中&#xff0c;例如電視機、微波爐。 溫補晶振&#xff0c;在晶振內部采取了對晶體頻率、溫度特性進行補償&#xff0c;已…

軟件工程理論與實踐 (呂云翔) 第十三章 軟件測試方法與過程課后習題及其答案解析

第十三章 軟件測試方法與過程 1.判斷題 &#xff08;1&#xff09;白盒測試無須考慮模塊內部的執行過程和程序結構&#xff0c;只需了解模塊的功能即可。() 解析&#xff1a;白盒測試需要考慮模塊內部的執行過程和程序結構&#xff0c;以便設計測試用例和覆蓋代碼路徑。 &a…

軟文推廣有什么作用?媒介盒子分享

數字時代&#xff0c;品牌方以往的營銷打法可能需要應時而變&#xff0c;傳統的廣告模式很難將品牌推廣出去&#xff0c;原因就在于傳統廣告的成本高昂并且針對性較弱&#xff0c;而軟文推廣能夠通過較低的成本將產品或品牌信息送到消費者面前&#xff0c;今天媒介盒子就來分享…

58同城算法工程師一面&二面 面試題

來源&#xff1a;投稿 作者&#xff1a;LSC 編輯&#xff1a;學姐 一面 40min 1.Gbdt和xgboost的區別 XGBoost是對GBDT的改進和擴展&#xff0c;它提供了更高的效率、更好的性能、正則化技術、內置特征選擇等功能。 (1)正則化: GBDT使用基本的樹模型&#xff0c;并在每一輪…

vue3.0 + qiankun遇到的問題

進入子應用再回到主應用切換動態路由時 TypeError: Cannot read properties of undefined (reading ‘appWrapperGetter’) application ‘plat’ died in status UNMOUNTING: instance.$destroy is not a function 第一個報錯是因為子應用切走時沒有銷毀 vue的實例&#xff0…

常用RFC規范匯總

官網&#xff1a;https://www.rfc-editor.org/ The RFC Series (ISSN 2070-1721) contains technical and organizational documents about the Internet, including the specifications and policy documents produced by five streams: the Internet Engineering Task Force …

TCP/IP

分層模型 TCP 傳輸控制協議 UDP 用戶數據包協議 四層 應用層 負責發送/接收消息 傳輸層 負責拆分和組裝 .期間會有編號 網絡層 TCP/UDP 屬于網絡層, 不會判斷和處理編號 數據鏈路層 以太網 ,網絡設備 TCP 連接 TCP連接需要端口,進行通信 Java 通過Socket 接收消息 發送 …

基于SpringBoot+Vue的體檢預約管理系統

基于SpringBootVue的體檢預約管理系統的設計與實現~ 開發語言&#xff1a;Java數據庫&#xff1a;MySQL技術&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系統展示 主頁 管理員界面 用戶界面 摘要 體檢預約管理系統是一種基于Spring Boot…

Vue3常用操作

一、Vue3項目構建 1、安裝最新版本vue npm create vuelatest 2、選擇需要的配置 3、進入項目 cd 項目名稱 4、下載依賴 npm install 5、啟動項目 npm run dev

chatGLM3微調

文章目錄 一、問答數據集生成器使用設置問題啟動使用產出效果 二、進行微調第一步&#xff1a;下載模型第二步&#xff1a;項目準備2.1 下載項目2.2 然后使用 pip 安裝依賴2.3 開始 第三步進行微調3.1安裝相關依賴3.2準備數據集&#xff0c;并且上傳3.3對數據集進行預處理3.4 進…

如何使用技術SEO來優化評論

你在網上購買嗎&#xff1f;我的意思是&#xff0c;在當今時代&#xff0c;誰不這樣做&#xff1f;作為買家&#xff0c;無論您想購買什么&#xff0c;您都了解全面和高質量評論的價值。這是您在決定是否購買產品時考慮的重要因素。 這就是為什么許多人在網上購物之前使用評論…

移動端click事件、touch事件、tap事件的區別

在移動端&#xff0c;有三種常見的事件類型&#xff0c;click事件、touch事件、tap事件。它們的區別如下&#xff1a; click事件&#xff1a;click事件是在用戶點擊屏幕的時候觸發&#xff0c;如果是移動設備&#xff0c;則會在用戶點擊屏幕的同時觸發touch事件。但是&#xff…