1. 程式人生 > >徹底搞定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:

[email protected]
到此為止,你掌握了類似&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分別能修改什麼,不能修改什麼?再問問自己,把你的理解告訴我吧,可以發帖也可以發到 我的郵箱(我的郵箱
[email protected]
)!我一定會答覆的。
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.考題一:程式程式碼如下:
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語言中函式引數的傳遞有:值傳遞,地址傳遞,引用傳遞這三種形式。題一為值 傳遞,題二為地址傳遞,題三為引用傳遞。不過,正是這幾種引數傳遞的形式,曾把我給搞得暈頭轉向 。我相信也有很多人與我有同感吧?
下面請讓我逐個地談談這三種傳遞形式。
二、函式 引數傳遞方式之一:值傳遞
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的操作了,這是一個錯誤的觀點啊!)。
指向另一指標的指標
一、針概念:
早在本系列第二篇中我就對指標的實質進行了闡述 。今天我們又要學習一個叫做指向另一指標地址的指標。讓我們先回顧一下指標的概念吧!
當我 們程式如下申明變數:
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;
你可以回看本系列的第二篇,那裡有更加詳細的解說。
二、指標的地址與指向另一指標地址的指標
在上一節中,我們看到,指標變數本身與其 它變數一樣也是在某個記憶體地址中的,如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的值。
呵呵!不用我說太多了,我相信你應明白這種 指標了吧!
三、一個應用例項
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函式,這個函式可以通過引數中的函式指標值不同來分別呼叫MyFun1、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);
}
輸出結果:略
分析:(看我寫的註釋。你可按我註釋的①②③④⑤順序自行 分析。)

相關推薦

徹底C語言指標-整版-時候初學者-必備

原文地址 :點這裡!  1.語言中變數的實質 要理解C指標,我認為一定要理解C中“變數”的儲存實質, 所以我就從“變數”這個東西開始講起吧! 先來理解理解記憶體空間吧!請看下圖: 記憶體地址→  6      7   8      9   10      11     

徹底C語言指標

1.語言中變數的實質     要理解C指標,我認為一定要理解C中“變數”的儲存實質,所以我就從“變數”這個東西開始講起吧!     先來理解理解記憶體空間吧!請看下圖:     記憶體地址→&nbs

C語言指標(經典,非常詳細)

C語言指標詳解(經典,非常詳細) 前言:複雜型別說明     要了解指標,多多少少會出現一些比較複雜的型別,所以我先介紹一下如何完全理解一個複雜型別,要理解複雜型別其實很簡單,一個型別裡會出現很多運算子,他們也像普通的表示式一樣,有優先順序,其優先順序和運算優先

C語言指標

前言 這不是我第一次寫關於C指標的文章了,只是因為指標對於C來說太重要,而且隨著自己程式設計經歷越多,對指標的理解越多,因此有了本文。然而,想要全面理解指標,除了要對C語言有熟練的掌握外,還要有計算機硬體以及作業系統等方方面面的基本知識。所以我想通過一篇文章來儘可能

典型C語言指標

一、細說指標 指標是一個特殊的變數,它裡面儲存的數值被解釋成為記憶體裡的一個地址。要搞清一個指標需要搞清指標的四方面的內容:指標的型別、指標所指向的型別、指標的值或者叫指標所指向的記憶體區、指標本身所佔據的記憶體區。讓我們分別說明。 先宣告幾個指標放著做例子: 例一

C語言 指標 C語言指標變數的運算

指標變數儲存的是地址,本質上是一個整數,可以進行部分運算,例如加法、減法、比較等,請看下面的程式碼: #include <stdio.h>int main(){ int a = 10, *pa = &a, *paa = &a;

[轉]C語言指標(經典,非常詳細)

寫得很好啊! 這裡寫一下筆記好了 1 int p; //這是一個普通的整型變數 2 int *p; //首先從P 處開始,先與*結合,所以說明P 是一個指標,然後再與int 結合,說明指標所指向的內容的型別為int 型.所以P是一個返回整型資料的指標 3 int p[3]; //首先從P 處開始,

C語言指標----指標宣告定義賦值

