1. 程式人生 > >CSDN特別收錄 --- 超強的指標 *一輩子都找不到的牛B貼

CSDN特別收錄 --- 超強的指標 *一輩子都找不到的牛B貼

 摘錄的別人的:

C 語言所有複雜的指標宣告,都是由各種宣告巢狀構成的。如何解讀複雜指標宣告呢?右左法則是一個既著名又常用的方法。不過,右左法則其實並不是C標準裡面的 內容,它是從C標準的宣告規定中歸納出來的方法。C標準的宣告規則,是用來解決如何建立宣告的,而右左法則是用來解決如何辯識一個宣告的,兩者可以說是相 反的。右左法則的英文原文是這樣說的:

The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.


這段英文的翻譯如下:

右左法則:首先從最裡面的圓括號看起,然後往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號裡面所有的東西,就跳出圓括號。重複這個過程直到整個宣告解析完畢。

        筆者要對這個法則進行一個小小的修正,應該是從未定義的識別符號開始閱讀,而不是從括號讀起,之所以是未定義的識別符號,是因為一個聲明裡面可能有多個識別符號,但未定義的識別符號只會有一個。

        現在通過一些例子來討論右左法則的應用,先從最簡單的開始,逐步加深:

int (*func)(int *p);

首 先找到那個未定義的識別符號,就是func,它的外面有一對圓括號,而且左邊是一個*號,這說明func是一個指標,然後跳出這個圓括號,先看右邊,也是一 個圓括號,這說明(*func)是一個函式,而func是一個指向這類函式的指標,就是一個函式指標,這類函式具有int*型別的形參,返回值型別是 int。

int (*func)(int *p, int (*f)(int*));

func被一對括號包含,且左邊有一個 *號,說明func是一個指標,跳出括號,右邊也有個括號,那麼func是一個指向函式的指標,這類函式具有int *和int (*)(int*)這樣的形參,返回值為int型別。再來看一看func的形參int (*f)(int*),類似前面的解釋,f也是一個函式指標,指向的函式具有int*型別的形參,返回值為int。

int (*func[5])(int *p);

func 右邊是一個[]運算子,說明func是一個具有5個元素的陣列,func的左邊有一個*,說明func的元素是指標,要注意這裡的*不是修飾func的, 而是修飾func[5]的,原因是[]運算子優先順序比*高,func先跟[]結合,因此*修飾的是func[5]。跳出這個括號,看右邊,也是一對圓括 號,說明func陣列的元素是函式型別的指標,它所指向的函式具有int*型別的形參,返回值型別為int。


int (*(*func)[5])(int *p);

func 被一個圓括號包含,左邊又有一個*,那麼func是一個指標,跳出括號,右邊是一個[]運算子號,說明func是一個指向陣列的指標,現在往左看,左邊有 一個*號,說明這個陣列的元素是指標,再跳出括號,右邊又有一個括號,說明這個陣列的元素是指向函式的指標。總結一下,就是:func是一個指向陣列的指 針,這個陣列的元素是函式指標,這些指標指向具有int*形參,返回值為int型別的函式。

int (*(*func)(int *p))[5];

func是一個函式指標,這類函式具有int*型別的形參,返回值是指向陣列的指標,所指向的陣列的元素是具有5個int元素的陣列。

要注意有些複雜指標宣告是非法的,例如:

int func(void) [5];

func是一個返回值為具有5個int元素的陣列的函式。但C語言的函式返回值不能為陣列,這是因為如果允許函式返回值為陣列,那麼接收這個陣列的內容的東西,也必須是一個數組,但C語言的陣列名是一個右值,它不能作為左值來接收另一個數組,因此函式返回值不能為陣列。

int func[5](void);

func是一個具有5個元素的陣列,這個陣列的元素都是函式。這也是非法的,因為陣列的元素除了型別必須一樣外,每個元素所佔用的記憶體空間也必須相同,顯然函式是無法達到這個要求的,即使函式的型別一樣,但函式所佔用的空間通常是不相同的。

作為練習,下面列幾個複雜指標宣告給讀者自己來解析,答案放在第十章裡。

