完全搞定C語言指針詳解-完整版-時候初學者-必備

推薦理由:對指針有很是詳細的介紹(內存分析等)和相關總結。很是適合初學者學習和理解指針的相關概念。html


原文內容以下:編程

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、指針是什麼東西

想說弄懂你不容易啊!咱們許多初學指針的人都要這樣的感慨。我經常在思索它,爲何呢?其實生活中到處都有指針。咱們也到處在使用它。有了它咱們的生活才更加方便 了。沒有指針,那生活纔不方便。不信?你看下面的例子。

這是一個生活中的例子:好比說你要 我借給你一本書,我到了你宿舍,可是你人不在宿舍,因而我把書放在你的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****************

下篇預告:函數參數的 指針傳遞,值傳遞,引用傳遞 迷惑(覺得a,b已經代替了x,y,對x,y的操做就是對a,b的操做了,這 是一個錯誤的觀點啊!)。

1、三道考題

開講以前,我先請你作三道題目。(嘿嘿,得先把你的頭腦搞昏才行 ……唉呀,誰扔我雞蛋?)

1.考題一:程序代碼以下:

void Exchg1(int x, int y)
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf (「x=%d,y=%d\n」,x,y)
}
void main()
{
int a=4,b=6;
Exchg1 (a,b) ;
printf(「a=%d,b=%d\n」,a,b)
}

輸出的結果 :

x=____, y=____

a=____, b=____

問下劃線的部分應是什麼,請完成。

2.考題二:代碼以下。

Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
*py=tmp;
print(「*px=%d,*py=%d\n」,*px,*py);
}
main()
{
int a=4;
int b=6;
Exchg2( &a,&b);
Print (「a=%d,b=%d\n」, a, b);
}

輸出的結果爲:

*px=____, *py=____

a=____, b=____

問下劃線的部分應是什麼,請完成。

3.考題三:

Exchg2(int &x, int &y)
{
int tmp=x;
x=y;
y=tmp;
print(「x=%d,y=%d\n」,x,y);
}
main()
{
int a=4;
int b=6;
Exchg2(a,b);
Print(「a=%d,b=%d\n」, a, b);
}

輸 出的結果:

x=____, y=____

a=____, b=____

問下劃線的部分輸出的應是什麼, 請完成。

你不在機子上試,能做出來嗎?你對你寫出的答案有多大的把握?

正確的答案 ,想知道嗎?(呵呵,讓我慢慢地告訴你吧!)

好,廢話少說,繼續咱們的探索之旅了。

咱們都知道:C語言中函數參數的傳遞有:值傳遞,地址傳遞,引用傳遞這三種形式。題一爲值 傳遞,題二爲地址傳遞,題三爲引用傳遞。不過,正是這幾種參數傳遞的形式,曾把我給搞得暈頭轉向 。我相信也有不少人與我有同感吧?

下面請讓我逐個地談談這三種傳遞形式。

2、函數 參數傳遞方式之一:值傳遞

1.值傳遞的一個錯誤認識

先看題一中Exchg1函數的定義:

void Exchg1(int x, int y)   //定義中的x,y變量被稱爲Exchg1函數的形式參數
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(「x=%d,y=% d\n」,x,y)
}

問:你認爲這個函數是在作什麼呀?

答:好像是對參數 x,y的值對調吧?

請往下看,我想利用這個函數來完成對a,b兩個變量值的對調,程序以下:

void main()
{
int a=4,b=6;
Exchg1 (a,b)     //a,b變量爲 Exchg1函數的實際參數。
/  printf(「a=%d,b=%d\n」,a,b)
}

我問:Exchg1 ()裏頭的  printf(「x=%d,y=%d\n」,x,y)語句會輸出什麼啊?

我再問:Exchg1 ()後的  printf(「a=%d,b=%d\n」,a,b)語句輸出的是什麼 ?

程序輸出的結果是:

x=6 , y=4

a=4 , b=6  //爲何不是a=6,b=4呢?

奇怪,明明我把a,b分別代入了x,y中,並在函數裏完成了兩個變量值的交換,爲何a,b變量 值仍是沒有交換(仍然是a==4,b==6,而不是a==6,b==4)?若是你也會有這個疑問,那是由於你跟本 就不知實參a,b與形參x,y的關係了。

2.一個預備的常識

爲了說明這個問題,我先給出 一個代碼:

int a=4;

int x;

