C語(yǔ)言零基礎(chǔ)講解指針和數(shù)組
一、指針和數(shù)組分析-上
1.數(shù)組的本質(zhì)
- 數(shù)組是一段連續(xù)的內(nèi)存空間
- 數(shù)組的空間大小為 sizeof(array_type) * array_size
- 數(shù)組名可看做指向數(shù)組第一個(gè)元素的常量指針
下面看一段代碼:
#include <stdio.h> int main() { int a[5] = {0}; int* p = NULL; printf("a = 0x%X\n", (unsigned int)(a)); printf("a + 1 = 0x%X\n", (unsigned int)(a + 1)); printf("p = 0x%X\n", (unsigned int)(p)); printf("p + 1 = 0x%X\n", (unsigned int)(p + 1)); return 0; }
輸出結(jié)果如下:
通過(guò)這段代碼說(shuō)明指針運(yùn)算是合法的。
2.指針的運(yùn)算
指針是一種特殊的變量,與整數(shù)的運(yùn)算規(guī)則為
p + n; <-->(unsigned int)p + n*sizeof(*p);
結(jié)論∶
當(dāng)指針 p 指向一個(gè)同類(lèi)型的數(shù)組的元素時(shí):p+1 將指向當(dāng)前元素的下一個(gè)元素;p-1 將指向當(dāng)前元素的上一個(gè)元素。
- 指針之間只支持減法運(yùn)算
- 參與減法運(yùn)算的指針類(lèi)型必須相同
p1- p2; <--> ((unsigned int)p1 - (unsigned int)p2) / sizeof(type);
注意:
- 只有當(dāng)兩個(gè)指針指向同一個(gè)數(shù)組中的元素時(shí),指針相減才有意義,其意義為指針?biāo)冈氐南聵?biāo)差
- 當(dāng)兩個(gè)指針指向的元素不在同一個(gè)數(shù)組中時(shí),結(jié)果未定義
下面看一段簡(jiǎn)單的指針運(yùn)算代碼:
#include <stdio.h> int main() { char s1[] = {'H', 'e', 'l', 'l', 'o'}; int i = 0; char s2[] = {'W', 'o', 'r', 'l', 'd'}; char* p0 = s1; char* p1 = &s1[3]; char* p2 = s2; int* p = &i; printf("%d\n", p0 - p1); //printf("%d\n", p0 + p2); //ERROR printf("%d\n", p0 - p2); //printf("%d\n", p0 - p); //ERROR //printf("%d\n", p0 * p2); //ERROR //printf("%d\n", p0 / p2); //ERROR return 0; }
輸出結(jié)果如下:
注意兩個(gè)指針指向不同的數(shù)組,雖然它們兩相減符合語(yǔ)法,但是最后的結(jié)果肯定沒(méi)有意義。
再來(lái)看一段指針運(yùn)算的應(yīng)用代碼:
#include <stdio.h> #define DIM(a) (sizeof(a) / sizeof(*a)) int main() { char s[] = {'H', 'e', 'l', 'l', 'o'}; char* pBegin = s; char* pEnd = s + DIM(s); // Key point char* p = NULL; printf("pBegin = %p\n", pBegin); printf("pEnd = %p\n", pEnd); printf("Size: %d\n", pEnd - pBegin); for(p=pBegin; p<pEnd; p++) { printf("%c", *p); } printf("\n"); return 0; }
輸出結(jié)果如下:
注意以下幾點(diǎn):
- 數(shù)組大小的計(jì)算方法:
#define DIM(a) (sizeof(a) / sizeof(*a))
char* pEnd = s + DIM(s); // Key point ==> pEnd 指向 'o' 后面的地址 ==>
這在 C 語(yǔ)言中是一個(gè)擦邊球的邊界位置,也是一個(gè)技巧,在這個(gè)邊界位置可以認(rèn)為該指針是合法的,可以和其他指針進(jìn)行比較運(yùn)算和減法運(yùn)算等,在 C++ 標(biāo)準(zhǔn)庫(kù)里面也合法
3.指針的比較
- 指針也可以進(jìn)行關(guān)系運(yùn)算 (<,<=,>,>=)
- 指針關(guān)系運(yùn)算的前提是同時(shí)指向同一個(gè)數(shù)組中的元素
- 任意兩個(gè)指針之間的比較運(yùn)算(==,!=)無(wú)限制
- 參與比較運(yùn)算的指針類(lèi)型必須相同
4.小結(jié)
- 數(shù)組聲明時(shí)編譯器自動(dòng)分配一片連續(xù)的內(nèi)存空間
- 指針聲明時(shí)只分配了用于容納地址值的 4 字節(jié)空間
- 指針和整數(shù)可以進(jìn)行運(yùn)算,其結(jié)果為指針
- 指針之間只支持減法運(yùn)算,其結(jié)果為數(shù)組元素下標(biāo)差
- 指針之間支持比較運(yùn)算,其類(lèi)型必須相同
二、指針與數(shù)組分析-下
1.數(shù)組的訪問(wèn)方式
以下標(biāo)的形式訪問(wèn)數(shù)組中的元素
以指針的形式訪問(wèn)數(shù)組中的元素
2.下標(biāo)形式 VS 指針形式
- 指針以固定增量在數(shù)組中移動(dòng)時(shí),效率高于下標(biāo)形式
- 指針增量為1且硬件具有硬件增量模型時(shí),效率更高
- 下標(biāo)形式與指針形式的轉(zhuǎn)換
a[n] <--> *(a +n) <--> *(n + a) <--> n[a]
注意:現(xiàn)代編譯器的生成代碼優(yōu)化率已大大提高,在固定增量時(shí),下標(biāo)形式的效率已經(jīng)和指針形式相當(dāng);但從可讀性和代碼維護(hù)的角度來(lái)看,下標(biāo)形式更優(yōu)。
下面看一個(gè)數(shù)組的訪問(wèn)方式代碼:
#include <stdio.h> int main() { int a[5] = {0}; int* p = a; int i = 0; for(i=0; i<5; i++) { p[i] = i + 1; } for(i=0; i<5; i++) { printf("a[%d] = %d\n", i, *(a + i)); } printf("\n"); for(i=0; i<5; i++) { i[a] = i + 10; } for(i=0; i<5; i++) { printf("p[%d] = %d\n", i, p[i]); } return 0; }
輸出結(jié)果如下:
注意這個(gè)奇怪的寫(xiě)法:i[a] = i + 10; ==> a[i] = i + 10;
下面通過(guò)一個(gè)實(shí)例,說(shuō)明數(shù)組和指針的不同:
ext.c:
int a[] = {1, 2, 3, 4, 5};
test.c:
#include <stdio.h> int main() { extern int a[]; printf("&a = %p\n", &a); printf("a = %p\n", a); printf("*a = %d\n", *a); return 0; }
輸出結(jié)果如下:
下面來(lái)驗(yàn)證一下數(shù)組名究竟是不是指針,將 test.c 改成:
#include <stdio.h> int main() { extern int* a; printf("&a = %p\n", &a); printf("a = %p\n", a); printf("*a = %d\n", *a); return 0; }
輸出結(jié)果如下:
ext.c 中 a[ ] 的地址為 0x804a014,test.c 中的extern int* a; 只是申明標(biāo)識(shí)符 a,編譯器會(huì)認(rèn)為在這之前就已經(jīng)給了地址值,就是 0x804a014,所以printf("a = %p\n", a); 就是打印0x804a014 地址中的 4 個(gè)字節(jié)的數(shù),也就是 a[ ] 數(shù)組中的第一個(gè)元素 1,所以打印 0x1,*a 就是取 0x1 地址中的數(shù),但是這個(gè)地址值是留給操作系統(tǒng)的,不可訪問(wèn),訪問(wèn)就會(huì)產(chǎn)生段錯(cuò)誤。
3.a 和 &a 的區(qū)別
- a 為數(shù)組首元素的地址
- &a 為整個(gè)數(shù)組的地址
- a 和 &a 的區(qū)別在于指針運(yùn)算
這個(gè)就能看出 a + 1 和 &a + 1 的不同,a + 1 增加的步長(zhǎng)是一個(gè)元素的大小,&a + 1 則是增加的步長(zhǎng)是整個(gè)數(shù)組的大小。
下面看一個(gè)指針運(yùn)算的經(jīng)典問(wèn)題:
#include <stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; int* p1 = (int*)(&a + 1); int* p2 = (int*)((int)a + 1); int* p3 = (int*)(a + 1); printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]); return 0; }
輸出結(jié)果如下:
p1[-1] 就是 *(p1 - 1),由于 p1 指向的元素是 5 后面的位置,減 1 之后就指向了 5;p2 的地址是 0x804a015(注意 linux 系統(tǒng)為小端系統(tǒng)),*p2 就是 0x02000000,對(duì)應(yīng)十進(jìn)制的值就是 33554432;p3 的地址為 &a[1],所以 p3[1] 就是 3 了。
4.數(shù)組參數(shù)
數(shù)組作為函數(shù)參數(shù)時(shí),編譯器將其編譯成對(duì)應(yīng)的指針
結(jié)論:一般情況下,當(dāng)定義的函數(shù)中有數(shù)組參數(shù)時(shí),需要定義另一個(gè)參數(shù)來(lái)標(biāo)示數(shù)組的大小。
下面看一段代碼:
#include <stdio.h> void func1(char a[5]) { printf("In func1: sizeof(a) = %d\n", sizeof(a)); *a = 'a'; a = NULL; } void func2(char b[]) { printf("In func2: sizeof(b) = %d\n", sizeof(b)); *b = 'b'; b = NULL; } int main() { char array[10] = {0}; func1(array); printf("array[0] = %c\n", array[0]); func2(array); printf("array[0] = %c\n", array[0]); return 0; }
輸出結(jié)果如下:
這段代碼就說(shuō)明數(shù)組參數(shù)退化成指針,因?yàn)?sizeof(a) 為 4 個(gè)字節(jié),而不是 5 個(gè)字節(jié)。
5.小結(jié)
數(shù)組名和指針僅使用方式相同
- 數(shù)組名的本質(zhì)不是指針
- 指針的本質(zhì)不是數(shù)組
數(shù)組名并不是數(shù)組的地址,而是數(shù)組首元素的地址
函數(shù)的數(shù)組參數(shù)退化為指針
到此這篇關(guān)于C語(yǔ)言零基礎(chǔ)講解指針和數(shù)組的文章就介紹到這了,更多相關(guān)C語(yǔ)言 指針和數(shù)組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言詳解如何應(yīng)用模擬字符串和內(nèi)存函數(shù)
這篇文章主要介紹了C語(yǔ)言詳解如何應(yīng)用模擬字符串和內(nèi)存函數(shù),文章有點(diǎn)長(zhǎng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02C/C++調(diào)用Fortran的DLL的操作過(guò)程
這篇文章主要介紹了C/C++調(diào)用Fortran的DLL,本文以一個(gè)簡(jiǎn)單的加法器為例,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03C++如何將二叉搜索樹(shù)轉(zhuǎn)換成雙向循環(huán)鏈表(雙指針或數(shù)組)
這篇文章主要介紹了C++如何將二叉搜索樹(shù)轉(zhuǎn)換成雙向循環(huán)鏈表(雙指針或數(shù)組),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05C++ boost 時(shí)間與日期處理詳細(xì)介紹
這篇文章主要介紹了C++ boost 時(shí)間與日期處理詳細(xì)介紹的相關(guān)資料,這里提供實(shí)例代碼,及實(shí)現(xiàn)效果,需要的朋友可以參考下2016-11-11QT網(wǎng)絡(luò)通信TCP客戶端實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了QT網(wǎng)絡(luò)通信TCP客戶端實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08C++實(shí)現(xiàn)LeetCode(206.倒置鏈表)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(206.倒置鏈表),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07