int (*(*func)[5][6])[7][8];

int (*(*(*func)(int *))[5])(int *);

int (*(*func[7][8][9])(int*))[5];

        實際當中,需要宣告一個複雜指標時,如果把整個宣告寫成上面所示的形式,對程式可讀性是一大損害。應該用typedef來對宣告逐層分解,增強可讀性,例如對於宣告:

int (*(*func)(int *p))[5];

可以這樣分解:

typedef  int (*PARA)[5];
typedef PARA (*func)(int *);

這樣就容易看得多了。
===============================================================

轉載述: 這是一篇比較老的關於指標的文章,作者站在初學者的角度對指標作了深入的剖析。如果你在學習指標的時候有什麼問題,看一看這篇文章定有收穫。

一。指標的概念
    1。指標的型別
    2。指標所指向的型別
    3。指標的值
二。指標的算術運算
三。運算子&和*
四。指標表示式
五。陣列和指標的關係 
       

一。指標的概念
    指標是一個特殊的變數,它裡面儲存的數值被解釋成為記憶體裡的一個地址。
    要搞清一個指標需要搞清指標的四方面的內容:指標的型別,指標所指向的型別,指標的值或者叫指標所指向的記憶體區,還有指標本身所佔據的記憶體區。讓我們分別說明。
    先宣告幾個指標放著做例子:
例一:
(1)int *ptr;
(2)char *ptr;
(3)int **ptr;
(4)int (*ptr)[3];
(5)int *(*ptr)[4];
如果看不懂後幾個例子的話,請參閱我前段時間貼出的文章<<如何理解c和c++的複雜型別宣告>>。

1。 指標的型別
    從語法的角度看,你只要把指標宣告語句裡的指標名字去掉,剩下的部分就是這個指標的型別。這是指標本身所具有的型別。讓我們看看例一中各個指標的型別:
(1)int *ptr; //指標的型別是int *
(2)char *ptr; //指標的型別是char *
(3)int **ptr; //指標的型別是 int **
(4)int (*ptr)[3]; //指標的型別是 int(*)[3]
(5)int *(*ptr)[4]; //指標的型別是 int *(*)[4]
怎麼樣?找出指標的型別的方法是不是很簡單?

2。指標所指向的型別
    當你通過指標來訪問指標所指向的記憶體區時,指標所指向的型別決定了編譯器將把那片記憶體區裡的內容當做什麼來看待。
    從語法上看,你只須把指標宣告語句中的指標名字和名字左邊的指標宣告符 *去掉,剩下的就是指標所指向的型別。例如:
(1)int *ptr; //指標所指向的型別是int
(2)char *ptr; //指標所指向的的型別是char
(3)int **ptr; //指標所指向的的型別是 int *
(4)int (*ptr)[3]; //指標所指向的的型別是 int()[3]
(5)int *(*ptr)[4]; //指標所指向的的型別是 int *()[4]
    在指標的算術運算中,指標所指向的型別有很大的作用。
    指標的型別(即指標本身的型別)和指標所指向的型別是兩個概念。當你對C越來越熟悉時,你會發現,把與指標攪和在一起的“型別”這個概念分成“指標的類 型”和“指標所指向的型別”兩個概念,是精通指標的關鍵點之一。我看了不少書,發現有些寫得差的書中,就把指標的這兩個概念攪在一起了,所以看起書來前後 矛盾,越看越糊塗。

3。 指標的值
    指標的值,或者叫指標所指向的記憶體區或地址。 指標的值是指標本身儲存的數值,這個值將被編譯器當作一個地址,而不是一個一般的數值。在32位程式裡,所有型別的指標的值都是一個32位整數,因為32位程式裡記憶體地址全都是32位長。
    指標所指向的記憶體區就是從指標的值所代表的那個記憶體地址開始,長度為sizeof(指標所指向的型別)的一片記憶體區。以後,我們說一個指標的值是XX,就 相當於說該指標指向了以XX為首地址的一片記憶體區域;我們說一個指標指向了某塊記憶體區域,就相當於說該指標的值是這塊記憶體區域的首地址。
    指標所指向的記憶體區和指標所指向的型別是兩個完全不同的概念。在例一中,指標所指向的型別已經有了,但由於指標還未初始化,所以它所指向的記憶體區是不存在的,或者說是無意義的。
    以後,每遇到一個指標,都應該問問:這個指標的型別是什麼?指標指向的型別是什麼?該指標指向了哪裡?