x=a;

x=x+3;

看好了沒,如今我問 你:最終a值是多少,x值是多少?

(怎麼搞的,給我這個小兒科的問題。還不簡單,不就是a==4   x==7嘛!)

在這個代碼中,你要明白一個東西:雖然a值賦給了x,可是a變量並非x變量哦 。咱們對x任何的修改,都不會改變a變量。呵呵!雖然簡單,而且一看就理所固然,不過但是一個很重 要的認識喔。

3.理解值傳遞的形式

看調用Exch1函數的代碼:

main()
{
int a=4,b=6;
Exchg1(a,b) //這裏調用了Exchg1函數  
printf(「a=% d,b=%d」,a,b)
}

Exchg1(a,b)時所完成的操做代碼以下所示。
int x=a;//←
int y=b;//←注意這裏,頭兩行是調用函數時的隱含操做
int tmp;
tmp=x;
x=y;
y=tmp;

請注意在調用執行Exchg1函數的操做中我人爲地加上 了頭兩句:

int x=a;

int y=b;

這是調用函數時的兩個隱含動做。它確實存在, 如今我只不過把它顯式地寫了出來而已。問題一下就清晰起來啦。(看到這裏,如今你認爲函數裏面交 換操做的是a,b變量或者只是x,y變量呢?)

原來 ,其實函數在調用時是隱含地把實參a,b 的 值分別賦值給了x,y,以後在你寫的Exchg1函數體內再也沒有對a,b進行任何的操做了。交換的只是x, y變量。並非a,b.固然a,b的值沒有改變啦!函數只是把a,b的值經過賦值傳遞給了x,y,函數裏頭 操做的只是x,y的值並非a,b的值。這就是所謂的參數的值傳遞了。

哈哈,終於明白了,正是 由於它隱含了那兩個的賦值操做,才讓咱們產生了前述的迷惑(覺得a,b已經代替了x,y,對x,y的操 做就是對a,b的操做了,這是一個錯誤的觀點啊!)。
指向另外一指針的指針

1、針概念:

早在本系列第二篇中我就對指針的實質進行了闡述 。今天咱們又要學習一個叫作指向另外一指針地址的指針。讓咱們先回顧一下指針的概念吧!

當我 們程序以下申明變量:

short int i;

char a;

short int * pi;

程序會 在內存某地址空間上爲各變量開闢空間,以下圖所示。

內存地址→6     7  8      9     10     11    12    13     14    15

------------------- ------------------------------------------------------------------

…  |      |  |  |  |  |  |  |  |  |

--------------------------------------- ----------------------------------------------

|short int i |char a|  |short int * pi|

圖中所示中可看出:

i 變量在內存地址5的位置,佔兩個字節。

a變量在內存 地址7的位置,佔一個字節。

pi變量在內存地址9的位置,佔兩個字節。(注:pi 是指針,我這 裏指針的寬度只有兩個字節,32位系統是四個字節)

接下來以下賦值:

i=50;

pi=&i;

通過上在兩句的賦值,變量的內存映象以下:

內存地址→6      7  8     9     10     11    12    13  14     15

----- ---------------------------------------------------------------------------------

…  |    50  |  |  |    6   |  |  |  |

----------- ---------------------------------------------------------------------------

|short int i |char a|  |short int * pi|

看到沒有:短整型指針變量pi的值爲6,它就是I變量的內 存起始地址。因此,這時當咱們對*pi進行讀寫操做時,其實就是對i變量的讀寫操做。如:

*pi=5;   //就是等價於I=5;

你能夠回看本系列的第二篇,那裏有更加詳細的解說。

2、指針的地址與指向另外一指針地址的指針

在上一節中,咱們看到,指針變量自己與其 它變量同樣也是在某個內存地址中的,如pi的內存起始地址是10.一樣的,咱們也可能讓某個指針指向這 個地址。

看下面代碼:

short int * * ppi;    //這是一個指向指針的指針,注意 有兩個*號

ppi=π

第一句:short int * * ppi;——申明瞭一個指針變量 ppi,這個ppi是用來存儲(或稱指向)一個short int * 類型指針變量的地址。

第二句: &pi那就是取pi的地址,ppi=π就是把pi的地址賦給了ppi.即將地址值10賦值給ppi.以下圖:

內存地址→6     7  8     9     10     11    12    13   14    15

------------------------------------------------------------------------ ------------

