- 論壇徽章:
- 0
|
C語言所有復(fù)雜的指針聲明,都是由各種聲明嵌套構(gòu)成的。如何解讀復(fù)雜指針聲明呢?右左法則是一個既著名又常用的方法。不過,右左法則其實并不是C標(biāo)準(zhǔn)里面
的內(nèi)容,它是從C標(biāo)準(zhǔn)的聲明規(guī)定中歸納出來的方法。C標(biāo)準(zhǔn)的聲明規(guī)則,是用來解決如何創(chuàng)建聲明的,而右左法則是用來解決如何辯識一個聲明的,兩者可以說是
相反的。右左法則的英文原文是這樣說的:
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.
這段英文的翻譯如下:
右左法則:首先從最里面的圓括號看起,然后往右看,再往左看。每當(dāng)遇到圓括號時,就應(yīng)該掉轉(zhuǎn)閱讀方向。一旦解析完圓括號里面所有的東西,就跳出圓括號。重復(fù)這個過程直到整個聲明解析完畢。
筆者要對這個法則進行一個小小的修正,應(yīng)該是從未定義的標(biāo)識符開始閱讀,而不是從括號讀起,之所以是未定義的標(biāo)識符,是因為一個聲明里面可能有多個標(biāo)識符,但未定義的標(biāo)識符只會有一個。
現(xiàn)在通過一些例子來討論右左法則的應(yīng)用,先從最簡單的開始,逐步加深:
int (*func)(int *p);
首
先找到那個未定義的標(biāo)識符,就是func,它的外面有一對圓括號,而且左邊是一個*號,這說明func是一個指針,然后跳出這個圓括號,先看右邊,也是一
個圓括號,這說明(*func)是一個函數(shù),而func是一個指向這類函數(shù)的指針,就是一個函數(shù)指針,這類函數(shù)具有int*類型的形參,返回值類型是
int。
int (*func)(int *p, int (*f)(int*));
func被一對括號包含,且
左邊有一個*號,說明func是一個指針,跳出括號,右邊也有個括號,那么func是一個指向函數(shù)的指針,這類函數(shù)具有 int *和int
(*)(int*)這樣的形參,返回值為int類型。再來看一看func的形參int
(*f)(int*),類似前面的解釋,f也是一個函數(shù)指針,指向的函數(shù)具有int*類型的形參,返回值為int。
int (*func[5])(int *p);
func
右邊是一個[]運算符,說明func是一個具有5個元素的數(shù)組,func的左邊有一個*,說明func的元素是指針,要注意這里的*不是修飾func的,
而是修飾func[5]的,原因是[]運算符優(yōu)先級比*高,func先跟[]結(jié)合,因此*修飾的是func[5]。跳出這個括號,看右邊,也是一對圓括
號,說明func數(shù)組的元素是函數(shù)類型的指針,它所指向的函數(shù)具有int*類型的形參,返回值類型為int。
int (*(*func)[5])(int *p);
func
被一個圓括號包含,左邊又有一個*,那么func是一個指針,跳出括號,右邊是一個[]運算符號,說明func是一個指向數(shù)組的指針,現(xiàn)在往左看,左邊有
一個*號,說明這個數(shù)組的元素是指針,再跳出括號,右邊又有一個括號,說明這個數(shù)組的元素是指向函數(shù)的指針?偨Y(jié)一下,就是:func是一個指向數(shù)組的指
針,這個數(shù)組的元素是函數(shù)指針,這些指針指向具有int*形參,返回值為int類型的函數(shù)。
int (*(*func)(int *p))[5];
func是一個函數(shù)指針,這類函數(shù)具有int*類型的形參,返回值是指向數(shù)組的指針,所指向的數(shù)組的元素是具有5個int元素的數(shù)組。
要注意有些復(fù)雜指針聲明是非法的,例如:
int func(void) [5];
func是一個返回值為具有5個int元素的數(shù)組的函數(shù)。但C語言的函數(shù)返回值不能為數(shù)組,這是因為如果允許函數(shù)返回值為數(shù)組,那么接收這個數(shù)組的內(nèi)容的東西,也必須是一個數(shù)組,但C語言的數(shù)組名是一個右值,它不能作為左值來接收另一個數(shù)組,因此函數(shù)返回值不能為數(shù)組。
int func[5](void);
func是一個具有5個元素的數(shù)組,這個數(shù)組的元素都是函數(shù)。這也是非法的,因為數(shù)組的元素除了類型必須一樣外,每個元素所占用的內(nèi)存空間也必須相同,顯然函數(shù)是無法達到這個要求的,即使函數(shù)的類型一樣,但函數(shù)所占用的空間通常是不相同的。
作為練習(xí),下面列幾個復(fù)雜指針聲明給讀者自己來解析,答案放在第十章里。
int (*(*func)[5][6])[7][8];
int (*(*(*func)(int *))[5])(int *);
int (*(*func[7][8][9])(int*))[5];
實際當(dāng)中,需要聲明一個復(fù)雜指針時,如果把整個聲明寫成上面所示的形式,對程序可讀性是一大損害。應(yīng)該用typedef來對聲明逐層分解,增強可讀性,例如對于聲明:
int (*(*func)(int *p))[5];
可以這樣分解:
typedef int (*PARA)[5];
typedef PARA (*func)(int *);
這樣就容易看得多了。
===============================================================
轉(zhuǎn)載述: 這是一篇比較老的關(guān)于指針的文章,作者站在初學(xué)者的角度對指針作了深入的剖析。如果你在學(xué)習(xí)指針的時候有什么問題,看一看這篇文章定有收獲。
一。指針的概念
1。指針的類型
2。指針?biāo)赶虻念愋?
3。指針的值
二。指針的算術(shù)運算
三。運算符&和*
四。指針表達式
五。數(shù)組和指針的關(guān)系
一。指針的概念
指針是一個特殊的變量,它里面存儲的數(shù)值被解釋成為內(nèi)存里的一個地址。
要搞清一個指針需要搞清指針的四方面的內(nèi)容:指針的類型,指針?biāo)赶虻念愋停羔樀闹祷蛘呓兄羔標(biāo)赶虻膬?nèi)存區(qū),還有指針本身所占據(jù)的內(nèi)存區(qū)。讓我們分別說明。
先聲明幾個指針放著做例子:
例一:
(1)int *ptr;
(2)char *ptr;
(3)int **ptr;
(4)int (*ptr)[3];
(5)int *(*ptr)[4];
如果看不懂后幾個例子的話,請參閱我前段時間貼出的文章>。
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。指針?biāo)赶虻念愋?
當(dāng)你通過指針來訪問指針?biāo)赶虻膬?nèi)存區(qū)時,指針?biāo)赶虻念愋蜎Q定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當(dāng)做什么來看待。
從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符 *去掉,剩下的就是指針?biāo)赶虻念愋。例如?
(1)int *ptr; //指針?biāo)赶虻念愋褪莍nt
(2)char *ptr; //指針?biāo)赶虻牡念愋褪莄har
(3)int **ptr; //指針?biāo)赶虻牡念愋褪?int *
(4)int (*ptr)[3]; //指針?biāo)赶虻牡念愋褪?int()[3]
(5)int *(*ptr)[4]; //指針?biāo)赶虻牡念愋褪?int *()[4]
在指針的算術(shù)運算中,指針?biāo)赶虻念愋陀泻艽蟮淖饔谩?
指
針的類型(即指針本身的類型)和指針?biāo)赶虻念愋褪莾蓚概念。當(dāng)你對C越來越熟悉時,你會發(fā)現(xiàn),把與指針攪和在一起的“類型”這個概念分成“指針的類型”
和“指針?biāo)赶虻念愋汀眱蓚概念,是精通指針的關(guān)鍵點之一。我看了不少書,發(fā)現(xiàn)有些寫得差的書中,就把指針的這兩個概念攪在一起了,所以看起書來前后矛
盾,越看越糊涂。
3。 指針的值
指針的值,或者叫指針?biāo)赶虻膬?nèi)存區(qū)或地址。 指針的值是指針本身存儲的數(shù)值,這個值將被編譯器當(dāng)作一個地址,而不是一個一般的數(shù)值。在32位程序里,所有類型的指針的值都是一個32位整數(shù),因為32位程序里內(nèi)存地址全都是32位長。
指
針?biāo)赶虻膬?nèi)存區(qū)就是從指針的值所代表的那個內(nèi)存地址開始,長度為sizeof(指針?biāo)赶虻念愋?的一片內(nèi)存區(qū)。以后,我們說一個指針的值是XX,就相
當(dāng)于說該指針指向了以XX為首地址的一片內(nèi)存區(qū)域;我們說一個指針指向了某塊內(nèi)存區(qū)域,就相當(dāng)于說該指針的值是這塊內(nèi)存區(qū)域的首地址。
指針?biāo)赶虻膬?nèi)存區(qū)和指針?biāo)赶虻念愋褪莾蓚完全不同的概念。在例一中,指針?biāo)赶虻念愋鸵呀?jīng)有了,但由于指針還未初始化,所以它所指向的內(nèi)存區(qū)是不存在的,或者說是無意義的。
以后,每遇到一個指針,都應(yīng)該問問:這個指針的類型是什么?指針指向的類型是什么?該指針指向了哪里?
4。 指針本身所占據(jù)的內(nèi)存區(qū)。
指針本身占了多大的內(nèi)存?你只要用函數(shù)sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據(jù)了4個字節(jié)的長度。
指針本身占據(jù)的內(nèi)存這個概念在判斷一個指針表達式是否是左值時很有用。
二。指針的算術(shù)運算
指針可以加上或減去一個整數(shù)。指針的這種運算的意義和通常的數(shù)值的加減運算的意義是不一樣的。例如:
例二:
1。 char a[20];
2。 int *ptr=a;
...
...
3。 ptr++;
在
上例中,指針ptr的類型是int*,它指向的類型是int,它被初始化為指向整形變量a。接下來的第3句中,指針ptr被加了1,編譯器是這樣處理的:
它把指針ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字節(jié)做單位的,故ptr所指向的地址由原來的變量a的地址
向高地址方向增加了4個字節(jié)。由于char類型的長度是一個字節(jié),所以,原來ptr是指向數(shù)組a的第0
號單元開始的四個字節(jié),此時指向了數(shù)組a中從第4號單元開始的四個字節(jié)。
我們可以用一個指針和一個循環(huán)來遍歷一個數(shù)組,看例子:
例三:
int array[20];
int *ptr=array;
...
//此處略去為整型數(shù)組賦值的代碼。
...
for(i=0;i>。
數(shù)組的數(shù)組名其實可以看作一個指針。看下例:
例八:
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);
上
例中,一般而言數(shù)組名array代表數(shù)組本身,類型是int [10],但如果把array看做指針的話,它指向數(shù)組的第0個單元,類型是int
*,所指向的類型是數(shù)組單元的類型即int。因此*array等于0就一點也不奇怪了。同理,array+3是一個指向數(shù)組第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是一個三單元的數(shù)組,該數(shù)組的每個單元都是一個指針,這些指針各指向一個字符串。把指針數(shù)組名str當(dāng)作一個指針的話,它指向數(shù)組的第0號單元,它的類型是char**,它指向的類型是char *。
*str也是一個指針,它的類型是char*,它所指向的類型是char,它指向的地址是字符串"Hello,this is a sample!"的第一個字符的地址,即'H'的地址。
str+1也是一個指針,它指向數(shù)組的第1號單元,它的類型是char**,它指向的類型是char *。
*(str+1)也是一個指針,它的類型是char*,它所指向的類型是char,它指向"Hi,good morning."的第一個字符'H',等等。
下
面總結(jié)一下數(shù)組的數(shù)組名的問題。聲明了一個數(shù)組TYPE
array[n],則數(shù)組名稱array就有了兩重含義:第一,它代表整個數(shù)組,它的類型是TYPE
[n];第二,它是一個指針,該指針的類型是TYPE*,該指針指向的類型是TYPE,也就是數(shù)組單元的類型,該指針指向的內(nèi)存區(qū)就是數(shù)組第0號單元,該
指針自己占有單獨的內(nèi)存區(qū),注意它和數(shù)組第0號單元占據(jù)的內(nèi)存區(qū)是不同的。該指針的值是不能修改的,即類似array++的表達式是錯誤的。
在不同的表達式中數(shù)組名array可以扮演不同的角色。
在表達式sizeof(array)中,數(shù)組名array代表數(shù)組本身,故這時sizeof函數(shù)測出的是整個數(shù)組的大小。
在表達式*array中,array扮演的是指針,因此這個表達式的結(jié)果就是數(shù)組第0號單元的值。sizeof(*array)測出的是數(shù)組單元的大小。
表達式array+n(其中n=0,1,2,....。)中,array扮演的是指針,故array+n的結(jié)果是一個指針,它的類型是TYPE*,它指向的類型是TYPE,它指向數(shù)組第n號單元。故sizeof(array+n)測出的是指針類型的大小。
例十:
int array[10];
int (*ptr)[10];
ptr=&array;
上例中ptr是一個指針,它的類型是int (*)[10],他指向的類型是int [10],我們用整個數(shù)組的首地址來初始化它。在語句ptr=&array中,array代表數(shù)組本身。
本節(jié)中提到了函數(shù)sizeof(),那么我來問一問,sizeof(指針名稱)測出的究竟是指針自身類型的大小呢還是指針?biāo)赶虻念愋偷拇笮。看鸢甘乔罢摺@纾?
int (*ptr)[10];
則在32位程序中,有:
sizeof(int(*)[10])==4
sizeof(int [10])==40
sizeof(ptr)==4
實際上,sizeof(對象)測出的都是對象自身的類型的大小,而不是別的什么類型的大小。
六。指針和結(jié)構(gòu)類型的關(guān)系
七。指針和函數(shù)的關(guān)系
八。指針類型轉(zhuǎn)換
九。指針的安全問題
十、指針與鏈表問題
六。指針和結(jié)構(gòu)類型的關(guān)系
可以聲明一個指向結(jié)構(gòu)類型對象的指針。
例十一:
struct MyStruct
{
int a;
int b;
int c;
}
MyStruct ss={20,30,40};//聲明了結(jié)構(gòu)對象ss,并把ss的三個成員初始化為20,30和40。
MyStruct *ptr=&ss;//聲明了一個指向結(jié)構(gòu)對象ss的指針。它的類型是MyStruct*,它指向的類型是MyStruct。
int *pstr=(int*)&ss;//聲明了一個指向結(jié)構(gòu)對象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上調(diào)式過上述代碼,但是要知道,這樣使用pstr來訪問結(jié)構(gòu)成員是不正規(guī)的,為了說明為什么不正規(guī),讓我們看看怎樣通過指針來訪問數(shù)組的各個單元:
例十二:
int array[3]={35,56,37};
int *pa=array;
通過指針pa訪問數(shù)組array的三個單元的方法是:
*pa;//訪問了第0號單元
*(pa+1);//訪問了第1號單元
*(pa+2);//訪問了第2號單元
從
格式上看倒是與通過指針訪問結(jié)構(gòu)成員的不正規(guī)方法的格式一樣。所有的C/C++編譯器在排列數(shù)組的單元時,總是把各個數(shù)組單元存放在連續(xù)的存儲區(qū)里,單元
和單元之間沒有空隙。但在存放結(jié)構(gòu)對象的各個成員時,在某種編譯環(huán)境下,可能會需要字對齊或雙字對齊或者是別的什么對齊,需要在相鄰兩個成員之間加若干個
“填充字節(jié)”,這就導(dǎo)致各個成員之間可能會有若干個字節(jié)的空隙。
所以,在例十二中,即使*pstr訪問到了結(jié)構(gòu)對象ss的第一個成員
變量a,也不能保證*(pstr+1)就一定能訪問到結(jié)構(gòu)成員b。因為成員a和成員b
之間可能會有若干填充字節(jié),說不定*(pstr+1)就正好訪問到了這些填充字節(jié)呢。這也證明了指針的靈活性。要是你的目的就是想看看各個結(jié)構(gòu)成員之間到
底有沒有填充字節(jié),
嘿,這倒是個不錯的方法。
通過指針訪問結(jié)構(gòu)成員的正確方法應(yīng)該是象例十二中使用指針ptr的方法。
七。指針和函數(shù)的關(guān)系
可以把一個指針聲明成為一個指向函數(shù)的指針。
int fun1(char*,int);
int (*pfun1)(char*,int);
pfun1=fun1;
....
....
int a=(*pfun1)("abcdefg",7);//通過函數(shù)指針調(diào)用函數(shù)。
可以把指針作為函數(shù)的形參。在函數(shù)調(diào)用語句中,可以用指針表達式來作為實參。
例十三:
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;
}
這
個例子中的函數(shù)fun統(tǒng)計一個字符串中各個字符的ASCII碼值之和。前面說了,數(shù)組的名字也是一個指針。在函數(shù)調(diào)用中,當(dāng)把str作為實參傳遞給形參
s后,實際是把str的值傳遞給了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存儲空間。在函數(shù)體內(nèi)對s進行自加1運
算,并不意味著同時對str進行了自加1運算。
八。指針類型轉(zhuǎn)換
當(dāng)我們初始化一個指針或給一個指針賦值時,賦值號的左邊是一個指針,賦值號的右邊是一個指針表達式。在我們前面所舉的例子中,絕大多數(shù)情況下,指針的類型和指針表達式的類型是一樣的,指針?biāo)赶虻念愋秃椭羔槺磉_式所指向的類型是一樣的。
例十四:
1。 float f=12.3;
2。 float *fptr=&f;
3。 int *p;
在上面的例子中,假如我們想讓指針p指向?qū)崝?shù)f,應(yīng)該怎么搞?是用下面的語句嗎?
p=&f;
不
對。因為指針p的類型是int*,它指向的類型是int。表達式&f的結(jié)果是一個指針,指針的類型是float*,它指向的類型是float。兩
者不一致,直接賦值的方法是不行的。至少在我的MSVC++6.0上,對指針的賦值語句要求賦值號兩邊的類型一致,所指向的類型也一致,其它的編譯器上我
沒試過,大家可以試試。為了實現(xiàn)我們的目的,需要進行“強制類型轉(zhuǎn)換”:
p=(int*)&f;
如果有一個指針p,我們需要把它的類型和所指向的類型改為TYEP*和TYPE,那么語法格式是:
(TYPE*)p;
這樣強制類型轉(zhuǎn)換的結(jié)果是一個新指針,該新指針的類型是TYPE*,它指向的類型是TYPE,它指向的地址就是原指針指向的地址。而原來的指針p的一切屬性都沒有被修改。
一個函數(shù)如果使用了指針作為形參,那么在函數(shù)調(diào)用語句的實參和形參的結(jié)合過程中,也會發(fā)生指針類型的轉(zhuǎn)換。 例十五:
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類型占了四個字節(jié),char類型占一個字節(jié)。函數(shù)fun的作用是把一個整數(shù)的四個字節(jié)的順序來個顛倒。注意到了嗎?在函數(shù)
調(diào)用語句中,實參&a的結(jié)果是一個指針,它的類型是int
*,它指向的類型是int。形參這個指針的類型是char*,它指向的類型是char。這樣,在實參和形參的結(jié)合過程中,我們必須進行一次從int*類型
到char*類型的轉(zhuǎn)換。結(jié)合這個例子,我們可以這樣來想象編譯器進行轉(zhuǎn)換的過程:編譯器先構(gòu)造一個臨時指針
char*temp,然后執(zhí)行temp=(char*)&a,最后再把temp的值傳遞給s。所以最后的結(jié)果是:s的類型是char*,它指向的
類型是char,它指向的地址就是a的首地址。
我們已經(jīng)知道,指針的值就是指針指向的地址,在32位程序中,指針的值其實是一個32位整數(shù)。那可不可以把一個整數(shù)當(dāng)作指針的值直接賦給指針呢?就象下面的語句:
unsigned int a;
TYPE *ptr;//TYPE是int,char或結(jié)構(gòu)類型等等類型。
...
...
a=20345686;
ptr=20345686;//我們的目的是要使指針ptr指向地址20345686(十進制)
ptr=a;//我們的目的是要使指針ptr指向地址20345686(十進制)
編譯一下吧。結(jié)果發(fā)現(xiàn)后面兩條語句全是錯的。那么我們的目的就不能達到了嗎?不,還有辦法:
unsigned int a;
TYPE *ptr;//TYPE是int,char或結(jié)構(gòu)類型等等類型。
...
...
a=某個數(shù),這個數(shù)必須代表一個合法的地址;
ptr=(TYPE*)a;//呵呵,這就可以了。
嚴(yán)格說來這里的(TYPE*)和指針類型轉(zhuǎn)換中的(TYPE*)還不一樣。這里的(TYPE*)的意思是把無符號整數(shù)a的值當(dāng)作一個地址來看待。上面強調(diào)了a的值必須代表一個合法的地址,否則的話,在你使用ptr的時候,就會出現(xiàn)非法操作錯誤。
想想能不能反過來,把指針指向的地址即指針的值當(dāng)作一個整數(shù)取出來。完全可以。下面的例子演示了把一個指針的值當(dāng)作一個整數(shù)取出來,然后再把這個整數(shù)當(dāng)作一個地址賦給一個指針:
例十六:
int a=123,b;
int *ptr=&a;
char *str;
b=(int)ptr;//把指針ptr的值當(dāng)作一個整數(shù)取出來。
str=(char*)b;//把這個整數(shù)的值當(dāng)作一個地址賦給指針str。
好了,現(xiàn)在我們已經(jīng)知道了,可以把指針的值當(dāng)作一個整數(shù)取出來,也可以把一個整數(shù)值當(dāng)作地址賦給一個指針。
九。指針的安全問題
看下面的例子:
例十七:
char s='a';
int *ptr;
ptr=(int*)&s;
*ptr=1298;
指
針ptr是一個int*類型的指針,它指向的類型是int。它指向的地址就是s的首地址。在32位程序中,s占一個字節(jié),int類型占四個字節(jié)。最后一條
語句不但改變了s所占的一個字節(jié),還把和s相臨的高地址方向的三個字節(jié)也改變了。這三個字節(jié)是干什么的?只有編譯程序知道,而寫程序的人是不太可能知道
的。也許這三個字節(jié)里存儲了非常重要的數(shù)據(jù),也許這三個字節(jié)里正好是程序的一條代碼,而由于你對指針的馬虎應(yīng)用,這三個字節(jié)的值被改變了!這會造成崩潰性
的錯誤。
讓我們來看一例:
例十八:
1。 char a;
2。 int *ptr=&a;
...
...
3。 ptr++;
4。 *ptr=115;
該
例子完全可以通過編譯,并能執(zhí)行。但是看到?jīng)]有?第3句對指針ptr進行自加1運算后,ptr指向了和整形變量a相鄰的高地址方向的一塊存儲區(qū)。這塊存儲
區(qū)里是什么?我們不知道。有可能它是一個非常重要的數(shù)據(jù),甚至可能是一條代碼。而第4句竟然往這片存儲區(qū)里寫入一個數(shù)據(jù)!這是嚴(yán)重的錯誤。所以在使用指針
時,程序員心里必須非常清楚:我的指針究竟指向了哪里。在用指針訪問數(shù)組的時候,也要注意不要超出數(shù)組的低端和高端界限,否則也會造成類似的錯誤。
在
指針的強制類型轉(zhuǎn)換:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的類型)大于sizeof(ptr1的類型),那么在使用指針
ptr1來訪問ptr2所指向的存儲區(qū)時是安全的。如果sizeof(ptr2的類型)小于sizeof(ptr1的類型),那么在使用指針ptr1來訪
問ptr2所指向的存儲區(qū)時是不安全的。至于為什么,讀者結(jié)合例十七來想一想,應(yīng)該會明白的。
十、指針與鏈表問題
紅色部分所示的程序語句有問題,改正后的程序在下面。
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;idata=a;
newPtr->nextPtr=NULL;
currentPtr=*sPtr;
}
if(currentPtr==NULL){
// 此處條件不確切,因為currentPtr沒有初始化,
// 如newPtr一旦為NULL,此句及以下就有問題。
newPtr->nextPtr=*sPtr;
*sPtr=newPtr;}
// 在第一個數(shù)來的時候,main里的newPtr通過sPtr被修改指向此節(jié)點。
// 在第二個數(shù)來的時候,main里的newPtr通過sPtr被修改指向此節(jié)點。
// 在第三個數(shù)來的時候,main里的newPtr通過sPtr被修改指向此節(jié)點。
// 最后,main里的newPtr指向第三個數(shù)。
}
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指向第三個數(shù)。你先打印了最后一個數(shù)。
// currentPtr=currentPtr->nextPtr->data;
// 此句非法, 類型不同, 有可能讓你只循環(huán)一次,如data為0。
}
printf("NULL\n\n");
}
}
// 對類似程序能運行,但結(jié)果似是而非的情況,應(yīng)該多利用跟蹤調(diào)試,看變量的變化。
改正后的正確程序
#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;inextPtr = list( sPtr->nextPtr, a ); // 遞歸,向后面的節(jié)點上加數(shù)據(jù)。
else
{
sPtr =(LISTNODEPTR) malloc(sizeof(LISTNODE)); // 注意,是節(jié)點的尺寸,類型轉(zhuǎn)換
sPtr->nextPtr = NULL;
sPtr->data = a;
}
return sPtr;
}
void freelist(LISTNODEPTR sPtr )
{
if ( sPtr != NULL )
{
freelist( sPtr->nextPtr ); // 遞歸, 先釋放后面的節(jié)點
free( sPtr ); // 再釋放本節(jié)點
}
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");
}
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u3/109094/showart_2159045.html |
|