4。 指標本身所佔據的記憶體區。
    指標本身佔了多大的記憶體?你只要用函式sizeof(指標的型別)測一下就知道了。在32位平臺裡,指標本身佔據了4個位元組的長度。
    指標本身佔據的記憶體這個概念在判斷一個指標表示式是否是左值時很有用。

二。指標的算術運算
    指標可以加上或減去一個整數。指標的這種運算的意義和通常的數值的加減運算的意義是不一樣的。例如:
例二:
1。 char a[20];
2。 int *ptr=a;
...
...
3。 ptr++;
    在上例中,指標ptr的型別是int*,它指向的型別是int,它被初始化為指向整形變數a。接下來的第3句中,指標ptr被加了1,編譯器是這樣處理 的:它把指標ptr的值加上了sizeof(int),在32位程式中,是被加上了4。由於地址是用位元組做 單位的,故ptr所指向的地址由原來的變數a的地址向高地址方向增加了4個位元組。由於char型別的長度是一個位元組,所以,原來ptr是指向陣列a的第0 號單元開始的四個位元組,此時指向了陣列a中從第4號單元開始的四個位元組。
    我們可以用一個指標和一個迴圈來遍歷一個數組,看例子:
例三:
int array[20];
int *ptr=array;
...
//此處略去為整型陣列賦值的程式碼。
...
for(i=0;i<20;i++)
{
(*ptr)++;
ptr++;
}

這個例子將整型陣列中各個單元的值加1。由於每次迴圈都將指標ptr加1,所以每次迴圈都能訪問陣列的下一個單元。
再看例子:
例四:
1。 char a[20];
2。 int *ptr=a;
...
...
3。 ptr+=5;
    在這個例子中,ptr被加上了5,編譯器是這樣處理的:將指標ptr的值加上5乘sizeof(int),在32位程式中就是加上了5乘4=20。由於地 址的單位是位元組,故現在的ptr所指向的地址比起加5後的ptr所指向的地址來說,向高地址方向移動了20個位元組。在這個例子中,沒加5前的ptr指向數 組a的第0號單元開始的四個位元組,加5後,ptr已經指向了陣列a的合法範圍之外了。雖然這種情況在應用上會出問題,但在語法上卻是可以的。這也體現出了 指標的靈活性。
    如果上例中,ptr是被減去5,那麼處理過程大同小異,只不過ptr的值是被減去5乘sizeof(int),新的ptr指向的地址將比原來的ptr所指向的地址向低地址方向移動了20個位元組。
    總結一下,一個指標ptrold加上一個整數n後,結果是一個新的指標ptrnew,ptrnew的型別和ptrold的型別相同,ptrnew所指向的 型別和ptrold所指向的型別也相同。ptrnew的值將比ptrold的值增加了n乘sizeof(ptrold所指向的型別)個位元組。就是說, ptrnew所指向的記憶體區將比ptrold所指向的記憶體區向高地址方向移動了n乘sizeof(ptrold所指向的型別)個位元組。
    一個指標ptrold減去一個整數n後,結果是一個新的指標ptrnew,ptrnew的型別和ptrold的型別相同,ptrnew所指向的型別和 ptrold所指向的型別也相同。ptrnew的值將比ptrold的值減少了n乘sizeof(ptrold所指向的型別)個位元組,就是說, ptrnew所指向的記憶體區將比ptrold所指向的記憶體區向低地址方向移動了n乘sizeof(ptrold所指向的型別)個位元組。

三。運算子&和*
    這裡&是取地址運算子,*是...書上叫做“間接運算子”。&a的運算結果是一個指標,指標的型別是a的型別加個*,指標所指向的型別是 a的型別,指標所指向的地址嘛,那就是a的地址。*p的運算結果就五花八門了。總之*p的結果是p所指向的東西,這個東西有這些特點:它的型別是p指向的 型別,它所佔用的地址是p所指向的地址。
