C++ 指针基本概念
Ⅰ.內(nèi)存和地址
我們已經(jīng)很熟悉一些基本的存儲(chǔ)單位了,比如一個(gè)bit(位)用存儲(chǔ)0或者1.也可以把幾個(gè)bit合起來表示更大的數(shù)字,比如一個(gè)byte(字節(jié))就包含了8個(gè)bit.這些都是很基礎(chǔ)很簡(jiǎn)單的東西.然后我們可以把計(jì)算機(jī)的內(nèi)存想象成一個(gè)字節(jié)數(shù)組,內(nèi)存中的每一個(gè)地址表示一個(gè)字節(jié).?
?
每個(gè)字節(jié)中都能夠存儲(chǔ)一定位數(shù)的內(nèi)容,因此,每個(gè)字節(jié)都能夠通過一些地址來標(biāo)識(shí).有時(shí)候,一個(gè)字節(jié)不夠,怎么辦呢?那么就同時(shí)用很多個(gè)字節(jié)來表示,比如一個(gè)int在我的系統(tǒng)里面就用了4個(gè)字節(jié)。?
下面的圖是上面那幅圖片每?jī)蓚€(gè)字節(jié)合并在一起之后樣子.這個(gè)因?yàn)槭莾蓚€(gè)字節(jié)合并,所以它成為比一個(gè)字節(jié)更大的內(nèi)存單位(廢話),能夠存儲(chǔ)的信息就更多(廢話).但是雖然一個(gè)字是兩個(gè)字節(jié)合并的,但是它仍然只包含一個(gè)地址,也就是說,合并之后只需要一個(gè)地址來找到合并之后的內(nèi)存.?
?
因此,我們可以得到:?
1.內(nèi)存中的每個(gè)位置都由一個(gè)獨(dú)一無二的地址表示.?
2.內(nèi)存中的每個(gè)位置都包含一個(gè)值.?
通俗一點(diǎn),我們可以通過一個(gè)地址,來找到內(nèi)存中的某個(gè)具體位置,然后訪問到(得到)該位置的值(允許的話).這就是內(nèi)存和地址簡(jiǎn)單的思想.
Ⅱ.指針含義與創(chuàng)建方式
指針這個(gè)名字確實(shí)折磨過很多人,這個(gè)名字是個(gè)好名字,同時(shí)也是一個(gè)非常不好的名字.說它好,是因?yàn)橹羔樳@個(gè)東西很形象的體現(xiàn)了它的功能:指針指針,指到某個(gè)地方某個(gè)位置.非常形象.它不是個(gè)好名字是因?yàn)樗拿钟袝r(shí)候掩蓋了它的真實(shí)含義.一般來說,指針是一個(gè)其值為地址的變量。(就是一個(gè)存儲(chǔ)地址的變量)?
所以,要養(yǎng)成一種條件反射,看到指針首先不是想到他能夠指向哪里,而是想到這個(gè)變量存放的是一個(gè)地址,是這個(gè)地址指向哪里哪里.(比如,char類型的變量中存放的是char類型的數(shù)據(jù).int變量中存放的是int類型的數(shù)據(jù).指針中存放的是一個(gè)地址!!!)?
反復(fù)的把上面的概念消化之后,我們就來看兩個(gè)基本的運(yùn)算符:&(取址運(yùn)算符)和*(間接訪問運(yùn)算符/解引用指針)
首先是&運(yùn)算符:當(dāng)它后面跟一個(gè)變量名的時(shí)候,給出這個(gè)變量名的地址.
#include<iostream>
usingnamespace std;
int main()
{
?? int a=5;
?? double b=10.4;
?? cout<<"Address ofa:"<<&a<<endl;
?? cout<<"Address ofb:"<<&b<<endl;
}
至于*運(yùn)算符:就是后面跟一個(gè)指針的時(shí)候,得到指針指向的內(nèi)存中的內(nèi)容.
#include<iostream>
usingnamespace std;
int main()
{
?? int a=5;
?? double b=10.4;
?
?? cout<<"Address ofa:"<<&a<<endl;
?? cout<<"Address ofb:"<<&b<<endl;
?
?? cout<<"a:"<<*(&a)<<endl;
}
?
通過上面的例子你會(huì)發(fā)現(xiàn),這里輸出的地址是16進(jìn)制的整形.其實(shí)事實(shí)上,在大多數(shù)系統(tǒng)的內(nèi)部,指針?biāo)娴牡刂分狄话闶且粋€(gè)無符號(hào)的整數(shù)。但是,這并不代表指針和整形有一樣的運(yùn)算規(guī)則。指針類型是一種新的類型,而不是一種整數(shù)類型。ANSI專門為指針提供了%p輸出格式
理解上面兩個(gè)基本的運(yùn)算符之后,就可以正式開始講指針的創(chuàng)建了.?
聲明指針的模板:
指向地址的數(shù)據(jù)類型 * 指針變量名;
- 1
其中,*號(hào)必須帶,用以表明現(xiàn)在創(chuàng)建的是一個(gè)指針類型的變量.同時(shí),當(dāng)你看到創(chuàng)建變量的語(yǔ)句中帶有星號(hào)*的話,那么說明那個(gè)變量必定是一個(gè)指針變量!?
是不是很簡(jiǎn)單!舉一個(gè)例子進(jìn)一步來理解上面那個(gè)的含義.比如我想創(chuàng)建一個(gè)指針變量(存放地址的變量),這個(gè)指針(地址)是指向一個(gè)存儲(chǔ)整形的內(nèi)存.那么我就可以寫為:int * leo;同理,指向char的我可以寫成char * c;其實(shí)是很簡(jiǎn)單的.?
這里結(jié)合前面的內(nèi)容,簡(jiǎn)單的寫一個(gè)例子,并且介紹一些寫法.(最開始讓初學(xué)者迷惑的地方就是這里了,因?yàn)閯?chuàng)建時(shí)候的*號(hào),解除指針時(shí)候的*號(hào),各種符號(hào)混在一起,一般就直接懵逼了.但是要是好好掌握一些經(jīng)驗(yàn)結(jié)論,這里很容易過去.)
#include<iostream>
usingnamespace std;
int main()
{
? int a=5,b=6,c=7,d=8;
? double e=3.1415;
? //單獨(dú)賦值,并且*和p_a緊挨著
? int *p_a=&a;
? //多個(gè)賦值(既有指針,又有普通變量)
? int * p_b=&b,* p_c=&c,*p_d=&d,temp=100;
?
? //單獨(dú)賦值,double類型
? double * p_e=&e;
?
? cout<<p_a<<endl<<p_b<<endl<<p_c<<endl<<p_d<<endl<<p_e<<endl;
? cout<<temp<<endl;
}
上面這個(gè)例子有一些可以提煉的經(jīng)驗(yàn):?
首先,就是創(chuàng)建指向int或者double的指針的創(chuàng)建方式,這個(gè)前面都講了很多次了.在這個(gè)例子里面也可以很容易的找到,所以這里就不啰嗦了.?
然后就是創(chuàng)建時(shí)候的寫法.比如?int *p_a=&a;這句話中,*號(hào)是緊挨著p_a的,而在int * p_b=&b,* p_c=&c,*p_d=&d,temp=100;?這句話中,*號(hào)是可以不挨著p_b和p_c的.也就是說,創(chuàng)建指針變量的時(shí)候,星號(hào)的位置是很自由的.只要是出現(xiàn)了星號(hào),而不管中間是不是有空格,我們便認(rèn)為,這算是創(chuàng)建了一個(gè)指針變量.?
在別人寫的代碼中,你會(huì)看到很多的寫法,其實(shí)本質(zhì)就是這樣.比如有些人喜歡寫成int* p_a=&a;在這里,*號(hào)緊挨著int,因?yàn)橛腥死斫鉃?/span>int的指針類型即int*.所以,寫法這么多,初學(xué)者肯定是會(huì)迷惑的,記住前面的經(jīng)驗(yàn),這樣就見怪不怪了.?
接下來,還是在int * p_b=&b,* p_c=&c,*p_d=&d,temp=100;這句話里面,最后接了一個(gè)temp=100,千萬(wàn)不要也把temp也當(dāng)做了一個(gè)指針變量,他只是一個(gè)普通的變量.也就是說,同一句話里面,可以混合多種類型的賦值,指針的帶*號(hào),普通的不帶*號(hào).
Ⅲ.使用指針
使用指針的方式有很多,我們這里談?wù)勛罨A(chǔ)的,后面會(huì)更加深入的講指針的使用.前面已經(jīng)講過了指針的創(chuàng)建,接下來主要是講指針的初始化問題和賦值以及解除引用等等問題.?
首先是初始化問題.在前面的那一個(gè)例子中,我們?cè)趧?chuàng)建指針的時(shí)候都順便初始化了,那是不是創(chuàng)建指針就一定要初始化?肯定不是!創(chuàng)建指針的時(shí)候也可以不初始化.因?yàn)?strong>有時(shí)候你創(chuàng)建一個(gè)指針只是為了后續(xù)的工作,根本沒有東西拿來初始化.那么到現(xiàn)在,我們解決了第一個(gè)問題,那就是創(chuàng)建指針可以初始化也可以不初始化,.那么你肯定會(huì)說,這么簡(jiǎn)單為什么要單獨(dú)拿出來講?是因?yàn)楹髞淼哪承┎僮魇且紤]是否初始化的問題的.好了,不繞彎了,這里的操作主要是講間接訪問(解引用指針)帶來的一些問題.不多說,直接上例子.
#include<iostream>
usingnamespace std;
int main()
{
??? int num=5;
?
??? //p_a沒有初始化
??? int *p_a1,*p_a2;
??? cout<<"p_a1:"<<p_a1<<endl;
??? cout<<"p_a2:"<<p_a2<<endl;
?
??? int *p_b=#
??? cout<<"p_b:"<<p_b<<endl;
?
??? //同類型指針可以賦值
??? //沒有初始化的指針依然可以被賦值(地址)
??? p_a1=p_b;
??? p_a2=#
??? cout<<"changedp_a1:"<<p_a1<<endl;
??? cout<<"changedp_a2:"<<p_a2<<endl;
}
?
一句一句來分析這個(gè)例子.首先我們創(chuàng)建了一個(gè)整形,值為5,然后我們創(chuàng)建了兩個(gè)指向int類型的指針p_a1,P_a2,但是我們沒有初始化.從最后的運(yùn)行結(jié)果來看,指針不初始化是可行的.然后創(chuàng)建了一個(gè)指針p_b并且初始化了,所以從結(jié)果來看,就是num的地址.?
接下來有兩句:p_a1=p_b; p_a2=#?
前面那句是直接把p_b這個(gè)指向int類型的指針直接賦給了p_a1這個(gè)也是指向int類型的指針,那么從效果上面來看,相當(dāng)于p_a1和p_b指向同樣的地址…..好了,不裝逼了,因?yàn)橹羔樉褪谴鎯?chǔ)地址的嘛,其實(shí)就是把這個(gè)變量中存儲(chǔ)的地址賦給了另外一個(gè)變量…..非常簡(jiǎn)單.?
后面第二句就是說,即使不初始化,我還是能夠接收地址.(類比int n;n=5).?
所以總結(jié)起來就是即使沒有初始化,我依然能夠兩個(gè)指針之間賦值(類型肯定要一樣啦)和接受地址.也沒有什么困難的.那么接著看.
把上面的代碼小小的改動(dòng)一些,代碼變成下面這樣.
#include<iostream>
usingnamespace std;
int main()
{
??? int num=5;
?
??? //p_a沒有初始化
??? int *p_a1,*p_a2;
??? cout<<"p_a1:"<<p_a1<<endl;
??? cout<<"p_a2:"<<p_a2<<endl;
?
??? *p_a1=12;
??? cout<<*p_a1<<endl;
?
}
這里就加了一句*p_a1=12;這句話是很危險(xiǎn)的.我們創(chuàng)建了一個(gè)指向int的指針,但是并沒有初始化,也就是說,指針會(huì)得到一個(gè)隨機(jī)的地址(至少大部分系統(tǒng)上面是這樣),可創(chuàng)建指針的過程并不包含對(duì)于某個(gè)整形內(nèi)存上空間的開辟.?
*p_a1=12;的過程就是把12這個(gè)整形放到p_a1指向的內(nèi)存的過程.但是空間都沒有開辟,怎么放呢?所以這個(gè)語(yǔ)句最終是不是運(yùn)行成功都取決于運(yùn)氣.不同的系統(tǒng)上面可以有不同的結(jié)果.但是這樣的話就算是運(yùn)行成功了又有什么意義呢??
所以,其他的都不說,至少在解引用指針的時(shí)候,你需要保證你的指針被正確的初始化或者正確的被賦過某個(gè)地址.不然,那樣的解引用指針操作無意義且危險(xiǎn).
重要:?
既然前面談到了解引用指針(間接訪問),那就再來說說指針常量與指針的強(qiáng)制轉(zhuǎn)化.?
假如我們想在一個(gè)地址500存放一個(gè)整數(shù)100,沒錯(cuò),我們已經(jīng)知道這個(gè)地址是500了,所以我們就能夠這么賦值
*500=100;
這句話的意思是很明確的,先把地址500解引用然后把100放進(jìn)這個(gè)內(nèi)存.但是這句話是錯(cuò)的,因?yàn)榍懊嬲f過指針類型是一種特殊的類型.但是我們這里的500就是一個(gè)很普通的整形.他是能夠表示500這個(gè)地址沒有錯(cuò),但是它的類型不適合.因此,我們要把這個(gè)普通的整形強(qiáng)制轉(zhuǎn)換為指向整形的指針類型.因此可以這樣寫
*(int *)500=100
其實(shí)使用這個(gè)的機(jī)會(huì)很少,但是并不意味這個(gè)不重要,首先在某些硬件問題里面確實(shí)是想訪問某些硬件上面特定的地址的時(shí)候我們可以用這個(gè)方法.后面講到內(nèi)存管理的時(shí)候,也會(huì)回來這里.
Ⅳ.指針運(yùn)算
指針的運(yùn)算有算術(shù)運(yùn)算和關(guān)系運(yùn)算(比較大小等等),但是在這里僅僅是提一下,因?yàn)檫@部分的內(nèi)容是和后面指針與數(shù)組有關(guān)系的.在后面指針與數(shù)組會(huì)單獨(dú)講這些.
Ⅴ.NULL指針和void*
有很多對(duì)于指針不是很熟悉的童鞋在上數(shù)據(jù)結(jié)構(gòu)課的時(shí)候一般是很懵逼的,因?yàn)橛?/span>C講數(shù)據(jù)結(jié)構(gòu)的時(shí)候,涉及到很多的內(nèi)存開辟回收,以及指針問題(鏈?zhǔn)浇Y(jié)構(gòu)).然后很多人沒有學(xué)好不是因?yàn)檫壿嬆芰Σ?/span>,而是因?yàn)樵谝恍┐a或者是偽代碼中對(duì)于指針的認(rèn)識(shí)模棱兩可.以至于本來代碼是學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)思想的好工具,變?yōu)榱藢W(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的負(fù)擔(dān).
首先先講NULL指針.?
NULL指針是一種非常特殊的指針(廢話,不然單獨(dú)說它干嘛?),不指向任何東西.表示不指向任何東西的指針(哦,感覺好厲害的樣子).但是事實(shí)上面,這種技巧非常有用.(當(dāng)你被鏈表虐的時(shí)候就知道了).?
我們?cè)趺窗岩粋€(gè)指針變?yōu)?/span>NULL指針呢?很簡(jiǎn)單,賦給這個(gè)指針一個(gè)0就行了.不用轉(zhuǎn)化,就是整形的0.同樣,直接賦NULL也是行的.看下面的例子.
#include<iostream>
usingnamespace std;
int main()
{
??? //測(cè)試NULL是不是0
??? if(NULL==0)
??????? cout<<"yes"<<endl;
?
??? //轉(zhuǎn)化為NULL指針
??? int *p_a=0;
??? int *p_b=NULL;
?
}
?
例子很簡(jiǎn)單,就不解釋了.?
有一個(gè)非常重要的是,因?yàn)?/span>NULL不指向任何地方,所以,也就肯定不能夠解引用了.這點(diǎn)一定要注意.因?yàn)檫B地址都沒有,怎么得到不存在的地址中的值呢?所以要是想你的程序健壯,最好是在解引用之前加一個(gè)判斷是否為NULL指針的步驟,要是你有足夠的信心以后都不會(huì)有問題,那么不加也罷.?
下面這個(gè)例子是當(dāng)我試圖對(duì)一個(gè)NULL指針解引用之后的程序運(yùn)行情況.(就是上面那個(gè)例子加了一句話而已)
#include<iostream>
usingnamespace std;
int main()
{
??? //測(cè)試NULL是不是0
??? if(NULL==0)
??????? cout<<"yes"<<endl;
?
??? //轉(zhuǎn)化為NULL指針
??? int *p_a=0;
??? int *p_b=NULL;
?
??? //試圖解引用
??? cout<<*p_a<<endl;
}
?
直接崩了,其實(shí)也挺好,至少比找不到的隱形錯(cuò)誤要好.?
前面的例子中有一句是判斷NULL==0的,這里需要注意一下,NULL是一個(gè)預(yù)處理的變量,值為0,在頭文件cstdlib中定義,(我這里并沒有顯式載入也能夠用),因此用到NULL這個(gè)預(yù)處理變量的時(shí)候,盡量帶上cstdlib頭文件,規(guī)范一點(diǎn),避免不必要的錯(cuò)誤.?
說到這里,其實(shí)你反而應(yīng)該疑惑了,我們前面已經(jīng)說過了,指針存放的是一個(gè)地址.一般來說,地址是整形沒錯(cuò),但是它是一種新的類型來表示地址.和整形并不能夠兼容或者運(yùn)算.但是當(dāng)使用0來表示空指針的時(shí)候,我們便會(huì)疑惑,0到底是整形常量還是一個(gè)指針常量??
因此,在C++11中,新引入了一種特殊類型的字面值nullptr來初始化指針為空指針.他能夠被轉(zhuǎn)換成任何類型的指針.
#include<iostream>
int fun(int num)
{
??? return num+10;
}
int main()
{
??? //表達(dá)式0==nullptr為真值
??? if(0==nullptr)
??????? std::cout<<"yes"<<std::endl;
?
??? int a=5;
??? int *p=nullptr;
??? p=&a;
??? std::cout<<"Address ofa:"<<p<<std::endl;
??? return 0;
}
?
講到現(xiàn)在,發(fā)現(xiàn)前面很多的錯(cuò)誤都是由于解引用沒有初始化的指針引起來的.所以,這里提個(gè)建議,就是盡量定義了對(duì)象之后再定義指向這個(gè)對(duì)象的指針,對(duì)于不清楚的指向哪里的指針,一律初始化為nullptr(C++11)或者NULL(0).之后再判斷是否指向?qū)ο笤龠M(jìn)行相應(yīng)的操作.
接下來講void*.?
Void*是一種特殊類型的指針,能夠用來存放任何類型對(duì)象的地址.通俗來說,就是我不知道這個(gè)指針指向的是什么類型的對(duì)象.
要是還是理解不了那就上例子:
#include<iostream>
int fun(int num)
{
??? return num+10;
}
int main()
{
??? double x=25.5;
??? //普通指針的話類型要嚴(yán)格保證
??? double *p=&x;
?
??? //void* 類型可以接受任意類型對(duì)象地址
??? void *p_v=&x;
??? void *p_v2=p;
??? std::cout<<"p_v:"<<p_v<<std::endl;
??? std::cout<<"p_v2:"<<p_v2<<std::endl;
}
?
Void *暫時(shí)了解到這里就行了,后面內(nèi)存分配的時(shí)候還會(huì)專門和他打交道.
Ⅵ.指針的指針
指針的指針就是指向指針的指針.再多講就繞暈了.直接看定義和例子吧.
#include<iostream>
int main()
{
?? int a=10;
?? int *p_a=&a;
?? int **pp_a=&p_a;
?
?? std::cout<<"p_a:"<<p_a<<std::endl<<"*p_a:"<<*p_a<<std::endl;
?? std::cout<<std::endl;
?
?? std::cout<<"PP_a:"<<pp_a<<std::endl<<"*pp_a:"<<*pp_a<<std::endl<<"**pp_a"<<**pp_a<<std::endl;
?
}
?
通過這個(gè)例子,我們發(fā)現(xiàn)其創(chuàng)建方式也是和普通指針的創(chuàng)建方式是差不多的,除了創(chuàng)建時(shí)候的兩個(gè)星號(hào)**.那創(chuàng)建指針必定需要指針類型,指針類型怎么選擇呢?通俗一點(diǎn)說,就是跟著前面的走:我們發(fā)現(xiàn)創(chuàng)建整形時(shí)候使用的是int,比如這里的int a=10;.那我們創(chuàng)建指向a的指針的時(shí)候,肯定必須也要是int了,比如這里的int *p_a=&a;.最后創(chuàng)建指針的指針的時(shí)候,也就用int了.比如這里的int **pp_a=&p_a;?.并不困難?
另外一個(gè)就是解引用的時(shí)候,帶上兩個(gè)星號(hào),就回到的最開始的那個(gè)變量.這么說有點(diǎn)模糊.詳細(xì)來說,**pp_a因?yàn)?/span>*的從右向左的結(jié)合性,這個(gè)表達(dá)式可以寫成*(*pp_a),那么我們知道pp_a存放的是p_a的地址,*pp_a就是表示p_a這個(gè)地址中存放的內(nèi)容即a的地址(不能暈!!!).那么*(*pp_a)就相當(dāng)于*p_a或者a.?
至此,基本的概念部分就結(jié)束啦.
?
總結(jié)
以上是生活随笔為你收集整理的C++ 指针基本概念的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab 中括号
- 下一篇: C++ 运算符优先级