【C語言】指針(3):探索-不同類型指針變量

目錄

一、字符指針變量

二、數組指針變量

三、二維數組傳參的本質

四、函數指針變量

4.1 函數指針變量

4.2 函數指針變量的使用

4.3 函數指針變量的拓展

五、函數指針數組

六、轉移表的應用

通過深入理解指針(1)和深入理解指針(2),我們對指針有了一個初步的了解,學會了一級指針、二級指針、指針數組……而深入理解指針(3),主要是為了學習不同數據類型的指針變量。

一、字符指針變量


? ? ?字符串指針變量的指針類型為char*,下面我們通過這段代碼來解析字符指針變量。

int main()
{printf("指針接收字符\n");char ch = 'w';char* pc = &ch;printf("\t*pc=%c\n", *pc);printf("----------------\n");printf("指針接收字符串\n");const char* pstr = "abcdef";//const 加了一層保護,使其變成常量字符串,被修改編譯器會報錯printf("\t*pstr=%c\n", *pstr);//其實是把字符串的首字符地址放到pstr,字符串出現在表達式中時,他的值就是第一個字符的地址printf("\tpstr=%s\n", pstr); //%s占位符的特點就是只要告訴他字符串的首地址, 就可以讀取整個字符串printf("\tpstr[3]=%c\n", pstr[3]);//[]是特殊的解引用操作符,等價于*(pstr+3),相當于得到第1個元素偏移3得到第四個元素printf("\tabcdef[3] = % c\n", "abcdef"[3]);//可以把字符串想象成一個字符數組,可以通過下標去訪問他return 0;
}

指針接收字符
? ? ? ? *pc=w
----------------
指針接收字符串
? ? ? ? *pstr=a
? ? ? ? pstr=abcdef
? ? ? ? pstr[3]=d
? ? ? ? abcdef[3] = d?

? ? ? ?字符指針變量,顧名思義就是指向字符的指針變量,所以利用指針接收字符的地址(第31行代碼),最后解引用該指針變量得到的是對應的字符,非常容易理解。 但字符指針變量還有一種方式,就是接收字符串的地址。

? ? ? ?通過第35行代碼,我們用字符指針變量pstr接收了字符串“abcdef”,那這是把整個字符串放到pstr指針變量里面了嗎?

? ? ? 其實并不是的,我們通過第36行代碼的運行結果,發現將指針變量pstr解引用后得到的是‘a’,這說明字符指針變量pstr接收字符串的本質是將字符串的首字符地址存放到pstr中,所以如果字符串出現在表達式中,他的值就是第一個字符的地址。

? ? ? 既然pstr存放的是字符串首字符的地址,那么我們打印出來的是一個地址,但我們在看向第37行代碼,當我們用%s的占位符時,卻可以直接將整個字符串打印出來,這說明了%s占位符的特點就是只要告訴他字符串首字符的地址,他就可以直接讀取整個字符串。

? ? ?那為什么,我們知道了字符串的首元素地址,就可以通過%s打印出字符串全體呢?這是因為其實我們可以把字符串理解成一個字符數組,他具有數組的特點,可以通過首元素地址找到后面的全部元素,并且也可以像數組一樣通過下標去訪問每個元素,比如我們想訪問字符串下標為3的元素(d),那么通過第39行代碼我們可以發現“abcdef”[3]是可行的,

? ? ? 既然可以通過下標去訪問字符串,那么既然pstr是接收字符串的指針變量,那么我們同樣可以通過首元素地址的指針偏移來找到下標為3的元素,第38行代碼中的pstr[3](等價“*(pstr+3)”)也是可行的。

下面是一道和字符串相關的面試題。

int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

str1 and str2 are not same
str3 and str4 are same

為什么str1和str2的地址不同,而str3和str4的地址相同呢??

? ? ? ?將常量字符串賦值給數組(str1和str2),本質上是將這個常量字符串復制一份到數組中,這兩個數組其實并不在一個空間,所以str1=str2,并且復制出來的常量字符串是可以修改的。

? ? ? ?而如果通過字符指針變量指向常量字符串(str3和str4),對于常量字符串來說,是只能讀不能改的,從內存利用率來說,內容相同的字符串只會保存一份,所以str3=str4.

二、數組指針變量


我們學過指針數組,它是一個存放指針的數組。

