在其它高級語言里,不管是定義(聲明)還是引用,a[i]或a[3]都是一個整體。在C/C++里,卻是一個表達式:a[i]是運算符[]連接兩個實體a和i。
說C/C++并沒有數(shù)組,有以下幾條理由。
理由一:C里沒有數(shù)組形式。
“數(shù)組”名a本身就是一個指針,與常規(guī)指針不同的是,它是一個不能移動的所謂常指針。
如在函數(shù)外有定義:
float a[3] = {1.0, 2.0, 3.0};
首先在初始化數(shù)據(jù)段分配一塊能容納三個float數(shù)的空間,并填入三個初始值,然后定義一個名為a指向float數(shù)據(jù)流的常指針,并使其指向該區(qū)域的首字節(jié)。
理由二:“數(shù)組”的定義,其實最終是對指針的定義。
說“指向float數(shù)據(jù)流”,和說“指向float型數(shù)組”,是兩個概念。共性是,計算偏移量(我不說移動,因為常指針是不能移動的。)時,計算單位都是float型數(shù)據(jù)的字節(jié)數(shù)。但是,數(shù)組是有邊界的,你的下標不能超出邊界。而偏移量可以超出數(shù)據(jù)流的邊界(后果自負)。
很多書里說,C“數(shù)組”沒有邊界檢查,是為了運行效率。但是,對邊界的檢查,系統(tǒng)開銷并不大。C里的“數(shù)組”其實是個數(shù)據(jù)流,它的邊界只有一頭:常指針所指向的下邊界。
理由三:數(shù)組名和下標竟然可以互換。
我們要訪問上面那個數(shù)據(jù)流的第2個數(shù)據(jù),可以使用a[1],也可以使用*(a + 1)。兩者完全等價。我懷疑,C的作者所提供的a[i],僅僅是*(a + i)的同義詞。按照加法交換率,顯然,*(a + i)等于*(i + a)。那i[a]是不是也等于a[i]呢?測試結果:等于。更奇怪的是,不但i[a]等于a[i],1[a]也等于a[1]!
看看下面的相等關系:
a[1] 等于 *(a + 1) 等于 *(1 + a) 等于 1[a]
上面的懷疑或許有點道理了。
理由四:a[i]無非是*(a + i)的同義詞。
對“數(shù)組”的訪問,最終總是通過指針的。其基本形式是:*(a + i)。
“數(shù)組”名是一個常指針,總是指向該區(qū)域的首址。“下標”其實是一個邏輯偏移量。說它是“邏輯”的,意思是在計算時,需要乘以步長(數(shù)據(jù)的長度)。但是,這個“乘法”對你是透明的,不必關心它。指向所訪問數(shù)據(jù)的是常指針“加”偏移量。
在X86系列CPU的指令系統(tǒng)里,有一個基址變址尋址方式。這種尋址方式和C對“數(shù)組”的訪問方式很相似。常指針相當于基址,偏移量相當于變址。
我懷疑,這個基址變址尋址方式是為C訪問“數(shù)組”而增加的。
理由五:C“數(shù)組”沒有上邊界。
對下面的定義
float a[3] = {1.0, 2.0, 3.0};
我們不僅可以訪問a[0]、a[1]和a[2],還可以訪問a[3]、a[4]等。C數(shù)組不知道自己的一畝三分到哪里為止。用C/C++開發(fā),與用其它語言不同,編程員必須記住自己定義的數(shù)組有多大。自己的家沒有柵欄,跑到鄰居割韭菜沒人管,但后果自負。
理由六:對“多維數(shù)組”的訪問總是可化解成對“一維數(shù)組”的方式。
誠然,不管是用哪種語言編程,最終生成的目的碼中,數(shù)組總是被轉換成一塊連續(xù)的存儲區(qū)(即它是線狀的)。不同的是,在C源碼里,所有的數(shù)組,不管是幾維的,都是線狀的。在源碼層面,都可以看作是一維的。定義了
int a[3][4];
可以用a[2][3],也可以用a[11]訪問其第2行、第3列元素。
結論
C里使用下標運算符[],無非是使指向一串等類型元素的指針對該區(qū)域的操作看起來像操作數(shù)組而已。沒有這東西,習慣了其它高級語言數(shù)組操作的編碼員會覺得不習慣。