官术网_书友最值得收藏!

1.3 數組指針、指針數組與數組名的指針操作

1.3.1 指針運算——算術運算、關系運算

C/C++常常把地址當成整數來處理,但這并不意味著程序員可以對地址(指針)進行各種算術操作,事實上,指針所能做的操作是十分有限的,像指針與其他變量的乘除、兩個指針間的乘除、兩個指針相加都是沒有意義、不被編譯器接受的。合法的運算具體包括以下幾種:指針與整數的加減(包括指針的自增和自減)、同類型指針間的比較、同類型的兩指針相減。

算術運算

指針加上一個整數的結果是另一個指針。問題是,它指向哪里?如果將一個字符指針加1,運算結果產生的指針指向內存中的下一個字符。float占據的內存空間不止1個字節,如果你將一個指向float的指針加1,將會發生什么?它會不會指向該float值內部的某個字節呢?

答案是否定的。當一個指針和一個整數量進行算術運算時,整數在執行加法運算前始終會根據合適的大小進行調整。這個“合適的大小”就是指針所指向類型的大小,“調整”就是把整數值和“合適的大小”相乘。為了更好地說明,試想在某臺機器上,float占據4個字節。在計算float型指針加3的表達式時候,這個3將根據float類型的大小(此例中為4)進行調整(相乘),這樣實際上加到指針上的整型值為12。

把3與指針相加使指針的值增加3個float的大小,而不是3個字節。這個行為較之獲得一個指向一個float值內部某個位置的指針更為合理。表1-1包含了一些加法運算的例子。如果p是指向float的指針,那么p+1就指向下一個float,其他類型也是如此。

表1-1 指針運算結果

C的指針的算術運算只局限于兩種形式。第一種形式是:指針+ /-整數。這種形式用于指向數組中某個元素的指針,如下所示。

這類表達式的結果類型也是指針。

對一個指針加1使它指向數組中的下一個元素,加5使它向右移動5個元素的位置,以此類推。把一個指針減去3使它向左移動3個元素的位置。

例1:What is output if you compile and execute the following code?(2012·微軟)

            void main(){
                int i=11;
                int const *p=&i;    //語句1
                p++;
                printf("%d", *p);
            }

A.11

B.12

C.Garbage value

D.Compile error

E.None of above

解答:C。語句1使得p指向i,且不能通過p修改i的值,但p本身不是const類型,可以修改。p++時,指針p跳過i(32位機器為4個字節)指向下一個內存單元,此內存單元未定義,為一垃圾值。

第二種類型的指針運算具有如下的形式:指針?指針

只有當兩個指針都指向同一個數組中的元素時,才允許從一個指針減去另一個指針,如下所示。

減法運算的值是兩個指針在內存中的距離(以數組元素的長度為單位,而不是以字節為單位)。在上圖中不論數組是什么類型,p2-p1都等于3,而p1-p2等于-3。

如果兩個指針所指向的不是同一個數組中的元素,那么它們之間相減的結果是未定義的。程序員無從知道兩個數組在內存中的相對位置,如果不知道這一點,兩個指針之間的距離就毫無意義。

關系運算

還可以進行<、<=、>、>=運算,不過前提是它們都指向同一個數組中的元素。根據你所使用的操作符,比較表達式將告訴你哪個指針指向數組中更前或更后的元素。

讓我們觀察以下代碼,它用于清除一個數組中所有的元素。

            #define N_VALUES 5
            float values[N_VALUES];
            float *vp;
            for(vp=&values[0]; vp < &values[N_VALUES];)
                    *vp++=0;

1.3.2 指針數組與數組指針

所謂指針數組,是指一個數組里面裝著指針,也即指針數組是一個數組。一個有10 個指針的數組,其中每個指針是指向一個整型數,那么此數組的定義為:

            int  *a[10];

如下圖所示。

所謂數組指針,是指一個指向數組的指針,它其實還是指針,只不過它指向整個數組。一個指向有10個元素整型數組的指針的定義為:

            int  (*p)[10];

