如果配合http://wenku.baidu.com/view/acf838ef856a561252d36fe3.html去看的話下面的內容比較容易理解
從一道題目說起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> using namespace std; int main() { int ia[3][4]={0,1,2,3,4,5,6,7,8,9,10,11}; typedef int int_array[4]; for (int_array *p =ia;p!= ia+3;++p) { for (int *q=*p;q!=*p+4;++q) { cout<<*q<<" "; } cout<<endl; } return 0; } |
一、先解釋 typedef int int_array[4];
關鍵字typedef用來自定義資料型別,這是所有教材都這樣講的,但不要理解為新建立了一個資料型別,而是將已有的一個型別賦予個新名稱而已,即起一個別名。
具體對這個語句來說,別名就是:int_array。而[4]不屬於名字,而表示一種已有的資料型別,即:給一個大小為4的int陣列取一個別名為int_array。
那如何知道是這樣定義的呢?很簡單。
首先,int a[4];這可是常見的定義格式。再在其前面新增關鍵字typedef,變成 typedef int a[4];最後將陣列名a改為自己想要的一個別名int_array即可。注意:原本的a本意是陣列名,屬於變數範疇,而int_array則是新資料型別名(即別名),本質不一樣了哦。祥見譚浩強的那本經典教材。
二、語句 int_array *p =ia; 的含義
首先,它是一個定義語句,即用自定義的資料型別int_array來定義一個該型別的一個指標變數ia。
ia是一個什麼東東呢?它是一個二維陣列名。
對於一維陣列a,我們有:&a[0]等價於a,即都表示該一維陣列首元地址。
那麼,對於二維陣列這個性質還成立嗎?沒錯,同樣成立。即:
&ia[0]等價於ia。
所以,int_array *p =ia;與int_array *p =&ia[0];是等價的。
但是,&a[0]與&ia[0]含義是不一樣的。前者代表了一維陣列中首元地址,而後者則代表二維陣列中第一行的行首地址。
行首地址與行首元地址,它們的值用cout輸出來那肯定是一樣的。但它們與指標的操作扯上關係時,就不一樣了,前者以行為基本單位,後者以一個元素為基本單位,切記。
現在我們應該明白了,語句int_array *p =ia;的作用是定義p後,並初始化p,即用p來指向二維陣列的第一行(整個這一行),即ia[0],不是第一行的首元ia[0][0]哦。當然,對p這樣初始化是正確的,因為p要指向的正是大小為4的一維陣列,而二維陣列ia的每一行正好就是4個元素。
ia[0]可認為是首行的陣列名。ia[1]、ia[2]類推。
三、語句 ++p 的含義
由上可知,p既然首先指向第一行ia[0],那麼(p+1)不就指向第二行ia[1]嗎?正是如此。
於是外迴圈的終止條件就應該是不存在的第四行,即ia[3],所以終止條件就是:p!= ia+3。
四、語句 int *q=*p; 的含義
如上所述,先定義整型指標q,並初始化為*p。
*p是什麼意思?
前面已得到:p 被初始化為ia,即&ia[0]。那麼*p就代表ia[0]。
即:p儲存的是首行地址,於是,*p就直接代表了該行,即整個這一行。
而前面已經明確的講了ia[0]代表的是首行的陣列名,當然它是一維的。而一維陣列名不就代表了這一行的首元地址嗎?於是,就有
q=*p等價於q=ia[0],也等價於q=&ia[0][0]。
再於是,q指向了一個一維陣列的首元。切記,不能說q指向了一個一維陣列。再再於是,++q就表示&ia[0][1]。當然了,再執行一遍++q,就表示&ia[0][2]了。
再解釋一下*p+4。
剛剛講了*p直接代表了某一行,即ia[0]或ia[1]或ia[2],也講了這些ia[0]或ia[1]或ia[2]就代表該行的行陣列名。當然,都是一維的。
回憶一下,一維陣列名加一個數字代表什麼呢?例如a是一個一維陣列名,a+4表示什麼呢?答案是:&a[4],即該一維陣列的第五個元素a[4]的地址。
所以*p+4表示:p所指那一行的第五個元素,當然這對於本題來說是不存在的,所以就做為內迴圈的終止條件咯。
*q代表什麼呢?
q指向的是一個具體的元素,那麼*q就直接代表了該元素的記憶體空間。那麼,cout它就是輸出該元素的值。
為了能更好地理解陣列指標,與普通指標及二級指標的區別,下面舉例說明一下。
例如:{int a[4][5];int (*p)[5]=a;}這裡a是個二維陣列的陣列名,相當於一個二級指標常量;p是一個指標變數,它指向包含5個int元素的一維陣列,此時p的增量以一維陣列長度為單位;p+i是二維陣列a的i行的起始地址,*(p+2)+3表示a陣列2行3列元素地址(第一行為0行,第一列為0列),*(*(p+2)+3)表示a[2][3]的值。
再看一道題
一道面試題引發的問題,首先要知道[]的優先順序高於*,題目:
char **p,a[6][8]; 問p=a是否會導致程式在以後出現問題?為什麼?
直接用程式說明:
1 2 3 4 5 6 7 8 9 |
#include<stdio.h> void main() { char **p,a[6][8]; p = a; printf("\n"); } |
編譯,然後就會發現通不過,報錯:錯誤 1 error C2440: “=”: 無法從“char [6][8]”轉換為“char **”
於是乎,我看了下《C專家程式設計》裡10.5節—使用指標向函式傳遞一個多維陣列。
方法一,函式是 void fun(int arr[2][3]); 這種方法只能處理2行3列的int型陣列。
方法二,可以省略第一維的長度。函式是 void fun(int arr[][3]);這種方式雖然限制寬鬆了一些,但是還是隻能處理每行是3個整數長度的陣列或者寫成這種形式 void fun(int (*arr)[3]);這是一個陣列指標或者叫行指標,arr和*先結合使得arr成為一個指標,這個指標指向具有3個int型別資料的陣列。
方法三,建立一個一維陣列,陣列中的元素是指向其他東西的指標,也即二級指標。函式是 int fun(int **arr);這種方法可以動態處理各行各列不一樣長度的資料。
注意:只有把二維陣列改成一個指向向量的指標陣列的前提下才可以這麼做!比如下面的程式可以正常輸出abc:
注意:陣列指標又叫指向一維陣列的指標,資料型別 (*指標變數)[一維陣列維數] eg:int (*p)[4];指向指標的指標(二級指標) eg int **pp;
pp就是行地址,*pp就是列地址;pp+1還是行地址,*pp+1還是列地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> using namespace std; void test(char **ptr) { for(;(strcmp(*ptr,"NULL"))!=0;ptr=ptr+1) cout << *(ptr) << endl; } int main() { char *p[] = {"abcsdad", "def", "ghiffaghjjkjkkk","NULL"}; test(p); return 0; } |
又如:
///////////////!!!!!!!!!!!!!!!!!!!!!!!!!!!!二維陣列訪問方式如下!!!!!!!////////////////////////////////////////////////////
(1) 方法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
int b[3][3] ={{1,2,3},{4,5,6},{7,8,9}}; int j,k,(*q)[3]; q=b; for(j=0; j<3; j++) { for(k=0; k<3; k++) { //cout << *(*(b+j)+k) << " "; cout << *(*(q+j)+k) << " "; } } cout << endl; char name[][17]={"Follow me","BASIC","Great Wall","FORTRAN","Computer desighn","ok"}; char (*p)[17]; for(p=name;strcmp(*p,"ok")!=0;p++) { cout<<*p<<endl; } |
(2)方法二
1 2 3 4 5 6 7 |
char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer desighn"}; char **p; int i; for(i=0;i<5;i++) {p=name+i; printf("%s\n",*p); } |
還可以改改:
1 2 3 4 5 6 7 8 9 10 11 |
char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer desighn"}; char **p; int i; for(i=0;i<sizeof(name)/4;i++) {p=name+i; //name+i是行指標,name[i]是列指標,&name[i]是行指標,而p本身也是行指標。 //等價於p=&name[i]; //cout<<sizeof(name)<<endl; //cout<<(name[i])<<endl;//輸出的結果和*p一樣,但表示的本質不一樣,name[i]本身就是列指標,p是行指標,*p後就變成了列指標 printf("%s\n",*p); } |
與下面的比較一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; #define dim(x) sizeof(x) / sizeof(x[0]) void main() { char name[][17] = { "Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer desighn", }; char (*p)[17] = name; int nCount = dim(name); while (nCount-- > 0) { cout << *p++ << endl; } } |
結果都是一樣的,但我們最好習慣用char*name[]這樣指標陣列的定義,因為char name[][17]雖然行可以任意,但列卻不能任意。只有指標陣列才能實現任意的行和列。
還比較一下對int 型的二維陣列的輸出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; #define dim(x) sizeof(x) / sizeof(x[0]) #define dam(x) sizeof(x) / sizeof(int) void main() { int aArray[]={1,2,3,4}; int bArray[]={5,6,7,8,9,10}; int cArray[]={11,12,14,15,16,17,18,19,20,21,22}; int *name[] = { aArray, bArray, cArray, }; int **p = name; int aColum=dam(aArray); int bColum=dam(bArray); int cColum=dam(cArray); int nCount=sizeof(name)/4; int i ,flag; for(i=0;i<nCount;i++) { int *j=name[i]; flag=i; switch(flag) { case 0: for(;j<name[i]+aColum;j++)//name[i]列指標,而name+i是行指標,而int *指向的是列指標,而name[i]+j還是列指標 cout<<*j<<" "; cout<<endl; break; case 1: for(;j<name[i]+bColum;j++) cout<<*j<<" "; cout<<endl; break; case 2: for(;j<name[i]+cColum;j++) cout<<*j<<" "; cout<<endl; break; } } } |
(3)方法三
用一維指標遍歷二維陣列
1 2 3 4 |
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11}; int *p; for(p=a[0];p<a[0]+12;p++) cout<<*p<<endl; |
看看 對一維陣列的訪問
1 2 3 4 5 6 7 |
int aq[12]={1,2,3,4,5}; int (*p)[12];//陣列指標也即行地址 p=&aq;//行地址 cout<<p<<endl; cout<<*p<<endl; for(int i=0;i<5;i++) cout<<*(p[0]+i)<<endl;//注意p是行地址,套上[]這個就是列地址了p[i] |
與下面的比較
1 2 3 4 5 |
int aq[12]={1,2,3,4,5}; int *p;//列地址 p=aq; for(int i=0;i<5;i++) cout<<*(p+i)<<endl; |
也可定義這樣的,只要記住指標陣列中的內容是指標即地址就行
1 2 3 4 |
int a[]={12,12,34,5}; int b[]={13,17}; int c[]={123,56,4}; int *ia[3]={a,b,c}; |
在《C專家程式設計》10.3節的小啟發裡講的很透徹:(以下這段文字及對比一定要認真分析!)
很有用的二維陣列知識,看看二維陣列!!!!!!!!!!!!!!!!!
1 2 3 4 5 6 7 |
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11}; cout<<a+1<<endl; cout<<&a[1]<<endl; cout<<a[1]<<endl; cout<<*(a+1)<<endl; cout<<&a[1][0]<<endl; |
這五句輸出的結果值是一樣的,都是地址值,但是表示的含義不一樣。
所以我總結的二維陣列一些性質:
a+1表示行地址 而前面加了個*號就表示列地址了 如*(a+1)。
a[1]表示列地址,前面加個&號就表示行地址了,如&a[1].
&a[1][0]表示列地址,注意當* & [] 這三個符號組合出現的次數為偶數次的話就表示真正的內容值,出現奇數次就表示該內容的地址。
但是請注意& 和()不能組合到一起,所以&(a+1)是錯誤的。
行地址前加*就表示列地址了,列地址前加*號就表示取內容值,而列地址前加&表示行地址了。所以只有行地址前加*號才能變列地址,加&報錯,而列地址前加&變行地址,
加*就取得值了,所以對二維陣列來說只有列地址前加*號,才能得到內容。如果是行地址,這先要轉化為列地址,才能得內容。而行地址套上[]符號就變成列地址瞭如
a->行地址 a[0]->列地址.總之一點只有當成為列地址後再在前面加*才能取到真正的內容.
看看如下內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; void main() { char name[][17] = { "Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer desighn", "ok"}; char (*p)[17] = name; for(;(**p)!='\0';p++) cout<<*p<<endl; } |
對這個的改進,不要輸出最後的亂碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; #define dim(x) sizeof(x) / sizeof(x[0]) void main() { char name[][17] = { "Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer desighn", "ok"}; char (*p)[17] = name; int nCount = dim(name); while (nCount-- > 0) { cout << *p++ << endl; } } |
就不會輸出最後的亂碼了,哈哈哈哈!!!!!!!!!!!!!!!!!!!!!!
再看看輸出int的二維陣列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include<iostream> #include<string.h> #include<stdio.h> using namespace std; #define dim(x) sizeof(x) / sizeof(x[0]) void main() { int name[][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, }; int (*p)[4] = name; int nRow = dim(name);//3行 int nColum=(sizeof(name[0])/sizeof(int));//4列 for (int j=0;j<nRow;j++) {for(int i=0;i<nColum;i++) cout << *(*(p+j)+i);//因為p是陣列指標,是行指標,而p+j還是行指標,*p後就是列指標,而*p+i還是列指標 cout<<endl; } } |
陣列和指標引數是如何被編譯器修改的?
“陣列名被改寫成一個指標引數”規則並不是遞迴定義的。陣列的陣列會被改寫成“陣列的指標”,而不是“指標的指標”:
實參 所匹配的形參
陣列的陣列 char c[8][10]; char (*)[10]; 陣列指標
指標陣列 char *c[10]; char **c; 指標的指標
陣列指標(行指標) char (*c)[10]; char (*c)[10]; 不改變
指標的指標 char **c; char **c; 不改變
下面再看一個網友的一段分析相當給力的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include "stdafx.h" #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { int arr1[3]; int arr2[3]; int arr3[3]; int * ptr; // ptr1是一個指向 int [3] 的指標,即ptr的型別和&arr1的型別是一樣的,注意:arr1指向的記憶體區域定長 int ptr1[3][3]={{1,2,3},{1,2,3},{1,2,3}}; // ptr2是一個指向 int * 的指標,即ptr2的型別和&ptr是一樣的,注意:ptr指向的記憶體區域不定長 int * ptr2[3]={arr1,arr2,arr3}; // ptr3是一個指向 int [3] 的指標,即ptr3的型別和&arr1的型別是一樣的,注意:arr1指向的記憶體區域定長 int(* ptr3)[3]=&arr1; ptr3=ptr1; // 沒錯,他們的型別相同 // ptr3=ptr2;//error 無法從“int *[3]”轉換為“int (*)[3] // ptr4是一個指向 int * 的指標,即ptr4的型別和&ptr是一樣的,注意:ptr指向的記憶體區域不定長 int ** ptr4; //ptr4=&arr1; //error 無法從“int (*)[3]”轉換為“int ** ptr4=ptr2; // 沒錯,他們的型別相同 //ptr4=ptr3; // error 無法從“int (*)[3]”轉換為“int ** return 0; } |
再看一個有意思的,看看對指標的掌握情況,“行地址”和“列地址”的區別
記住 一維陣列的陣列名是列地址,二維陣列名是行地址,雖然陣列名都是地址,但本質不一樣,行地址始終要轉化為列地址,才能得到內容值
轉化規則:行->列:在行前加*號,列->轉化->行,在列前加&。
1 2 3 4 5 6 7 8 9 10 |
int a[]={12,34,54}; int aa[][3]={{13,4,5},{5,6,3}}; int (*pp)[3];//pp為行地址,如果是int **rr;則rr為列地址 int **rr; pp=a;//為什麼這樣不可以,因為一維陣列名為列地址,而pp為行地址,本質不一樣,按照上面的轉化規則,加上&就行 pp=&a;//為什麼這樣就行 pp=aa;//都是行地址 pp=&aa;//錯誤,為什麼這樣就不行,不能從int(*)[2][3]轉化為int(*)[3] pp=&aa[0];//為什麼這樣可以,aa[0]是列地址加上&就是行地址 |
再看下面
1 2 3 4 5 6 |
int a[]={12,34,54}; int *r=a;//int *為列指標, int **rr;//int **好像為行指標,但是如 rr=&a不行,所以rr不是行地址 rr=&r; for(int i=0;i<sizeof(a)/4;i++) cout<<*(*rr+i)<<endl;//*rr就等於r |
五、總結
陣列指標和二維陣列有關,你可以到定義了陣列指標後將二維陣列名賦值給他;指標陣列和指向指標的指標有關,你可以將兩者賦值。