天天看點

指向多元數組的指針變量

8.4.3 指向多元數組的指針變量

指向多元數組的指針變量

1 多元數組的指針

多元數組可以看作是一維數組的延伸,多元數組的記憶體單元也是連續的記憶體單元。換句話說,c語言實際上是把多元數組當成一維數組來處理的。下面以二維數組為例說明這個概念。

比如,現在有一個int型的二維數組a[3][4],計算機認為這是一個一維的數組a[3],數組的三個元素分别是a[0],a[1]和a[2]。其中每個元素又是一個一維數組,例如a[0]又是一個包含a[0][0],a[0][1],a[0][2]和a[0][3]共4個元素的數組。如果我們要引用數組元素a[1][2],可以首先根據下标1找到a[1],然後在a[1]中找到第3個元素a[1][2]。假設數組a的起始位址為ff10,對應的記憶體情況如圖:

ds:ff10

a[0][0] ds:ff12

a[0][1] ds:ff14

a[0][2] ds:ff16

a[0][3]

ds:ff18

a[1][0] ds:ff1a

a[1][1] ds:ff1c

a[1][2] ds:ff1e

a[1][3]

ds:ff20

a[2][0] ds:ff22

a[2][1] ds:ff24

a[2][2] ds:ff26

a[2][3]

指向多元數組的指針變量

可以看到,二維數組a[3][4]的12個元素在記憶體中是順序排列的,是以&a[0][0]是數組第一個元素的位址,為ff10。

a[0][0],a[0][1]這些數組元素都有具體的記憶體單元值。但是,a[0],a[1]和a[2]這三個數組元素不占用記憶體單元,它們隻是代号(其實就是一個指針常量),是虛拟的東西。a[0]本身又是一個數組,包含a[0][0],a[0][1],a[0][2]和a[0][3],那麼a[0]作為數組名稱,按照c語言的文法,a[0]就是數組首位址,也就是數組第一個元素的位址,即a[0][0]的位址 ff10。同理,a[1]是第一個元素a[1][0]的位址,即ff18;a[2]是第一個元素a[2][0]的位址,即ff20。

相對于a[0],a[1]和a[2]來說,a也是數組的名稱,是數組的首位址,即ff10。a是指向數組首位址的指針,a+1代表什麼?a是數組名稱,a[0],a[1]和a[2]是元素,那麼a+1就是&a[1](如果一個一維數組int b[3],那麼b+1是不是&b[1]?),即第二行元素的首位址,指針值為ff18。同理,a+2就是&a[2],指針值為ff20。

也可以換個角度去了解。a是數組首位址,指向二維數組第一行的首位址;計算a+1時,指針a跳過的是整個a[0],a+1指向二維數組第二行的首位址。指針a在做加法時,跳過的是一行元素。

a[1]+1代表什麼?指針a[1]是一維數組a[1]中第一個元素a[1][0]的位址ff18。對于一維數組a[1]來說,指針a[1]+1指向了a[1]中第二個元素a[1][1]的位址,即ff1a。這裡指針加法的機關是一列,每次跳過a[1]中的一個數組元素。

*a代表什麼?*a也就是*(a+0),數組第一個元素的值,即a[0]。前面講過,a[0]是一個代号,它不是一個具體元素的值,而是内嵌的一維數組a[0]的名字,a[0]本身也是一個指針值。同理,*(a+1)就是a[1],*(a+2)就是a[2]。

如果要通路二維數組裡第i行第j列的某個元素,可以直接引用a[i-1][j-1],也可以使用指針的方法。指向第i行第j列元素的指針為a[i]+j,或者*(a+i)+j,是以,間接用指針引用二維數組裡第i行第j列的某個元素,可以表示為*(a[i]+j)或者*(*(a+i)+j)。

特别需要指出的是,a[i]不是一個具體的數組元素,它是一個代号,實際上是一個指針值,代表i+1行裡中第一個元素的首位址。是以&a[i]不能直接了解為a[i]的實體位址,a[i]不是變量,不存在實體位址;&a[i]代表第i+1行的行指針值,和a+i等價。

同樣,*a[i]也不能了解為對一個數組元素進行指針運算符操作。a[i]是一個指針值,是i+1行中第一個元素的首位址,那麼*a[i]就是i+1行裡中第一個元素的值,即a[i][0]。

總之,二維數組a[i][j]的指針由于出現了虛拟的a[i]這個概念,是以對于初學者很不好了解,請讀者仔細消化了解。下表是上述概念的綜合:

指向多元數組的指針變量

main()

{

int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12},i,j;

/*列印a*/

printf("\na=%x", a);

/*列印a+i, &a[i]*/

for(i=0;i<3;i++)

printf("\na+%d=%x, &a[%d]=%x", i,a+i,i,&a[i]);

/*列印a[i], *(a+i)*/

printf("\na[%d]=%x, *(a+%d)=%x", i,a[i],i,*(a+i));

/*列印a[i]+j, *(a+i)+j, &a[i][j]*/

for(j=0;j<4;j++)