C語言的指標是讓新手很頭疼的事情,但是由於其太過於靈活,以至於可以很好得的解決一些複雜的問題,因此不得不掌握。我最近正在學習指標相關的內容,因此在這裡做一個小的總結。本篇是不涉及到函式以及結構體

徹底C指標---指向指標指標

http://www.eefocus.com/max_lpy/blog/10-09/195288_05ca9.html 一. 回顧指標概念: 今天我們又要學習一個叫做指向另一指標地址的指標。讓我們先回顧一下指標的概念吧!當我們程式如下申明變數:short int i;cha

C語言指標

指標應該算得上是c語言的精華,但也是難點。很多教程或者部落格都有對其詳細的講解與分析。我這一節的內容,也是講解指標,但我會盡量使用圖解的方式,使大家很容易理解及掌握。一、基本使用先來看看下面的程式碼:int i = 3; int *p; p = &i;

C++ 智慧指標

C++ 智慧指標詳解   一、簡介 由於 C++ 語言沒有自動記憶體回收機制,程式設計師每次 new 出來的記憶體都要手動 delete。程式設計師忘記 delete,流程太複雜,最終導致沒有 de

Canny邊緣檢測演算法原理及C語言實現

Canny運算元是John Canny在1986年提出的,那年老大爺才28歲,該文章發表在PAMI頂級期刊上的(1986. A computational approach to edge detection. IEEE Transactions on Pattern Analy

[C] C語言sizeof - 全部型別

VC++6.0環境 文章目錄 總結 結構體struct 聯合union union與struct混合 參考文章 總結 union看最大 找struct最大的基礎資料型別,每個成員變數都要與

c語言移位

實驗平臺 vs2017 左移位和右移位有區別. 左移位比較簡單,就是直接在低位後面補0; 右移位則比較複雜, 假設有10000000的二進位制數,往右移動一位,則數變為11000000,原因在於補的高位是原來的第一位, 也就是說如果是01000000,則往右移動

連結串列的插入和刪除操作C語言實現+註釋)

連結串列的基本操作中,連結串列結點的插入和刪除相對比較複雜,需根據結點插入位置的不同,使用合理的方法在不破壞原連結串列結構的前提下將其插入到連結串列中。 本節將詳解介紹這兩種操作的具體實現方法,讀者只需牢記實現步驟,即可輕鬆解決這兩大難點。 連結串列中插入結點 連結串列中插入結點,根據插入位置的不同,可

順序表的插入操作原理及實現(C語言

順序表中存放資料的特點和陣列這種資料型別完全吻合,所以順序表的實現使用的是陣列。換句話說,順序表中插入元素問題也就等同於討論如何向陣列中插入資料。 因此,順序表中插入資料元素,無非三種情況: 在表頭插入; 在表的中間某個位置插入; 直接尾隨順序表,作為表的最後一個元素; 無論在順序表的什麼位置插

全!面!的!c語言操作符

算術操作符:+ - * / % 除了%操作符以外,其他幾個操作符都可以作用與整數和浮點數 對於/操作符若兩數都是整數執行整數除法;只要有浮點數就執行浮點數除法 %操作符的兩個運算元必須是整數 移位操作符:<< 左移操作符 >> 右移操

順序棧的基本操作(入棧和出棧)及C語言實現

棧,可以理解為遵循“後入先出”原則的線性表,因此棧結構可以採用順序表或連結串列實現。 順序棧的實現採用的是順序表,也就是陣列。 順序棧的實現思想是:在陣列中設定一個隨時指向棧頂元素的變數(一般命名為 top ),當 top 的值為 -1 時,說明陣列中沒有資料,即棧中沒有資料元素,為“空棧”;只要資料元素

C++ this指標

                 C++this指標操作 在這裡總結一下this 指標的相關知識點。   首先,我們都知道類的成員函式可以訪問類的資料(限定符只是限定於類外的一些操作,類內的一切對於成員函式來說都是透明的),那麼成員函式如何知道哪個物件的資料成員要被操作呢,

C語言操作符

對於C語言操作符,根據我所掌握的內容可以總結如下: 1.算數操作符 算數操作符就是我們最常見的“加減乘數模”:+    -   *    /     %,需要注意的有兩點 1.兩個整數相除是整數,所