C語言學(xué)習(xí)之指針知識總結(jié)
一、地址
內(nèi)存中的最小單元是字節(jié),一個字節(jié)對應(yīng)一個編號,這里的編號就是對應(yīng)字節(jié)的地址。換句話說,地址就是內(nèi)存單元的編號。
二、指針與指針變量
指針與指針變量是兩個不同的概念,指針是某個普通變量的地址,所以可以理解為,指針就是地址,地址就是指針。指針變量是一種變量,它的作用是存儲其它變量的地址。
#include <stdio.h> int main() { int * p; // int *是指針類型,p是對應(yīng)的變量,定義的指針變量p只能用來存儲int類型變量的地址 int i = 3, j; p = &i; // 指針變量只能用來存儲對應(yīng)類型變量的地址,所以這里需要對變量i進(jìn)行取地址操作,即&i /* 指針變量p保存了變量i的地址,這樣的話,我們稱:p指向i。通俗地說,通過p可以找到i 雖然p指向i,但變量p和變量i不是同一個變量 更準(zhǔn)確地說,修改其中任何一個變量p值不會影響變量i,反之同理 */ printf("%d %d\n", *p, i); /* 如果一個指針變量指向某個普通變量,則在指針變量前加上*后就完全等同于指向的普通變量 換句話說,可以通過指針變量前加*來找到那個指向的普通變量 以本程序?yàn)槔@里的*p就是以p的內(nèi)容為地址的變量 */ j = *p; // *p就是i,所以相當(dāng)于將i的值賦給了j printf("i = %d, j = %d\n", i, j); // 輸出結(jié)果為i = 3, j = 3 return 0; }
三、指針的作用
指針是C語言的靈魂
- 通過指針可以表示一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu),例如,鏈表、樹、圖等
- 通過指針可以提高傳輸效率
- 利用指針可以在被調(diào)函數(shù)中修改主調(diào)函數(shù)中的多個值
- 利用指針可以直接訪問硬件
- 通過指針可以更方便地處理字符串
- 指針是理解面向?qū)ο笳Z言中引用的基礎(chǔ)
四、初學(xué)指針時常見的錯誤
錯誤1:指針變量無明確指向
#include <stdio.h> int main() { int * p; int i = 5; *p = i; // 出錯行 printf("%d\n", *p); return 0; }
錯誤原因:由于p未初始化,所以p的內(nèi)容是垃圾值。*p是以p的內(nèi)容為地址的變量,即以一個垃圾值為地址的變量,該變量是未知的,所以當(dāng)把i的值賦值給p指向的未知變量時,有可能會篡改內(nèi)存中其它變量的值,而這樣的操作是不允許的。
錯誤2:賦值時變量類型不一致
#include <stdio.h> int main() { int * p; int * q; int i = 5; p = &i; *q = p; // 出錯行 printf("%d\n", *q); return 0; }
錯誤原因:由于p是指針類型變量,而*q是int類型變量,所以不能相互復(fù)制。
五、通過調(diào)用函數(shù)修改主調(diào)函數(shù)中的值
思考1:在下述程序中,f函數(shù)中的變量i與main函數(shù)中的變量i是不是同一個變量?
f函數(shù)中的變量i與main函數(shù)中的變量i都屬于局部變量,只在自己對應(yīng)的函數(shù)中起作用,所以,f函數(shù)中的變量i與main函數(shù)中的變量i不是同一個變量。
#include <stdio.h> void f(int i) { i = 99; } int main() { int i = 66 printf("%d\n", i); f(i); printf("%d\n", i); return 0; }
思考2:在上述程序中,能否通過調(diào)用f函數(shù)修改main函數(shù)中變量i的值?
由于f函數(shù)中的變量i與main函數(shù)中的變量i不是同一個變量,所以把實(shí)參i傳遞給形參i只會改變f函數(shù)中變量i的值,當(dāng)f函數(shù)執(zhí)行完畢后,分配給形參i的空間會被釋放,故而無法改變main函數(shù)中變量i的值。換句話說,f函數(shù)中的變量i與main函數(shù)中的變量i本質(zhì)上沒有任何關(guān)系,所以不管怎么修改f函數(shù)中變量i的值都不會影響main函數(shù)中變量i的值。
那要如何才能通過其它函數(shù)來修改主調(diào)函數(shù)中的值?
此時,指針就派上用場了,如下述程序:
#include <stdio.h> void f(int * p) // 通過接收地址來確定要修改的變量 { *p = 99; // *p就是以p變量的內(nèi)容為地址的變量,也就是要通過該函數(shù)修改的變量 } int main() { int i = 66 printf("%d\n", i); // 輸出結(jié)果為66 f(&i); // 由于函數(shù)f的形參是指針變量,故需將變量i的地址發(fā)送過去 printf("%d\n", i); // 輸出結(jié)果為99 return 0; }
上述程序可以實(shí)現(xiàn)在被調(diào)函數(shù)中修改主調(diào)函數(shù)中變量的值是因?yàn)橥ㄟ^向被調(diào)函數(shù)傳遞了需要修改的變量的地址,從而確定并指向了需要修改的變量,但如果不傳入地址,就會導(dǎo)致主調(diào)函數(shù)中的變量無法與被調(diào)函數(shù)產(chǎn)生關(guān)聯(lián),從而無法實(shí)現(xiàn)目的。
活學(xué)活用:自定義一個swap函數(shù),用該函數(shù)互換main函數(shù)中的兩個變量的值
常見錯誤:只傳入數(shù)值,不傳入地址
void swap(int a, int b) { int t; t = a; a = b; b = t; } int main() { int a = 3, b = 5; swap(a, b); printf("a = %d, b = %d\n", a, b); return 0; }
出現(xiàn)上述錯誤的原因是,main函數(shù)中的變量a和b與swap函數(shù)中的形參a和b無關(guān),導(dǎo)致的結(jié)果是,主函數(shù)將3和5發(fā)送給形參a和b后,swap函數(shù)只是對3和5進(jìn)行了操作,而未能對main函數(shù)中的變量a和b進(jìn)行操作,所以無法互換main函數(shù)中的變量a和b的值。
正確實(shí)現(xiàn)方法:傳入地址,定位需要互換的變量
void swap(int * p, int * q) { int t; t = *p; *p = *q; *q = t; } int main() { int a = 3, b = 5; swap(&a, &b); printf("a = %d, b = %d\n", a, b); return 0; }
思考:如下方法是否可以實(shí)現(xiàn)互換功能?
#include <stdio.h> void swap(int * p, int * q) { int * t; t = p; p = q; q = t; } int main() { int a = 3, b = 5; swap(&a, &b); printf("a = %d, b = %d\n", a, b); return 0; }
答案是不行的,上述程序?qū)⒆兞縜和b的地址發(fā)送給了指針變量p和q,此時,變量p和q中儲存的是變量a和b的地址,然而,swap函數(shù)中的操作是互換變量p和q的內(nèi)容,也就是說,當(dāng)swap函數(shù)執(zhí)行完畢后,變量p中儲存的是變量b的地址變量q中儲存的是變量a的地址,言下之意,只是將變量p和q的內(nèi)容互換了而已,并沒有對main函數(shù)中的變量a和b進(jìn)行操作,所以無法實(shí)現(xiàn)互換功能,此外,幾乎所有的編程語言都無法通過互換兩個變量的地址實(shí)現(xiàn)互換變量中的值。
六、指針與一維數(shù)組
一維數(shù)組的數(shù)組名
一維數(shù)組的數(shù)組名是一個指針常量,該常量是一維數(shù)組中第一個元素的地址。
#include <stdio.h> int main() { int a[5]; int b[5]; a = b; // 錯誤,因?yàn)閍和b都是常量,所以無法進(jìn)行賦值操作 a = &a[2]; // 錯誤,因?yàn)閍是常量,無法對一個常量進(jìn)行賦值操作 return 0; }
#include <stdio.h> int main() { int a[5]; printf("%#X", &a[0]); printf("%#X", a); // 與上一行輸出結(jié)果相同,因?yàn)橐痪S數(shù)組名就是數(shù)一維組中第一個元素的地址 return 0; }
引用一維數(shù)組中的元素
通過下標(biāo)引用:如a[i]表示第i+1個元素
通過指針引用:如*(a+i)表示第i+1個元素
#include <stdio.h> int main() { int a[5]; int i; for (i = 0; i < 5; i++) // 向一維數(shù)組中讀入元素 scanf("%d", &a[i]); // 通過下標(biāo)引用數(shù)組中的元素 for (i = 0; i < 5; i++) // 輸出一維數(shù)組的內(nèi)容 printf("%d ", *(a + i)); // 通過指針引用數(shù)組中的元素 return 0; }
指針變量的運(yùn)算
指針變量不能相加,相乘以及相除,只能進(jìn)行相減。如果兩個指針變量指向同一塊連續(xù)空間的不同存儲單元,則這兩個指針變量才可以進(jìn)行相減運(yùn)算。兩個指針變量相減得到的結(jié)果是兩個指針變量間相隔的元素個數(shù)。
#include <stdio.h> int main() { int a[5]; int * p = &a[1]; int * q = &a[4]; printf("%d\n", q - p); // 輸出結(jié)果為3,證明相隔3個元素 return 0; }
七、使用函數(shù)操作一維數(shù)組
使用函數(shù)對一維數(shù)組進(jìn)行操作,首先要將數(shù)組名傳遞給函數(shù),因?yàn)橐痪S數(shù)組名是函數(shù)第一個元素的地址,傳遞數(shù)組名就相當(dāng)于傳遞起始位置,其次,普通數(shù)組不同于字符數(shù)組,它們沒有結(jié)束的標(biāo)志,所以還需要向函數(shù)傳遞數(shù)組長度以確定數(shù)組何時結(jié)束。故想要在另外一個函數(shù)中對一維數(shù)組進(jìn)行操作需要向該函數(shù)傳入兩個參數(shù),數(shù)組名和數(shù)組長度。
定義函數(shù)時的形參有兩種寫法,第一種是(int a[], int length),第二種是(int * a, int length)??梢詫懙诙N的原因是一位數(shù)組名本身就是指針常量,所以可以直接用指針變量來接收。
定義一個函數(shù),該函數(shù)的功能是對一維數(shù)組的內(nèi)容進(jìn)行輸出
#include <stdio.h> // 自定義的print函數(shù),其功能是將一維數(shù)組輸出 void print(int a[], int length) { int i; for (i = 0; i < length; i++) printf("%d ", a[i]); // 也可以寫成printf("%d ", *(a+i)); printf("\n"); } int main() { int a[5] = { 1, 2, 3, 4, 5 }; int b[6] = { -1, -2, -3, -4, -5, -6 }; int c[100] = { 23, 88, 99, 44 }; print(a, 5); print(b, 6); print(c, 100); return 0; }
八、指針變量所占字節(jié)數(shù)
預(yù)備知識:sizeof運(yùn)算符的用法
sizeof(數(shù)據(jù)類型):其值為對應(yīng)數(shù)據(jù)類型所占的字節(jié)數(shù)
例如:sizeof(int)值為4;sizeof(double)值為8;sizeof(char)值為1
sizeof(變量):其值為對應(yīng)變量所占的字節(jié)數(shù)
#include <stdio.h> int main() { char c = 'A'; int i = 99; double x = 66.66; char * p = &ch; int * q = &i; double r = &x; printf("%d %d %d\n", sizeof(c), sizeof(i), sizeof(x)); // 輸出結(jié)果為1 4 8 printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r)); // 輸出結(jié)果均為4 return 0; }
上述程序證明,盡管普通類型變量所占的空間大小不一致,但它們對應(yīng)的指針變量都占四個字節(jié)。
九、靜態(tài)數(shù)組的缺陷
數(shù)組長度必須事先指定,且只能是常整數(shù),不能是變量
數(shù)組的長度在長度不能在函數(shù)執(zhí)行過程中動態(tài)的增減,數(shù)組一旦定義,其長度就無法改變
程序員無法手動釋放靜態(tài)數(shù)組的內(nèi)存,數(shù)組一旦定義,操作系統(tǒng)為該數(shù)組分配的存儲空間就會一直存在,直到該數(shù)組所在的函數(shù)執(zhí)行完畢后,該數(shù)組的空間才會被操作系統(tǒng)釋放
在某個函數(shù)內(nèi)定義的靜態(tài)數(shù)組在該函數(shù)執(zhí)行期間可以被其它函數(shù)使用,但當(dāng)該函數(shù)執(zhí)行完畢后,該函數(shù)中定義的數(shù)組就無法在其它函數(shù)中使用,這是因?yàn)樵摵瘮?shù)執(zhí)行完畢后,靜態(tài)數(shù)組的內(nèi)存就被會被釋放
十、malloc函數(shù)
malloc這個詞是由memory(內(nèi)存)與allocate(分配)這兩個單詞合成的,顧名思義,malloc函數(shù)就是用來分配內(nèi)存的函數(shù)。
#include <stdio.h> #include <malloc.h> int main() { int i; // 靜態(tài)分配了4個字節(jié)的存儲空間 int* p = (int*)malloc(4); /* 1.要使用malloc函數(shù),需添加malloc.h頭文件 2.malloc函數(shù)只有一個形參,其類型為整型 3.實(shí)參中的4表示請求操作系統(tǒng)為本程序分配4個字節(jié)的動態(tài)存儲空間 4.malloc的返回值是分配給該系程序的第一個字節(jié)的地址 5.由于第一個字節(jié)的地址不能確定具體的變量類型,所以需要強(qiáng)制類型轉(zhuǎn)換 6.第7行代碼一共分配了8個字節(jié)的存儲空間,其中變量p占4個字節(jié),p指向的空間也占是4個字節(jié) 7.變量p所占的內(nèi)存是靜態(tài)分配的,p所指向的內(nèi)存是動態(tài)分配的 */ free(p); /* free(p)表示釋放p所指向的內(nèi)存 free函數(shù)只能用來釋放動態(tài)內(nèi)存,不能用來釋放靜態(tài)內(nèi)存,靜態(tài)內(nèi)存只能由操作系統(tǒng)自動釋放 free(p)只是釋放了p對應(yīng)的內(nèi)存空間,但p的內(nèi)容依舊存在 */ return 0; }
十一、動態(tài)數(shù)組的構(gòu)造
#include <stdio.h> #include <malloc.h> int main() { int a[10]; // 靜態(tài)構(gòu)造一維數(shù)組 int length; int* pArray; int i; scanf("%d", &length); pArray = (int*)malloc(sizeof(int) * length); /* 動態(tài)構(gòu)造一維數(shù)組 該動態(tài)數(shù)組數(shù)組名為pArray,數(shù)組長度為length,數(shù)組中每個元素都是int類型 */ // 對該動態(tài)一維數(shù)組手動賦值 for (i = 0; i < length; i++) scanf("%d", &pArray[i]); // 輸出該動態(tài)一維數(shù)組的內(nèi)容 for (i = 0; i < length; i++) printf("%d ", *(pArray + i)); printf("\n"); free(pArray); // 釋放該動態(tài)數(shù)組 return 0; }
十二、靜態(tài)內(nèi)存與動態(tài)內(nèi)存的對比
靜態(tài)內(nèi)存是由操作系統(tǒng)自動分配,自動釋放的。靜態(tài)內(nèi)存是在棧中分配的;動態(tài)內(nèi)存是由程序員手動分配,手動釋放,但如果只分配不釋放就會導(dǎo)致內(nèi)存泄露,也就是內(nèi)存越用越少。動態(tài)內(nèi)存是在堆中分配的。
十三、多級指針
#include <stdio.h> int main() { int i = 10; int * p = &i; int ** q = &p; int *** r = &q; r = &p; // 錯誤,因?yàn)閞是int *** 類型,它只能用來存儲int ** 類型變量的地址 printf("i = %d\n", ***r); return 0; }
表解上述程序:
變量名 | 變量地址 | 變量內(nèi)容 |
---|---|---|
i | 1000H | 10 |
p | 2000H | 1000H |
q | 3000H | 2000H |
r | 4000H | 3000h |
變量名 | 對應(yīng)變量 |
---|---|
*r | q |
**r | p |
***r | i |
#include <stdio.h> void g(int ** q) // 由于p的類型是int *,所以q的類型必須是int **,因?yàn)閝要用來存放p的地址 { } void f() { int i; int * p; p = &i; g(&p); // 要通過g函數(shù)修改p的內(nèi)容,則必須發(fā)送p變量的地址 } int main() { f(); return 0; }
十四、跨函數(shù)使用內(nèi)存
由于靜態(tài)內(nèi)存是在棧中分配的,而函數(shù)執(zhí)行完畢后,棧中的靜態(tài)內(nèi)存就會全部出棧,而動態(tài)內(nèi)存是在堆中分配的,當(dāng)函數(shù)執(zhí)行完畢后,堆中分配的內(nèi)存并不會像棧中分配的內(nèi)存一樣直接被釋放掉,所以
靜態(tài)內(nèi)存是不能跨函數(shù)使用的,而動態(tài)內(nèi)存是可以的。
思考:下述程序是否有語法錯誤?是否有邏輯錯誤?
#include <stdio.h> void f(int** q) { int i = 5; *q = &i; // 由于q儲存了p的地址,所以*q就是p,這行代碼實(shí)質(zhì)是將i的地址賦給了p } int main() { int* p; f(&p); printf("i = %d\n", *p); // 由于p儲存了i的地址,所以*p就是i return 0; }
上述程序沒有語法錯誤,但是有邏輯上的錯誤,這是因?yàn)?,?dāng)f函數(shù)執(zhí)行完畢后,f函數(shù)中所有的靜態(tài)變量的內(nèi)存都會被釋放掉,所以當(dāng)執(zhí)行到printf("i = %d\n", *p);時,p所指向的變量空間的訪問權(quán)限已經(jīng)返還給了操作系統(tǒng),這樣就會導(dǎo)致*p訪問了不屬于該程序的空間。這個程序說明了在一個函數(shù)內(nèi)部定義的靜態(tài)變量在該函數(shù)中執(zhí)行完畢后就不再可以垮函數(shù)使用。
思考:對比上一程序,下述程序是否有語法錯誤?是否有邏輯錯誤?
#include <stdio.h> void f(int** q) { *q = (int*)malloc(sizeof(int)); **q = 5; } int main() { int* p; f(&p); printf("%d\n", *p); return 0; }
上述程序是完全沒有語法錯誤的,因?yàn)楫?dāng)f函數(shù)執(zhí)行完畢后,其中分配的動態(tài)內(nèi)存不會自動釋放,所以在main函數(shù)中依然可以使用這段內(nèi)存。這個程序體現(xiàn)出,在一個函數(shù)中分配的動態(tài)存儲空間在該函數(shù)執(zhí)行完之后,仍然可以在另外一個函數(shù)中使用。
趁熱打鐵:下列四個程序中,哪個程序能夠通過調(diào)用fun函數(shù)使main函數(shù)中的指針變量p指向一個合法的整型單元?
#include <stdio.h> void fun(int * p) { int s; p = &s; } int main() { int * p; fun(p); return 0; }
#include <stdio.h> void fun(int ** p) { int s; *p = &s; } int main() { int * p; fun(&p); return 0; }
#include <stdio.h> #include <malloc.h> void fun(int * p) { p = (int *)malloc(sizeof(int)); } int main() { int * p; fun(p); return 0; }
#include <stdio.h> #include <malloc.h> void fun(int ** p) { *p = (int *)malloc(4); } int main() { int * p; fun(&p); return 0; }
以上就是C語言學(xué)習(xí)之指針知識總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于C語言指針的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c++如何控制對象的創(chuàng)建方式(禁止創(chuàng)建棧對象or堆對象)和創(chuàng)建的數(shù)量
這篇文章主要介紹了c++如何控制對象的創(chuàng)建方式和創(chuàng)建的數(shù)量,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-08-08C++設(shè)計(jì)模式之Proxy模式(代理模式)詳解
這篇文章主要為大家詳細(xì)介紹了C++設(shè)計(jì)模式之Proxy模式的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07C/C++ 中怎樣使用SetConsoleTextAttribute()函數(shù)來控制輸出字符的顏色
這篇文章主要介紹了C/C++ 中如何使用SetConsoleTextAttribute()函數(shù)來控制輸出字符的顏色,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03帶頭結(jié)點(diǎn)單鏈表與不帶頭結(jié)點(diǎn)單鏈表的區(qū)別
這篇文章主要介紹了帶頭結(jié)點(diǎn)單鏈表與不帶頭結(jié)點(diǎn)單鏈表的區(qū)別,需要的朋友可以參考下2023-07-07C語言實(shí)現(xiàn)銀行管理系統(tǒng)(文件操作)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)銀行管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03Qt使用windeployqt工具實(shí)現(xiàn)程序打包發(fā)布方法
本文主要介紹了Qt使用windeployqt工具實(shí)現(xiàn)程序打包發(fā)布方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11