日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C_C++指针指针应用详解

發布時間:2024/4/17 c/c++ 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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?位程序中就是加上了54=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?的運算結果就五花八門了。總之*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=012.....)中,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?的成員初始化為2030?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?的值傳遞給了ss?所指向的

地址就和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?intchar?或結構類型等等類型。

a=20345686;?//無符號整數a的值用來表示一個地址.

ptr=20345686;?//我們的目的是要使指針ptr?指向地址20345686

ptr=a;?//我們的目的是要使指針ptr?指向地址20345686

編譯一下吧。結果發現后面兩條語句全是錯的。那么我們的目的就不能

達到了嗎?不,還有辦法:

?unsigned?int?a;

TYPE?*ptr;?//TYPE?intchar?或結構類型等等類型。

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;?//?取得pMevalue成員數據.

?????int?key?=?me.*pMK;?//?取得mekey成員數據.

????

?????那么,在什么場合下會使用到成員數據指針呢?

?????確實,成員指針本來就不是一種很常用的指針.不過,在某些時候還是很有用處的.我們先來看看下面的一個函數:

?????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;

?????}

?????這個函數的功能是什么,你能看明白嗎?它的功能就是,給定countMyStruct結構的指針,計算出給定成員數據的總和.

有點拗口對吧?看看下面的程序,你也許就明白了:

?????

?????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);

?????//計算10MyStruct結構的value成員的總和:?sum_value值為110???(2+4+6+8+...+20)

?????int?sum_key?=?sum(me,?&MyStruct::key,?10);

?????//計算10MyStruct結構的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

注意到沒有,*ab的值都改變了,c沒有變.這是由于a1是指向*a(=6)的指針,也即與a是指向同一個地址,

所以當a1指向的值改變了,*a的值也就改變了.在函數中的參數使用了引用(int?&b1),b1b的別名,也可以

把它當作特殊的指針來理解,所以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*類型的形參,返回值是指向數組的指針,所指向的數組的元素是具有5int元素的數組.?

要注意有些復雜指針聲明是非法的,例如:?

int?func(void)[5];?

func是一個返回值為具有5int元素的數組的函數.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++指针指针应用详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。