網(wǎng)站首頁 編程語言 正文
1、數(shù)組參數(shù)和指針參數(shù)
1.1 一維數(shù)組傳參
這里在前幾期我們已經(jīng)初略的見識過了,但是這里我們要提一個概念,數(shù)組給函數(shù)傳參是會發(fā)生降維的,降維成什么呢?我們看代碼:
這里通過打印形參的大小,發(fā)現(xiàn)是 4,其實(shí)也不奇怪,目前我們是 32 位操作環(huán)境,所以一個指針也就是 4 個字節(jié),所以從這里我們可以看出,數(shù)組傳參的時候,是發(fā)生降維的,數(shù)組名除了 &數(shù)組名 和 sizeof(數(shù)組名) 其他所有情況都是首元素地址,所以本質(zhì)上我們是降維成指向其數(shù)組內(nèi)部元素類型的指針,為什么呢,因?yàn)樗菙?shù)組首元素的地址,首元素是int 類型,所以傳過去的也是對應(yīng)的 int 類型的指針,同理我們需要拿同類型指針變量來接收,所以本質(zhì)上我們 p 變量中保存的就是 arr[0] 的地址!
我們在看一段代碼:
void printSize(int arr[100], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printSize(arr, 10);
return 0;
}
如上這段代碼有問題嗎?其實(shí)是沒有問題的,實(shí)際傳遞數(shù)組大小與函數(shù)形參指定的數(shù)組大小沒有關(guān)系,因?yàn)樗呀?jīng)是指針了,只是訪問方式被打通了,第二期我們有講過,那么既然如此,我們也可以不要里面的元素個數(shù)直接成 printSize(int arr[], int n) 這樣也是可以的,至少不會讓閱讀者感到誤會。
1.2 一級指針傳參
void print(int* p, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一級指針p,傳給函數(shù)
print(p, sz);
return 0;
}
這里我們需要討論一個問題,指針作為參數(shù)需要發(fā)生拷貝嗎?
答案是需要的,因?yàn)橹羔樧兞恳彩亲兞浚趥鲄⑸系梅献兞康囊螅簿褪窃跅I祥_辟空間,同時我們也知道,main 函數(shù)中的 p 是一個局部變量,它只在 main 函數(shù)內(nèi)有效,所以只能對實(shí)參做一份拷貝,并傳遞給被調(diào)用的函數(shù)。
1.3 二維數(shù)組參數(shù)和二級指針參數(shù)
這個例子我們發(fā)現(xiàn),二維數(shù)組傳參的時候也會發(fā)生降維,如何理解呢?上一期我們用了數(shù)組指針來接收了二級指針傳參,這里我們就來做一個總結(jié):
任何維度的數(shù)組,傳參的時候都要發(fā)生降維,降維成指向其內(nèi)部元素類型的指針,那么,二維數(shù)組內(nèi)部元素我們可以看成是多個一維數(shù)組,所以,二維數(shù)組傳參其實(shí)是降維成指向一維數(shù)組的指針,而這里的 arr 也就代表著首元素地址,也就是第一行一維數(shù)組的地址!這也就是我們之前可以拿指針數(shù)組來接收的原因了。
這里我們還是可以省略第一個下標(biāo)的值:char arr[][4] ,但是為什么不能省略第二個下標(biāo)值呢?我們可以想一下,之前寫用數(shù)組指針接收是這樣寫的 char (*p)[4] ,上面我們提到過,int arr[] 用來接收實(shí)參,它本質(zhì)上就是個指針,所以 char arr[][4] 本質(zhì)上是個數(shù)組指針,從他的角度看,他指向了一個存放 4 個 char 類型元素的數(shù)組,所以如果省略了第二個下標(biāo)則指針類型不明確!
1.4 野指針的問題
這個問題其實(shí)很多書中都會有寫,我們這里就簡單提一下:
- 指針未初始化,默認(rèn)是隨機(jī)值,如果直接訪問會非法訪問內(nèi)存
- 指針越界訪問,當(dāng)指針指向不屬于我們的內(nèi)存,p就是野指針
- 指針指向的空間被釋放,如果動態(tài)開辟的內(nèi)存被釋放但是指針沒置NULL,就會形成野指針,他仍然記錄者已經(jīng)不屬于他的內(nèi)存
- 返回局部變量的地址,如果我們一個函數(shù)被銷毀后但是仍然返回函數(shù)內(nèi)局部變量的地址也會造成也會造成野指針
2、函數(shù)指針
指針變量是用來保存地址的,那么函數(shù)有地址嗎?有!函數(shù)是由我們自己寫的一些語句構(gòu)成的,程序運(yùn)行的時候就會把定義好的函數(shù)中的語句調(diào)用到內(nèi)存中去,那么函數(shù)代碼在內(nèi)存中開始的那個內(nèi)存空間的地址也就是函數(shù)的地址!
這里我們也能發(fā)現(xiàn),函數(shù)是有地址的,而且 &函數(shù)名 和 單獨(dú)的函數(shù)名 都能表示函數(shù)的地址。
那么我們?nèi)绻氚押瘮?shù)的地址存起來該如何做呢?有了上面學(xué)習(xí)指針數(shù)組和數(shù)組指針的經(jīng)驗(yàn),其實(shí)函數(shù)指針也很好理解:
void (*pfun) () 其實(shí)這么寫可以了,我們來解讀下這句代碼:pfun 先和 * 結(jié)合,正如我們之前所說,就能說明他是一個指針,指向的是一個無參數(shù)并且無返回類型的函數(shù)。
那我們?nèi)绻赶蛞粋€ int add (int x, int y) 這樣的一個函數(shù),我們應(yīng)該如何定義函數(shù)指針呢?
int (*p) (int, int) 如同上面一樣,首先要保證 p 是指針,所以帶上括號,指向的是一個返回值為 int 參數(shù)為 int int 的函數(shù)。
接下來我們來使用函數(shù)指針,使用方法跟函數(shù)一樣,直接把指針變量名當(dāng)函數(shù)名使用即可:
讓我們來看一道有意思的題:
int main()
{
(*(void (*)())0)();
return 0;
}
首先這道題的解法肯定先從 0 下手,我們先分析,0 前面的 (void (*) ()) 是什么?這很明顯是一個函數(shù)指針類型,所以可以理解成把 0 強(qiáng)轉(zhuǎn)成函數(shù)指針,也就是把 0 當(dāng)成了一個函數(shù)的地址,然后再 * 引用這個地址,也就是找到 0 地址處的函數(shù)進(jìn)行調(diào)用。所以此代碼就是一次函數(shù)調(diào)用,被調(diào)函數(shù)無參,返回類型是void。
3、函數(shù)指針數(shù)組
有了上面的學(xué)習(xí)就很好理解了,無非就是保存函數(shù)地址的數(shù)組,那么它的語法格式是什么呢?
int (*arr[10]) (int, int)
這里我們可以分析到:首先 arr 跟 [ ] 先結(jié)合,所以它是個數(shù)組,這個數(shù)組的每個元素是 int (*) (int int) 類型的函數(shù)指針,它的作用主要是轉(zhuǎn)移表,那我們這里就簡單用一下即可
假設(shè)我們需要兩個整數(shù)的 + - * / 我們寫完了四個函數(shù)是不是可以放到一個數(shù)組里,然后通過訪問數(shù)組下標(biāo)就能調(diào)用我們想用的函數(shù)了:
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
int main()
{
int (*arr[4]) (int, int) = { add, sub, mul, div };
printf("加法:%d\n", arr[0](1, 2));
printf("減法:%d\n", arr[1](5, 2));
printf("乘法:%d\n", arr[2](3, 3));
printf("除法:%d\n", arr[3](6, 2));
return 0;
}
4、指向函數(shù)數(shù)組的指針
看到這可能有的小伙伴覺得越來越套娃了,但其實(shí)這個也很好理解,無非就是一個指針指向了一個數(shù)組,數(shù)組每個元素是函數(shù)指針,這里我們簡單了解下概念即可,用的其實(shí)也不是很多,當(dāng)別人如果寫了這種代碼我們能看懂就行:
函數(shù)指針如何定義:
int test(char* str)
{
if (str == NULL) {
return 0;
}
else
{
printf("%s\n", str);
return 1;
}
}
int main()
{
//函數(shù)指針pfun
int (*pfun)(char*) = test;
//函數(shù)指針的數(shù)組pfunArr
int (*pfunArr[5])(char* str);
pfunArr[0] = test;
//指向函數(shù)指針數(shù)組pfunArr的指針ppfunArr
int (*(*ppfunArr)[5])(char*) = &pfunArr;
return 0;
}
我們來分析一下這個:int(*(*ppfunArr)[5])(char*),首先看到 (*ppfunArr) 這括號括起來先跟 * 結(jié)合證明它是一個指針,指向的類型是什么呢?把它去掉剩下的就是它的類型,int(*[5])(char*),通過這個可以發(fā)現(xiàn),是一個帶有5個元素的數(shù)組,每個元素的類型是一個函數(shù)指針,而函數(shù)的返回值為int,參數(shù)為 char*
這里我們能看懂即可。
5、回調(diào)函數(shù)
回調(diào)函數(shù)指的就是一個通過函數(shù)指針調(diào)用的函數(shù),如果你把函數(shù)的指針(地址),作為參數(shù)傳遞給另一個函數(shù)的話,當(dāng)這個指針被用來調(diào)用其指向的函數(shù),這里就被稱為回調(diào)函數(shù)。其實(shí) qsort 函數(shù)就是很典型使用了回調(diào)函數(shù)的例子,感興趣的可以自行下來了解一下,這里我們就簡單的演示下如何使用,用回調(diào)函數(shù)實(shí)現(xiàn)三個數(shù)比較大小:
int max(int x, int y, int z, int(*pfun)(int, int))
{
if (x > pfun(y, z)) {
return x;
}
else
{
return pfun(y, z);
}
}
int tmp(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int ret = max(10, 20, 30, tmp);
printf("%d\n", ret);
return 0;
}
比較三個數(shù)的最大值是有更優(yōu)的解決方案的,我們這里只是演示一下回調(diào)函數(shù)的簡單使用,跟上面一樣,會用即可,其實(shí)不用研究的特別深入
6、一道筆試題
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *-- * ++cpp + 3);
printf("%s\n", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
return 0;
}
這道題我就不講解了,學(xué)習(xí)一定得有自己研究的一個過程,包括后續(xù) Java 的文章,每一期基本上都會留一個小疑問讓大家自己下去解答,其實(shí)這道題很簡單,耐心畫畫圖就能理解了,如果你能自己解決這道題,說明你的指針的數(shù)組這兩章的內(nèi)容已經(jīng)通關(guān)了,實(shí)在是難以解決的話,可以問一下博主。
后續(xù)其實(shí)還有動態(tài)內(nèi)存管理,但是這個知識點(diǎn)無非就是掌握對 malloc calloc realloc free 的使用,如果你是以后 C++ 方向可學(xué)習(xí)一下,如果你是 Java 方向其實(shí)有個基本認(rèn)識就行,畢竟 Java接觸底層不多,有了前面學(xué)習(xí)的鋪墊,去網(wǎng)上看看內(nèi)存管理的文章是很輕松學(xué)會的,學(xué)習(xí)最主要是培養(yǎng)學(xué)習(xí)的能力,
最后來個大總結(jié):從剛開始我們一共講解了32個關(guān)鍵字,在關(guān)鍵字中也穿插了很多內(nèi)容,比如大小端,結(jié)構(gòu)體,往后就是符號的理解了,包括我們平常用的注釋,以及各種運(yùn)算符但是除法和取模我們沒有放進(jìn)去,這個在JavaSE系列中會介紹,再往后就是對預(yù)處理的深入理解了,最終我們以數(shù)組和指針結(jié)尾,C語言系列就到此結(jié)束了。
原文鏈接:https://blog.csdn.net/m0_61784621/article/details/126129515
相關(guān)推薦
- 2023-01-19 GO語言的數(shù)組array與切片slice詳解_Golang
- 2022-04-01 Kubernetes命令行工具--kubectl管理
- 2024-07-18 spring @retryable不生效的一種場景
- 2022-10-23 Python?Pandas數(shù)據(jù)合并pd.merge用法詳解_python
- 2022-06-12 C語言?深入探究動態(tài)規(guī)劃之區(qū)間DP_C 語言
- 2022-05-28 python爬蟲框架scrapy下載中間件的編寫方法_python
- 2022-02-28 Uncaught Error: Module parse failed: Unexpected to
- 2022-07-16 BOM與DOM的進(jìn)階知識
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支