例五:
int a=12;
int b;
int *p;
int **ptr;
p=&a;//&a的結果是一個指標,型別是int*,指向的型別是int,指向的地址是a的地址。
*p=24;//*p的結果,在這裡它的型別是int,它所佔用的地址是p所指向的地址,顯然,*p就是變數a。
ptr=&p;//&p的結果是個指標,該指標的型別是p的型別加個*,在這裡是int **。該指標所指向的型別是p的型別,這裡是int*。該指標所指向的地址就是指標p自己的地址。
*ptr=&b;//*ptr是個指標,&b的結果也是個指標,且這兩個指標的型別和所指向的型別是一樣的,所以用&b來給*ptr賦值就是毫無問題的了。
**ptr=34;//*ptr的結果是ptr所指向的東西,在這裡是一個指標,對這個指標再做一次*運算,結果就是一個int型別的變數。

四。指標表示式
    一個表示式的最後結果如果是一個指標,那麼這個表示式就叫指標表示式。
    下面是一些指標表示式的例子:
例六:
int a,b;
int array[10];
int *pa;
pa=&a;//&a是一個指標表示式。
int **ptr=&pa;//&pa也是一個指標表示式。
*ptr=&b;//*ptr和&b都是指標表示式。
pa=array;
pa++;//這也是指標表示式。
例七:
char *arr[20];
char **parr=arr;//如果把arr看作指標的話,arr也是指標表示式
char *str;
str=*parr;//*parr是指標表示式
str=*(parr+1);//*(parr+1)是指標表示式
str=*(parr+2);//*(parr+2)是指標表示式
    由於指標表示式的結果是一個指標,所以指標表示式也具有指標所具有的四個要素:指標的型別,指標所指向的型別,指標指向的記憶體區,指標自身佔據的記憶體。
    好了,當一個指標表示式的結果指標已經明確地具有了指標自身佔據的記憶體的話,這個指標表示式就是一個左值,否則就不是一個左值。在例七中,&a不 是一個左值,因為它還沒有佔據明確的記憶體。*ptr是一個左值,因為*ptr這個指標已經佔據了記憶體,其實*ptr就是指標pa,既然pa已經在記憶體中有 了自己的位置,那麼*ptr當然也有了自己的位置。

五。陣列和指標的關係
    如果對宣告陣列的語句不太明白的話,請參閱我前段時間貼出的文章<<如何理解c和c++的複雜型別宣告>>。
    陣列的陣列名其實可以看作一個指標。看下例:
例八:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
...
...
value=array[0];//也可寫成:value=*array;
value=array[3];//也可寫成:value=*(array+3);
value=array[4];//也可寫成:value=*(array+4);
    上例中,一般而言陣列名array代表陣列本身,型別是int [10],但如果把array看做指標的話,它指向陣列的第0個單元,型別是int *,所指向的型別是陣列單元的型別即int。因此*array等於0就一點也不奇怪了。同理,array+3是一個指向陣列第3個單元的指標,所以* (array+3)等於3。其它依此類推。
例九:
char *str[3]={
"Hello,this is a sample!",
"Hi,good morning.",
"Hello world"
};
char s[80];
strcpy(s,str[0]);//也可寫成strcpy(s,*str);
strcpy(s,str[1]);//也可寫成strcpy(s,*(str+1));
strcpy(s,str[2]);//也可寫成strcpy(s,*(str+2));
    上例中,str是一個三單元的陣列,該陣列的每個單元都是一個指標,這些指標各指向一個字串。把指標陣列名str當作一個指標的話,它指向陣列的第0號單元,它的型別是char**,它指向的型別是char *。