那什么是數組指針變量呢?我們通過已經學過的指針變量來類比一下。

所以數組指針變量是一個存放的是數組的地址,并且能夠指向數組的指針變量。?

int* p1[10];
int(*p2)[10];

以上哪個是數組指針變量呢?

? ? ?對于int*p1[10]來說,首先p1會先和[ ]結合,然后int和*結合,所以p1有10個元素,并且每個元素是int*類型,所以p1是一個存放指針的數組,p1是指針數組。([ ]的優先級高于*)

? ? ?對于int(*p2)[10]來說,p2先和*結合了,所以*p2是一個指針,int和[10]代表p2指向的是一個數組,并且有10個元素,并且每個元素的類型是int,所以p2是數組指針。(因為[ ]的優先級高于*,所以必須加上( )來保證p和*先結合)

? ? ?那數組指針如何初始化呢?既然指針變量是用來存放數組地址的,而&arr是取整個數組的地址,所以寫法就是int(*p2)[10]=&arr。

三、二維數組傳參的本質


數組指針有什么用呢?其實數組指針有自己的應用場景,在此之前要先了解二維數組傳參的本質

以往我們對有一個二維數組需要傳遞給函數時,我們是這樣寫的

void test(int a[][5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", p[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7?

形參和實參都是二維數組的形式,但其實還有其他寫法。

? ? ? 對于二維數組來說,可以看做是每個元素是一維數組的數組,也就是二維數組的每個元素是一個一維數組。那么二維數組的首元素就是第一行,是個一維數組。

? ? ?根據一維數組的數組名名就是首元素地址、一維數組傳參本質是傳遞首元素地址這個規則,我們可以推出二維數組的數組名就是就是第一行(一維數組)的地址,二維數組傳參本質是傳遞第一行這個一維數組的地址。

? ? ?根據上面的代碼,我們知道該二維數組第一行的一維數組的數據類型是int[5],所以第一行的地址類型就是數組指針類型int(*)[5],所以我們可以將形參類型寫成指針形式。 ??

? ? 接下來對上面的代碼進行改寫,將形參寫成數組指針類型。

void test(int(*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ",p[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7?

怎么去理解*(*(p+i)+j))呢?我們要進行拆解!(假設訪問二維數組中的一個元素)

首先是p+i,二維數組的首元素地址是第一行的一維數組,所以p存放的是第一行的地址,所以+i會跳過i行,i=0時,此時跳過0行,拿到的是第一行的地址,i=1時,跳過1行,拿到的是第二行的地址,i=2時,跳過2行,拿到的是第三行的地址。

然后是*(p+i),假設i已經確定,此時就是通過解引用拿到了一行的數據。

然后是*(p+i)+j,此時*(p+i)已經拿到一行的數據了,通過j來訪問這一行的其他元素地址,當j=0時,就是首元素地址,j=1時,就跳過一個元素,拿到第二個元素的地址,以此類推,找到了該行所有元素的地址。

然后是*(*(p+i)+j)),假設j已經確定,此時*(p+i)+j就是一個元素的地址,再對他進行解引用,找到該元素。

底層邏輯還是通過指針的偏移量去訪問每個元素。所以p[i][j]的寫法也是可行的。

所以根據二維數組傳參的本質-----傳遞首行這個一維數組的地址,我們找到了數組指針變量的應用場景。

四、函數指針變量


4.1 函數指針變量


通過類比,函數指針就是指向函數的指針,那么函數指針變量就是用來存放函數的地址。

對應數組arr來說,arr是數組首元素地址,而&arr代表是整個數組的地址,而對于函數來說,函數名是函數的地址,&函數名也是函數的地址。

既然函數指針變量是用來存放函數的地址的,所以未來也可以通過函數的地址去調用函數

函數指針怎么創建?

int(*p)(int, int) = Add;
int(*p)(int x, int y) = &Add;

( )將*和p結合起來,說明這是一個指針,(int,int)說明這個指針指向一個函數,并且形參類型是int和int,開頭的int說明該函數的返回類型是int。

add和&add是一樣的,因為對于函數來說,函數名是地址,&函數名也是地址

同理*p和p也是一樣的,函數指針變量是可以不需要解引用。

形參的形參名可寫可不寫

int       (*pf3)   (int x,  int y)|            |      ————————————————|            |              ||            |     pf3指向函數的參數類型和個數交代|       函數指針變量名
pf3指向函數的返回類型int (*)(int x, int y)//pf3函數指針變量的類型

4.2 函數指針變量的使用

int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));printf("%d\n", pf3(3, 5));return 0;
}

