網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
賭你會(huì)懵的C語(yǔ)言指針進(jìn)階數(shù)組場(chǎng)景解析_C 語(yǔ)言
作者:?jiǎn)虇碳业凝堼?? 更新時(shí)間: 2022-04-19 編程語(yǔ)言正片開(kāi)始
細(xì)化指針這一部分內(nèi)容,現(xiàn)在著重把一些指針的運(yùn)用情景搬出來(lái)康康,如果對(duì)指針盤(pán)的不是非常熟練,或者指針還出于入門(mén)階段的鐵子請(qǐng)繞道(暈頭警告)
直接給大家盤(pán)個(gè)套餐:
一維數(shù)組
int a[] = {1,2,3,4,5}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a+0)); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(a[1])); printf("%d\n",sizeof(&a)); printf("%d\n",sizeof(*&a)); printf("%d\n",sizeof(&a+1)); printf("%d\n",sizeof(&a[0])); printf("%d\n",sizeof(&a[0]+1));
問(wèn)題很簡(jiǎn)單,這組 printf 的值是多少?小朋友你是否有些許害怕,但是沒(méi)有關(guān)系我們逐個(gè)擊破,這是后續(xù)內(nèi)容的基礎(chǔ)
記住你的答案,來(lái)看看編譯器是怎么解析的:
首先我們應(yīng)該知道數(shù)組名代表的是數(shù)組首元素的地址,但是要知道我們有兩種例外:
1.sizeof (),()內(nèi)是數(shù)組名時(shí),代表的就是整個(gè)數(shù)組,計(jì)算的就是數(shù)組的大小,單位是字節(jié);
2. & 數(shù)組名時(shí),表示的也是整個(gè)數(shù)組,取出的就是整個(gè)數(shù)組地址;
除了以上兩種情況,其余的所有數(shù)組名都表示首元素地址!
所以
1.sizeof(a)
就是直接將數(shù)組名放進(jìn)去,算出的就應(yīng)該是 sizeof(int)*5 = 20
2.sizeof(a+0)
此時(shí)不只有數(shù)組名,因此 a 代表首元素地址,a+0 等價(jià)于 a,是地址大小,在32位/64位平臺(tái)下對(duì)應(yīng) 4/8 字節(jié)大小
3.*a
,不只有數(shù)組名 a 代表首元素地址,解引用得到首元素,sizeof(a)= sizeof(int)= 4
4.a+1
老規(guī)矩還是首元素地址+1,就是第二個(gè)元素地址,是地址大小為 4/8 字節(jié)
5.a[ 1 ]
,大小為4
6.&a
為整個(gè)數(shù)組地址,但是地址終歸是 4/8 字節(jié)
7. * &a
,&a
是類型為 int()[4] 的數(shù)組指針,解引用數(shù)組地址為整個(gè)數(shù)組,大小 20
8. &a +1
取出整個(gè)數(shù)組地址,一個(gè)數(shù)組指針的單位就是整個(gè)數(shù)組,+1 就會(huì)跳過(guò)整個(gè)數(shù)組,大小還是為第二個(gè)數(shù)組地址,大小 4/8
9. &a[0]
,首元素地址,4/8
10. &a[0] + 1
,第二個(gè)元素地址,4/8
字符數(shù)組
char a[] = {'a','b','c','d','e','f'}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a+0)); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a[1])); printf("%d\n",sizeof(&a)); printf("%d\n",sizeof(&a+1)); printf("%d\n",sizeof(&a[0]+1));
和上面同理,再來(lái)看看編譯器怎么解答的:
1.a
是數(shù)組名,首元素地址計(jì)算整個(gè)數(shù)組大小為 6
2.a + 0
,首元素地址 + 0 還是首元素地址,4/8
3.*a
,首元素大小 ,1
4.1
5.&數(shù)組名為整個(gè)數(shù)組地址,4/8
6.跳過(guò)一個(gè)數(shù)組,下一個(gè)數(shù)組地址,4/8
7.第二個(gè)元素地址,4/8
這么簡(jiǎn)單?nonono,這才剛剛開(kāi)始
char a[] = {'a','b','c','d','e','f'}; printf("%d\n",strlen(a)); printf("%d\n",strlen(a+0)); printf("%d\n",strlen(*a)); printf("%d\n",strlen(a[1])); printf("%d\n",strlen(&a)); printf("%d\n",strlen(&a+1)); printf("%d\n",strlen(&a[0]+1));
我還是先把運(yùn)行結(jié)果啪出來(lái)吧:
誒?怪誒,打印7下出來(lái)2個(gè)?nnd給我玩陰的是吧。這里首先強(qiáng)調(diào)一下,sizeof 是操作符,海納百川來(lái)啥算啥,‘ \0 ’也會(huì)照收,而 strlen 是傲嬌的庫(kù)函數(shù),傲嬌在于strlen 針對(duì)的是 \0 之前的字符串長(zhǎng)度(個(gè)數(shù)),而且不包含 ‘ \0 ’。
1.a 數(shù)組名,沒(méi)有在 sizeof 內(nèi)部,為首元素地址,我們知道 strlen 在遇到 \0 之前是不會(huì)停下來(lái)的,字符數(shù)組我們沒(méi)有給到 \0就是沒(méi)有,因此什么時(shí)候遇到他我們不知道,起碼會(huì)比當(dāng)前數(shù)組大,不知道嘛時(shí)候停就是個(gè)隨機(jī)值
2.同理,隨機(jī)值
3.首元素地址解引用為首元素 ‘a(chǎn)’,strlen 需要的是一個(gè)地址,此時(shí)會(huì)從把‘a(chǎn)’ 默認(rèn)為一個(gè)地址,a的ASCII碼值為 97,就會(huì)從內(nèi)存中地址為 97 的地方開(kāi)始往后數(shù)字符個(gè)數(shù),但是注意 97 不屬于我原本分配的內(nèi)容,屬于非法訪問(wèn)內(nèi)存,直接報(bào)錯(cuò)崩潰垮掉
4.同理,報(bào)錯(cuò)
5.數(shù)組的地址,雖然和 strlen 參數(shù)類型有所差異,但還是從第一個(gè)位置向后數(shù),那就是隨機(jī)值啦
6.跳過(guò)一個(gè)數(shù)組的地址,依然是隨機(jī)值
7.第二個(gè)元素地址,依然依然是隨機(jī)值
那么就又雙可以聯(lián)想到字符串了,引入指針變量進(jìn)行討論:
char* p = "abcdef"; printf("%d\n",sizeof(p)); printf("%d\n",sizeof(p+1)); printf("%d\n",sizeof(*p)); printf("%d\n",sizeof(p[0])); printf("%d\n",sizeof(&p)); printf("%d\n",sizeof(&p+1)); printf("%d\n",sizeof(&p[0]+1)); printf("%d\n",strlen(p)); printf("%d\n",strlen(p+1)); printf("%d\n",strlen(*p)); printf("%d\n",strlen(p[0])); printf("%d\n",strlen(&p)); printf("%d\n",strlen(&p+1)); printf("%d\n",strlen(&p[0]+1));
直接看結(jié)果吧,這下就感覺(jué)出有點(diǎn)意義不明了,結(jié)果對(duì)應(yīng)哪個(gè)數(shù)據(jù)都不知道了,所以這時(shí)候自主分析的價(jià)值就出來(lái)了
- p是一個(gè)指針,sizeof 算指針的大小,4/8字節(jié)
- p+1,就是 p 的值加1,字符指針p是一個(gè)地址,數(shù)值上+1是 b 的地址,所以大小 4/8字節(jié)
- p 為char* 的指針,解引用訪問(wèn)首元素,大小為 1
- 小問(wèn)號(hào)你是否有很多朋友,指針為啥還可以 [0] ?其實(shí) p[0] 等價(jià)于 *(p+0),p是首元素地址,p+0亦是,解引用出來(lái)就是首元素,1
- p是地址,&p是對(duì)地址取地址,也就是二級(jí)指針,是地址那就是 4/8
- 跳過(guò)一個(gè)char*的地址即跳過(guò)了一個(gè)p的地址,本質(zhì)上還是地址,4/8
- p[0]為首元素,取地址+1 為第二個(gè)元素的地址,4/8
1.在 “abcdef”中是隱含了一個(gè)‘ \0 ’的,所以傳入p算出大小為 6
2.同理,從第二個(gè)元素開(kāi)始,5
3.解引用為a,97,報(bào)錯(cuò)
4,等價(jià)于*p,就是首元素,報(bào)錯(cuò)
5,取數(shù)組地址的地址往后數(shù)字符串,為隨機(jī)值
6.+1跳過(guò)一個(gè)地址的地址往后數(shù),依然是隨機(jī)值
7.第二個(gè)元素的地址往后數(shù),隨機(jī)值,等價(jià)于 p+1
二維數(shù)組
int a[3][4] = {0}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a[0][0])); printf("%d\n",sizeof(a[0])); printf("%d\n",sizeof(a[0]+1)); printf("%d\n",sizeof(*a[0]+1)); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(*(a+1))); printf("%d\n",sizeof(&a[0]+1)); printf("%d\n",sizeof(*(&a[0]+1))); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a[3]));
看一下運(yùn)行結(jié)果:
1.a數(shù)組名,為整個(gè)數(shù)組,124 = 48
2.數(shù)組首元素,14 = 4
3.數(shù)組第一行元素,44 = 16
4.a[0]此時(shí)為首元素地址,即a[0]|0] 地址,+1為 a[0]|1] 地址,4/8
5.等價(jià)于a[0] [1],,14 = 4
6.數(shù)組名不是單獨(dú)存放為第一行地址,+1表示第二行地址,4/8
7.解引用為第二行元素,44 = 16
8.a[0]為第一行地址,&a[0]+1為第二行地址,4/8
9.解引用為第二行元素,44 = 16
10.解引用首元素地址為第一行,44 = 16
11.淦!好怪!數(shù)組只有3行卻搞出個(gè) a[3],這不越界了嗎?其實(shí)編譯器會(huì)很聰明的自行推算,按照已給數(shù)組排列就是 44 = 16
整點(diǎn)硬菜
熱身完了,來(lái)整點(diǎn)題目,讓你混亂的大腦脈動(dòng)回來(lái)(雪上加霜)
1.
int main() { int a[6] = {1,2,3,4,5,6}; int *ptr = (*int)(&a+1); printf("%d %d",*(a + 1),*(ptr - 1)); return 0; }
ptr 是int類型指針,&a是數(shù)組地址,+1跳過(guò)一個(gè)數(shù)組,指向數(shù)組末位的地址,強(qiáng)轉(zhuǎn)為 int* 類型,*(a+1)首元素地址+1再解引用得到第二個(gè)元素 ,ptr - 1指向 5 的地址,所以答案為 2,5。
2.
//假設(shè)stu 大小為 20 字節(jié) struct stu { char* name; int age; float score; } *p; int main() { (struct stu*)p =0x100000; printf("%p\n",p + 0x1); printf("%p\n",(unsigned long)p + 0x1); printf("%p\n",(unsigned int*)p + 0x1); return 0; }
p是一個(gè)指針類型指向結(jié)構(gòu)體,代表結(jié)構(gòu)體的地址, p + 0x1 就是地址數(shù)值上進(jìn)行 + 0x1 操作,p = 0x100000 ,我們知道 int* +1 跳過(guò)4個(gè)字節(jié),char* + 1跳過(guò)1 個(gè)字節(jié),我們這里是結(jié)構(gòu)體類型,就跳過(guò) 20 個(gè)字節(jié),20 化成16進(jìn)制相加就是 0x100014
p 強(qiáng)轉(zhuǎn)為 unsigned long 其實(shí)就是整型,p 就是一個(gè)純純的數(shù)字了,那0x100000 既然已經(jīng)是“數(shù)字”了,那就有了可以直接加減的屬性,就是 0x100001
p強(qiáng)轉(zhuǎn)成無(wú)符號(hào)整型指針類型, 一個(gè)指針類型是 4 個(gè)字節(jié),結(jié)果就是 0x100004
到這里是不是有內(nèi)味兒了,那咱繼續(xù)
3.
int main() { int a[4] = {1,2,3,4}; int* ptr = (int*)(&a+1); int* ptr2 = (int*)((int)a+1); printf("%x %x",ptr[-1],*ptr2); return 0; }
此題請(qǐng)仔細(xì)思考,大坑警告!
ptr 為整型指針,&a為整個(gè)數(shù)組地址,+1跳過(guò)整個(gè)數(shù)組來(lái)到末位的地址,再?gòu)?qiáng)轉(zhuǎn)成 int * 類型(這里大可不必,因?yàn)樗緛?lái)就是一個(gè) int * 指針)ptr[-1] 等價(jià)于 * (ptr - 1),再代入 ptr 就代表最末地址 -1 來(lái)到 4 的地址,ptr[-1 ]就是 4。
ptr2中 a為首元地址,這里注意優(yōu)先級(jí)問(wèn)題,先強(qiáng)轉(zhuǎn)成 int 就是個(gè)純純的數(shù)字可以直接進(jìn)行 +1 操作,即地址進(jìn)行了數(shù)值+1,格局高不高就要看下一步,接下來(lái)需要考慮 大小端問(wèn)題,因?yàn)槲覀兪切《舜鎯?chǔ),按照低位字節(jié)在低地址處,所以 1,2,3,4 在內(nèi)存中是這樣的:
…… | 01 | 00 | 00 | 00 | 02 | 00 | 00 | 00 | 03 | 00 | ……
假設(shè) 01 地址是 0x01,int 強(qiáng)轉(zhuǎn)后 a +1 偏移一個(gè)字節(jié),指向的就是 01 后面一個(gè) 00 的位置,所以 ptr2 就指向這個(gè)位置,我們解引用拿到他的值就是整型大小的值就是 00 00 00 02,再以小端的形式拿出來(lái)就是 20 00 00 00。
4.
int main() { int a[3][2] = {(0,1),(2,3),(4,5)}; int* p; p = a[0]; printf("%d",p[0]); return 0; }
p[0] 等價(jià)于*(p+0),p = a[0], 就是*a[0] ,a[0] 是首元素地址,那么問(wèn)題來(lái)了:是 0 的地址嗎?
請(qǐng)仔細(xì)看看我們二維數(shù)組的逗號(hào)表達(dá)式,其實(shí)整個(gè)數(shù)組就只有 3 個(gè)元素:1,3 5;他們?cè)跀?shù)組中排列出來(lái)是:
1 | 3
5 | 0
0 | 0
所以解引用出來(lái)就是 1。
今天就到這兒吧,摸了家人們
原文鏈接:https://blog.csdn.net/qq_61500888/article/details/122584321?spm=1001.2014.3001.5502
相關(guān)推薦
- 2022-08-10 Python進(jìn)階學(xué)習(xí)修改閉包內(nèi)使用的外部變量_python
- 2022-07-27 P標(biāo)簽如何取消上下間隔
- 2022-08-05 雪花算法工具類
- 2022-07-09 python連接clickhouse數(shù)據(jù)庫(kù)的兩種方式小結(jié)_python
- 2022-05-11 C#實(shí)現(xiàn)搶紅包算法的示例代碼_C#教程
- 2022-03-14 Spring mvc解決跨域請(qǐng)求:Response to preflight request doe
- 2022-08-20 swift?framework使用OC?代碼兩種方式示例_Swift
- 2022-04-05 svn使用命令忽略指定目錄 svn propset svn:ignore “要忽略的目錄“ .
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- 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)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤: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)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支