printf("\na[%d]+%d=%x, *(a+%d)+%d=%x, &a[%d][%d]=%x",

i,j,a[i]+j, i,j,*(a+i)+j, i,j,&a[i][j]);

/*列印**(a+i), *a[i]*/

printf("\n**(a+%d)=%x, *a[%d]=%x", i, **(a+i),i, *a[i]);

/*列印*(a[i]+j), *(*(a+i)+j), a[i][j]*/

printf("\na[%d]+%d=%d, *(a+%d)+%d=%d, &a[%d][%d]=%d",

i,j,*(a[i]+j), i,j,*(*(a+i)+j), i,j,a[i][j]);

}

程式運作的結果為(列印的位址值是動态配置設定的,不同環境下可能不同;但數組的位址偏移量是相同的):

a=ffbe

a+0=ffbe, &a[0]=ffbe

a+1=ffc6, &a[1]=ffc6

a+2=ffce, &a[2]=ffce

a[0]=ffbe, *(a+0)=ffbe

a[1]=ffc6, *(a+1)=ffc6

a[2]=ffce, *(a+2)=ffce

a[0]+0=ffbe, *(a+0)+0=ffbe, &a[0][0]=ffbe

a[0]+1=ffc0, *(a+0)+1=ffc0, &a[0][1]=ffc0

a[0]+2=ffc2, *(a+0)+2=ffc2, &a[0][2]=ffc2

a[0]+3=ffc4, *(a+0)+3=ffc4, &a[0][3]=ffc4

a[1]+0=ffc6, *(a+1)+0=ffc6, &a[1][0]=ffc6

a[1]+1=ffc8, *(a+1)+1=ffc8, &a[1][1]=ffc8

a[1]+2=ffca, *(a+1)+2=ffca, &a[1][2]=ffca

a[1]+3=ffcc, *(a+1)+3=ffcc, &a[1][3]=ffcc

a[2]+0=ffce, *(a+2)+0=ffce, &a[2][0]=ffce

a[2]+1=ffd0, *(a+2)+1=ffd0, &a[2][1]=ffd0

a[2]+2=ffd2, *(a+2)+2=ffd2, &a[2][2]=ffd2

a[2]+3=ffd4, *(a+2)+3=ffd4, &a[2][3]=ffd4

**(a+0)=1, *a[0]=1

**(a+1)=5, *a[1]=5

**(a+2)=9, *a[2]=9

a[0]+0=1, *(a+0)+0=1, &a[0][0]=1

a[0]+1=2, *(a+0)+1=2, &a[0][1]=2

a[0]+2=3, *(a+0)+2=3, &a[0][2]=3

a[0]+3=4, *(a+0)+3=4, &a[0][3]=4

a[1]+0=5, *(a+1)+0=5, &a[1][0]=5

a[1]+1=6, *(a+1)+1=6, &a[1][1]=6

a[1]+2=7, *(a+1)+2=7, &a[1][2]=7

a[1]+3=8, *(a+1)+3=8, &a[1][3]=8

a[2]+0=9, *(a+2)+0=9, &a[2][0]=9

a[2]+1=10, *(a+2)+1=10, &a[2][1]=10

a[2]+2=11, *(a+2)+2=11, &a[2][2]=11

a[2]+3=12, *(a+2)+3=12, &a[2][3]=12

從輸出的結果可以看到,&a[i]和a[i]的輸出值都是一樣的。第i+1行的首位址是ffbe,第i+1行、第1列元素的首位址也是ffbe,這并不沖突,因為二者都是一個指針值。但是,二者的含義是截然不同的,指針加法的偏移量也是不同的:前者加1指針跳到下一行;後者加1指針跳到下一列。

2 指向多元數組的指針變量

可以定義指針變量,指向多元數組及其元素。下面仍以二維數組為例進行說明。

二維數組在記憶體中是連續的記憶體單元,我們可以定義一個指向記憶體單元起始位址的指針變量,然後依次撥動指針,這樣就可以周遊二維數組的所有元素。例如:

float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0},*p;

int i;

for(p=*a;p<*a+2*3;p++)

printf("\n%f ",*p);

結果輸出:

1.000000

2.000000

3.000000

4.000000

5.000000

6.000000

在上述例子中,定義了一個指向float型變量的指針變量。語句p=*a将數組第1行,第1列元素的位址賦給了p,p指向了二維數組第一個元素a[0][0]的位址。根據p的定義,指針p的加法運算機關正好是二維數組一個元素的長度,是以語句p++使得p每次指向了二維數組的下一個元素,*p對應該元素的值。

根據二維數組在記憶體中存放的規律,我們也可以用下面的程式找到二維數組元素的值:

int i,j;

printf("please input i =");

scanf("%d", &i);

printf("please input j =");

scanf("%d", &j);

p=a[0];

printf("\na[%d][%d]=%f ",i,j,*(p+i*3+j));

輸入下标i和j的值後,程式就會輸出a[i][j]的值。這裡我們利用了公式p+i*3+j計算出了a[i][j]的首位址。計算二維數組中任何一個元素位址的一般公式如下:

二維數組首位址+i*二維數組列數+j

請讀者分析二維數組的記憶體配置設定情況,自行證明上述公式。