其中,由于[]的優先級高于*,所以必須添加(*p)。

二維數組的數組名是一個數組指針,若有:

            int  a[4][10];
            int  (*p)[10];
            p=a; //a的類型是int(*)[10]。

則如下圖所示。

上圖中,p可被替換為a。但需注意的是a是常量,不可以進行賦值操作。“int (*p)[10];”中的10表明指針指向的數組有10個元素,因而不能修改。

若有如下代碼:

            int  a[10];
            int  (*p)[10]=&a;//注意此處是&a,不是a,a的類型是int*,&a的類型是int(*)[10]。
            int  *q=a;

則如下圖所示。

可見,p與q雖然都指向數組的一個元素,但由于p的類型與q的類型不同,p是指向有10個元素整型數組的指針,*p的大小是40個字節,故p+1跳過40個字節;

而q是指向整型的指針,*p的大小是4個字節,故q+1跳過4個字節。

注意:根據漢語的習慣,指針數組與數組指針主要看后面兩個字是什么(前面兩字起修飾作用),指針數組是數組,而數組指針是指針。

例1:設有“int w[3][4];”,pw是與數組名w等價的數組指針,則pw的初始化語句為_____。(2010·中興)

解答:int (*pw)[4]=w;

1.3.3 指針運算在數組中的應用

用指針可以方便地訪問數組或者模擬數組,但由于指針可以隨時指向任意類型的內存塊,因而也要注意指針的指向是否是數組中的某個元素。

例1:下述代碼是否正確?

            char  a[]="hello";
            a[0]='x';
            char* q=a;
            q[0]='b';
            char *p="hello";/*并不是把整個字符串裝入指針變量,而是把存放該字符串的首地址裝入指針變量*/
            p[0]='x';

解答:最后一個語句錯誤。a是數組,內存分配在棧上,故可以通過數組名或指向數組的指針進行修改,而p指向的是位于文字常量區的字符串,是不允許被修改的,故通過指針修改錯誤。但使用p[0]訪問相應元素是正確的,只是不能修改。

指針和數組密切相關。特別是在表達式中使用數組名時,該名字會自動轉換為指向數組首元素(第0元素)的指針。

            int  ia[]={0, 2, 4, 6, 8};
            int  *ip=ia;  //指針ip指向了數組ia的首元素

如果希望使指針指向數組中的另一個元素,則可使用下標操作符給某個元素定位,然后用取地址操作符 & 獲取該元素的存儲地址。

            ip=&ia[4];  //ip指向了數組ia的末尾元素8

通過指針的算術操作可以獲取數組中指定內容的存儲地址。使用指針的算術操作在指向數組某個元素的指針上加上(或減去)一個整型數值,就可以計算出指向數組另一元素的指針值:

            ip=ia; // ip指向ia[0]
            int *ip2=ip+4;  // ip2 指向ia[4]

在指針ip上加4得到一個新的指針,指向數組中ip當前指向的元素后的第4個元素,此時ip2指向元素8。

如果有:

            int  ia[]={0, 2, 4, 6, 8};
            int  *ip=ia;

則要修改第四個元素為9,則可如下操作:

            ia[4]=9; 或 *(ia+4)=9; 或ip[4]=9; 或 *(ip+4)=9;

例2:針對int a[10]; 以下表達式不可以表示a[1] 的地址的是 ?(2013·騰訊)

A.a+sizeof(int)

B.&a[0]+1

C.(int*)&a+1

D.(int*)((char*)&a+sizeof(int))

解答:A。sizeof(int)為4,a是指向數組首元素的指針,指向的元素類型為int,每加1跳過4個字節;

&a[0]為首元素的地址,故也是指向首元素的指針,即&a[0]等價于a;

&a為指向數組的指針,與a的類型不同(&a類型為int(*)[10]),但指向的單元相同;

則a+4指向a[4],A錯誤;

B為a+1,正確;