5

8

注意:因為Add和&Add都是函數的地址,所以對于pf3來說,即使不解引用也是可以調用函數的,但如果解引用了,一定要記得用括號( )將*和pf3放在一起!!

4.3 函數指針變量的拓展

fun1(char* p, int (*)(char*));(*(void (*)())0)();void (*signal(int, void(*)(int)))(int);

分析這3個代碼

1.fun1的的第1個形參的類型是字符指針,第2個形參int(*)(char*),(*)代表這個形參是個指針,int和(char*)表名這是一個函數指針,形參類型為字符指針,返回值為整型。函數指針作為其他函數的形參時,其自身的函數名和形參名可以省略,僅保留數據類型即可。

2.多個括號要逐步拆解,void(*)( )說明這是一個void類型的函數指針,沒有形參,類型放在(),就是強制類型轉換,所以(void(*)( )0)的意思時將0這個整數值強制轉換成一個void(*)( )類型的函數指針,再進行解引用,得到的是函數指針的地址,結尾的( )就是調用0地址處的函數。所以上述代碼實際上是一個函數調用,將0轉化成一個void(*)( )類型的函數地址,再去調用0地址處的函數。

3.首先,*沒有和signal在一起,signal(int,void(*)(int))說明signal是一個函數名,該函數的形參有兩個類型,一個是int,一個是void(*)(int)類型的函數指針,剩下的部分就是該函數的返回類型,所以signal的返回類型是void(*)(int)類型的函數指針上述代碼其實是一個函數聲明。

通過上述的擴展,我們復習到了

1.認識函數指針類型

2.強制類型轉換

3.通過函數指針調用函數的方式

4.函數的定義、聲明、調用

4.4 typedef關鍵字


typedef是用來類型重命名的,可以將復雜的類型簡單化

typedef unsigned int uint;
//將unsigned int 重命名為uinttypedef int* ptr_t;//整形指針
//int*重命名為ptr_ttypedef int(*parr_t)[5];//數組指針
//int(*5)重命名為parr_ttypedef void(*pfun_t)(int);//函數指針
//void(*)(int)重名名為pfun_tvoid (*signal(int, void(*)(int)))(int);//進行改寫
pfun_t signal(int, pfun_t);

關于typedef,常規寫法是 ?typedef 類型 重命名 ?,但是對于數組指針類型和函數指針類型稍有區別,重命名部分要寫在*的后面。

五、函數指針數組


? ? ? 數組是一個存放相同類型數據的存儲空間,所以函數指針數組存放的是具有相同返回類型和形參的函數指針。

? ? ?函數指針數組怎么創建呢?

int (*parr1[3])();
int* parr2[3]();

?如上圖代碼,其實是parr1,首先parr1先和[ ]結合,說明parr1是個數組,且有3個元素,存放的是int(*)()類型的函數指針。

? ?函數指針數組的應用場景,我們可以通過轉移表來理解。

六、轉移表的應用


函數指針數組,用數組取每個元素的方式去調用函數,就叫轉移表。

當我們想要對兩個數進行加減乘除運算操作時,以下是計算機的一般實現。

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input){case 1:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}

假設我們想要對這兩個數進行更多的運算,那么由于增加了更多的選擇,switch語句的相關代碼會變得非常冗長,且重復性很高,所以此時用函數指針數組,可以很好地解決這個問題。下面我們通過函數指針數組來實現。

int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //轉移表do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("輸入操作數:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0)printf("退出計算器\n");elseprintf("輸入有誤\n");} while (input);return 0;
}

我們發現原本通過switch語句的選擇代碼,直接變成了函數指針數組的下標訪問,代碼簡潔清晰。

? ? ? 為什么可以使用函數指針數組?因為add、sub、mul、div這四個函數的形參以及返回類型是意義的,所以他們的函數指針類型也是一致的,根據數組只能存放相同數據類型的特點,所以這幾個函數可以被放在一個函數指針數組里,當放進函數指針數組時,我們就可以通過下標去訪問并調用對應的函數!

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

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

