指針也就是內(nèi)存地址,指針變量是用來(lái)存放內(nèi)存地址的變量,不同類型的指針變量所占用的存儲(chǔ)單元長(zhǎng)度是相同的,而存放數(shù)據(jù)的變量因數(shù)據(jù)的類型不同,所占用的存儲(chǔ)空間長(zhǎng)度也不同。有了指針以后,不僅可以對(duì)數(shù)據(jù)本身,也可以對(duì)存儲(chǔ)數(shù)據(jù)的變量地址進(jìn)行操作。
計(jì)算機(jī)中所有的數(shù)據(jù)都必須放在內(nèi)存中,不同類型的數(shù)據(jù)占用的字節(jié)數(shù)不一樣,例如 int 占用 4 個(gè)字節(jié),char 占用 1 個(gè)字節(jié)。為了正確地訪問(wèn)這些數(shù)據(jù),必須為每個(gè)字節(jié)都編上號(hào)碼,就像門(mén)牌號(hào)、身份證號(hào)一樣,每個(gè)字節(jié)的編號(hào)是唯一的,根據(jù)編號(hào)可以準(zhǔn)確地找到某個(gè)字節(jié)。
在計(jì)算機(jī)中, 所有的數(shù)據(jù)都是存放在存儲(chǔ)器中的, 不同的數(shù)據(jù)類型占有的內(nèi)存空間的大小各不相同。內(nèi)存是以字節(jié)為單位的連續(xù)編址空間, 每一個(gè)字節(jié)單元對(duì)應(yīng)著一個(gè)唯一的編號(hào), 這個(gè)編號(hào)被稱為內(nèi)存單元的地址。比如: int類型占兩個(gè)字節(jié), char類型占1個(gè)字節(jié)等。內(nèi)存為變量分配存儲(chǔ)空間的首個(gè)字節(jié)單元的地址, 稱之為該變量的地址。地址用來(lái)標(biāo)識(shí)每一個(gè)存儲(chǔ)單元, 方便用戶對(duì)存儲(chǔ)單元中的數(shù)據(jù)進(jìn)行正確的訪問(wèn)。在高級(jí)語(yǔ)言中地址形象地稱為指針, CPU 訪問(wèn)內(nèi)存時(shí)需要的是地址,而不是變量名和函數(shù)名!變量名和函數(shù)名只是地址的一種助記符,當(dāng)源文件被編譯和鏈接成可執(zhí)行程序后,它們都會(huì)被替換成地址。編譯和鏈接過(guò)程的一項(xiàng)重要任務(wù)就是找到這些名稱所對(duì)應(yīng)的地址。
需要注意的是 變量名和函數(shù)名為我們提供了方便,讓我們?cè)诰帉?xiě)代碼的過(guò)程中可以使用易于閱讀和理解的英文字符串,不用直接面對(duì)二進(jìn)制地址,那場(chǎng)景簡(jiǎn)直讓人崩潰。雖然變量名、函數(shù)名、字符串名和數(shù)組名在本質(zhì)上是一樣的,它們都是地址的助記符,但在編寫(xiě)代碼的過(guò)程中,我們認(rèn)為變量名表示的是數(shù)據(jù)本身,而函數(shù)名、字符串名和數(shù)組名表示的是代碼塊或數(shù)據(jù)塊的首地址。
指針相對(duì)于一個(gè)內(nèi)存單元來(lái)說(shuō),指的是單元的地址,該單元的內(nèi)容里面存放的是數(shù)據(jù)。在C語(yǔ)言中,允許用指針變量來(lái)存放指針,因此,一個(gè)指針變量的值就是某個(gè)內(nèi)存單元的地址或稱為某內(nèi)存單元的指針。
指針的定義與使用
變量的指針與指針變量:
在C語(yǔ)言中,允許用一個(gè)變量來(lái)存放指針,這種變量稱為指針變量。指針變量的值就是某份數(shù)據(jù)的地址,這樣的一份數(shù)據(jù)可以是數(shù)組、字符串、函數(shù),也可以是另外的一個(gè)普通變量或指針變量
變量的指針就是變量的存儲(chǔ)地址,指針變量就是存儲(chǔ)指針的變量。指針變量是存放一個(gè)變量地址的變量,不同于其他類型變量,它是專門(mén)用來(lái)存放內(nèi)存地址的,也稱為地址變量。定義指針變量的一般形式為:類型說(shuō)明符 * 變量名 *
類型說(shuō)明符表示指針變量所指向變量的數(shù)據(jù)類型;* 表示這是一個(gè)指針變量;變量名表示定義的指針變量名,其值是一個(gè)地址,例如:char * p1;表示p1是一個(gè)指針變量,它的值是某個(gè)字符變量的地址
//定義指針變量與定義普通變量非常類似,不過(guò)要在變量名前面加星號(hào)*,格式為: int *fp;//*表示這是一個(gè)指針變量,fp是一個(gè)指向int數(shù)據(jù)類型的指針 float *a,*b; //表示a和b都是指針變量,都指向一個(gè)為float數(shù)據(jù)類型的指針
指針變量的使用:
取地址運(yùn)算符&:?jiǎn)文窟\(yùn)算符&是用來(lái)取操作對(duì)象的地址。
例:&i 為取變量 i 的地址。對(duì)于常量表達(dá)式、寄存器變量不能取地址(因?yàn)樗鼈兇鎯?chǔ)在存儲(chǔ)器中,沒(méi)有地址)。
指針運(yùn)算符 * (間接尋址符):與&為逆運(yùn)算,作用是通過(guò)操作對(duì)象的地址,獲取存儲(chǔ)的內(nèi)容。
例:x = &i x 為 i 的地址,*x 則為通過(guò) i 的地址,獲取 i 的內(nèi)容。
//賦值 int a = 100;//定義了一個(gè)a的整形變量 int *p_a = &a;//將一個(gè)int類型的指針p_a,p_a指向了a(也叫p_a指向了a的地址) 在定義指針變量 p_a 的同時(shí)對(duì)它進(jìn)行初始化,并將變量 a 的地址賦予它,此時(shí) p_a 就指向了 a。 值得注意的是,p_a 需要的一個(gè)地址,a 前面必須要加取地址符&,否則是不對(duì)的。
和普通變量一樣,指針變量也可以被多次寫(xiě)入,只要你想,隨時(shí)都能夠改變指針變量的值:
//定義普通變量 float a = 99.5, b = 10.6; char c = '@', d = '#'; //定義指針變量 float *p1 = &a;//P1指向a的地址 char *p2 = &c;//p2指向c的地址 //修改指針變量的值 p1 = &b;//將p1改變指向?yàn)閎 p2 = &d;//將p2改變指向?yàn)閍
* 是一個(gè)特殊符號(hào),表明一個(gè)變量是指針變量,定義 p1、p2 時(shí)必須帶 *。而給 p1、p2 賦值時(shí),因?yàn)橐呀?jīng)知道了它是一個(gè)指針變量,就沒(méi)必要多此一舉再帶上 *,后邊可以像使用普通變量一樣來(lái)使用指針變量。也就是說(shuō),定義指針變量時(shí)必須帶 *,給指針變量賦值時(shí)不能帶 *。
//注意 不允許把一個(gè)數(shù)賦予指針變量 int *p; p = &a; *p = 100;//這樣是錯(cuò)誤的 或者: int b=200; int *a; a=b; //這樣也錯(cuò)誤,因?yàn)闆](méi)有加上取地址符&
指針變量存儲(chǔ)了數(shù)據(jù)的地址,通過(guò)指針變量能夠獲得該地址上的數(shù)據(jù):
#includeint main(){ int a = 66;//定義整形變量 int *p = &a; //定義int的指針變量并指向a變量的地址 printf("%d, %d ", a, *p); //兩種方式都可以輸出a的值 return 0; } //假設(shè) a 的地址是 0X1000,p 指向 a 后,p 本身的值也會(huì)變?yōu)?0X1000,*p 表示獲取地址 0X1000 上的數(shù)據(jù), 也即變量 a 的值。所以從運(yùn)行結(jié)果看,*p 和 a 是等價(jià)的。 CPU 讀寫(xiě)數(shù)據(jù)必須要知道數(shù)據(jù)在內(nèi)存中的地址,普通變量和指針變量都是地址的助記符,雖然通過(guò) *p 和 a 獲取到的數(shù)據(jù)一樣, 但它們的運(yùn)行過(guò)程稍有不同:a 只需要一次運(yùn)算就能夠取得數(shù)據(jù),而 *p 要經(jīng)過(guò)兩次運(yùn)算,多了一層“間接”。 //程序被編譯和鏈接后,a、p 被替換成相應(yīng)的地址。使用 *p 的話,要先通過(guò)地址 0XF0A0 取得變量 p 本身的值, 這個(gè)值是變量 a 的地址,然后再通過(guò)這個(gè)值取得變量 a 的數(shù)據(jù) 也就是說(shuō),使用指針是間接獲取數(shù)據(jù),使用變量名是直接獲取數(shù)據(jù),前者比后者的代價(jià)要高。
可以用指針來(lái)改變被指向那個(gè)變量的值 如:
#includeint main(void){ int a = 1, b = 66, c = 2;//定義普通變量 int *p = &a; //定義指針變量并指向a的地址 *p = b; //通過(guò)指針變量將a的值改變了 (因?yàn)樵谶@里,*p指向了a 就等于*p和a身處同一個(gè)內(nèi)存空間了, 所以對(duì)*p修改 就相當(dāng)于對(duì)a修改) c = *p; //把指針p的值的賦值給了C (根據(jù)前面說(shuō)的,相當(dāng)于將a賦值給了C) printf("%d, %d, %d, %d ", a, b, c, *p);//所以他們的值都是同一個(gè)了 return 0; } *在不同的場(chǎng)景下有不同的作用:*可以用在指針變量的定義中, 表明這是一個(gè)指針變量,以和普通變量區(qū)分開(kāi);使用指針變量時(shí)在前面加*表示獲取指針指向的數(shù)據(jù),或者說(shuō)表示的是指針指向的數(shù)據(jù)本身。
也就是說(shuō),定義指針變量時(shí)的*和使用指針變量時(shí)的*意義完全不同。以下面的語(yǔ)句為例:
int *p = &a;//這里表示指向a的地址 *p = 100; //這里表示獲取指針?biāo)赶虻臄?shù)據(jù)
其他一些騷操作:
int x, y, *px = &x, *py = &y; y = *px + 5; //表示把x的內(nèi)容加5并賦給y,*px+5相當(dāng)于(*px)+5 y = ++*px; //px的內(nèi)容加上1之后賦給y,++*px相當(dāng)于++(*px) y = *px++; //相當(dāng)于y=(*px)++ py = px; //把一個(gè)指針的值賦給另一個(gè)指針
關(guān)于“&”和“*”
“&”和“ * ”都是右結(jié)合的。假設(shè)有變量 x = 10,則*&x 的含義是,先獲取變量 x 的地址,再獲取地址中的內(nèi)容。因?yàn)椤?”和“ * ”互為逆運(yùn)算,所以 x = *&x。
假設(shè)有一個(gè) int 類型的變量 a, pa 是指向a的指針,那么*&a和&*pa分別是什么意思呢?
*&a可以理解為*(&a),&a表示取變量 a 的地址(等價(jià)于 pa),*(&a)表示取這個(gè)地址上的數(shù)據(jù)(等價(jià) * pa),繞來(lái)繞去,又回到了原點(diǎn),*&a仍然等價(jià)于 a。
&*pa可以理解為&(*pa),*pa表示取得 pa 指向的數(shù)據(jù)(等價(jià)于 a),&(*pa)表示數(shù)據(jù)的地址(等價(jià)于 &a),所以&*pa等價(jià)于 pa。
野指針與空指針
空指針是說(shuō),這個(gè)指針沒(méi)有指向一塊有意義的內(nèi)存,比如說(shuō):char* k; 這里這個(gè)k就叫空指針.我們并未讓它指向任意點(diǎn).
又或者char* k = NULL; 這里這個(gè)k也叫空指針,因?yàn)樗赶騈ULL 也就是0,注意是整數(shù)0,不是'?'
一個(gè)空指針我們也無(wú)法對(duì)它進(jìn)行取內(nèi)容操作,空指針只有在真正指向了一塊有意義的內(nèi)存后,我們才能對(duì)它取內(nèi)容.也就是說(shuō)要這樣 k = "hello world!"; 這時(shí)k就不是空指針了.
對(duì)于空指針值,一般的文檔中傾向于用 NULL 表示,而沒(méi)有直接說(shuō)成 0。但是我們應(yīng)該清楚:對(duì)于指針類型來(lái)說(shuō),返回 NULL 和 返回 0 是完全等價(jià)的,因?yàn)?NULL 和 0 都表示 “null pointer”(空指針)。一句話, 空指針是什么,就是一個(gè)被賦值為0的指針,在沒(méi)有被具體初始化之前,其值為0.(百度解釋)
如 :
int *a;//定義一個(gè)指針a=NULL;//讓這個(gè)指針指向空a =0;//這樣也是讓一個(gè)指針指向空的方式
注意:void* 這不叫空指針,這叫無(wú)確切類型指針.這個(gè)指針指向一塊內(nèi)存,卻沒(méi)有告訴程序該用何種方式來(lái)解釋這片內(nèi)存.所以這種類型的指針不能直接進(jìn)行取內(nèi)容的操作.必須先轉(zhuǎn)成別的類型的指針才可以把內(nèi)容解釋出來(lái).
還有'?',這也不是空指針?biāo)傅膬?nèi)容. '?'是表示一個(gè)字符串的結(jié)尾而已,并不是NULL的意思
void*因?yàn)槭潜硎静恢酪赶蚴裁礀|西的指針,計(jì)算時(shí)于char相同(但不相通)
野指針不同于空指針,空指針是指一個(gè)指針的值為null,而野指針的值并不為null,野指針會(huì)指向一段實(shí)際的內(nèi)存,只是它指向哪里我們并不知情,或者是它所指向的內(nèi)存空間已經(jīng)被釋放,所以在實(shí)際使用的過(guò)程中,我們并不能通過(guò)指針判空去識(shí)別一個(gè)指針是否為野指針。避免野指針只能靠我們自己養(yǎng)成良好的編程習(xí)慣
野指針就是指針指向的位置是不可知的(隨機(jī)的、不正確的、沒(méi)有明確限制的)指針變量在定義時(shí)如果未初始化,其值是隨機(jī)的,指針變量的值是別的變量的地址,意味著指針指向了一個(gè)地址是不確定的變量,此時(shí)去解引用就是去訪問(wèn)了一個(gè)不確定的地址,所以結(jié)果是不可知的。(百度解釋)
下面說(shuō)說(shuō)哪些情況下會(huì)產(chǎn)生野指針,以及怎樣避免。
1、指針變量的值未被初始化: 聲明一個(gè)指針的時(shí)候,沒(méi)有顯示的對(duì)其進(jìn)行初始化,那么該指針?biāo)赶虻牡刂房臻g是亂指一氣的。如果指針聲明在全局?jǐn)?shù)據(jù)區(qū),那么未初始化的指針缺省為空,如果指針聲明在棧區(qū),那么該指針會(huì)隨意指向一個(gè)地址空間。所以良好的編程習(xí)慣就是在聲明指針的時(shí)候就對(duì)其進(jìn)行初始化,如果暫時(shí)不知道該初始化成什么值,就先把指針置空。
int main(void){ int *a;//野指針 if(a!=NULL){ .... } /* int *a; a=NULL/0; 正確的引用 */ }
2、指針?biāo)赶虻牡刂房臻g已經(jīng)被free或delete:在堆上malloc或者new出來(lái)的地址空間,如果已經(jīng)free或delete,那么此時(shí)堆上的內(nèi)存已經(jīng)被釋放,但是指向該內(nèi)存的指針如果沒(méi)有人為的修改過(guò),那么指針還會(huì)繼續(xù)指向這段堆上已經(jīng)被釋放的內(nèi)存,這時(shí)還通過(guò)該指針去訪問(wèn)堆上的內(nèi)存,就會(huì)造成不可預(yù)知的結(jié)果,給程序帶來(lái)隱患,所以良好的編程習(xí)慣是:內(nèi)存被free或delete后,指向該內(nèi)存的指針馬上置空。
void func() { int *ptr = new int[5]; delete [ ]ptr; // 執(zhí)行完delete后,ptr野指針 //還應(yīng)該這樣做:ptr=NULL; 正確 }
3、指針操作超越了作用域,如果在一個(gè)程序塊中讓一個(gè)指針指向那個(gè)塊中的某個(gè)變量,但是那個(gè)變量只是在塊中有效,出了那個(gè)程序塊,此變量就無(wú)效了,此時(shí)指向它的指針也就變成了野指針
void func() { int *ptr = nullptr; { int a = 10; ptr = &a; } // a的作用域到此結(jié)束 int b = *ptr; // ptr指向的a,但是a已經(jīng)被回收,所以ptr變成野指針 //還應(yīng)該這樣做:ptr=NULL; 正確 }
所以 使用指針時(shí)應(yīng)當(dāng)注意”規(guī)避“:初始化時(shí)置 NULL,釋放時(shí)置 NULL
3、指針的運(yùn)算
C 指針是一個(gè)用數(shù)值表示的地址。因此,您可以對(duì)指針執(zhí)行算術(shù)運(yùn)算。可以對(duì)指針進(jìn)行四種算術(shù)運(yùn)算:++、--、+、-。遞增遞減加減,兩個(gè)指針相減
#includeint main(void) { int a=10; int *pa = &a,*pA=&a; double b = 22.2; double *pb = &b; char c = 'C'; char *pc = &c; //最初的值 printf("1- %#x %#x %#x ", &a, &b, &c);//%#x表示 轉(zhuǎn)換成十六進(jìn)制帶格式輸出地址, //效果為在輸出前加0x printf("2- %#x %#x %#x ", pa, pb, pc); //指針加法 pa += 2; pb += 2; pc += 2; printf("3- %#x %#x %#x ", pa, pb, pc); //指針減法 pa -= 2; pb -= 2; pc -= 2; printf("4- %#x %#x %#x ", pa, pb, pc); //指針的比較 if (pa == pA) { printf("5=%d ", *pA); } else { printf("6=%d ", *pa); } return 0; } //從運(yùn)算結(jié)果可以看出:pa、pb、pc 每次加 1,它們的地址分別增加 4、8、1,正好是 int、double、char 類型的長(zhǎng)度; 減 2 時(shí),地址分別減少 8、16、2,正好是 int、double、char 類型長(zhǎng)度的 2 倍。 /*簡(jiǎn)單的概括就是: 指針的每一次遞增,它其實(shí)會(huì)指向下一個(gè)元素的存儲(chǔ)單元。 指針的每一次遞減,它都會(huì)指向前一個(gè)元素的存儲(chǔ)單元。 指針在遞增和遞減時(shí)跳躍的字節(jié)數(shù)取決于指針?biāo)赶蜃兞繑?shù)據(jù)類型長(zhǎng)度,比如 int 就是 4 個(gè)字節(jié)。 指針變量除了可以參與加減運(yùn)算,還可以參與比較運(yùn)算。當(dāng)對(duì)指針變量進(jìn)行比較運(yùn)算時(shí),比較的是指針變量本身的值,也就是數(shù)據(jù)的地址。 如果地址相等,那么兩個(gè)指針就指向同一份數(shù)據(jù),否則就指向不同的數(shù)據(jù)。當(dāng)然還有其他邏輯運(yùn)算符 上面的代碼(第一個(gè)例子)在比較 pa 和 pA 的值時(shí),pa 已經(jīng)指向了 a 的上一份數(shù)據(jù),所以它們不相等。而 a 的上一份數(shù)據(jù)又不知道是什么, 所以會(huì)導(dǎo)致 printf() 輸出一個(gè)沒(méi)有意義的數(shù),這正好印證了上面的觀點(diǎn),不要對(duì)指向普通變量的指針進(jìn)行加減運(yùn)算 注意:不能對(duì)指針變量進(jìn)行乘法、除法、取余等其他運(yùn)算,除了會(huì)發(fā)生語(yǔ)法錯(cuò)誤,也沒(méi)有實(shí)際的含義。
#include//指針的加減法其實(shí)上的地址上的移動(dòng) int main(void) { char a[] = {2,3,4,5,6}; char *p = &a[0]; char *p1 = &a[10]; printf("p1-p=%d ", p1 - p); printf("p=%p ", p); printf("p1=%p ", p1); //這里如果運(yùn)算為大于零,就是真 輸出 0 //如果運(yùn)算為小于零,就是假 輸出 -1 int *t = a[0]; int *k = a[3]; printf("* %d ", t-k); int b[] = { 1,2,3,4,5,6 }; int *q = &b[0]; int *q1 = &b[6]; printf("q1-q=%d ", q1 - q); printf("%p ", q1); printf("%p ", q); return 0; }
常見(jiàn)的指針運(yùn)算:
*(++p): 先移動(dòng)指針,取下一個(gè)單元的值
*(p++): 先取出當(dāng)前單元的值,再移動(dòng)指針
( * p)++ : 先取出當(dāng)前單元的值,再使當(dāng)前單元的值加1 (指針不移動(dòng))
++( * p) : 先使當(dāng)前單元的值加1,再取出當(dāng)前單元的值 (指針不移動(dòng))
指針的類型轉(zhuǎn)換:
int *p=&i;
void *q=(void * )p;
這并沒(méi)有改變p所指向的變量的類型,而是讓后人用不同的眼光通過(guò)p看它所指的變量
意思是:這里的p我不再當(dāng)你是int了,認(rèn)為你就是個(gè)void類型的
注意 由于優(yōu)先級(jí)的問(wèn)題 *p++和 * (p++)是等價(jià)的
取地址符 &補(bǔ)充:
獲得變量的地址,它的操作必須是變量,
int i,printf("%x",&i); 取得i的地址并輸出。
int i,printf("%p",&i); 取得i的地址并輸出。
地址的大小是否于int相同取決于編譯器
注意 使用指針的時(shí)候的類型,無(wú)論指向什么類型,所有的指針的大小都是一樣的,因?yàn)槎际堑刂罚侵赶虿煌愋偷闹羔樖遣荒芟嗷ベx值的,這是為了避免用錯(cuò)指針。
-
存儲(chǔ)
+關(guān)注
關(guān)注
13文章
4502瀏覽量
87063 -
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7630瀏覽量
140305 -
指針
+關(guān)注
關(guān)注
1文章
484瀏覽量
70994
原文標(biāo)題:【零基礎(chǔ)學(xué)C語(yǔ)言】知識(shí)總結(jié)十:指針及其相關(guān)知識(shí)(一)
文章出處:【微信號(hào):cyuyanxuexi,微信公眾號(hào):C語(yǔ)言編程學(xué)習(xí)基地】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
C語(yǔ)言中指針的定義

評(píng)論