*str也是一個指標,它的型別是char*,它所指向的型別是char,它指向的地址是字串"Hello,this is a sample!"的第一個字元的地址,即'H'的地址。
    str+1也是一個指標,它指向陣列的第1號單元,它的型別是char**,它指向的型別是char *。
    *(str+1)也是一個指標,它的型別是char*,它所指向的型別是char,它指向"Hi,good morning."的第一個字元'H',等等。

    下面總結一下陣列的陣列名的問題。聲明瞭一個數組TYPE array[n],則陣列名稱array就有了兩重含義:第一,它代表整個陣列,它的型別是TYPE [n];第二,它是一個指標,該指標的型別是TYPE*,該指標指向的型別是TYPE,也就是陣列單元的型別,該指標指向的記憶體區就是陣列第0號單元,該 指標自己佔有單獨的記憶體區,注意它和陣列第0號單元佔據的記憶體區是不同的。該指標的值是不能修改的,即類似array++的表示式是錯誤的。
    在不同的表示式中陣列名array可以扮演不同的角色。
    在表示式sizeof(array)中,陣列名array代表陣列本身,故這時sizeof函式測出的是整個陣列的大小。
    在表示式*array中,array扮演的是指標,因此這個表示式的結果就是陣列第0號單元的值。sizeof(*array)測出的是陣列單元的大小。
    表示式array+n(其中n=0,1,2,....。)中,array扮演的是指標,故array+n的結果是一個指標,它的型別是TYPE*,它指向的型別是TYPE,它指向陣列第n號單元。故sizeof(array+n)測出的是指標型別的大小。
例十:
int array[10];
int (*ptr)[10];
ptr=&array;
    上例中ptr是一個指標,它的型別是int (*)[10],他指向的型別是int [10],我們用整個陣列的首地址來初始化它。在語句ptr=&array中,array代表陣列本身。

    本節中提到了函式sizeof(),那麼我來問一問,sizeof(指標名稱)測出的究竟是指標自身型別的大小呢還是指標所指向的型別的大小?答案是前者。例如:
int (*ptr)[10];
則在32位程式中,有:
sizeof(int(*)[10])==4
sizeof(int [10])==40
sizeof(ptr)==4
    實際上,sizeof(物件)測出的都是物件自身的型別的大小,而不是別的什麼型別的大小。

六。指標和結構型別的關係
七。指標和函式的關係
八。指標型別轉換
九。指標的安全問題
十、指標與連結串列問題   


六。指標和結構型別的關係
    可以宣告一個指向結構型別物件的指標。
例十一:
struct MyStruct
{
int a;
int b;
int c;
}
    MyStruct ss={20,30,40};//聲明瞭結構物件ss,並把ss的三個成員初始化為20,30和40。
    MyStruct *ptr=&ss;//聲明瞭一個指向結構物件ss的指標。它的型別是MyStruct*,它指向的型別是MyStruct。
    int *pstr=(int*)&ss;//聲明瞭一個指向結構物件ss的指標。但是它的型別和它指向的型別和ptr是不同的。
    請問怎樣通過指標ptr來訪問ss的三個成員變數?
答案:
ptr->a;
ptr->b;
ptr->c;
    又請問怎樣通過指標pstr來訪問ss的三個成員變數?
答案:
*pstr;//訪問了ss的成員a。
*(pstr+1);//訪問了ss的成員b。
*(pstr+2)//訪問了ss的成員c。
    呵呵,雖然我在我的MSVC++6.0上調式過上述程式碼,但是要知道,這樣使用pstr來訪問結構成員是不正規的,為了說明為什麼不正規,讓我們看看怎樣通過指標來訪問陣列的各個單元:
例十二:
int array[3]={35,56,37};
int *pa=array;
    通過指標pa訪問陣列array的三個單元的方法是:
*pa;//訪問了第0號單元
*(pa+1);//訪問了第1號單元
*(pa+2);//訪問了第2號單元
    從格式上看倒是與通過指標訪問結構成員的不正規方法的格式一樣。所有的C/C++編譯器在排列陣列的單元時,總是把各個陣列單元存放在連續的儲存區裡,單 元和單元之間沒有空隙。但在存放結構物件的各個成員時,在某種編譯環境下,可能會需要字對齊或雙字對齊或者是別的什麼對齊,需要在相鄰兩個成員之間加若干 個“填充位元組”,這就導致各個成員之間可能會有若干個位元組的空隙。
    所以,在例十二中,即使*pstr訪問到了結構物件ss的第一個成員變數a,也不能保證*(pstr+1)就一定能訪問到結構成員b。因為成員a和成員b 之間可能會有若干填充位元組,說不定*(pstr+1)就正好訪問到了這些填充位元組呢。這也證明了指標的靈活性。要是你的目的就是想看看各個結構成員之間到 底有沒有填充位元組,
    嘿,這倒是個不錯的方法。
    通過指標訪問結構成員的正確方法應該是象例十二中使用指標ptr的方法。