相關文章

67.SAP FICO-憑證類型學習

目錄 SAP憑證類型 憑證類型的作用 - OBA7 SAP默認的憑證類型更改 FI相應事務代碼默認憑證類型 - OBU1 對FB50、60、70默認憑證類型的更改 - OBZO 后勤貨物移動默認憑證類型 - OMBA 發貨憑證類型 收貨憑證類型 自動移動憑證類型 存貨盤點憑證類型 發票默認的憑證類…

深度學習Day-24:ResNeXt-50算法思考

&#x1f368; 本文為&#xff1a;[&#x1f517;365天深度學習訓練營] 中的學習記錄博客 &#x1f356; 原作者&#xff1a;[K同學啊 | 接輔導、項目定制] 要求&#xff1a; 閱讀給出代碼&#xff0c;判斷是否存在錯誤&#xff0c;正確與否都請給出你的思考&#xff1b;查找…

如何減少開發過程中的bug-數據庫篇

1.1慢查詢 1.1.1 是否命中索引 提起慢查詢&#xff0c;我們馬上就會想到加索引。如果一條SQL沒加索引&#xff0c;或者沒有命中索引的話&#xff0c;就會產生慢查詢。 索引哪些情況會失效&#xff1f; 查詢條件包含or&#xff0c;可能導致索引失效 如果字段類型是字符串&am…

LeetCode 0724.尋找數組的中心下標:前綴和(時空復雜度O(n)+O(1))

title: 724.尋找數組的中心下標 date: 2024-07-08 13:22:58 tags: [題解, LeetCode, 簡單, 數組, 前綴和] 【LetMeFly】724.尋找數組的中心下標&#xff1a;前綴和&#xff08;時空復雜度O(n)O(1)&#xff09; 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/find-pi…

數據結構--二叉樹相關習題5(判斷二叉樹是否是完全二叉樹 )

1.判斷二叉樹是否是完全二叉樹 辨別&#xff1a; 不能使用遞歸或者算節點個數和高度來判斷。 滿二叉樹可以用高度和節點來判斷&#xff0c;因為是完整的。 但是完全二叉樹前面是滿的&#xff0c;但是最后一層是從左到右連續這種 如果仍然用這種方法的話&#xff0c;如下圖…

暑期備考2024小學生古詩文大會:吃透真題和知識點(持續)

2024年上海市小學生古詩文大會的自由報名初賽將于10月19日&#xff08;星期六&#xff09;正式開始&#xff0c;還有3個多月的時間。 為幫助孩子們備考&#xff0c;我持續分享往年上海小學生古詩文大會真題&#xff0c;這些題目來自我去重、合并后的1700在線題庫&#xff0c;每…

加密與安全_密鑰體系的三個核心目標之完整性解決方案

文章目錄 Pre機密性完整性1. 哈希函數&#xff08;Hash Function&#xff09;定義特征常見算法應用散列函數常用場景散列函數無法解決的問題 2. 消息認證碼&#xff08;MAC&#xff09;概述定義常見算法工作原理如何使用 MACMAC 的問題 不可否認性數字簽名&#xff08;Digital …

SketchUp Pro 2024:現代科技之詩意體驗

在那遙遠的唐朝&#xff0c;李白曾以詩酒為伴&#xff0c;游歷山川&#xff0c;揮灑才情。而今&#xff0c;若李白穿越時空&#xff0c;手握現代科技之利器——SketchUp Pro 2024&#xff0c;定會以詩意之筆&#xff0c;描繪這款軟件的神奇與魅力。 初識SketchUp Pro 2024 初…

Vue Router:History 模式 vs. Hash 模式

在開發 SPA&#xff08;單頁應用程序&#xff09;時&#xff0c;路由管理是不可或缺的一部分。Vue.js 框架中的 Vue Router 提供了兩種主要的路由模式&#xff1a;History 模式和 Hash 模式。理解這兩種模式的區別及其實現方式&#xff0c;對于開發和部署 Vue 應用至關重要。 …

k8s record 20240708

一、PaaS 云平臺 web界面 資源利用查看 Rancher 5臺 CPU 4核 Mem 4g 100g的機器 映射的目錄是指docker重啟后&#xff0c;數據還在 Rancher可以創建集群也可以托管已有集群 先docker 部署 Rancher&#xff0c;然后通過 Rancher 部署 k8s 想使用 kubectl 還要yum install 安…

