C_C++指针指针应用详解
前言:復雜類型說明
要了解指針,多多少少會出現一些比較復雜的類型,所以我先介紹一下如何完全理解一個復雜類型,要理解復雜類型其實很簡單,一個類型里會出現很多運算符,他們也像普通的表達式一樣,有優先級,其優先級和運算優先級一樣,所以我總結了一下其原則:
從變量名處起,根據運算符優先級結合,一步一步分析.?
下面讓我們先從簡單的類型開始慢慢分析吧:
int?p;?//這是一個普通的整型變量
int?*p;?//首先從P?處開始,先與*結合,所以說明P?是一個指針,然后再與int?結合,說明指針所指向的內容的類型為int?型.所以P?是一個返回整型數據的指針
int?p[3];?//首先從P?處開始,先與[]結合,說明P?是一個數組,然后與int?結合,說明數組里的元素是整型的,所以P?是一個由整型數據組成的數組
?int?*p[3];?//首先從P?處開始,先與[]結合,因為其優先級比*高,所以P?是一個數組,然后再與*結合,說明數組里的元素是指針類型,然后再與int結合,說明指針所指向的內容的類型是整型的,所以P?是一個由指向整型數據的指針所組成的數組.
int?(*p)[3];?//首先從P?處開始,先與*結合,說明P?是一個指針
//然后再與[]結合(與"()"這步可以忽略,只是為
//了改變優先級),說明指針所指向的內容是一個
//數組,然后再與int?結合,說明數組里的元素是
//整型的.所以P?是一個指向由整型數據組成的數
//組的指針.
int?**p;?//首先從P?開始,先與*結合,說是P?是一個指針,然
//后再與*結合,說明指針所指向的元素是指針,然
//后再與int?結合,說明該被指向的指針所指向的元素是整
//型數據.?由于二級指針以及更高級的指針極少用
//在復雜的類型中,所以后面更復雜的類型我們就
//不考慮多級指針了,最多只考慮一級指針.
int?p(int);?//從P?處起,先與()結合,說明P?是一個函數,然后進入
//()里分析,說明該函數有一個整型變量的參數
//然后再與外面的int?結合,說明函數的返回值是
//一個整型數據
?int?(*p)(int);?//從P?處開始,先與指針結合,說明P?是一個指針,然后與
//()結合,說明指針指向的是一個函數,然后再與()里的
//int?結合,說明函數有一個int?型的參數,再與最外層的
//int?結合,說明函數的返回類型是整型,所以P?是一個指
//向有一個整型參數且返回類型為整型的函數的指針.
Int?(*a[10])(int);//一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數并返回一個整型數(An?array?of?ten?pointers?to?functions?that?take?an?integer?argument?and?return?an?integer).
int?*(*p(int))[3];?//可以先跳過,不看這個類型,過于復雜
//從P?開始,先與()結合,說明P?是一個函數,然后進
//入()里面,與int?結合,說明函數有一個整型變量
//參數,然后再與外面的*結合,說明函數返回的是
//一個指針,然后到最外面一層,先與[]結合,說明
//返回的指針指向的是一個數組,然后再與*結合,說
//明數組里的元素是指針,然后再與int?結合,說明指
//針指向的內容是整型數據.所以P?是一個參數為一個
//整數據且返回一個指向由整型指針變量組成的數組
//的指針變量的函數.?
說到這里也就差不多了,我們的任務也就這么多,理解了這幾個類型,其它
的類型對我們來說也是小菜了,不過我們一般不會用太復雜的類型,那樣會
大大減小程序的可讀性,請慎用,這上面的幾種類型已經足夠我們用了.
[1]、細說指針
指針是一個特殊的變量,它里面存儲的數值被解釋成為內存里的一個地址。
要搞清一個指針需要搞清指針的四方面的內容:指針的類型、指針所指向的
類型、指針的值或者叫指針所指向的內存區、指針本身所占據的內存區。讓
我們分別說明。
先聲明幾個指針放著做例子:
例一:
(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
a.?指針的類型
從語法的角度看,你只要把指針聲明語句里的指針名字去掉,剩下的部
分就是這個指針的類型。這是指針本身所具有的類型。讓我們看看例一中各
個指針的類型:
(1)int*ptr;//指針的類型是int*
(2)char*ptr;//指針的類型是char*
(3)int**ptr;//指針的類型是int**
(4)int(*ptr)[3];//指針的類型是int(*)[3]
(5)int*(*ptr)[4];//指針的類型是int*(*)[4]
怎么樣?找出指針的類型的方法是不是很簡單?
b.?指針所指向的類型
當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了
編譯器將把那片內存區里的內容當做什么來看待。
從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲
明符*去掉,剩下的就是指針所指向的類型。例如:
(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?越來越熟悉時,你會發現,把與指針攪和在一起的"類型"這個概念分成
"指針的類型"和"指針所指向的類型"兩個概念,是精通指針的關鍵點之一。
我看了不少書,發現有些寫得差的書中,就把指針的這兩個概念攪在一起了,
所以看起書來前后矛盾,越看越糊涂。
c.?指針的值?----或者叫指針所指向的內存區的地址
指針的值是指針本身存儲的數值,這個值將被編譯器當作一個地址,而
不是一個一般的數值。在32?位程序里,所有類型的指針的值都是一個32?位
整數,因為32?位程序里內存地址全都是32?位長。指針所指向的內存區就
是從指針的值所代表的那個內存地址開始,長度為sizeof(指針所指向的類
型)的一片內存區。以后,我們說一個指針的值是XX,就相當于說該指針指
向了以XX?為首地址的一片內存區域;我們說一個指針指向了某塊內存區域,
就相當于說該指針的值是這塊內存區域的首地址。
指針所指向的內存區和指針所指向的類型是兩個完全不同的概念。在例
一中,指針所指向的類型已經有了,但由于指針還未初始化,所以它所指向
的內存區是不存在的,或者說是無意義的。
以后,每遇到一個指針,都應該問問:這個指針的類型是什么?指針指向
的類型是什么?該指針指向了內存區中的哪里?(重點注意)
d.?指針本身所占據的內存區
指針本身占了多大的內存?你只要用函數sizeof(指針的類型)測一下
就知道了。在32?位平臺里,指針本身占據了4?個字節的長度。
指針本身占據的內存這個概念在判斷一個指針表達式(后面會解釋)是
否是左值時很有用。
[2]、指針的算術運算
指針可以加上或減去一個整數。指針的這種運算的意義和通常的數值的加減
運算的意義是不一樣的,以單元為單位。例如:
例二:
char?a[20];
int?*ptr=(int?*)a;?//強制類型轉換并不會改變a?的類型,?只改變ptr由它的值開始的所指向的
//內存區的的長度(sizeof(int)).
ptr++;
在上例中,指針ptr?的類型是int*,它指向的類型是int,它被初始化
為指向整型變量a。接下來的第3?句中,指針ptr?被加了1,編譯器是這樣
處理的:它把指針ptr的值加上了1*sizeof(int)?,在32?位程序中,是被加上
了4,因為在32?位程序中,int?占4?個字節。由于地址是用字節做單位的,
故ptr?所指向的地址由原來的變量a?的地址向高地址方向增加了4?個字節。
由于char?類型的長度是一個字節,所以,原來ptr?是指向數組a?的第0?號
單元開始的四個字節,此時指向了數組a?中從第4?號單元開始的四個字節。
我們可以用一個指針和一個循環來遍歷一個數組,看例子:
例三:
int?array[20]={0};
int?*ptr=array;
for(i=0;i<20;i++)
{
?(*ptr)++;??//指針所指向的元素的值+1
?ptr++;???//指針指向下一個元素
}
這個例子將整型數組中各個單元的值加1。由于每次循環都將指針ptr
加1?個單元,所以每次循環都能訪問數組的下一個單元。
再看例子:
例四:
char?a[20]="You_are_a_girl";
int?*ptr=(int?*)a;
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?個字節。
下面請允許我再舉一個例子:(一個誤區)
例五:
#include<stdio.h>
int?main()
{
char?a[20]="?You_are_a_girl";
char?*p=a;
char?**ptr=&p;
//printf("p=%d\n",p);
//printf("ptr=%d\n",ptr);
//printf("*ptr=%d\n",*ptr);
printf("**ptr=%c\n",**ptr);
ptr++;
//printf("ptr=%d\n",ptr);
//printf("*ptr=%d\n",*ptr);
printf("**ptr=%c\n",**ptr);
}
誤區一、輸出答案為Y?和o
誤解:ptr?是一個char?的二級指針,當執行ptr++;時,會使指針加一個
sizeof(char),所以輸出如上結果,這個可能只是少部分人的結果.
誤區二、輸出答案為Y?和a
誤解:ptr?指向的是一個char?*類型,當執行ptr++;時,會使指針加一個
sizeof(char?*)(有可能會有人認為這個值為1,那就會得到誤區一的答
案,這個值應該是4,參考前面內容),?即&p+4;?那進行一次取值運算不
就指向數組中的第五個元素了嗎?那輸出的結果不就是數組中第五個元
素了嗎?答案是否定的.
正解:??ptr?的類型是char?**,指向的類型是一個char?*類型,該指向的
地址就是p的地址(&p),當執行ptr++;時,會使指針加一個sizeof(char
*),即&p+4;?那*(&p+4)指向哪呢,這個你去問上帝吧,或者他會告訴你在
哪?所以最后的輸出會是一個隨機的值,或許是一個非法操作.
總結一下:
一個指針ptrold?加(減)一個整數n?后,結果是一個新的指針ptrnew,
ptrnew?的類型和ptrold?的類型相同,ptrnew?所指向的類型和ptrold
所指向的類型也相同。ptrnew?的值將比ptrold?的值增加(減少)了n?乘
sizeof(ptrold所指向的類型)個字節。就是說,ptrnew?所指向的內存
區將比ptrold?所指向的內存區向高(低)地址方向移動了n乘
sizeof(ptrold?所指向的類型)個字節。
指針和指針相減:
兩個指針不能進行加法運算,這是非法操作,因為進行加法后,得到的
結果指向一個不知所向的地方,而且毫無意義。兩個指針可以進行減法
操作,但必須類型相同,一般用在數組方面,不多說了。
[3]、運算符&和*
這里&是取地址運算符,*是間接運算符。
?&a?的運算結果是一個指針,指針的類型是a?的類型加個*,指針所
指向的類型是a?的類型,指針所指向的地址(指針的值)嘛,那就是a?的地址。
*p?的運算結果就五花八門了??傊?/span>*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?就是p所指向的東西即變量a。
ptr=&p;?//&p?的結果是個指針,該指針的類型是p?的類型加個*,
//在這里是int?**。該指針所指向的類型是p?的類型,這
//里是int*。該指針所指向的地址就是指針p?自己的地址。
*ptr=&b;?//*ptr?是個指針,&b?的結果也是個指針,且這兩個指針
//的類型和所指向的類型是一樣的,所以用&b?來給*ptr?賦
//值就是毫無問題的了。
**ptr=34;?//*ptr?的結果是ptr?所指向的東西,在這里是一個指針,
//對這個指針再做一次*運算,結果是一個int?類型的變量。
[4]、指針表達式
一個表達式的結果如果是一個指針,那么這個表達式就叫指針表式。
下面是一些指針表達式的例子:
例七:
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?當然也有了自己的位
置。
[5]、數組和指針的關系
數組的數組名其實可以看作一個指針。看下例:
例九:
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,thisisasample",
"Hi,goodmorning.",
"Helloworld"
};
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,thisisasample!"的第一個字符的地址,即
'H'的地址。注意:字符串相當于是一個數組,在內存中以數組的形式儲
存,只不過字符串是一個數組常量,內容不可改變,且只能是右值.如果
看成指針的話,他即是常量指針,也是指針常量.
str+1?也是一個指針,它指向數組的第1?號單元,它的類型是char**,
它指向的類型是char*。
*(str+1)也是一個指針,它的類型是char*,它所指向的類型是char,
它指向"Hi,goodmorning."的第一個字符'H'.?
下面總結一下數組的數組名(數組中儲存的也是數組)的問題:
聲明了一個數組?TYPE?array[n]?,則數組名稱array?就有了兩重含義:
第一,它代表整個數組,它的類型是TYPE[n];第二,它是一個常量
指針,該指針的類型是TYPE*,該指針指向的類型是TYPE,也就是數組
單元的類型,該指針指向的內存區就是數組第0?號單元,該指針自己占
有單獨的內存區,注意它和數組第0?號單元占據的內存區是不同的。該
指針的值是不能修改的,即類似array++的表達式是錯誤的。
在不同的表達式中數組名array?可以扮演不同的角色:
(i)在表達式?sizeof(array)??(等價于sizeof(int[N]))中,數組名array?代表數組本身,故這時sizeof函數測出的是整個數組的大小而不是指針的大小。
(i)在表達式?*array?中,array?扮演的是指針,因此這個表達式的結果就是
數組第0?號單元的大小。?sizeof(*array)?測出的是數組單元的大小。
(i)表達式?array+n(其中n=0,1,2,.....)中,array?扮演的是指
針,故array+n?的結果是一個指針,它的類型是TYPE?*,它指向的類型是TYPE,它指向數組第n?號單元。故?sizeof(array+n)?測出的是指針類型的大小。在32?位程序中結果是4.
例十一:
?int?array[10];//?array:指向數組首個單元的指針(數組首個單元的地址)或代表數組本身.
int?(*ptr)[10];//?ptr:指向整個數組的指針.
ptr=&array;//?&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(對象)測出的都是對象自身的類型的大小,而不是別的
什么類型的大小。
[6]、指針和結構類型的關系
可以聲明一個指向結構類型對象的指針。
例十二:
struct?MyStruct
{
int?a;
int?b;
int?c;
};
struct?MyStruct?ss={20,30,40};
//聲明了結構對象ss,并把ss?的成員初始化為20,30?和40。
struct?MyStruct?*ptr=&ss;
//聲明了一個指向結構對象ss?的指針。它的類型是
//MyStruct?*,它指向的類型是MyStruct。
int?*pstr=(int*)&ss;
//聲明了一個指向結構對象ss?的指針。但是pstr?和
//ptr所指向的類型是不同的。
請問怎樣通過指針ptr?來訪問ss?的三個成員變量?
答案:
ptr->a;?//指向運算符,或者可以這們(*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?的
方法。
[7]、指針和函數的關系
可以把一個指針聲明成為一個指向函數的指針。
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;;)
{
num+=*s;s++;
}
return?num;
}
這個例子中的函數fun?統計一個字符串中各個字符的ASCII?碼值之
和。前面說了,數組的名字也是一個指針。在函數調用中,當把str
作為實參傳遞給形參s?后,實際是把str?的值傳遞給了s,s?所指向的
地址就和str?所指向的地址一致,但是str?和s?各自占用各自的存儲空
間。在函數體內對s?進行自加1?運算,并不意味著同時對str?進行了自
加1?運算。
[8]、指針類型轉換
當我們初始化一個指針或給一個指針賦值時,賦值號的左邊是一個指
針,賦值號的右邊是一個指針表達式。在我們前面所舉的例子中,絕大
多數情況下,指針的類型和指針表達式的類型是一樣的,指針所指向的
類型和指針表達式所指向的類型是一樣的。
例十五:
?float?f=12.3;?
float?*fptr=&f;
?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)
{
charc;
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;?//無符號整數a的值用來表示一個地址.
ptr=20345686;?//我們的目的是要使指針ptr?指向地址20345686
ptr=a;?//我們的目的是要使指針ptr?指向地址20345686
編譯一下吧。結果發現后面兩條語句全是錯的。那么我們的目的就不能
達到了嗎?不,還有辦法:
?unsigned?int?a;
TYPE?*ptr;?//TYPE?是int,char?或結構類型等等類型。
a=N?//N?必須代表一個合法的地址;
ptr=(TYPE*)a;?//呵呵,這就可以了。
嚴格說來這里的(TYPE?*)和指針類型轉換中的(TYPE?*)還不一樣。這里
的(TYPE*)的意思是把無符號整數a?的值當作一個地址來看待。上面強
調了a?的值必須代表一個合法的地址,否則的話,在你使用ptr?的時候,
就會出現非法操作錯誤。
想想能不能反過來,把指針指向的地址即指針的值當作一個整數取
出來。完全可以。下面的例子演示了把一個指針的值當作一個整數取出
來,然后再把這個整數當作一個地址賦給一個指針:
例十七:
int?a=123,b;
int?*ptr=&a;?//ptr的值是a的地址,即ptr指向a的地址.
char?*str;
b=(int)ptr;?//把指針ptr的值當作一個整數取出來賦給整數b。
str=(char*)b;?//把這個整型值當作一個地址賦給char*型指針str。
現在我們已經知道了,可以把指針的值當作一個整數取出來,也可
以把一個整數值當作地址賦給一個指針。
[9]、指針的安全問題
看下面的例子:
例十八:
char?s='a';
int?*ptr;
ptr=(int?*)&s;
*ptr=1298;
指針ptr?是一個int?*類型的指針,它指向的類型是int。它指向
的地址就是s?的首地址。在32?位程序中,s?占一個字節,int?類型占四
個字節。最后一條語句不但改變了s?所占的一個字節,還把和s?相臨的
高地址方向的三個字節也改變了。這三個字節是干什么的?只有編譯程
序知道,而寫程序的人是不太可能知道的。也許這三個字節里存儲了非
常重要的數據,也許這三個字節里正好是程序的一條代碼,而由于你對
指針的馬虎應用,這三個字節的值被改變了!這會造成崩潰性的錯誤。
讓我們再來看一例:
例十九:
char?a;
int?*ptr=&a;
ptr++;
*ptr=115;
該例子完全可以通過編譯,并能執行。但是看到沒有?第3句對指
針ptr?進行自加1?運算后,ptr?指向了和整形變量a?相鄰的高地址方向
的一塊存儲區。這塊存儲區里是什么?我們不知道。有可能它是一個非
常重要的數據,甚至可能是一條代碼。而第4句竟然往這片存儲區里寫
入一個數據!這是嚴重的錯誤。所以在使用指針時,程序員心里必須非
常清楚:我的指針究竟指向了哪里。在用指針訪問數組的時候,也要注
意不要超出數組的低端和高端界限,否則也會造成類似的錯誤。
在指針的強制類型轉換:ptr1=(TYPE?*)ptr2?中,如果sizeof(ptr2
的類型)大于sizeof(ptr1?的類型),那么在使用指針ptr1?來訪問ptr2
所指向的存儲區時是安全的。如果sizeof(ptr2?的類型)?小于
sizeof(ptr1?的類型),那么在使用指針ptr1?來訪問ptr2?所指向的存
儲區時是不安全的。至于為什么,讀者結合例十八來想一想,應該會明
白的。
?
===================================================================================================================================================================
C++是一種靜態類型的語言,類型安全在C++中舉足輕重.在C語言中,你可以用void*來指向一切;但在C++中,void*并不能指向一切,
事實上,在C++中,想找到一個通用的指針,特別是通用的函數指針簡直是一個"不可能任務".就算能,也失去了類型安全的意義了.類型
安全往往能幫我們找出程序中潛在的一些BUG.
????1、數據指針:
?????下面我們來探討一下,C++中如何存儲各種類型數據的指針.?數據指針分為兩種:常規數據指針和成員數據指針.
????1.1?[常規數據指針]
?????這個不用說明了,和C語言一樣,定義、賦值是很簡單明了的.常見的有:int*,?double*等等.
?????如:
?????int?value?=?123;
?????int?*?pn?=?&value;????????
????1.2?[成員數據指針]
?????有如下的結構:
?????struct?MyStruct
?????{
???????int?key;
???????int?value;
?????};
?????現在有一個結構對象:
?????MyStruct?me;
?????MyStruct*?pMe?=?&me;
?????我們需要?value?成員的地址,我們可以:
?????int?*?pValue?=?&me.value;//或int?*?pValue?=?&pMe->value;?
?????當然了,這個指針仍然是屬于第一種范籌----常規數據指針.
?????好了,我們現在需要一種指針,它指向MyStruct中的任一數據成員,那么它應該是這樣的子:
?????int?MyStruct::*?pMV?=?&MyStruct::value;//或int?MyStruct::*?pMK?=?&MyStruct::key;
?????這種指針的用途是用于取得結構成員在結構內的地址.我們可以通過該指針來訪問成員數據:
?????int?value?=?pMe->*pMV;?//?取得pMe的value成員數據.
?????int?key?=?me.*pMK;?//?取得me的key成員數據.
????
?????那么,在什么場合下會使用到成員數據指針呢?
?????確實,成員指針本來就不是一種很常用的指針.不過,在某些時候還是很有用處的.我們先來看看下面的一個函數:
?????int?sum(MyStruct*?objs,?int?MyStruct::*?pm,?int?count)
?????{
?????????int?result?=?0;
?????????for(int?i?=?0;?i?<?count;?++i)
?????????????result?+=?objs[i].*pm;
?????????return?result;
?????}
?????這個函數的功能是什么,你能看明白嗎?它的功能就是,給定count個MyStruct結構的指針,計算出給定成員數據的總和.
有點拗口對吧?看看下面的程序,你也許就明白了:
?????
?????MyStruct?me[10]?=
?????{
??????{1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{13,14},{15,16},{17,18},{19,20}
?????};?????
?????int?sum_value?=?sum(me,?&MyStruct::value,?10);
?????//計算10個MyStruct結構的value成員的總和:?sum_value值為110???(2+4+6+8+...+20)
?????int?sum_key?=?sum(me,?&MyStruct::key,?10);
?????//計算10個MyStruct結構的key成員的總和:?sum_key值為100???????(1+3+5+7+...+19)
?????也許,你覺得用常規指針也可以做到,而且更易懂.Ok,沒問題:
?????int?sum(MyStruct*?objs,?int?count)
?????{
??????int?result?=?0;
??????for(int?i?=?0;?i?<?count;?++i)
???????result?+=?objs[i].value;
??????return?result;
?????}
?????你是想這么做嗎?但這么做,你只能計算value,如果要算key的話,你要多寫一個函數.有多少個成員需要計算的話,
你就要寫多少個函數,多麻煩啊.
2、參數傳遞的問題:
可以相當于隱式的返回值,可以返回更多的值:
#include?"iostream.h"
void?example(int?*a1,int?&b1,int?c1)
{
*a1*=3;
++b1;
++c1;
}
void?main()
{
int?*a;
int?b,c;
*a=6;
b=7;c=10;
example(a,b,c);
cout?<<"*a="<<*a<
cout?<<"b="<
cout?<<"c="<
}
輸出:*a=18
b=8
c=10
注意到沒有,*a和b的值都改變了,而c沒有變.這是由于a1是指向*a(=6)的指針,也即與a是指向同一個地址,
所以當a1指向的值改變了,*a的值也就改變了.在函數中的參數使用了引用(int?&b1),b1是b的別名,也可以
把它當作特殊的指針來理解,所以b的值會改變.函數中的參數int?c1只是在函數中起作用,當函數結束時候
便消失了,所以在main()中不起作用.
3、全局變量和局部變量的問題:
#include?"iostream.h"
int?a=5;
int?*example1(int?b)
{
????a+=b;
????return?&a;
}
int?*example2(int?b)
{
????int?c=5;
????b+=c;
return?&b;
}
void?main()
{
int?*a1=example1(10);
int?*b1=example2(10);
cout?<<"a1="<<*a1<
cout?<<"b1="<<*b1<
}
輸出結果:
a1=15
b1=4135
*b1怎么會是4135,而不是15呢?
由于a是全局變量,存放在全局變量的內存區,它一直是存在的;而局部變量則是存在于函數的棧區,當函數
example2()調用結束后便消失,使b指向了一個不確定的區域,產生指針懸掛.
????4、內存問題:
????使用指針過程中應該給變量一個適當的空間,以免產生不可見的錯誤.請看以下代碼:
#include?"iostream.h"
void?main()
{
char?*a1;
char?*a2;
cin?>>a1;
cin?>>a2;
cout?<<"a1="<
cout?<<"a2="<
}
輸入:abc
123
輸出:
a1=123
a2=
Null?pointer?assignment
指針指向了"空".解決辦法就是分配適當的內存給這兩個字符串.修正后的代碼如下:
#include?"iostream.h"
void?main()
{
char?*a1;
char?*a2;
a1=new?char?[10];
a2=new?char?[10];
cin?>>a1;
cin?>>a2;
cout?<<"a1="<
cout?<<"a2="<
delete(a1);別忘了釋放內存空間
delete(a2);
}
===================================================================================================================================================================
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?*);?
這樣就容易看得多了.
轉載于:https://www.cnblogs.com/Zyf2016/p/6337831.html
總結
以上是生活随笔為你收集整理的C_C++指针指针应用详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LWIP总结
- 下一篇: oc-14-对象方法调用类方法