學號:16030140019
姓名:莫益彰
【嵌牛導讀】初學C或C++的你,想必肯定被書上所講的指針搞得頭皮發麻,而這篇總結將帶你深入淺出地了解指針。
【嵌牛提問】指針的應用有哪些?指針和系統棧堆的關系?
【嵌牛正文】
1.語言中變量的實質
要理解C指針,我認為一定要理解C中“變量”的存儲實質, 所以我就從“變量”這個東西開始講起吧!
先來理解理解內存空間吧!請看下圖:
內存地址→ 6 7 8 9 10 11 12 13
-----------------------------------------------------------------
。。。 | | | | | | | |.。
------------------------------- ----------------------------------
如圖所示,內存只不過是一個存放數據的空間,就好像我 的看電影時的電影院中的座位一樣。每個座位都要編號,我們的內存要存放各種各樣的數據,當然我們 要知道我們的這些數據存放在什么位置吧!所以內存也要象座位一樣進行編號了,這就是我們所說的內 存編址。座位可以是按一個座位一個號碼的從一號開始編號,內存則是按一個字節一個字節進行編址, 如上圖所示。每個字節都有個編號,我們稱之為內存地址。好了,我說了這么多,現在你能理解內存空 間這個概念嗎?
我們繼續看看以下的C、C++語言變量申明:
int I;
char a;
每次我們要使用某變量時都要事先這樣申明它,它其實是內存中申請了一個名為i的整型變量寬 度的空間(DOS下的16位編程中其寬度為二個字節),和一個名為a的字符型變量寬度的空間(占一個字 節)。
我們又如何來理解變量是如何存在的呢。當我們如下申明變量時:
int I;
char a;
內存中的映象可能如下圖:
內存地址→ 6 7 8 9 10 11 12 13
----------------------- -------------------------------------------
。。。| | | | | | | |.。
------------------------------------------------------------------
變量名|→i ←|→a ←|
圖中可看出,i在內存起始地址為6上申請了 兩個字節的空間(我這里假設了int的寬度為16位,不同系統中int的寬度是可能不一樣的),并命名為 i. a在內存地址為8上申請了一字節的空間,并命名為a.這樣我們就有兩個不同類型的變量了。
2.賦值給變量
再看下面賦值:
i=30
a=‘t’
你當然知 道個兩個語句是將30存入i變量的內存空間中,將‘t’字符存入a變量的內存空間中。我們可 以這樣的形象理解啦:
內存地址→ 6 7 8 9 10 11 12 13
------------------------------------------------ -----------------------
。。。 | 30 | ‘t’ | | | | |.。
-------------------------------------------------------------------- ---
|→i ←|→a ←|
3.變量在哪里?(即我想知道變量的地 址)
好了,接下來我們來看看&i是什么意思?
是取i變量所在的地址編號嘛!我們可 以這樣讀它:返回i變量的地址編號。你記住了嗎?
我要在屏幕上顯示變量的地址值的話,可以 寫如下代碼:
printf(“%d”,&i);
以上圖的內存映象所例,屏幕上 顯示的不是i值30,而是顯示i的內存地址編號6了。當然實際你操作的時,i變量的地址值不會是這個數 了。
這就是我認為作為初學者們所應想象的變量存儲實質了。請這樣理解吧!
最后總結代碼如下:
int main()
{
int i=39;
printf(“%d\n”,i); //①
printf(“%d\n”, &i); //②
}
現在你可知道 ①、②兩個printf分別在屏幕上輸出的是i的什么東西啊?
好啦!下面我們就開始真正進入指針 的學習了。
二、指針是什么東西
想說弄懂你不容易啊!我們許多初學指針的人都要這樣的感慨。我常常在思索它,為什么呢?其實生活中處處都有指針。我們也處處在使用它。有了它我們的生活才更加方便 了。沒有指針,那生活才不方便。不信?你看下面的例子。
這是一個生活中的例子:比如說你要 我借給你一本書,我到了你宿舍,但是你人不在宿舍,于是我把書放在你的2層3號的書架上,并寫了一 張紙條放在你的桌上。紙條上寫著:你要的書在第2層3號的書架上。當你回來時,看到這張紙條。你就 知道了我借與你的書放在哪了。你想想看,這張紙條的作用,紙條本身不是書,它上面也沒有放著書。 那么你又如何知道書的位置呢?因為紙條上寫著書的位置嘛!其實這張紙條就是一個指針了。它上面的 內容不是書本身,而是書的地址,你通過紙條這個指針找到了我借給你的本書。
那么我們C,C++ 中的指針又是什么呢?請繼續跟我來吧,看下面看一個申明一整型指針變量的語句如下:
int * pi;
pi是一個指針,當然我們知道啦,但是這樣說,你就以為pi一定是個多么特別的東西了。其 實,它也只過是一個變量而已。與上一篇中說的變量并沒有實質的區別。不信你看下面圖。
內存 地址→6 7 8 9 10 11 12 13 14
--------------------------------------------------------------
...| 30 | ‘t’ | | | | | | |……
--------------------------------------------------- -----------
變量 |→i ←|→a ←| |→ pi ←|
(說明:這里我假設了指針只占2個字節寬度,實際上在32位系統中,指針的寬度 是4個字節寬的,即32位。)由圖示中可以看出,我們使用int *Pi申明指針變量; 其實是在內存的某處 申明一個一定寬度的內存空間,并把它命名為Pi.你能在圖中看出pi與前面的i,a 變量有什么本質區別 嗎,沒有,當然沒有!pi也只不過是一個變量而已嘛!那么它又為什么會被稱為指針?關鍵是我們要讓 這個變量所存儲的內容是什么。現在我要讓pi成為真正有意義上的指針。請接著看下面語句:
pi=&i;
你應該知道 &i是什么意思吧!再次提醒你啦:這是返回i變量的地址編 號。整句的意思就是把i地址的編號賦值給pi,也就是你在pi上寫上i的地址編號。結果如下圖所示:
內存地址→6 7 8 9 10 11 12 13 14
------------------------------------------------------------------
...| 30 | ‘t’ | | | 6 | | |……
----------------------------------------------- -------------------
變量 |→i ←|→a ←| |→ pi ←|
你看,執行完pi=&i;后,在圖示中的系統中,pi的值是6.這 個6就是i變量的地址編號,這樣pi就指向了變量i了。你看,pi與那張紙條有什么區別?pi不就是那張紙 條嘛!上面寫著i的地址,而i就是那個本書。你現在看懂了嗎?因此,我們就把pi稱為指針。所以你要 記住,指針變量所存的內容就是內存的地址編號!好了,現在我們就可以通過這個指針pi來訪問到i這個 變量了,不是嗎?。看下面語句:
printf(“%d”,*pi);
那么*pi什么意 思呢?你只要這樣讀它:pi內容所指的地址的內容(嘻嘻,看上去好像在繞口令了),就pi這張“ 紙條”上所寫的位置上的那本 “書”——i .你看,Pi內容是6,也就是說 pi指向內存編號為6的地址。*pi嘛!就是它所指地址的內容,即地址編號6上的內容了。當然就是30的值 了。所以這條語句會在屏幕上顯示30.也就是說printf(“%d”,*pi);語句等價于printf ( “%d”, i ) ,請結合上圖好好體會吧!各位還有什么疑問,可以發Email: yyf977@163.com.
到此為止,你掌握了類似&i , *pi寫法的含義和相關操作嗎。總的一句話 ,我們的紙條就是我們的指針,同樣我們的pi也就是我們的紙條!剩下的就是我們如何應用這張紙條了 。最后我給你一道題:
程序如下
char a,*pa
a=10
pa=&a
*pa=20
printf( “%d”, a)
你能直接看出輸出的結果是什么嗎?如 果你能,我想本篇的目的就達到了。好了,就說到這了。Happy to Study!在下篇中我將談談“指 針的指針”即對int * * ppa;中ppa 的理解。
1.數組元素
看下面代碼
int i,a[]={3,4,5,6,7,3,7,4,4,6};
for (i=0;i<=9;i++)
{
printf ( “%d”, a[i] );
}
很顯然,它是顯示a 數組的各元素值。
我們還可以這樣訪問元素,如下
int i,a[]={3,4,5,6,7,3,7,4,4,6};
for (i=0;i<=9;i++)
{
printf ( “%d”, *(a+i) );
}
它的結果和作用完全一樣
2. 通過指針訪問數組元素
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a ;//請注意數組名a直接賦值給指針 pa
for (i=0;i<=9;i++)
{
printf ( “%d”, pa[i] );
}
很顯然,它也是顯示a 數組的各元素值。
另外與數組名一樣也可如下:
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a;
for (i=0;i<=9;i++)
{
printf ( “%d”, *(pa+i) );
}
看pa=a即數組名賦值給指針,以及通過數組名、指針對元素的訪問形式看,它們并沒有什么區別,從 這里可以看出數組名其實也就是指針。難道它們沒有任何區別?有,請繼續。
3. 數組名與指針變量的區別
請看下面的代碼:
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a;
for (i=0;i<=9;i++)
{
printf ( “%d”, *pa );
pa++ ; //注意這里,指針值被修改
}
可以看出,這段代碼也是將數組各元素值輸出。不過,你把{}中的pa改成a試試。你會發現程序編譯 出錯,不能成功。看來指針和數組名還是不同的。其實上面的指針是指針變量,而數組名只是一個指針 常量。這個代碼與上面的代碼不同的是,指針pa在整個循環中,其值是不斷遞增的,即指針值被修改了 。數組名是指針常量,其值是不能修改的,因此不能類似這樣操作:a++.前面4,5節中pa[i],*(pa+i )處,指針pa的值是使終沒有改變。所以變量指針pa與數組名a可以互換。
4. 申明指針常量
再請看下面的代碼:
int i, a[]={3,4,5,6,7,3,7,4,4,6};
int * const pa=a;//注意const的位置:不是 const int * pa,
for (i=0;i<=9;i++)
{
printf ( “%d”, *pa );
pa++ ; //注意這里,指針值被修改
}
這時候的代碼能成功編譯嗎?不能。因為pa指針被定義為常量指針了。這時與數組名a已經沒有不同 。這更說明了數組名就是常量指針。但是…
int * const a={3,4,5,6,7,3,7,4,4,6};//不行
int a[]={3,4,5,6,7,3,7,4,4,6};//可以,所以初始化數組時必定要這樣。
以上都是在VC6.0上實驗。
1 int i 說起
你知道我們申明一個變量時象這樣int i ;這個i是可能在它處重新變賦值的。 如下:
int i=0;
//…
i=20;//這里重新賦值了
不過有一天我的程 序可能需要這樣一個變量(暫且稱它變量),在申明時就賦一個初始值。之后我的程序在其它任何處都 不會再去重新對它賦值。那我又應該怎么辦呢?用const .
//**************
const int ic =20;
//…
ic=40;//這樣是不可以的,編譯時是無法通過,因為我們不能對 const 修飾的ic重新賦值的。
//這樣我們的程序就會更早更容易發現問題了。
//**************
有了const修飾的ic 我們不稱它為變量,而稱符號常量,代表著20這 個數。這就是const 的作用。ic是不能在它處重新賦新值了。
認識了const 作用之后,另外,我 們還要知道格式的寫法。有兩種:const int ic=20;與int const ic=20;。它們是完全相同的。這一 點我們是要清楚。總之,你務必要記住const 與int哪個寫前都不影響語義。有了這個概念后,我們來看 這兩個家伙:const int * pi與int const * pi ,按你的邏輯看,它們的語義有不同嗎?呵呵,你只要 記住一點,int 與const 哪個放前哪個放后都是一樣的,就好比const int ic;與int const ic;一樣 。也就是說,它們是相同的。
好了,我們現在已經搞定一個“雙包胎”的問題。那么 int * const pi與前兩個式子又有什么不同呢?我下面就來具體分析它們的格式與語義吧!
2 const int * pi的語義
我先來說說const int * pi是什么作用 (當然int const * pi也是一樣 的,前面我們說過,它們實際是一樣的)。看下面的例子:
//*************代碼開始 ***************
int i1=30;
int i2=40;
const int * pi=&i1;
pi=&i2; //4.注意這里,pi可以在任意時候重新賦值一個新內存地址
i2=80; //5.想想看:這里能用*pi=80;來代替嗎?當然不能
printf( “%d”, *pi ) ; //6. 輸出是80
//*************代碼結束***************
語義分析:
看出來了 沒有啊,pi的值是可以被修改的。即它可以重新指向另一個地址的,但是,不能通過*pi來修改i2的值。 這個規則符合我們前面所講的邏輯嗎?當然符合了!
首先const 修飾的是整個*pi(注意,我 寫的是*pi而不是pi)。所以*pi是常量,是不能被賦值的(雖然pi所指的i2是變量,不是常量)。
其次,pi前并沒有用const 修飾,所以pi是指針變量,能被賦值重新指向另一內存地址的。你可 能會疑問:那我又如何用const 來修飾pi呢?其實,你注意到int * const pi中const 的位置就大概可 以明白了。請記住,通過格式看語義。哈哈,你可能已經看出了規律吧?那下面的一節也就沒必要看下 去了。不過我還得繼續我的戰斗!
3 再看int * const pi
確實,int * const pi與前面 的int const * pi會很容易給混淆的。注意:前面一句的const 是寫在pi前和*號后的,而不是寫在*pi 前的。很顯然,它是修飾限定pi的。我先讓你看例子:
//*************代碼開始 ***************
int i1=30;
int i2=40;
int * const pi=&i1;
//pi=&i2; 4.注意這里,pi不能再這樣重新賦值了,即不能再指向另一個新地址。
//所以我已經注釋了它。
i1=80; //5.想想看:這里能用*pi=80;來代替嗎?可以,這 里可以通過*pi修改i1的值。
//請自行與前面一個例子比較。
printf( “% d”, *pi ) ; //6.輸出是80
//***************代碼結束 *********************
語義分析:
看了這段代碼,你明白了什么?有沒有發現 pi值是不能重新賦值修改了。它只能永遠指向初始化時的內存地址了。相反,這次你可以通過*pi來修改 i1的值了。與前一個例子對照一下吧!看以下的兩點分析
1)pi因為有了const 的修飾,所以只 是一個指針常量:也就是說pi值是不可修改的(即pi不可以重新指向i2這個變量了)(看第4行)。
2)整個*pi的前面沒有const 的修飾。也就是說,*pi是變量而不是常量,所以我們可以通過 *pi來修改它所指內存i1的值(看5行的注釋)
總之一句話,這次的pi是一個指向int變量類型數 據的指針常量。
我最后總結兩句:
1) 如果const 修飾在*pi前則不能改的是*pi(即不能 類似這樣:*pi=50;賦值)而不是指pi.
2) 如果const 是直接寫在pi前則pi不能改(即不能類似 這樣:pi=&i;賦值)。
請你務必先記住這兩點,相信你一定不會再被它們給搞糊了。現在 再看這兩個申明語句int const *pi和int * const pi時,呵呵,你會頭昏腦脹還是很輕松愜意?它們各 自申明的pi分別能修改什么,不能修改什么?再問問自己,把你的理解告訴我吧,可以發帖也可以發到 我的郵箱(我的郵箱yyf977@163.com)!我一定會答復的。
3) 補充三種情況。
這里, 我再補充以下三種情況。其實只要上面的語義搞清楚了,這三種情況也就已經被包含了。不過作為三種 具體的形式,我還是簡單提一下吧!
情況一:int * pi指針指向const int i常量的情況
//**********begin*****************
const int i1=40;
int *pi;
pi=&i1; //這樣可以嗎?不行,VC下是編譯錯。
//const int 類型的i1的地址 是不能賦值給指向int 類型地址的指針pi的。否則pi豈不是能修改i1的值了嗎!
pi=(int* ) &i1; // 這樣可以嗎?強制類型轉換可是C所支持的。
//VC下編譯通過,但是仍 不能通過*pi=80來修改i1的值。去試試吧!看看具體的怎樣。
//***********end***************
情況二:const int * pi指針指向const int i1的 情況
//*********begin****************
const int i1=40;
const int * pi;
pi=&i1;//兩個類型相同,可以這樣賦值。很顯然,i1的值無論是通過pi還是i1都不能修 改的。
//*********end*****************
情況三:用const int * const pi申明 的指針
//***********begin****************
int i
const int * const pi=&i;//你能想象pi能夠作什么操作嗎?pi值不能改,也不能通過pi修改i的值。因為不管是*pi還 是pi都是const的。
//************end****************