C中,將&a強制轉換為int*類型,則執行+1跳過一個int的大小(4),指向a[1],正確;

D中將&a轉換為char*類型,則+1跳過一個char的大小(1),故指向a[1]的首字節需要+4,然后轉換為int*類型(指向a[1]的指針類型為int*),正確。

例3:以下程序的運行結果是(  )。(2012·迅雷)

            int  main(void){
                char  a[]={"programming"}, b[]={"language"};
                char  *p1, *p2;
                int  i;
                p1=a, p2=b;
                for(i=0;i<7;i++){
                    if(*(p1+i)==*(p2+i))
                        printf("%c", *(p1+i));
                }
                return  0;
            }

A.gm

B.rg

C.or

D.ga

解答:D。

雖然使用數組名時,其會自動轉換為指向數組首元素(第0元素)的指針。但需注意的是數組的首地址常量,不可以進行賦值操作。

例4:下面程序執行的結果是(  )。(2011·趨勢科技)

            void main(){
                char s[]="abcde";
                s += 2;
                printf("%c\n", s[0]);
            }

A.a

B.b

C.c

D.編譯錯誤

解答:D。數組的首地址是常量,不可以變更,上述程序在Visual Studio 2010下提示s不是可修改的左值。但若char* p=s。p是允許有p+=2的操作的。

當數組作為函數實參傳遞時,傳遞給函數的是數組首元素的地址。而將數組某一個元素的地址當作實參時,傳遞的是此元素的地址,這時可以理解為傳遞的是子數組(以此元素作為首元素的子數組)首元素的地址。

