參考http://blog.csdn.net/touch_2011/article/details/6966980 1、概述 指標數組:指標數組可以說成是”指標的數組”,首先這個變數是一個數組,其次,”指標”修飾這個數組,意思是說這個數組的所有元素都是指標類型,在32位系統中,指標佔四個位元組。 數組指標:數組指標可以說成是”數組的指標”,首先這個變數是一個指標,其次,”數組”修飾這個指標,意思是說這個指標存放著一個數組的首地址,或者說這個指標指向一個數組的首地址。
根據上面的解釋,可以瞭解到指標數組和數組指標的區別,因為二者根本就是種類型的變數。 2、數組指標(指向數組的指標) (1)數組在記憶體中的表示
建立一個數組就是在記憶體裡面開闢一塊連續的空間,比如int a[4];就是在記憶體裡面開闢了一個大小為4*sizeof(int)位元組的記憶體空間。二維數組是特殊的一維數組。
先來看一段代碼:
void main() { int a[2][2]={1,2,3,4};//這是一個2*2的二維數組 int (*p)[2];//數組指標 p=a;//令p指向數組a }
注意到代碼中這句話:int (*p)[2];這裡的p是一個數組指標變數。
a中各個元素在記憶體中是這樣存放的:
(2)理解數組名和數組指標變數
現在我們思考a,a[0],a[1],p,a+1,a[0]+1,p+1到底是什麼。
分析:
1. a是一個數組名,類型是指向一維數組的指標,不是變數,a的值是指標常量,即不能有a++或者a=p這些操作。a指向這塊連續空間的首地址,值是&a[0][0]。
2. a[0]是一維數組名,類型是指向整型的指標,值是&a[0][0],這個值是一個常量。
3. a[1]是一維數組名,類型是指向整型的指標,值是&a[1][0],這個值是一個常量。
4. p是一個數組指標變數,指向一維數組的指標變數,值是&a[0][0]。可以執行p++;p=a等操作。
5. a+1表示指向下一行元素,也可以理解為指向下一個一維數組。
6. *(a+1)是取出第一行的首地址。
7. a[0]+1是指向第0行第1個元素,也可以理解為指向一維數組a[0]的第一個元素。
8. p+1同a+1
9. *(p+1)和*(a+1)
10. 雖然a跟a[0]值是一樣,但類型不一樣,表示的意義不一樣。通過分析就不難理解為什麼((a+i)+j)和a[i][j]等效了。
既然pa是一個指標,存放一個數組的地址,那麼在我們定義一個數組時,數組名稱就是這個數組的首地址,那麼這二者有什麼區別和聯絡呢。
int a[2];
a是一個長度為2的整型數組,a是這個數組的首元素首地址。既然a是地址,p是指向數組的指標,那麼能將a賦值給p嗎。答案是不行的。因為a是數組首元素首地址,p存放的卻是數組首地址,a是int*類型,a+1,a的值會實實在在的加1,而p是int[2]類型的,p+1,p則會加2,雖然數組的首地址和首元素首地址的值相同,但是兩者操作不同,所以類型不符不能直接賦值,但是可以這樣:p = &a,p相當與二維數組的行指標,現在它指向a[2]的地址。 (3)指標是數組的迭代器
#include<stdio.h> #define M 2 #define N 3 int main() { int a[M][N]={1,2,3,4,5,6}; int *start=&a[0][0]; int * const end=start+M*N; for(;start!=end;start++) printf("%-5d",*start); putchar('\n'); return 0; }
理解這段代碼,用指標遍曆一個二維數組,是不是很像C++標準庫裡面vector的迭代器。注意這裡只用了一個for迴圈,這也可以說明二維數組其實就是特殊的一維數組。 (4)數組名與數組指標變數的區別
從(2)中的分析中得出數組名是指標,類型是指向元素類型的指標,但值是指標常量,聲明數組時編譯器會為聲明所指定的元素數量保留記憶體空間。數組指標是指向數組的指標,聲明指標變數時編譯器只為指標本身保留記憶體空間。
看看這個代碼:
#include<stdio.h> void main() { int a[2][2]={1,2,3,4};//這是一個2*2的二維數組 int (*p)[2];//數組指標 p=a;//令p指向數組a printf("%d\n%d\n",sizeof a,sizeof p); }
注意當sizeof用於變數時返回這個變數佔用的實際空間的大小。當sizeof用於數組名時,返回整個數組的大小(這裡的大小指佔用的位元組數)。p是一個指標變數,這個變數佔用四個位元組。而a是數組名,所以sizeof a返回數組a中的全部元素佔用的位元組數。瞭解了sizeof,看下面這段代碼輸出什麼
#include<stdio.h> void main() { int a[2][2]={1,2,3,4};//這是一個2*2的二維數組 int (*p)[2];//數組指標 p=a;//令p指向數組a printf("%d\n%d\n",sizeof(a+1),sizeof(p+1)); printf("%d\n%d\n",sizeof(a+0),sizeof(p+0)); }
從結果中看出,a在做+運算時是轉化成了指標變數,此時a+i的類型是一個指標變數,而不是一個數組名。但a[i]是一個一維數組的數組名,sizeof(a[0])的值是8。
現在再來看一段代碼:
#include<stdio.h> void f(int a[][2]) { printf("%d\n",sizeof a); } void main() { int a[2][2]={1,2,3,4};//這是一個2*2的二維數組 printf("%d\n",sizeof a); f(a); }
解釋:這是因為傳參的時候數組名轉化成指標變數,注意到函數f中f(int a[][2])這裡並不需要指定二維數組的長度,此處可以改為int (*a)[2]。所以傳過來的就是一個數組指標變數。
總結: 數組名的類型是指向元素類型的指標,值是指標常量。(a+1)的類型是一個指標變數。把數組名作為參數傳遞的時候實際上傳遞的是一個指標變數。sizeof對變數和數組名操作時返回的結果會不一樣。數組指標是指向數組的指標,其值可以是變數。 2、指標數組(存放指標的數組) (1)認識指標數組
一個存放int類型的數組稱為整型數組,那麼存放指標的數組就叫指標數組。
#include<stdio.h> void main() { int i=1,j=2; //p先跟[]結合,然後再跟*結合 int *p[2];//指標數組,存放指標的數組 p[0]=&i; p[1]=&j; printf("%d",sizeof(p)); }
此例數組p就兩個元素,p[0]是指向i的指標,p[1]是指向j的指標。這兩個指標都是int型指標,所以p是存放int型指標的數組。sizeof(p)返回數組佔用的總空間,所以程式輸出是8。 (2)深入瞭解
首先先定義一個指標數組,既然是數組,名字就叫arr。
char *arr[4] = {"hello", "world", "shannxi", "xian"};//arr就是我定義的一個指標數組,它有四個元素,每個元素是一個char *類型的指標,這些指標存放著其對應字串的首地址。
當一個變數出現左右都出現一個運算子時,沒有記住運算子優先順序的人就會糾結arr變數到底跟哪一個運算子先結合。如果是自己定義一個指標數組,搞不清楚運算子的優先順序,那麼就加上小括弧(),比如定義一個指標數組,可以寫成char *(arr[4]),不過在定義之前一定要清楚自己定義的變數,如果目的是一個數組,那就把arr[4]括起來,如果是一個指標,就把*arr括起來。如果是看到一段這樣的代碼,可以從他的初始化來分別它是數組還是指標,很明顯,我這定義的是一個數組,如果是指標,會用NULL來初始化。
這個指標數組有多大呢。答案是16個位元組,因為它是一個指標數組。每當出現這些問題時,腦子裡一定要第一時間反應出記憶體映像圖。
這裡最左側一列是一個很簡陋但能說明意思的記憶體配置圖,一般情況下,從棧區到代碼區,是從高地址到低地址。棧向下增長,堆向上增長。
arr[4]是一個在主函數定義的數組。把它對應到對應到記憶體中,arr是一個在棧區,有四個元素的數組,而每一個數組又是一個指標,所以說它的四個元素各佔四個位元組,所以變數arr的大小是16個位元組。
初始化arr的{“hello”, “world”, “shannxi”, “xian”};的這四個存在在記憶體中,只是跟arr這個變數不在同一段空間,它們被分配在唯讀資料區,數組arr[4]的四個指標元素,分別存放著這四個字串的首地址,想象一下,從棧區有四隻無形的手指向資料區的空間。arr+1會跳過四個位元組,。也就是一個指標的大小
這就相當與定義char *p1 = “hello”,char *p1 = “world”,char *p3 = “shannxi”, char *p4 = “xian”,這是四個指標,每個指標存放一個字串首地址,然後用arr[4]這個數組分別存放這四個指標,就形成了指標數組。 (3)指標數組排序
指標數組的排序非常有趣,因為這個數組中存放的是指標,通過比較指標指向的字串的字典序,排序這些指標。函數實現如下:
void sort(char **pa, int n)//冒泡排序{ int i, j; char *tmp = NULL; for(i = 0; i < n-1; i++){ for(j = 0; j < n-1-i; j++){ if(strcmp(*(pa+j), *(pa+j+1)) > 0){ tmp = *(pa + j); *(pa + j) = *(pa + j + 1); *(pa + j + 1) = tmp; } } }}
3、聲明返回數組指標的函數
1、數組不能被拷貝,函數不能返回數組,只能返回數組的指標或者引用。
typedet int arr[10]; //arr是類型別名,表示的類型含有10個整數的數組 using arr=int[10]; //與上面等效
int arr[10]; //arr是一個含有10個整數的數組 int *p1[10]; //p1和[ ]的結合度要高,所以p1首先是一個10個大小的數組,即這裡的p是一個數組名,其次是與*結合,表示數組中每個元素都是整型指標 int (*p)[10]=&arr; //由於括弧p首先與*結合,即p是一個指標,然後才與[ ]結合,說明p指標指向大小為10的一個數組,且p指標初始化,p是一個數組指標
2、聲明返回數組的指標
①返回函數指標的函數形式如下:
Type(*function(parameter_list))[dimension] int (*func(int i))[10];
func(int i) 意味著調用func函數時需要一個int類型的實參 (*func(int)) 意味著對函數調用的結果是一個指標 (*func(int i))[10] 意味著對函數調用結果的指標是一個指向數組,數組大小為10 int (*func(int))[10] 意味著對函數調用結果的指標指向的數組中,數組元素都是整型的。
但如果我們使用類型別名,聲明一個返回數組指標的函數的函數就看起來要優雅多了
using arrT=int[10]; //arr是大小為10的數組的一個別名,即相當於把int[10]看成是一種類型,該類型是一個整型數組,大小為10 arrT* func(int i);
3、使用尾置傳回型別
任何函數的定義都能使用尾置返回,它在形參列表後以一個 -> 符號開頭,為表示函數真正的傳回型別在形參列表之後,我們在最前放置一個auto,比如:
auto func(int i)->int(*)[10]; //func接受一個int類型的實參,返回一個指標,指標指向10個整數的數組
於是現在就可以清楚地看到func函數返回的是一個指標,該指標指向10個整數的數組。 4、使用decltype
如果知道函數返回的指標指向哪個數組還可以使用decltype關鍵字聲明傳回型別,比如一個典型的例子:
#include<iostream> int odd[] = { 1, 3, 5, 7, 9 }; int even[] = { 0, 2, 4, 6, 8 }; decltype(odd)* func(int i) { return (i & 1) ? &odd : &even; } int main() { std::cout << (*func(1))[2]<< std::endl; std::cout << (*func(2))[2] << std::endl; }
deltype的作用只是負責推匯出運算式的類型,並不負責將數群組類型轉換成對應的指標,在這裡是推匯出odd的類型是一個大小為5的數組,但只是數組這種類型,要讓成為一個指向這個類型的的指標,和正常一樣加上*即可。