…  |    50     |  |  |  6  |  10  |   |

---------------------------------------------------------------------------------- --

|short int i|char a|  |short int * pi|short int ** ppi|

從圖中看出,指針變 量ppi的內容就是指針變量pi的起始地址。因而……

ppi的值是多少呢? ——10.

*ppi的值是多少呢?——6,即pi的值。

**ppi的值是多少 呢?——50,即I的值,也是*pi的值。

呵呵!不用我說太多了,我相信你應明白這種 指針了吧!

3、一個應用實例

1. 設計一個函數:void find1(char array[], char search, char * pi)

要求:這個函數參數中的數組array是以0值爲結束的字符串,要求在字符 串array中查找字符是參數search裏的字符。若是找到,函數經過第三個參數(pa)返回值爲array字符 串中第一個找到的字符的地址。若是沒找到,則爲pa爲0.

設計:依題意,實現代碼以下

void find1(char [] array, char search, char * pa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
}

你以爲這個函數能實現所要求的功能嗎?

調試:

我下面調用這個函數 試試。

void main()
{
char str[]={「afsdfsdfdf\0」};  //待 查找的字符串
char a=’d’;   //設置要查找的字符
char * p=0;  //若是 查找到後指針p將指向字符串中查找到的第一個字符的地址。
find1(str,a,p);  //調用函數以實 現所要操做。
if (0==p )
{
printf (「沒找到!\n」);//1.若是沒找到則 輸出此句
}
else
{
printf(「找到了,p=%d」,p);  //若是找到則 輸出此句
}
}

分析:

上面代碼,你認爲會是輸出什麼呢?

運 行試試。

唉!怎麼輸出的是:沒有找到!

而不是:找到了,……。

明明a值爲‘d’,而str字符串的第四個字符是‘d’,應該找獲得呀!

再 看函數定義處:void find1(char [] array, char search, char * pa)

看調用處:find1( str,a,p);

依我在第五篇的分析方法,函數調用時會對每個參數進行一個隱含的賦值操做 。

整個調用以下:

array=str;
search=a;
pa=p;    //請注意:以 上三句是調用時隱含的動做。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (* (array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0) 
{
pa=0;
break;
}
}

哦!參數pa與參數search的傳遞並沒 有什麼不一樣,都是值傳遞嘛(小語:地址傳遞其實就是地址值傳遞嘛)!因此對形參變量pa值(固然值 是一個地址值)的修改並不會改變實參變量p值,所以p的值並無改變(即p的指向並無被改變)。

(若是還有疑問,再看一看《第五篇:函數參數的傳遞》了。)

修正:

void find2(char [] array, char search, char ** ppa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
}

主函數的調用處改以下:

find2(str,a, &p);  //調用函數以實現所要操做。

再分析:

這樣調用函數時的整個操做變成如 下:

array=str;
search=a;
ppa=&p;    //請注意:以上三句是調用 時隱含的動做。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i) ==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}

看明白了嗎?

ppa指向指針p的地址 。

對*ppa的修改就是對p值的修改。

你自行去調試。

通過修改後的程序就能夠完 成所要的功能了。

看懂了這個例子,也就達到了本篇所要求的目的。

函數名與函數指針

一 數調用

一個一般的函數調用的例子:

//自行包含 頭文件
void MyFun(int x);    //此處的申明也可寫成:void MyFun( int );
int main(int argc, char* argv[])
{
MyFun(10);     //這裏是調用MyFun(10);函數
return 0;
}
void MyFun(int x)  //這裏定義一個MyFun函數
{
printf (「%dn」,x);
}

這個MyFun函數是一個無返回值的函數,它並不完成什 麼事情。這種調用函數的格式你應該是很熟悉的吧!看主函數中調用MyFun函數的書寫格式:

MyFun(10);

咱們一開始只是從功能上或者說從數學意義上理解MyFun這個函數,知道 MyFun函數名錶明的是一個功能(或是說一段代碼)。

直到——

學習到函數指 針概念時。我纔不得不在思考:函數名到底又是什麼東西呢?

(不要覺得這是沒有什麼意義的事 噢!呵呵,繼續往下看你就知道了。)

二 函數指針變量的申明

就象某一數據變量的內存 地址能夠存儲在相應的指針變量中同樣,函數的首地址也以存儲在某個函數指針變量裏的。這樣,我就 能夠經過這個函數指針變量來調用所指向的函數了。