七。指標和函式的關係
    可以把一個指標宣告成為一個指向函式的指標。
int fun1(char*,int);
int (*pfun1)(char*,int);
pfun1=fun1;
....
....
int a=(*pfun1)("abcdefg",7);//通過函式指標呼叫函式。
    可以把指標作為函式的形參。在函式呼叫語句中,可以用指標表示式來作為實參。
例十三:
int fun(char*);
int a;
char str[]="abcdefghijklmn";
a=fun(str);
...
...
int fun(char*s)
{
int num=0;
for(int i=0;i {
num+=*s;s++;
}
return num;
}
    這個例子中的函式fun統計一個字串中各個字元的ASCII碼值之和。前面說了,陣列的名字也是一個指標。在函式呼叫中,當把str作為實參傳遞給形參 s後,實際是把str的值傳遞給了s,s所指向的地址就和str所指向的地址一致,但是str和s各自佔用各自的儲存空間。在函式體內對s進行自加1運 算,並不意味著同時對str進行了自加1運算。

八。指標型別轉換
    當我們初始化一個指標或給一個指標賦值時,賦值號的左邊是一個指標,賦值號的右邊是一個指標表示式。在我們前面所舉的例子中,絕大多數情況下,指標的型別和指標表示式的型別是一樣的,指標所指向的型別和指標表示式所指向的型別是一樣的。
例十四:
1。 float f=12.3;
2。 float *fptr=&f;
3。 int *p;
    在上面的例子中,假如我們想讓指標p指向實數f,應該怎麼搞?是用下面的語句嗎?
    p=&f;
    不對。因為指標p的型別是int*,它指向的型別是int。表示式&f的結果是一個指標,指標的型別是float*,它指向的型別是float。 兩者不一致,直接賦值的方法是不行的。至少在我的MSVC++6.0上,對指標的賦值語句要求賦值號兩邊的型別一致,所指向的型別也一致,其它的編譯器上 我沒試過,大家可以試試。為了實現我們的目的,需要進行“強制型別轉換”:
    p=(int*)&f;
    如果有一個指標p,我們需要把它的型別和所指向的型別改為TYEP*和TYPE,那麼語法格式是:
    (TYPE*)p;
    這樣強制型別轉換的結果是一個新指標,該新指標的型別是TYPE*,它指向的型別是TYPE,它指向的地址就是原指標指向的地址。而原來的指標p的一切屬性都沒有被修改。
    一個函式如果使用了指標作為形參,那麼在函式呼叫語句的實參和形參的結合過程中,也會發生指標型別的轉換。 例十五:
void fun(char*);
int a=125,b;
fun((char*)&a);
...
...
void fun(char*s)
{
char c;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
    注意這是一個32位程式,故int型別佔了四個位元組,char型別佔一個位元組。函式fun的作用是把一個整數的四個位元組的順序來個顛倒。注意到了嗎?在函 數呼叫語句中,實參&a的結果是一個指標,它的型別是int *,它指向的型別是int。形參這個指標的型別是char*,它指向的型別是char。這樣,在實參和形參的結合過程中,我們必須進行一次從int*型別 到char*型別的轉換。結合這個例子,我們可以這樣來想象編譯器進行轉換的過程:編譯器先構造一個臨時指標 char*temp,然後執行temp=(char*)&a,最後再把temp的值傳遞給s。所以最後的結果是:s的型別是char*,它指向的 型別是char,它指向的地址就是a的首地址。
    我們已經知道,指標的值就是指標指向的地址,在32位程式中,指標的值其實是一個32位整數。那可不可以把一個整數當作指標的值直接賦給指標呢?就象下面的語句:
    unsigned int a;
    TYPE *ptr;//TYPE是int,char或結構型別等等型別。
    ...
    ...
    a=20345686;
    ptr=20345686;//我們的目的是要使指標ptr指向地址20345686(十進位制)
    ptr=a;//我們的目的是要使指標ptr指向地址20345686(十進位制)
    編譯一下吧。結果發現後面兩條語句全是錯的。那麼我們的目的就不能達到了嗎?不,還有辦法:
    unsigned int a;
    TYPE *ptr;//TYPE是int,char或結構型別等等型別。
    ...
    ...
    a=某個數,這個數必須代表一個合法的地址;
    ptr=(TYPE*)a;//呵呵,這就可以了。
    嚴格說來這裡的(TYPE*)和指標型別轉換中的(TYPE*)還不一樣。這裡的(TYPE*)的意思是把無符號整數a的值當作一個地址來看待。上面強調了a的值必須代表一個合法的地址,否則的話,在你使用ptr的時候,就會出現非法操作錯誤。    
    想想能不能反過來,把指標指向的地址即指標的值當作一個整數取出來。完全可以。下面的例子演示了把一個指標的值當作一個整數取出來,然後再把這個整數當作一個地址賦給一個指標:
例十六:
int a=123,b;
int *ptr=&a;
char *str;
b=(int)ptr;//把指標ptr的值當作一個整數取出來。
str=(char*)b;//把這個整數的值當作一個地址賦給指標str。
    好了,現在我們已經知道了,可以把指標的值當作一個整數取出來,也可以把一個整數值當作地址賦給一個指標。

九。指標的安全問題
    看下面的例子:
例十七:
char s='a';
int *ptr;
ptr=(int*)&s;
*ptr=1298;
    指標ptr是一個int*型別的指標,它指向的型別是int。它指向的地址就是s的首地址。在32位程式中,s佔一個位元組,int型別佔四個位元組。最後一 條語句不但改變了s所佔的一個位元組,還把和s相臨的高地址方向的三個位元組也改變了。這三個位元組是幹什麼的?只有編譯程式知道,而寫程式的人是不太可能知道 的。也許這三個位元組裡儲存了非常重要的資料,也許這三個位元組里正好是程式的一條程式碼,而由於你對指標的馬虎應用,這三個位元組的值被改變了!這會造成崩潰性 的錯誤。
    讓我們來看一例:
例十八:
1。 char a;
2。 int *ptr=&a;
...
...
3。 ptr++;
4。 *ptr=115;
    該例子完全可以通過編譯,並能執行。但是看到沒有?第3句對指標ptr進行自加1運算後,ptr指向了和整形變數a相鄰的高地址方向的一塊儲存區。這塊存 儲區裡是什麼?我們不知道。有可能它是一個非常重要的資料,甚至可能是一條程式碼。而第4句竟然往這片儲存區裡寫入一個數據!這是嚴重的錯誤。所以在使用指 針時,程式設計師心裡必須非常清楚:我的指標究竟指向了哪裡。在用指標訪問陣列的時候,也要注意不要超出陣列的低端和高階界限,否則也會造成類似的錯誤。
在 指標的強制型別轉換:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的型別)大於sizeof(ptr1的型別),那麼在使用指標 ptr1來訪問ptr2所指向的儲存區時是安全的。如果sizeof(ptr2的型別)小於sizeof(ptr1的型別),那麼在使用指標ptr1來訪 問ptr2所指向的儲存區時是不安全的。至於為什麼,讀者結合例十七來想一想,應該會明白的。

十、指標與連結串列問題
紅色部分所示的程式語句有問題,改正後的程式在下面。
 list1.c

#include
#include

struct listNode{
    int data;
     struct listNode *nextPtr;
};
typedef struct listNode LISTNODE;
typedef LISTNODE * LISTNODEPTR;
void list(LISTNODEPTR *, int);
void printlist(LISTNODEPTR);
main()
{
    LISTNODEPTR newPtr=NULL;
    int i,a;
    for(i=0;i<3;i++){
        printf("please enter a number/n");
        scanf("%d,",&a);
        list(&newPtr,a);
        // 此處給的是newPtr的地址, 注意!
      }
      printlist(newPtr);

    free(newPtr);
     // 連結串列的釋放不能這樣寫,這樣,只釋放了newPtr指向的一個節點。
     // 可以先找到連結串列的尾,然後反向釋放;或者,利用 printlist的順序釋放,
     // 改函式printlist,或在此函式裡釋放。
    return 0;
}

void list(LISTNODEPTR *sPtr, int a)
{
    LISTNODEPTR newPtr,currentPtr;
    newPtr=malloc(sizeof(LISTNODEPTR));
    // 此處錯, LISTNODEPTR 是指標型別,不是結構型別,
    // malloc返回void指標,應該強制轉換型別,此處會告警不報錯,但應有良好的程式設計風格與習慣。
    if(newPtr!=NULL){
        newPtr->data=a;
        newPtr->nextPtr=NULL;
         currentPtr=*sPtr;
    }
    if(currentPtr==NULL){
     // 此處條件不確切,因為currentPtr沒有初始化,
     // 如newPtr一旦為NULL,此句及以下就有問題。
    newPtr->nextPtr=*sPtr;
    *sPtr=newPtr;}
     // 在第一個數來的時候,main裡的newPtr通過sPtr被修改指向此節點。
     // 在第二個數來的時候,main裡的newPtr通過sPtr被修改指向此節點。
     // 在第三個數來的時候,main裡的newPtr通過sPtr被修改指向此節點。
     // 最後,main裡的newPtr指向第三個數。
}

void printlist(LISTNODEPTR currentPtr)
{
    if(currentPtr==NULL)
        printf("The list is empty/n");
    else{
        printf("This list is :/n");
       while(currentPtr!=NULL){
            printf("%d-->",currentPtr->data);
            // main裡的newPtr指向第三個數。你先列印了最後一個數。
            // currentPtr=currentPtr->nextPtr->data;
            // 此句非法, 型別不同, 有可能讓你只迴圈一次,如data為0。
       }
       printf("NULL/n/n");
    }
}
    // 對類似程式能執行,但結果似是而非的情況,應該多利用跟蹤除錯,看變數的變化。


改正後的正確程式
#include
#include
struct listNode{
    int data;
    struct listNode *nextPtr;
};
typedef struct listNode LISTNODE;
typedef LISTNODE * LISTNODEPTR;

LISTNODEPTR list(LISTNODEPTR , int); // 此處不同
void printlist(LISTNODEPTR);
void freelist(LISTNODEPTR); // 增加

main()
{
    LISTNODEPTR newPtr=NULL;
    int i,a;
    for(i=0;i<3;i++){
        printf("please enter a number/n");
        scanf("%d,",&a);
        newPtr = list(newPtr,a); // 此處注意
    }
    printlist(newPtr);
    freelist(newPtr); // 此處
    return 0;
}

LISTNODEPTR list(LISTNODEPTR sPtr, int a)
{
    if ( sPtr != NULL )
        sPtr->nextPtr = list( sPtr->nextPtr, a ); // 遞迴,向後面的節點上加資料。
    else
    {
        sPtr =(LISTNODEPTR) malloc(sizeof(LISTNODE)); // 注意,是節點的尺寸,型別轉換
        sPtr->nextPtr = NULL;
        sPtr->data = a;
    }
    return sPtr;
}

void freelist(LISTNODEPTR sPtr )
{
    if ( sPtr != NULL )
    {
        freelist( sPtr->nextPtr ); // 遞迴, 先釋放後面的節點
        free( sPtr ); // 再釋放本節點
    }
    else //
    return ; // 此兩行可不要
}

void printlist(LISTNODEPTR currentPtr)
{
    if(currentPtr==NULL)
        printf("The list is empty/n");
    else
    {
        printf("This list is :/n");
        while(currentPtr!=NULL)
        {
            printf("%d-->",currentPtr->data);
            currentPtr=currentPtr->nextPtr; // 這裡不一樣
        }
        printf("NULL/n/n");
    }
}
================================================

牛人哪都有,CSDN特別多 -_-!



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1727164