上述的指針變量指向的是數組具體的某個元素,是以指針加法的機關是數組元素的長度。我們也可以定義指向一維數組的指針變量,使它的加法機關是若幹個數組元素。定義這種指針變量的格式為:

資料類型 (*變量名稱)[一維數組長度];

說明:

(1) 括号一定不能少,否則[]的運算級别高,變量名稱和[]先結合,結果就變成了後續章節要講的指針數組;

(2) 指針加法的記憶體偏移量機關為:資料類型的位元組數*一維數組長度。

例如,下面的語句定義了一個指向long型一維、5個元素數組的指針變量p:

long (*p)[5];

指針變量p的特點在于,加法的機關是4*5個位元組,p+1跳過了數組的5個元素。

上述指針變量的特點正好和二維數組的行指針相同,是以我們也可以利用指針變量進行整行的跳動:

float a[2][3]={1.0,2.0,3.0,4.0,5.0,6.0};

float (*p)[3];

p=a;

printf("\na[%d][%d]=%f ",i,j,*(*(p+i)+j));

(1) p定義為一個指向float型、一維、3個元素數組的指針變量p。

(2) 語句p=a将二維數組a的首位址賦給了p。根據p的定義,p加法的機關是3個float型單元,是以p+i等價于a+i,*(p+i)等價于*(a+i),即a[i][0]元素的位址,也就是該元素的指針。

(3) *(p+i)+j等價于& a[i][0]+j,即數組元素a[i][j]的位址;

(4) *(*(p+i)+j)等價于(*(p+i))[j],即a[i][j]的值。

p在定義時,對應數組的長度應該和a的列長度相同。否則編譯器檢查不出錯誤,但指針偏移量計算出錯,導緻錯誤結果。

例 8-1 輸入一組整數(數量不超過100),統計其中偶數和奇數的總和(用指針方式)。

可以使用一個int型數組儲存輸入的一組整數,然後利用指針依次周遊數組的元素,統計偶數和奇數的總和。a[i]和*(a+i)是等價的。

偶數和奇數的辨識可以用整數%2的方法,%2為0則為偶數,否則為奇數。

int a[100],i,j,nlen, nevencount=0, noddcount=0,*p=a;

/*輸入整數的個數*/

printf("\nplease input count of integers: ");

scanf("%d",&nlen);

/*依次輸入一組整數*/

printf("please input integers: ");

for(i=0;i<nlen;i++)

scanf("%d",p+i); /*等價于scanf("%d", &a[i])*/

/*統計偶數和奇數的個數*/

for(i=0;i<nlen;i++,p++)

if(*p%2==0)

nevencount++;

else

noddcount++;

/*列印輸出結果*/

printf("there are %d even numbers, %d odd numbers. ",nevencount, noddcount);

程式的運作結果為:

please input count of integers: 6

please input integers: 1 3 4 65 8 66

there are 3 even numbers, 3 odd numbers.

上面的例子中,指針變量指向了數組a的首位址,p+i即為數組元素a[i]的指針,是以scanf語句中可以使用p+i作為參數。

統計偶數和奇數時,循環每次使p指向數組的下一個元素,循環中*p的值是數組對應元素的值。注意nevencount和noddcount一定要初始化為零,否則它們都是随機數。

例 8-2 編寫一個函數,可以實作2*3 3*4矩陣相乘運算(用指針方式)。

使用一個2×3的二維數組和一個3×4的二維數組儲存原矩陣的資料;用一個2×4的二維數組儲存結果矩陣的資料。

結果矩陣的每個元素都需要進行計算,可以用一個嵌套的循環(外層循環2次,内層循環4次)實作。

根據矩陣的運算規則,上述矩陣每個元素的計算方法為:

是以,内層循環裡可以再使用一個循環,累加得到每個元素的值。一共使用三層嵌套的循環。

int i,j,k, a[2][3],b[3][4],c[2][4];

/*輸入a[2][3]的内容*/

printf("\nplease input elements of a[2][3]:\n");

for(i=0;i<2;i++)

for(j=0;j<3;j++)

scanf("%d", a[i]+j); /* a[i]+j等價于&a[i][j]*/

/*輸入b[3][4]的内容*/

printf("please input elements of b[3][4]: \n");

scanf("%d", *(b+i)+j); /* *(b+i)+j等價于&b[i][j]*/

/*用矩陣運算的公式計算結果*/

*(c[i]+j)=0; /* *(c[i]+j)等價于c[i][j]*/

for(k=0;k<3;k++)

*(c[i]+j)+=a[i][k]*b[k][j];

/*輸出結果矩陣c[2][4]*/

printf("\nresults: ");

printf("\n");

printf("%d ",*(*(c+i)+j)); /* *(*(c+i)+j)等價于c[i][j]*/

運作結果為:

please input elements of a[2][3]:

1 2 3

4 5 6

please input elements of b[3][4]:

1 2 3 4

5 6 7 8

9 10 11 12

results:

38 44 50 56

83 98 113 128

上面的例子複習了二維指針的各種表示方法,在實際應用中可以靈活使用。

繼續閱讀