在C系列語言中,任何一個變量,老是要先 申明,以後才能使用的。那麼,函數指針變量也應該要先申明吧?那又是如何來申明呢?以上面的例子 爲例,我來申明一個能夠指向MyFun函數的函數指針變量FunP.下面就是申明FunP變量的方法:

void (*FunP)(int) ;   //也可寫成void (*FunP)(int x);

你看,整個函 數指針變量的申明格式如同函數MyFun的申明處同樣,只不過——咱們把MyFun改爲(*FunP) 而已,這樣就有了一個能指向MyFun函數的指針FunP了。(固然,這個FunP指針變量也能夠指向全部其它 具備相同參數及返回值的函數了。)

三 經過函數指針變量調用函數

有了FunP指針變量後 ,咱們就能夠對它賦值指向MyFun,而後經過FunP來調用MyFun函數了。看我如何經過FunP指針變量來調 用MyFun函數的:

//自行包含頭文件
void MyFun(int x);    //這個申明也可寫 成:void MyFun( int );
void (*FunP)(int );   //也可申明成void(*FunP)(int x),但習慣 上通常不這樣。
int main(int argc, char* argv[])
{
MyFun(10);     //這是 直接調用MyFun函數
FunP=&MyFun;  //將MyFun函數的地址賦給FunP變量
(*FunP)(20);     //這是經過函數指針變量FunP來調用MyFun函數的。
}
void MyFun(int x)  //這裏 定義一個MyFun函數
{
printf(「%dn」,x);
}

請看黑體字部 分的代碼及註釋。

運行看看。嗯,不錯,程序運行得很好。

哦,個人感受是:MyFun與 FunP的類型關係相似於int 與int *的關係。函數MyFun好像是一個如int的變量(或常量),而FunP則像 一個如int *同樣的指針變量。

int i,*pi;

pi=&i;    //與FunP=&MyFun 比較。

(你的感受呢?)

呵呵,其實否則——

四 調用函數的其它書 寫格式

函數指針也可以下使用,來完成一樣的事情:

//自行包含頭文件
void MyFun(int x);
void (*FunP)(int );    //申明一個用以指向一樣參數,返回值函數 的指針變量。
int main(int argc, char* argv[])
{
MyFun(10);     //這裏是 調用MyFun(10);函數
FunP=MyFun;  //將MyFun函數的地址賦給FunP變量
FunP(20);    //這是經過函數指針變量來調用MyFun函數的。
return 0;
}
void MyFun(int x)  // 這裏定義一個MyFun函數
{
printf(「%dn」,x);
}

我改了黑 體字部分(請自行與以前的代碼比較一下)。

運行試試,啊!同樣地成功。

咦?

FunP=MyFun;

能夠這樣將MyFun值同賦值給FunP,難道MyFun與FunP是同一數據類型(即 如同的int 與int的關係),而不是如同int 與int*的關係了?(有沒有一點點的糊塗了?)

看 來與以前的代碼有點矛盾了,是吧!因此我說嘛!

請允許我暫不給你解釋,繼續看如下幾種狀況 (這些可都是能夠正確運行的代碼喲!):

代碼之三:

int main(int argc, char* argv[])
{
MyFun(10);     //這裏是調用MyFun(10);函數
FunP=&MyFun;  //將MyFun函數的地址賦給FunP變量
FunP(20);    //這是經過函數指 針變量來調用MyFun函數的。
return 0;
}

代碼之四: 
int main(int argc, char* argv[])
{
MyFun(10);     //這裏是調用MyFun(10);函數
FunP=MyFun;  //將MyFun函數的地址賦給FunP變量
(*FunP)(20);    //這是經過函數指針 變量來調用MyFun函數的。
return 0;
}

真的是能夠這樣的噢!

(哇 !真是要暈倒了!)

還有吶!看——

int main(int argc, char* argv[])
{
(*MyFun)(10);     //看,函數名MyFun也能夠有這樣的調用格式
return 0;
}

你也許第一次見到吧:函數名調用也能夠是這樣寫的啊!(只不過 咱們日常沒有這樣書寫罷了。)

那麼,這些又說明了什麼呢?

呵呵!依據以往的知識和 經驗來推理本篇的「新發現」,我想就連「福爾摩斯」也一定會由此分析並推斷 出如下的結論:

