1. 程式人生 > >C語言中的指針和數組

C語言中的指針和數組

一個 數組和指針 ray 能夠 基本結構 軟件 line clas com

下面的內容節選自由我所執筆的會議記錄。對於本文的不足之處,各位可以提出自己的看法。

Q1:指針和數組到底是怎麽一回事?

A:指針和數組有本質的不同。指針就是一個內存地址,在32位系統下,一個指針永遠占4個字節;數組是一塊連續的內存空間,我們從一個已定義的數組中可以獲得數組大小以及這塊連續內存空間的起始地址。這個起始地址即數組首元素的地址,更具體的說是數組中首個元素的首地址。

在C語言中,只有一維數組。但是,當一個一維數組的元素是仍然是一維數組時,就形成了所謂的一維數組。如何印證這一點?二維數組在內存中的存儲方式仍然是按照一維數組那樣線性存儲的,只不過這個數組的元素按照另一個一維數組線性存儲。多維數組照此類推。

Q2:我還能獲得關於數組和指針更詳細的說明嗎?

A:當然可以。

我們通常會這樣使用一個指針:

int a[5];
int *p=a;

我們首先定義了一個指向int類型的指針p,在定義p的同時將其初始化為a;通常我們將這種行為稱為“p指向一個大小為5的整形數組a”。事實上,我們可以更具體的想一下:p真的指向整個數組嗎?當然不是。p只是指向一個int型變量而已。在這個例子中,p指向的是a數組的首元素。換句話說,p存儲的是&a[0]。另外,p是變量,我們可以對p進行自加自減(在a數組有效的範圍內),讓p指向a數組的其他元素。換句話說,p可以存儲整個數組中任意一個元素的地址(更具體的是這個元素的首地址)。而數組名a雖然也是個地址,但是它是一個常量,它永遠存儲的是數組的首地址,不可改變。

對於上述所言的p指針,我們可以通過指針方式*(p+i)和下標方式p[i]訪問數組。對於數組名a而言,我們也可以通過*(a+i)和下標方式a[i]來訪問數組。對於p指針而言,以指針方式訪問數組的過程是這樣的:我們通過p指針首先獲取這個數組的首地址,然後加上i個偏移量,得到數組中第i個元素的地址,最後通過引用,得到具體的值。而對於p[i]這種下標訪問方式,最終還是會被轉化成上述指針訪問方式。對於數組名a而言,與p方式相同。

另外還要註意,上述所說的i個偏移量,並不是加上i個字節那麽簡單;而是i個元素的總字節數。因此,給指針p加上一個整數和給指針p的二進制表示形式加上同樣的整數,兩者的含義是截然不同的。
另外,剛說到a是一個常量的問題,我想到了const關鍵字。上次在現代軟件工程課程上,我聽到有的同學說const所修飾的是常量。const修飾的當然是一個變量,只不過這個變量是只讀的,即便這個變量的特性和常量相似。

Q3:對於數組a[3][4],我該如何理解更多的信息?

A:我們首先可以獲知以下信息:數組a是一個包含有3個元素的數組,每個數組元素是一個大小為4的一維數組。因此,我們通常說這個數組有三行四列(但是你要清楚:內存中並不會出現3*4的那樣的表格)。具體來說,二維數組a有以下三個元素:a[0],a[1],a[2];每一個元素又都是一個一維數組。對於一維數組a[0]來說,它又有4個元素:a[0][0],a[0][1],a[0][2],a[0][3];這4個元素是int型的。

以上就是關於這個數組的基本結構,下面從指針的角度來分析這個數組。

對於上述的數組名a,我們稱為指向指針的指針(a是一個常量)。對於一維數組我們知道,數組名是數組中首元素的地址。對於上述所言的數組a,a當然也是這個數組首元素的地址。那麽,數組a的首元素是什麽?對了,就是a[0]。那麽a這個指針存儲的就是a[0]的地址,即a等價於&a[0]。那麽a+i也就很明顯了:a[i]的地址。再來想a[i]是什麽?它是數組a中第i個元素。在數組a中,第i個元素是什麽?它是一個大小為4的一維數組。對應到上述所言的“三行四列”,a+i即是指向第i行的指針。可以看到,i這個偏移量此刻是以行為單位的。

我們上述已說明,a+i指向a[i],a[i]是一個一維數組。a+i是一個指向指針的指針,那麽現在取出a+i的值,也就是*(a+i)。從我們所說的“指向指針的指針”來看,*(a+i)也是一個指針,但是這個指針指向什麽?它指向數組a[i]的首元素的地址,也就是a[i][0]的地址,即&a[i][0]。那麽數組a[i]中第j個元素的地址是什麽?那就是*(a+i)+j,即&a[i][j]。當然,也可以這麽寫:a[i]+j。

另外要說明的是,上述的a+i和*(a+i)的內存地址其實是相同,但是含義是完全不同的。具體原因,你可以從上面的陳述中得到答案。

Q4:難道指針就只有這麽多內容嗎?

A:當然不是。關於指針更高級的使用,還有指向一維數組的指針;指針數組;函數指針。可能它們的定義方式會讓你迷惑,但是,無論如何,它們本質上仍然是指針。先了解最基本的指針,然後再理解這些高級指針就簡單了許多。

上述Q1~Q3從某種角度來說,敘述的過於羅嗦,甚至有時候必須得咬文嚼字;並且,必須在實踐的基礎上理解上述內容。我個人的建議是,指針部分必須建立在“理論——實踐——理論”這樣反復的過程中,否則——套用現在最fashion的話——“指針神馬的一切都是浮雲~”。

指向一維數組的指針

今天在看到typedef int (*int_arry)[10];這條語句時,因為對這樣的定義使用較少,就想著編寫一個test.c來試試看。不過,當我編寫完一個簡單的測試程序時,卻發現我對指向一維數組的指針的使用了解甚少。

起初,我的程序是這樣:

#include < stdio.h >
typedef int (*int_array)[10];
int main()
{

int a[10]={1,2,3,4,5};
int_array i=&a;

printf("%d=%d\n",i[4],a[4]);
return 0;11
}

編譯後,提示如下錯誤:

test.c: In function ‘main’:

test.c:10: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘int *’

也就是說,i[4]是一個int*型的指針。為什麽會出現這樣的錯誤?既然i是一個指向有10個整形元素數組的指針。那麽將i指向數組a,然後使用i[4]獲取第四個元素有什麽錯?

那我們從另一個角度來分析,一般i[4]這樣的形式都可以看成*(i+4)這樣的形式。i+4是什麽?對了!i是一個數組指針,那麽i+4也就是一個數組指針。如果將i所指的數組看作一個二維表的第1行,那麽i+4就是指向第5行的指針。也就是說它相對於i所指向位置的偏移量為4*sizeof(int)*10個字節。因此*(i+4)仍然是一個指針,只不過它指向第5行的首個元素。

看來我們找到問題所在,i[4]並不是一個整形元素,而是一個指向整形元素的指針。上面程序中,我原本的意思是通過i來打印數組a中第四個元素。那麽此刻我們應該這麽修改:

printf("%d=%d\n",i[0][4],a[4]);

或者下面任意一句:

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

你會發現,如果p是一個指向指針的指針,那麽總能夠通過兩次的*、兩次的[]或者一次*和一次[]取得這個指針最終指向的數據,因為說到底[]總能夠化成*的形式。理解了這些,上面的語句對你也就不成問題了。got it?

C語言中的指針和數組