C和C++中所謂的陣列

Michael@Wu發表於2014-07-04

在其它高階語言裡,不管是定義(宣告)還是引用, a[i] 或 a[3] 都是一個整體。在 C/C++ 裡,卻是一個表示式: a[i] 是運算子 [] 連線兩個實體 a 和 i 。

說 C/C++ 並沒有陣列 , 有以下幾條理由。

 

理由一: C 裡沒有陣列形式。

“陣列”名 a 本身就是一個指標,與常規指標不同的是,它是一個不能移動的所謂常指標。

如在函式外有定義:

float a[3] = {1.0, 2.0, 3.0};

首先在初始化資料段分配一塊能容納三個 float 數的空間,並填入三個初始值,然後定義一個名為 a 指向 float 資料流的常指標,並使其指向該區域的首位元組。

理由二:“陣列”的定義,其實最終是對指標的定義。

說“指向 float 資料流”,和說“指向 float 型陣列”,是兩個概念。共性是,計算偏移量(我不說移動,因為常指標是不能移動的。)時,計算單位都是 float 型資料的位元組數。但是,陣列是有邊界的,你的下標不能超出邊界。而偏移量可以超出資料流的邊界(後果自負)。

很多書裡說, C “陣列”沒有邊界檢查,是為了執行效率。但是,對邊界的檢查,系統開銷並不大。 C 裡的“陣列”其實是個資料流,它的邊界只有一頭:常指標所指向的下邊界。

理由三:陣列名和下標竟然可以互換。

我們要訪問上面那個資料流的第 2 個資料,可以使用 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) 的同義詞。

對“陣列”的訪問,最終總是通過指標的。其基本形式是: *(a + i) 。

“陣列”名是一個常指標,總是指向該區域的首址。“下標”其實是一個邏輯偏移量。說它是“邏輯”的,意思是在計算時,需要乘以步長(資料的長度)。但是,這個“乘法”對你是透明的,不必關心它。指向所訪問資料的是常指標“加”偏移量。

在 X86 系列 CPU 的指令系統裡,有一個基址變址定址方式。這種定址方式和 C 對“陣列”的訪問方式很相似。常指標相當於基址,偏移量相當於變址。

我懷疑,這個基址變址定址方式是為 C 訪問“陣列”而增加的。

理由五: C “陣列”沒有上邊界。

對下面的定義

float a[3] = {1.0, 2.0, 3.0};

我們不僅可以訪問 a[0] 、 a[1] 和 a[2] ,還可以訪問 a[3] 、 a[4] 等。 C 陣列不知道自己的一畝三分到哪裡為止。用 C/C++ 開發,與用其它語言不同,程式設計員必須記住自己定義的陣列有多大。自己的家沒有柵欄,跑到鄰居割韭菜沒人管,但後果自負。

理由六:對“多維陣列”的訪問總是可化解成對“一維陣列”的方式。

誠然,不管是用哪種語言程式設計,最終生成的目的碼中,陣列總是被轉換成一塊連續的儲存區(即它是線狀的)。不同的是,在 C原始碼裡,所有的陣列,不管是幾維的,都是線狀的。在原始碼層面,都可以看作是一維的。定義了

      int a[3][4];

可以用 a[2][3] ,也可以用 a[11] 訪問其第 2 行、第 3 列元素。

結論

C 裡使用下標運算子 [] ,無非是使指向一串等型別元素的指標對該區域的操作看起來像運算元組而已。沒有這東西,習慣了其它高階語言陣列操作的編碼員會覺得不習慣。

相關文章