1. 其實,MyFun的函數名與FunP函數指針都是同樣的,即都是函數指針。MyFun 函數名是一個函數指針常量,而FunP是一個函數數指針變量,這是它們的關係。

2. 但函數名調 用若是都得如(*MyFun)(10);這樣,那書寫與讀起來都是不方便和不習慣的。因此C語言的設計者們 纔會設計成又可容許MyFun(10);這種形式地調用(這樣方便多了並與數學中的函數形式同樣,不是嗎 ?)。

3. 爲統一塊兒見,FunP函數指針變量也能夠FunP(10)的形式來調用。

4. 賦值時 ,便可FunP=&MyFun形式,也可FunP=MyFun.

上述代碼的寫法,隨便你愛怎麼着!

請 這樣理解吧!這但是有助於你對函數指針的應用嘍!

最後——

補充說明一點 :在函數的申明處:

void MyFun(int );    //不能寫成void (*MyFun)(int )。

void (*FunP)(int );   //不能寫成void FunP(int )。

(請看註釋)這一點 是要注意的。

五 定義某一函數的指針類型:

就像自定義數據類型同樣,咱們也能夠先定 義一個函數指針類型,而後再用這個類型來申明函數指針變量。

我先給你一個自定義數據類型的 例子。

typedef int* PINT;    //爲int* 類型定義了一個PINT的別名 
int main()
{
int x;
PINT px=&x;   //與int * px=&x;是等價的。PINT類型其 實就是int * 類型
*px=10;  //px就是int*類型的變量
return 0;
}

根據註釋,應該不難看懂吧!(雖然你可能不多這樣定義使用,但之後學習Win32編程時會常常見到的。 )

下面咱們來看一下函數指針類型的定義及使用:(請與上對照!)

//自行包含 頭文件
void MyFun(int x);    //此處的申明也可寫成:void MyFun( int );
typedef void (*FunType)(int );   //這樣只是定義一個函數指針類型
FunType FunP;    //而後 用FunType類型來申明全局FunP變量
int main(int argc, char* argv[])
{
//FunType FunP;    //函數指針變量固然也是能夠是局部的 ,那就請在這裏申明瞭。
MyFun(10);
FunP=&MyFun;
(*FunP)(20);
return 0;
}
void MyFun(int x)
{
printf(「%dn」,x);
}

看黑體部分:

首先,在void (*FunType)(int ); 前加了一個typedef .這樣只是定義一個名爲FunType函數指針類型,而不是一 個FunType變量。

而後,FunType FunP;  這句就如PINT px;同樣地申明一個FunP變量。

其它相同。整個程序完成了相同的事。

這樣作法的好處是:

有了FunType類型後 ,咱們就能夠一樣地、很方便地用FunType類型來申明多個同類型的函數指針變量了。以下:

FunType FunP2;

FunType FunP3;

//……

六 函數指針做爲某個函數的參數

既然函數指針變量是一個變量,固然也能夠做爲某個函數的參數來使用的。因此 ,你還應知道函數指針是如何做爲某個函數的參數來傳遞使用的。

給你一個實例:

要求 :我要設計一個CallMyFun函數,這個函數能夠經過參數中的函數指針值不一樣來分別調用MyFun一、MyFun2 、MyFun3這三個函數(注:這三個函數的定義格式應相同)。

實現:代碼以下:

//自行包含頭文件
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); //②. 定義一個函數指針類型FunType,與①函 數類型一至
void CallMyFun(FunType fp,int x);
int main(int argc, char* argv[])
{
CallMyFun(MyFun1,10);   //⑤. 經過CallMyFun函數分別調用三個不一樣的函數
CallMyFun(MyFun2,20);
CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) //③. 參數fp的類型是FunType。
{
fp(x);//④. 經過fp的指針執行傳遞進來的 函數,注意fp所指的函數是有一個參數的
}
void MyFun1(int x) // ①. 這是個有一個參數 的函數,如下兩個函數也相同
{
printf(「函數MyFun1中輸出:%dn」,x);
}
void MyFun2(int x)
{
printf(「函數MyFun2中輸出:%dn」,x);
}
void MyFun3(int x)
{
printf(「函數MyFun3中輸出:%dn」,x);
}

輸出結果:略

分析:(看我寫的註釋。你可按我註釋的①②③④⑤順序自行 分析。)
數組


原文連接:http://blog.chinaunix.net/uid-22889411-id-59688.htmlapp

相關文章
相關標籤/搜索