如何分析前后端bug?

如何分析前后端bug&#xff0c;我來支你1??招 &#x1f4dd;一般通過查看接口的方式分析前后端bug。 . 【方法】&#xff1a; web項目&#xff0c;用瀏覽器自帶的F12抓包看接口請求。 app客戶端&#xff0c;一般用fiddler等工具進行抓包接口。 . ?用經典的電商項目舉例&…

應用軟件受到網絡攻擊怎么辦?

大家都知道在目前的互聯網社會中&#xff0c;大型的網絡游戲與電商網站企業是網絡攻擊的重要對象&#xff0c;同時軟件應用也無法避免地會受到各種網絡攻擊&#xff0c;那么當我們的軟件應用被攻擊時&#xff0c;該怎么辦呢&#xff1f; 首先我們可以使用高防CDN&#xff0c;安…

2. 年齡問題

年齡問題 題目描述 本題為填空題&#xff0c;只需要算出結果后&#xff0c;在代碼中使用輸出語句將所填結果輸出即可。 S 夫人一向很神秘。這會兒有人問起她的年齡&#xff0c;她想了想說&#xff1a; "2020 年前&#xff0c;我丈夫的年齡剛好是我的 22 倍&#xff0c;…

ATA-8035射頻功率放大器在聲動力療法中的應用

聲動力療法是一種基于聲波能量的治療方法&#xff0c;廣泛應用于醫療和美容領域。它利用高強度聚焦的聲波來實現切割、破碎或加熱組織&#xff0c;以治療各種疾病和美容問題。在聲動力療法中&#xff0c;射頻功率放大器起著至關重要的作用&#xff0c;它負責提供足夠的能量來激…

達夢數據庫的系統視圖v$auditrecords

達夢數據庫的系統視圖v$auditrecords 在達夢數據庫&#xff08;DM Database&#xff09;中&#xff0c;V$AUDITRECORDS 是專門用來存儲和查詢數據庫審計記錄的重要系統視圖。這個視圖提供了對所有審計事件的訪問權限&#xff0c;包括操作類型、操作用戶、時間戳、目標對象等信…

詳解 | 什么是GeoTrust

GeoTrust是一家全球知名的數字證書頒發機構&#xff08;Certificate Authority&#xff0c;簡稱CA&#xff09;&#xff0c;專注于提供SSL/TLS證書和其他相關的網絡安全產品。 1、歷史背景&#xff1a; GeoTrust成立于2001年&#xff0c;最初作為一個獨立的公司運營。2006年&a…

js+spring boot實現簡單前后端文件下載功能

jsboot項目實現自定義下載 一、前端頁面 1、先導入axios的js包 2、注意axios響應的格式&#xff1a;result.data.真實的數據內容 3、這里請求的url就是你boot項目的getMapping的url&#xff0c;保持一致即可 4、如果想在后端設置文件名&#xff0c;那么后端生成后&#xf…

目標檢測算法介紹來了!

隨著人工智能技術的迅猛發展&#xff0c;目標檢測算法在計算機視覺領域扮演著越來越重要的角色。它廣泛應用于安防監控、自動駕駛、醫學影像分析、機器人視覺等多個領域&#xff0c;極大地推動了智能化進程。本文將對目標檢測算法進行深入的探討&#xff0c;包括其基本原理、發…

使用 Streamlit 和 asyncio 模塊進行異步編程

概述 Streamlit 是一個用于構建數據應用程序的強大工具&#xff0c;但它本身并不直接支持異步編程。然而&#xff0c;通過結合 Python 的 asyncio 模塊&#xff0c;我們可以在 Streamlit 應用中實現異步處理&#xff0c;從而提高應用的響應性和效率。 為什么需要異步編程 在…

安卓應用開發學習:騰訊地圖SDK應用改進,實現定位、搜索、路線規劃功能集成

一、引言 我的上一篇學習日志《安卓應用開發學習&#xff1a;通過騰訊地圖SDK實現定位功能》記錄了利用騰訊地圖SDK實現手機定位功能&#xff0c;并能獲取地圖中心點的經緯度信息。這之后的幾天里&#xff0c;我對《Android App 開發進階與項目實戰》一書第九章的內容深入解讀…