網(wǎng)站首頁 編程語言 正文
一、指針與一維數(shù)組
1. 指針與數(shù)組基礎(chǔ)
先說明幾點干貨:
1. 數(shù)組是變量的集合,并且數(shù)組中的多個變量在內(nèi)存空間上是連續(xù)存儲的。
2. 數(shù)組名是數(shù)組的入口地址,同時也是首元素的地址,數(shù)組名是一個地址常量,不能更改。
3. 數(shù)組的指針是指數(shù)組在內(nèi)存中的起始地址,數(shù)組元素的地址是指數(shù)組元素在內(nèi)存中的其實地址。
??????對于第一點數(shù)組中的變量在內(nèi)存空間上是連續(xù)的相信沒有什么疑問,這點在講解數(shù)組的時候就已經(jīng)提到過了。對于第二點,可以得到,數(shù)組名就是一個地址,并且是整個數(shù)組的內(nèi)存起始地址。數(shù)組名一個是常量地址我們不能對數(shù)組名進(jìn)行賦值操作,數(shù)組名是數(shù)組的起始地址,數(shù)組的第一個元素的地址也是數(shù)組的起始地址,他們兩個在數(shù)值上是相等的。對于第三點,舉個例子,假如定義一個 int 類型變量 a,int 占四個字節(jié),假如他的第一個字節(jié)的地址是 0x00000010, 第四個字節(jié)的地址那么就是 0x00000013。而 &a 的地址是 a 的內(nèi)存空間上的首地址,即 &a 的值為 0x00000010。
??????通過下面示例可以來證明一下上面講述的結(jié)論。
源代碼:
#include <stdio.h> int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int b = 20; char *pb = &b; /*a是地址常量,不能被賦值*/ //a = pb; /*在數(shù)值上 a &a[0] &a 的數(shù)值是相等的 */ printf("a = %p, &a[0] = %p, &a = %p\n", a, &a[0], &a); printf("&b = %p, pb = %p, pb + 3 = %p\n", &b, pb, pb + 3); return 0; }
運行結(jié)果:
a = 0xbfb82f38, &a[0] = 0xbfb82f38, &a = 0xbfb82f38
&b = 0xbfb82f30, pb = 0xbfb82f30, pb + 3 = 0xbfb82f33
2. 指針與數(shù)組
以一個實例開始,數(shù)組的打印你會幾種方法?
源代碼:
#include <stdio.h> int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p = a, i; //1. 數(shù)組元素訪問法 for (i = 0; i < 10; i++) { printf("a[%d] = %-3d", i, a[i]); } putchar(10); //等價于 putchar('\n'), 因為 \n 的 ASCII 碼就是 10 //2. 數(shù)組地址偏移訪問法 for (i = 0; i < 10; i++) { printf("a[%d] = %-3d", i, *(a + i)); } putchar(10); //3. 指針元素訪問法 for (i = 0; i < 10; i++) { printf("a[%d] = %-3d", i, p[i]); } putchar(10); //4. 指針地址偏移訪問法 for (i = 0; i < 10; i++) { printf("a[%d] = %-3d", i, *(p + i)); } putchar(10); return 0; }
運行結(jié)果:
a[0] = 1 ?a[1] = 2 ?a[2] = 3 ?a[3] = 4 ?a[4] = 5 ?a[5] = 6 ?a[6] = 7 ?a[7] = 8 ?a[8] = 9 ?a[9] = 10?
a[0] = 1 ?a[1] = 2 ?a[2] = 3 ?a[3] = 4 ?a[4] = 5 ?a[5] = 6 ?a[6] = 7 ?a[7] = 8 ?a[8] = 9 ?a[9] = 10a[0] = 1 ?a[1] = 2 ?a[2] = 3 ?a[3] = 4 ?a[4] = 5 ?a[5] = 6 ?a[6] = 7 ?a[7] = 8 ?a[8] = 9 ?a[9] = 10
a[0] = 1 ?a[1] = 2 ?a[2] = 3 ?a[3] = 4 ?a[4] = 5 ?a[5] = 6 ?a[6] = 7 ?a[7] = 8 ?a[8] = 9 ?a[9] = 10
??????從上面可以看到,如果一個指針p指向數(shù)組a,那么 a[i],*(a+i), p[i],*(p+i) 這四種寫法是完全等價的,都是訪問數(shù)組中第 i+1 個元素。其實數(shù)組對于元素的訪問根本上就是地址的偏移,a[i] 之所以能夠訪問到第 i+1 個元素其實他所進(jìn)行的操作和 *(a+i) 是一樣的,都是在地址 a 的基礎(chǔ)上偏移 i 個單位的內(nèi)存單元進(jìn)行元素訪問。a 是一個地址,p 也是一個地址,且當(dāng) p 指向 a 的時候,能夠以 a[i] 來訪問第 i+1 元素,那么同理也能以 p[i] 的方式來訪問第 i+1 個元素。同樣的 *(p+i) 也是以地址偏移的方式來進(jìn)行數(shù)組元素的訪問。其實這幾種寫法唯一不同的就是 a 是地址常量,不能被賦值,也就是 a 不被允許再指向其他的內(nèi)存空間,而 p 是指針變量,可以被任意賦值,可以指向其他的內(nèi)存空間。
3. 一個思考
通過上面的講解請大家看一下下面程序應(yīng)該輸出多少?
#include <stdio.h> int main() { int a[10] = {1, 2, 3, 4, 5}; int *p = &a[1]; p[0] = 20; p[1] = 30; p[-1] = 10; printf("a[0] = %d, a[1] = %d, a[2] = %d\n", a[0], a[1], a[2]); return 0; }
??????其實大家可能或許能夠猜到程序的輸出結(jié)果是10,20,30。但是可能并不是所有人都能夠清楚的明白編譯器是怎么個處理邏輯的,在這里來為大家再做進(jìn)一步的講解,讓大家對指針和數(shù)組的關(guān)系有更深一步的認(rèn)識。
??????上圖簡單從地址偏移角度來畫出了指針的指向,大家先看右邊,地址 a 在數(shù)值上指向數(shù)組的首地址,那么 a+1 就是偏移了一個 int 類型,也就是 4 字節(jié),所以 a+1 指向數(shù)組的第二個元素。大家再看左邊,p 指向 a[1] 的首地址,那么從指針變量 p 的角度來看,p[0] 就是他指向的元素 a[1],所以 p[0] 和 a[1] 是完全相等的,呢么 p[1] ,也可以寫成 *(p+1) 指向的就是 a[2] 元素的首地址,因為 p+1 要在 p 的基礎(chǔ)上偏移 4 字節(jié),p 指向 a[1] 的首地址,那么 p+1 就是指向 a[2] 的首地址,再用取值運算符 *(p+1) 的值就是a[2] 的值也就是 3。p+1 理解了那么 p-1 也就不能理解了,他們兩個只是偏移的方向不同,p+1 是向右移,也就是地址增加的方向一定,而 p-1 是向左移,向地址減小的方向移動。
二、指針與字符串
??????C語言處理字符串通常實講字符串放在字符數(shù)組中,因為C語言沒有字符串類型,而字符串在地址空間上是連續(xù)的,而數(shù)組元素在內(nèi)存空間上也是連續(xù),字符串就是若干字符的集合,所以就用字符數(shù)組來處理字符串,對于常量字符串也可以用指針對其直接指向操作。
??????關(guān)于指針與字符串的關(guān)系大家可以先看看下面程序:
#include <stdio.h> int main() { char a[] = "hello world"; char *b = "hello world"; a[1] = 'z'; //b[1] = 'z'; //error printf("a = %s\n", a); return 0; }
??????其實如上面注釋所示,可以對 a 里面的元素進(jìn)行賦值操作,而不能對 b 里面的元素進(jìn)行賦值操作。為什么呢?其實原因與內(nèi)存單元的分配有關(guān)。a 是一個局部變量數(shù)組,局部變量分配在棧上,棧上的內(nèi)容具有可讀可寫操作,所以對 a 里面的元素進(jìn)行賦值操作沒有問題。而 b 是一個指針,也是一個局部變量,但是 b 指向的是一個字符串常量,字符串常量存儲在只讀區(qū),只讀區(qū)里面的內(nèi)容只有讀權(quán)限而沒有寫權(quán)限,這點尤其注意,所以我們上述操作中對 b 指向的內(nèi)容進(jìn)行寫操作編譯器是會報錯的。報錯內(nèi)容就是段錯誤。導(dǎo)致段錯誤的原因就是訪問了非法內(nèi)存,非法內(nèi)存一般就是內(nèi)存地址不存在或者對于該塊地址沒有權(quán)限。
三、指針和二維數(shù)組
1. 指針數(shù)組與數(shù)組指針
??????該部分主要講解指針數(shù)組與數(shù)組指針??赡軐τ诔鯇W(xué)者而言對指針數(shù)組和數(shù)組指針比價容易弄混,其實記后面兩個字就可以,指針數(shù)組 后面兩個字是數(shù)組,說明指針數(shù)組是一個數(shù)組,那么數(shù)組里面存儲的內(nèi)容就是前兩個字 指針。數(shù)組指針 后面兩個字是指針,說明數(shù)組指針是一個指針,那么這個指針指向那里,前面兩個字就有體現(xiàn),數(shù)組指針指向一個數(shù)組。一句話概括之,指針數(shù)組是一個數(shù)組,數(shù)組里面每個元素存儲的是一個指針;數(shù)組指針是一個指針,是指向數(shù)組的指針。
指針數(shù)組的定義方法:
char a[5]; //字符數(shù)組
char *a[5]; //指針數(shù)組
數(shù)組指針的定義方法:
char a[5]; //字符數(shù)組
char (*a)[5] //數(shù)組指針
??????上面數(shù)組指針和指針數(shù)組的定義方法很像,其實不管是這里的指針數(shù)組 數(shù)組指針 還是后面文章中會講解的 指針函數(shù) 函數(shù)指針,其實分辨他們有一個訣竅,那就是右左法則,何謂右左法則,即在運算符的優(yōu)先級范圍內(nèi),先往右看,再往左看。打個比方,看上面定義的指針數(shù)組,先找到 a ,a 的右邊與 a 結(jié)合是一個數(shù)組,那么這個定義就是一個數(shù)組,是個什么樣的數(shù)組呢?再往左看,a 的左邊與 a 結(jié)合是一個指針,那么就是一個指針數(shù)組。再來看看數(shù)組指針,先找到 a,a 的右邊是一個括號,有括號先看括號里面的內(nèi)容,也就是往左看,括號里面的內(nèi)容是一個指針,是個什么樣的指針呢?再往右看,是一個數(shù)組,所以就是數(shù)組指針。掌握了這個方法,不管是給出定義來辨別名字,還是告訴你名字讓其寫出定義都不在話下。
2. 指針數(shù)組
??????指針數(shù)組是一個數(shù)組,里面每個成員都是指針,定義一個指針數(shù)組相當(dāng)于定義了多個指針變量的集合。 例如 int *a[3];
這是一個指針數(shù)組,數(shù)組里面每個成員都是指向int類型地址的。為了讓大家更加熟悉指針數(shù)組的使用,大家請看下面這個例子:
源代碼:
#include <stdio.h> int main() { int a = 1, b= 2, c = 3; int *p[3]; //定義一個指針數(shù)組,數(shù)組里面有3個成員,每個成員都是指向int類型的指針。 /*數(shù)組成員的初始化*/ p[0] = &a; p[1] = &b; p[2] = &c; /*通過指針改變其指向地址中的內(nèi)容*/ *p[0] = 10; *p[1] = 20; *p[2] = 30; printf("a = %d, b = %d, c = %d\n", a, b, c); return 0; }
運行結(jié)果:
a = 10, b = 20, c = 30
??????通過上面例子大家就不難發(fā)現(xiàn),定義好指針數(shù)組之后,數(shù)組成員的使用方法和普通指針的使用是一樣的,定義好一個指針數(shù)組唯獨就是可以一次性定義好多個指向相同類型的指針。其實大家想一下,我們當(dāng)時引入數(shù)組的時候說C語言引入數(shù)組就是因為數(shù)組可以一次性定義好多個具有相同類型的普通變量,其實這里的指針數(shù)組也是一樣的,不同的是,普通數(shù)組里面的成員都是普通變量,而指針數(shù)組里面的成員都是指針。
3. 數(shù)組指針
??????數(shù)組指針是一個指針,指向一個數(shù)組的指針。 例如 int (*a)[3];
這是一個數(shù)組指針,指向的是一個3列的數(shù)組,a+1 就相當(dāng)于步進(jìn)為1列,1列有3個int類型,相當(dāng)于步進(jìn)了 3 * 4 = 12(字節(jié))。示例如下:
源代碼:
#include <stdio.h> int main(int argc, const char *argv[]) { int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; /*定義數(shù)組指針*/ int (*p)[3] = a; int (*q)[2] = a; /*數(shù)組指針步進(jìn)加1為1行*/ printf("p = %p, p + 1 = %p\n", p, p+1); /*先對行指針進(jìn)行一次解引用就是普通指針,普通指針步進(jìn)就是指向的普通數(shù)據(jù)類型的大小*/ *(*(p+1) + 1) = 56; printf("%d\n", a[1][1]); printf("q = %p, q + 1 = %p\n", q, q+1); *(*(q+1) + 1) = 56; printf("%d\n", a[1][0]); return 0; }
運行結(jié)果:
p = 0xbfc208ec, p + 1 = 0xbfc208f8
56
q = 0xbfc208ec, q + 1 = 0xbfc208f4
56
圖示如下:
??????數(shù)組指針的步進(jìn)大家一定要清楚,步進(jìn)主要看定義時數(shù)組個數(shù)的大小,比如 int (*p)[3];
步進(jìn)加1就是步進(jìn) int [3] 大小;int (*q)[2];
步進(jìn)加1就是 int [2] 大小。數(shù)組指針就是行指針,因為數(shù)組指針指向相同行的數(shù)組時,指針偏移加1就是1行。
原文鏈接:https://blog.csdn.net/liung_/article/details/124551512
相關(guān)推薦
- 2022-04-26 使用jquery庫實現(xiàn)電梯導(dǎo)航效果_jquery
- 2023-06-16 Qt6實現(xiàn)調(diào)用攝像頭并顯示畫面_C 語言
- 2022-12-14 Qt設(shè)置窗體(QWidget)透明度的方法總結(jié)_C 語言
- 2022-09-13 Nginx如何限制IP訪問只允許特定域名訪問_nginx
- 2022-08-31 python中ndarray數(shù)組的索引和切片的使用_python
- 2022-11-04 Android自定義View實現(xiàn)時鐘功能_Android
- 2021-12-03 Android消息機(jī)制Handler深入理解_Android
- 2022-03-18 .NET?6開發(fā)TodoList應(yīng)用之實現(xiàn)PUT請求_實用技巧
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 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)雅實現(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)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支