陣列與指標的藝術 第十章 動態陣列

2021-05-21 21:43:22 字數 2547 閱讀 9544

注意:本系列文章** csdn部落格 http://blog.csdn.net/supermegaboy/archive/2009/11/23/4855027.aspx

感謝飛天御女豬大牛!

當寫下這個題目的時候,筆者心裡其實非常犯難。因為從本質上來說,本章想闡述的內容與題目所宣示的概念,其實是不一樣的。在程式設計中,我們常常要處理一段長度未知的資料,而且,執行過程中長度可能會發生變化,現行的c/c++標準沒有提供在棧段和資料段記憶體中的實現,只提供堆中的實現,例如可以象下面**那樣在堆中分配一段記憶體,以處理一組長度不確定的整數:

int *p = ( int* )malloc( n * sizeof( int ) );

現在我們常常將這段堆記憶體稱為「動態陣列」。這正確嗎?陣列是乙個高層概念,是c/c++物件模型及型別系統的重要組成。乙個物件欲成為乙個陣列,引用此物件的表示式或識別符號必須具有高層的陣列型別,但這段記憶體沒有任何陣列型別的引用,只有乙個指向它的指標,因此,這段記憶體不是c/c++高層語義上的陣列。雖然p可以使用下標運算子訪問記憶體塊中的資料,但其實只不過得益於下標運算子的指標性質(如第四章所述)而已。乙個真正的動態陣列,不僅長度在執行期內可變,還需要具備陣列型別的抽象,這要求語言規則的支援,這些條件是p所不具備的。但是,真正的動態陣列的實現也不容易,往往受到效率等多重因素的制約,即使實現了也可能需要付出很大的代價,得不償失,正因如此,c/c++標準都沒有提供對動態陣列的支援。不過,這段堆記憶體被稱為「動態陣列」多年來已經習慣成自然了,筆者沒有為其重新命名的技術能力和資歷,也就只有隨波逐流,暫且也稱之為動態陣列吧,重要的是明白兩者本質的不同。

鑑於動態陣列不是真正的受c/c++規則支援的動態陣列,因此需要通過指針對陣列內部各維位址進行構造,整個陣列才能使用下標運算子。這就使動態陣列的內部構造分成兩部分,一部分叫資料儲存區,用來儲存真正的陣列元素,另一部分叫中間位址緩衝區,儲存陣列內部各維的中間位址。

根據資料儲存區的空間連續性,可以將動態陣列分成兩大類,一類是具有連續儲存空間的動態陣列,另一類是非連續儲存空間的動態陣列。筆者分別將它們稱為連續動態陣列和離散動態陣列。

int **p = ( int** )malloc( m * sizeof( int* ) );

for( i = 0; i < m; ++i )

p[i] = ( int* )malloc( n * sizeof( int ) );

for( i = 0; i < m; ++i )

free( p[i] );

free( p );

離散動態陣列是先構造好中間位址緩衝區,再構造資料儲存區,這是造成資料空間不連續的原因,雖然構造過程簡單,但非連續性帶來很多缺點。一是不利於陣列內部的直接定址,例如通過資料區首位址計算元素位址;二是當需要對陣列長度進行改變時,過程複雜;三是空間的釋放需要對中間位址緩衝區重新遍歷。但其實,完全可以先構造資料儲存區,再構造中間位址緩衝區,這種方法使連續資料儲存空間有了可能,而且,連續動態陣列不會帶來離散動態陣列那些缺點。下面是構造連續動態陣列的示例:

int *p = ( int* )malloc( m * n * sizeof( int ) );

int **q = ( int** )malloc( m * sizeof( int* ) );

for( i = 0; i < m; ++i )

q[i] = p + i * n;

首先p分配m*n個int資料的儲存區,再由q根據這段空間構造中間位址。現在,不僅可以通過q[m][n]使用這個陣列,還可以直接通過p和下標運算子訪問陣列的元素。釋放空間的時候直接釋放p和q就行了,需要改變陣列長度的話,只須重新分配p指向的空間,再重新構造一下中間位址緩衝區,例如將上述m*n個int元素的陣列改為k*j個int元素,可以這樣做:

int *p = ( int* )realloc( p, k * j * sizeof( int ) );

int **q = ( int** )realloc( q, k * sizeof( int* ) );

for( i = 0; i < k; ++i )

q[i] = p + i * j;

而離散動態陣列就必須先動態分配好k*j個int的新空間,然後把舊資料都複製過去,再釋放舊空間,整個過程比連續空間麻煩得多。

double a[100];

double **p = ( double** )malloc( 5 * sizeof( double* ) );

for( i = 0; i < 5; ++i )

p[i] = a + i * 20;

這樣就把一維double陣列a的空間重新在邏輯上改造成了乙個二維陣列p[5][20],注意重新構造的動態陣列的長度不能超出a的空間,否則結果是不確定的,是危險的。

上述例子在乙個一維陣列上構造了乙個二維陣列,維度發生了變化,這說明連續動態陣列不僅可以方便地改變長度,還可以方便地改變維度。當目標維度可變時,中間位址的構造需要使用遞迴演算法。筆者的部落格中就提供了乙個維度可變的陣列adt的例子。

要注意的是,動態陣列的中間位址不具陣列型別。例如上述動態陣列q[m][n]的第一維q[m]型別依然是int*,而乙個陣列物件int a[m][n]的第一維a[m]的型別是陣列型別int[n]。

綜合來看,由於連續動態陣列的優點比離散動態陣列多得多,在程式設計實踐中應優先使用連續動態陣列。

第十章 動態陣列

分類 陣列與指標的藝術2009 11 23 10 46 6542人閱讀 收藏舉報 儲存 程式設計演算法語言 當寫下這個題目的時候,筆者心裡其實非常犯難。因為從本質上來說,本章想闡述的內容與題目所宣示的概念,其實是不一樣的。在程式設計中,我們常常要處理一段長度未知的資料,而且,執行過程中長度可能會發生...

第十章 陣列與指標( C primer plus)

0.陣列長度建議定義為巨集。由於編譯器不檢查陣列下標的合法性,這樣可減少下標越界錯誤。1.唯讀陣列宣告最前面加const,且必須同時初始化。2.陣列屬於自動儲存類,陣列元素的值不初始化時是不定的 部分初始化,後面預設為0 多初始化出錯。3.可以讓陣列長度空著,由編譯器根據初始化的數值的個數確定陣列大...

第十章 陣列和指標

陣列初始化 當初始化列表中的值少於陣列元素個數時,編譯器會把剩餘的元素都初始化為0 個數多於陣列元素個數時,會視為錯誤 省略陣列中括號中的數字,編譯器會根據初始化列表中的專案來確定陣列的大小。days是陣列 sizeof days是整個陣列的大小 sizeof day 0 是陣列中乙個元素的大小 c...