摘要:在語(yǔ)言中,允許用指針變量來(lái)存放指針,因此,一個(gè)指針變量的值就是某個(gè)內(nèi)存單元的地址或稱為某內(nèi)存單元的指針。指針詳解一基礎(chǔ)知識(shí)在語(yǔ)言中,定義變量時(shí),如果在變量名前加上一個(gè),那么這個(gè)變量就變成了對(duì)應(yīng)變量類型的指針變量。
指針,是C語(yǔ)言中的一個(gè)重要概念及其特點(diǎn),也是掌握C語(yǔ)言比較困難的部分。作為一個(gè)C語(yǔ)言初學(xué)者,我對(duì)指針也有了一定的了解,正好這幾天在做一個(gè)C語(yǔ)言指針的知識(shí)點(diǎn)匯總,于是就有了這篇文章,向大家分享一些我的見解,與大家一起共勉。
在計(jì)算機(jī)中,每一個(gè)數(shù)據(jù)都是存放在儲(chǔ)存器中的,而不同的數(shù)據(jù)類型所占的內(nèi)存空間不同(例如:int類型占用4個(gè)字節(jié),double類型占用8個(gè)字節(jié)等等),內(nèi)存空間又是以字節(jié)為單位的,每一個(gè)字節(jié)又對(duì)應(yīng)了一個(gè)編號(hào),這個(gè)編號(hào)我們稱為這一個(gè)內(nèi)存單元的地址。
而系統(tǒng)在內(nèi)存中又為變量分配了存儲(chǔ)空間的首個(gè)字節(jié)單元的地址即變量的地址。為了方便用戶對(duì)存儲(chǔ)空間進(jìn)行正確的訪問(wèn),指針便應(yīng)運(yùn)而生了。
指針相對(duì)于一個(gè)內(nèi)存單元來(lái)說(shuō),指的是單元的地址,該單元的內(nèi)容里面存放的是數(shù)據(jù)。在 C 語(yǔ)言中,允許用指針變量來(lái)存放指針,因此,一個(gè)指針變量的值就是某個(gè)內(nèi)存單元的地址或稱為某內(nèi)存單元的指針。
指針變量是存放一個(gè)內(nèi)存地址的變量,不同于其他類型變量,它是專門用來(lái)存放內(nèi)存地址的,也稱為地址變量。定義指針變量的一般形式為:類型說(shuō)明符*變量名。
在C語(yǔ)言中,定義變量時(shí),如果在變量名前加上一個(gè) “ * ”,那么這個(gè)變量就變成了對(duì)應(yīng)變量類型的指針變量。
對(duì)于一個(gè)指針變量,我們需要讓它來(lái)保存其他變量的地址的時(shí)候,就需要用 到 &運(yùn)算符。
下面舉個(gè)例子:
例1
#include int main(){ int num = 10;//在內(nèi)存中開辟一塊空間 int* p = #//這里我們對(duì)變量num取地址,使用了&運(yùn)算符 //將num的地址存放在p變量中,p就是一個(gè)指針變量。 return 0;}
&num就取得了num的地址,指針p指向的num的地址,就形成了一個(gè)簡(jiǎn)單的指針變量。
但是對(duì)于某些特殊情況,我們可以不需要用&運(yùn)算符,例如:數(shù)組,函數(shù)等等,我們后邊會(huì)講到。
上面我們了解了如何取得一個(gè)數(shù)據(jù)的地址,那么接下來(lái)我們可以嘗試運(yùn)用指針來(lái)解地址,從而得到這個(gè)變量的內(nèi)存數(shù)據(jù)。
在指針前加一個(gè)“ * "即解引用地址,也就是從指針指向的內(nèi)存中,取出這段內(nèi)存地址對(duì)應(yīng)的數(shù)據(jù)。
輸出例1中的指針p:
例2
#include int main(){ int num = 10; int* p = # printf("%d", *p); return 0;}
運(yùn)行結(jié)果為:10
指針就是一個(gè)變量,用來(lái)存放地址的變量。(存放在指針中的值,均會(huì)被看作地址)
一個(gè)小的單元會(huì)有多大:(一個(gè)字節(jié))
那么地址是如何編寫的呢?
目前經(jīng)過(guò)仔細(xì)的技術(shù)及思考后,最合適的結(jié)論為:一個(gè)字節(jié)對(duì)應(yīng)了一個(gè)地址。
對(duì)于32位機(jī)器,可以看作有32根地址線,每一根地址線在尋址的時(shí)候都會(huì)產(chǎn)生一個(gè)電信號(hào)(正電1/負(fù)電0),所以它的地址從000……000(32個(gè)0)到111……111(32個(gè)1)共有2的32次方個(gè)地址。
在32位機(jī)器上,地址是32個(gè)0或1組成的二進(jìn)制序列,一個(gè)地址需要用4個(gè)字節(jié)的空間來(lái)存儲(chǔ),所以一個(gè)指針變量的大小就是4個(gè)字節(jié)。
同樣,可以類比推理得到:64位機(jī)器有2的64次方個(gè)地址,一個(gè)指針變量的大小位8個(gè)字節(jié)。
我們來(lái)做一個(gè)練習(xí):
例3
#include int main(){ printf("%d/n", sizeof(char*)); printf("%d/n", sizeof(short*)); printf("%d/n", sizeof(double*)); printf("%d/n", sizeof(int*)); return 0;}
運(yùn)行結(jié)果為:
4
4
4
4
總結(jié)
(1)指針是一個(gè)變量,它存放的是地址。
(2)指針在32位機(jī)器中占4個(gè)字節(jié),在64位機(jī)器中占8個(gè)字節(jié),與類型無(wú)關(guān)。
我們前邊提到,指針的定義方式是:type + * :
char* 類型的指針是為了存放char類型變量的地址;
int* 類型的指針是為了存放int類型變量的地址;
short* 類型的指針是為了存放short類型變量的地址;
double* 類型的指針是為了存放double類型變量的地址。
指針的類型決定了指針向前或者向后的空間有多大。
指針類型決定了指針進(jìn)行解引用操作的時(shí)候,能夠訪問(wèn)空間的大?。?br /> (1) int* p:* p能夠訪問(wèn)4個(gè)字節(jié)。
(2)char* p:* p能夠訪問(wèn)1個(gè)字節(jié)。
(3)double* p: *p能夠訪問(wèn)8個(gè)字節(jié)。
指針類型的意義
先來(lái)看一個(gè)例子:
例4
#include int main(){ int a = 10; char* p1 = (char*)&a; int* p2 = &a; printf("%p/n", &a); printf("%p/n", p1); printf("%p/n", p1+1); printf("%p/n", p2); printf("%p/n", p2+1); return 0;}
輸出的結(jié)果為:
003CFC80
003CFC80
003CFC81
003CFC80
003CFC84
我們可以看到不同類型的指針p1和p2都指向了變量a的地址,但是p1+1指向的是003CFC81,而p2+1指向了003CFC84。一個(gè)地址向后移動(dòng)了1,一個(gè)地址向后移動(dòng)了4。
這是因?yàn)椋褐羔樀念愋蜎Q定了指針向前或者向后移動(dòng)一步有多大的距離。
(1)概念:
野指針就是指針指向的位置是不可知的(隨機(jī)的、不正確的、沒(méi)有明確限制的)。
(2)成因:
①指針未初始化
例5
#include int main(){ int* p;//局部變量指針未初始化,默認(rèn)未隨機(jī)值。 *p = 10; return 0;}
②指針越界訪問(wèn)
例6
#include int main(){ int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i <= 11; i++) { //當(dāng)指針指向的范圍超出數(shù)組arr的范圍時(shí),p就是野指針 *(p++) = i; } return 0;}
③指針指向的空間釋放
例7
#include int* func(){ int a = 10; return &a;}int main(){ int* p = func(); *p = 20; return 0;}
這里的func函數(shù)雖然確實(shí)返回了地址,而p也確實(shí)接受到了返回的地址,但是當(dāng)返回的時(shí)候,已經(jīng)來(lái)不及保存了,因?yàn)閒unc函數(shù)一結(jié)束,函數(shù)申請(qǐng)的內(nèi)存等等就返回給操作系統(tǒng)了,已經(jīng)無(wú)法再通過(guò)指針p去訪問(wèn)a的地址了,后邊再用*p=20去訪問(wèn)的是已經(jīng)被釋放的a的地址。
(3)如何避免野指針
①指針初始化
例8
#include int main(){ int a = 10; int* p1 = &a; int* p2 = NULL;//NULL:用來(lái)初始化指針的,給指針賦值。 return 0;}
②小心指針越界
③指針指向空間釋放即設(shè)置NULL
④指針使用之前檢查有效性
例9
#include int main(){ int* p = NULL; int a = 10; p = &a; if (p != NULL) { *p = 20; } return 0;}
(1)指針±整數(shù)
例10:輸出數(shù)組的每一個(gè)元素
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } return 0;}
運(yùn)行結(jié)果為:1 2 3 4 5 6 7 8 9 10
(2)指針-指針
指針-指針得到的是中間的元素個(gè)數(shù)。(注意盡量大-?。?/p>
易錯(cuò)點(diǎn)提示:兩個(gè)指針不能進(jìn)行加法運(yùn)算,這是非法的?。?!兩個(gè)指針在進(jìn)行減法運(yùn)算時(shí),類型要相同,否則結(jié)果不可預(yù)知!??!
例11
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p1 = &arr[0]; int* p2 = &arr[9]; printf("%d/n", *p2 - *p1); return 0;}
運(yùn)行結(jié)果為:9
我們?cè)谥皩W(xué)過(guò)了用遞歸和迭代兩種方法來(lái)實(shí)現(xiàn)strlen函數(shù),那么今天我們就學(xué)會(huì)了第三種方法——指針相減法:
例12:自己的strlen函數(shù)(指針相減法)
#include int my_strlen(char* str){ char* ret = str; while (*str != "/0") { str++; } return str - ret;}int main(){ char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d", len); return 0;}
(3)指針的關(guān)系運(yùn)算——"<" “>” “<=” “>=” “==” “!=”
指針進(jìn)行關(guān)系運(yùn)算的前提是它們都指向同一個(gè)數(shù)組中的元素。
易錯(cuò)點(diǎn)提示:指針的關(guān)系運(yùn)算是相同類型的指針之間的關(guān)系運(yùn)算,不同類型的指針之間的關(guān)系運(yùn)算沒(méi)有意義,指針與非0整數(shù)的關(guān)系運(yùn)算也沒(méi)有意義。
例13
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0]; int* q = &arr[9]; p < q; //當(dāng)p所指的元素在q所指的元素之前時(shí),表達(dá)式的值為1;反之為0。 p > q; //當(dāng)p所指的元素在q所指的元素之后時(shí),表達(dá)式的值為1;反之為0。 p == q; //當(dāng)p和q指向同一元素時(shí),表達(dá)式的值為1;反之為0。 p != q; //當(dāng)p和q不指向同一元素時(shí),表達(dá)式的值為1;反之為0。 printf("%d %d %d %d", p < q, p > q, p == q, p != q); return 0;}
運(yùn)行結(jié)果為:1 0 0 1
例14
#include int main(){ int a = 10; int* p1 = &a; int** p2 = &p1;//p2就是二級(jí)指針。 printf("%d/n", **p2); **p2 = 20;//二級(jí)指針p2改變,其指向的a隨之改變。 printf("%d/n", **p2); printf("%d/n", a); return 0;}
運(yùn)行結(jié)果為:
10
20
20
指向字符型數(shù)據(jù)的指針變量。每個(gè)字符串在內(nèi)存中都占用一段連續(xù)的存儲(chǔ)空間,并有唯一確定的首地址。即將字符串的首地址賦值給字符指針,可讓字符指針指向一個(gè)字符串。
下面舉一個(gè)例子:
例15
#include int main(){ char arr[] = "abcdef"; char* p1 = arr; printf("%s/n", arr); printf("%s/n", p1); //這里的字符指針p1指向的是arr[]中首位的地址,所以在打印時(shí)不用解引用,它表示的是從arr[]的首位開始打印至"/0"停止。 char* p2 = "abcdef";//"abcdef"是一個(gè)常量字符串。 printf("%c/n", *p);//說(shuō)明p存的只是首元素a的地址。 printf("%s/n", p);//同上 return 0;}
運(yùn)行結(jié)果為:
abcdef
abcdef
a
abcdef
易錯(cuò)點(diǎn)提示:在常量字符串前加一個(gè)“const”。
例16:常量字符串
#include int main(){ const char* p = "abcdef"; //*p = "w"; printf("%s/n", p); return 0;}
運(yùn)行結(jié)果為:abcdef
有了const以后,我們就不能對(duì)指針p進(jìn)行賦值修改了,這樣可以使其更加安全的儲(chǔ)存數(shù)據(jù)。
例17:經(jīng)典易錯(cuò)題
#include int main(){ char arr1[] = "abcdef"; char arr2[] = "abcdef"; char* p1 = "abcdef"; char* p2 = "abcdef"; if (arr1 == arr2) printf("1/n"); else printf("0/n"); if (p1 == p2) printf("1/n"); else printf("0/n"); return 0;}
運(yùn)行結(jié)果為:
0
1
這是因?yàn)閍rr1和arr2是分別開辟的內(nèi)存,雖然元素相同,但所處內(nèi)存空間不同,所以arr1 != arr2;但是字符指針p1和p2都指向了字符串"abcdef",指向相同,所以p1 == p2。
數(shù)組名表示的是數(shù)組首元素的地址。
例18
#include int main(){ int arr[10] = { 0 }; printf("%p/n", arr);//arr:首元素的地址。 printf("%p/n", arr + 1); printf("%p/n", &arr[0]);//&arr[i]:數(shù)組中對(duì)應(yīng)元素的地址。 printf("%p/n", &arr[0] + 1); printf("%p/n", &arr);//&arr:整個(gè)數(shù)組的地址。 printf("%p/n", &arr + 1); //1. &arr — &數(shù)組名:數(shù)組名不是首元素地址,數(shù)組名表示整個(gè)數(shù)組,&數(shù)組名-取出的是整個(gè)數(shù)組的地址。 //2.sizeof(arr) — sizeof(數(shù)組名):數(shù)組名表示的整個(gè)數(shù)組,sizeof(數(shù)組名)計(jì)算的是整個(gè)數(shù)組的大小。 return 0;}
運(yùn)行結(jié)果為:
00B3F828
00B3F82C
00B3F828
00B3F82C
00B3F828
00B3F850
也就是說(shuō),對(duì)于上述代碼,arr與&arr[0]通過(guò)加1運(yùn)算后發(fā)現(xiàn)地址加了4,而&arr通過(guò)加1運(yùn)算后發(fā)現(xiàn)地址加了40,從而說(shuō)明了&arr表示的是整個(gè)數(shù)組的地址。
概念:數(shù)組元素全為指針變量的數(shù)組稱為指針數(shù)組。
也就是指針數(shù)組是一個(gè)數(shù)組,用來(lái)存放指針的數(shù)組。
如:*p[10]是一個(gè)指針數(shù)組。
舉一個(gè)例子:
例19
#include int main(){ int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; int* arr[] = { arr1,arr2,arr3 }; int i = 0; //分別遍歷出arr1,arr2,arr3。 for (i = 0; i < 3; i++) { int j = 0; //分別遍歷出arr1,arr2,arr3中的每個(gè)元素。 for (j = 0; j < 5; j++) { printf("%d ", *(arr[i] + j)); } printf("/n"); } return 0;}
運(yùn)行結(jié)果為:
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
概念:指的是數(shù)組名的指針,即數(shù)組首元素地址的指針。
也就是數(shù)組指針是一個(gè)指針,指向數(shù)組的指針。
如:(*p)[10]是一個(gè)數(shù)組指針。
舉一個(gè)例子:
例20
#include void print1(int arr[3][5], int x, int y)//二維數(shù)組打印。{ int i, j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("/n"); }}void print2(int (*p)[5], int x, int y)//數(shù)組指針打印{ int i = 0; for (i = 0; i < x; i++) { int j = 0; for (j = 0; j < y; j++) { printf("%d ", *(*(p+i)+j)); } printf("/n"); }}int main(){ int arr[3]
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/123067.html
目錄 前言 一、 什么是指針? 引例 計(jì)算機(jī)是怎么對(duì)內(nèi)存單元編號(hào)的呢? 內(nèi)存空間的地址如何得到 想存地址怎么辦? ? 本質(zhì)目的不是為了存地址 ?二、指針和指針類型 為什么有不同類型的指針 1.指針的解引用 2.指針+-整數(shù) 三、野指針 造成野指針的原因 1.未主動(dòng)初始化指針 ?2.指針越界訪問(wèn) 3.指針指向的空間釋放 規(guī)避野指針 四、指針運(yùn)算 1.指針+-整數(shù) ?2.指針-指針 ?3.指針的關(guān)系運(yùn)...
摘要:這里分塊講解六函數(shù)棧幀的銷毀過(guò)程一解析的作用是將棧頂?shù)臄?shù)據(jù)彈出,彈出數(shù)據(jù)儲(chǔ)存到相應(yīng)寄存器中。 ?前言? 讀完這篇博客,你可以明白什么? ①局部變量到底是怎么在棧上創(chuàng)建的? ②為什么局部變量不初始化為隨機(jī)值? ③函數(shù)是怎么傳參的?傳參的先后順序是什么? ④形參和實(shí)參是什么關(guān)系? ⑤函數(shù)調(diào)用是怎...
目錄 一、什么是C語(yǔ)言? 二、第一個(gè)C語(yǔ)言程序 代碼 程序分析 ?程序運(yùn)行 一個(gè)工程中出現(xiàn)兩個(gè)及以上的main函數(shù) 代碼 運(yùn)行結(jié)果 分析 三、數(shù)據(jù)類型 數(shù)據(jù)各種類型 為什么會(huì)有這么多的數(shù)據(jù)類型? 計(jì)算機(jī)單位 ?各個(gè)數(shù)據(jù)類型的大小 ?注意事項(xiàng) 數(shù)據(jù)類型的使用 四、變量和常量 變量的分類 變量的使用 變量的作用域和生命周期 ?常量 五、字符串+轉(zhuǎn)義字符+注釋 字符串 ?轉(zhuǎn)義字符 注釋 六、選擇語(yǔ)句 ?...
閱讀 2582·2023-04-26 00:57
閱讀 946·2021-11-25 09:43
閱讀 2249·2021-11-11 16:55
閱讀 2293·2019-08-30 15:53
閱讀 3623·2019-08-30 15:52
閱讀 1496·2019-08-30 14:10
閱讀 3405·2019-08-30 13:22
閱讀 1238·2019-08-29 11:18