例5:以下程序執行后的輸出結果是(  )。(2012·中興)

            #include "stdio.h"
            void sum(int * a){
                a[0]=a[1];
            }
            main(){
                int aa[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, i;
                for(i=2; i >= 0; i--)       sum(&aa[i]);
                printf("%d\n", aa[0]);
            }

A.1

B.2

C.3

D.4

解答:D。sum(&aa[i])可以理解為傳遞的是子數組(以第i個元素為首元素的子數組)首元素的地址。在循環中,當i為2,傳遞到函數sum的是元素3的地址,然后sum函數將aa[2]賦值為4,以此類推……最后aa數組元素為{4, 4, 4, 4, 5, 6, 7, 8, 9, 10}。

指針運算在高維數組中的應用

事實上,C++沒有提供高維數組類型。以二維數組為例,用戶創建的二維數組其實是每個元素本身都是數組的數組。

例如這樣聲明數組:int a[4][5];

該聲明意味著a是一個包含4個元素的數組,其中每個元素都是一個由5個整數組成的數組。可以將a數組視為由4行組成,其中每一行有5個整數,如下圖所示。

可見a數組的第一個元素是a[0],然后是a[1]、a[2]、a[3],a表示指向數組首元素a[0]的指針。

而a[0]本身就是一個由5個int組成的數組。a[0]數組的第一個元素是a[0][0],該元素是一個int,a[0]表示指向數組a[0]首元素a[0][0]的指針。

而&a表示數組的首地址。

則有:

a:類型為int(*)[5],即a為指向數組a第0個元素a[0]的指針,且a為常量,不可進行賦值運算,a+i的類型也同為int(*)[5],指向a[i];&a+1如圖中所示,跳過4行5列共20元素。

*a或a[0]:類型為int*,*a為指向數組a[0]首元素a[0][0]的指針;

*(a+1)或a[1]:類型也為int*,因a的類型為int(*)[5],即a指向一個有5個元素的一維數組,故a+1將跳過5個元素。則(a+1)為指向數組a的第1個元素a[1]的指針,即*(a+1)或a[1]為指向數組a[1]首元素a[1][0]的指針;

*(*(a+1)+2):類型為int,因*(a+1)類型為int*,故(*(a+1)+2)將跳個2個int元素。則(*(a+1)+2)為指向數組a[1]第二個元素a[1][2]的指針,即*(*(a+1)+2)為數組a[1]的第2個元素a[1][2]。

由上可總結得到:

&a的類型為int(*)[4][5];

a+i的類型為int(*)[5];

*(a+i)的類型為int*;

*(*(a+i)+j)的類型為int;

*(a+i)=a[i];

*(*(a+i)+j)=*(a[i]+j)=a[i][j]。

例1:下列關于數組的初始化正確的是(  )?(2012·迅雷)

A.char str[2]={"a","b"}

B.char str[2][3]={"a","b"}

C.char str[2][3]={{'a', 'b'}, {'e', 'f'}, {'g', 'h'}}

D.char str[]={"a", "b"}

解答:B。A、D中應是單引號,C的行與列反了。B中str可以理解為是一個一維數組,str[0]、str[1]是它的元素,初始化即為str[0]="a",str[1]="b",選項B等價于“char str[2][3]={{"a"}, {"b"}};”。

例2:數組int a[3][4];則下列能表示a[1][2]元素值的是(  )。(2012·迅雷)

A.*(*(a+1)+2)

B.*(a+1+2)

C.(&a[0]+1)[2]

D.*(a[0]+1)

解答:A。B中*(a+1+2)是*(a+3),類型為int*,為指向數組a[3]第0個元素a[3][0]的指針,但數組a僅有0~2行元素,越界。

C中a[0]類型為int*,&a[0]等價于a,類型為int(*)[4],則(&a[0]+1)[2]=(a+1)[2],a+1為一指向一維數組a[1]的指針,故(a+1)[2]=*(a+1+2)=a[3],類型為int*,同B。這里我們需要注意對于指針p后面加[]時的轉換,此時p[i]等同于*(p+i)。

D中*(a[0]+1)是a[0][1]。

例3:寫出如下程序片段的輸出結果。

            int a[]={1, 2, 3, 4, 5};
            int *ptr=(int*)(&a+1);
            printf(%d", *(ptr-1);

解答:5。&a+1不是a+1,&a+i類型為int(*)[5],故&a+1使得指針跳過整個數組a的大小(也就是5個int的大小)。所以“int *ptr=(int *)(&a+1);”,經過強制轉換ptr實際是&(a[5]), 也就是a+5,所以ptr-1指向數組a的最后一個元素。故輸出為5。

例4:求下述代碼的輸出結果。(2012·創新工場)

            int a[2][2][3]= { {{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}}};
            int *ptr=(int *)(&a+1);
            printf("%d_%d", *(int*)(a+1), *(ptr-1));

解答:7_12。原理同上題。考察多級指針,一定要明確指針指向的是什么,才能知道它加1后跳過了多少字節。&a類型為int(*)[2][2][3],指向的是a這樣的數組,所以它加1,就會跳過整個數組。&a類型可驗證如下:

            int a[2][2][3]= { {{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}}};
            int(*p)[2][2][3]=&a;

以上代碼編譯通過,可見&a的類型確實是int(*)[2][2][3]。

例5:有以下程序, 程序運行后的輸出結果是 ?(2011·淘寶)

            void main(){
                    char  str[][10]={"China", "Beijing"}, *p=str[0];
                    printf("%s\n", p+10);
            }

解答:Beijing。str是一個2行10列的char數組,一行10個元素,p+10跳過第一行,指向第二行首元素,故輸出Beijing。

主站蜘蛛池模板: 苗栗县| 上虞市| 阜宁县| 寿宁县| 巧家县| 四平市| 凤城市| 宿松县| 漯河市| 余庆县| 葵青区| 南和县| 鄂尔多斯市| 广饶县| 班戈县| 延庆县| 西乌| 偃师市| 颍上县| 乌苏市| 东方市| 武平县| 手游| 兴安县| 福建省| 青河县| 济宁市| 山丹县| 南岸区| 内丘县| 江陵县| 岱山县| 民乐县| 济阳县| 弥勒县| 南华县| 宣化县| 承德市| 平谷区| 星座| 黄骅市|