第十章 動態陣列

2021-06-23 03:08:39 字數 2619 閱讀 5757

分類: 

陣列與指標的藝術2009-11-23 10:46 

6542人閱讀

收藏舉報 儲存

程式設計演算法語言

當寫下這個題目的時候,筆者心裡其實非常犯難。因為從本質上來說,本章想闡述的內容與題目所宣示的概念,其實是不一樣的。在程式設計中,我們常常要處理一段長度未知的資料,而且,執行過程中長度可能會發生變化,現行的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]。

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

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

注意 本系列文章 csdn部落格 http blog.csdn.net supermegaboy archive 2009 11 23 4855027.aspx 感謝飛天御女豬大牛!當寫下這個題目的時候,筆者心裡其實非常犯難。因為從本質上來說,本章想闡述的內容與題目所宣示的概念,其實是不一樣的。在程...

移動端 第十章 動畫

動畫屬性 animation animation name 動畫的名字 animation duration 動畫完成乙個週期所花費的時間 秒 毫秒 animation timing function 動畫的速度曲線 linear ease ease in ease out ease in out ...

第十章 Lua陣列

1.一維陣列 array for i 0,2 do print array i end執行結果 nil lua tutorial正如你所看到的,我們可以使用整數索引來訪問陣列元素,如果知道的索引沒有值則返回nil。在 lua 索引值是以 1 為起始,但你也可以指定 0 開始。除此外我們還可以以負數為...