C、C++之動態數組的實現
本篇博客基於筆者本人正在學習的C++上機課程作業,主要代碼由C語言構成。由於C語言沒有 string 、vector、valarray等完善的類,所以在實現動態數組時,需要自行考慮內存的分配和管理,C語言中,對內存管理的函數如malloc、realloc、free等被包括在 < malloc .h >頭文件中。關於這些函數使用的具體實例,可以參考這篇文章: C語言動態內存管理malloc、calloc、realloc、free的用法和注意事項
具體實現時,使用了某些 C++ 的特有語法,如在for循環里定義 變量,這是C語言不允許的,但由於現有現有編譯器一般都同時支持,因此不特別注明
下面會貼出實驗課上所用的測試代碼,針對測試代碼,可以發現,實現一個動態數組不難,因為已經有現成的函數可以調用,而針對數組的操作較多,需要逐一討論
測試文件如下:
// LibArray.cpp : 定義控制台應用程序的入口點。
//
// 實驗內容:
// 1:使用C語言實現一個長度可擴充的數組(包含必要的數據結構及函數);
// 2:要求能存放任意類型的數據(建議先實現存儲整形的代碼,之后改寫成適應任意類型的代碼);
// 3:所寫程序需能通過測試程序
// 4:除本文件(測試文件)之外,其他文件(如CLibArray.cpp及CLibArray.h文件)、以及工程由同學自己建立。過程中可翻書,可查看msdn。
// 實驗目的:
// 1:熟悉相關的指針操作, 復習動態內存的相關操作.
// 2:理解C程序如何實現數據類型和圍繞數據類型上操作的集合
// 3:為未來理解類實現的數組vector做准備
// 只提交CLibArray.cpp及CLibArray.h
#include "stdafx.h"
#include <assert.h>
#include<stdlib.h>
#include "CLibArray.h"
int _tmain(int argc, _TCHAR* argv[])
{
CArray array;
array_initial(array);
array_recap(array, 10);
assert(array_capacity(array) == 10);
//////////////////////////////////////////////////////////////////////////
for (int i = 0; i < 20; ++i)
{
array_append(array, i);
}
assert(array_size(array) == 20);
for (int i = 0; i < array_size(array); ++i)
{
assert(array_at(array, i) == i);
}
//////////////////////////////////////////////////////////////////////////
CArray array2, array3;
array_initial(array2);
array_initial(array3);
array_copy(array, array2);
assert(array_compare(array, array2) == true);
array_copy(array, array3);
assert(array_compare(array, array3) == true);
//////////////////////////////////////////////////////////////////////////
array_insert(array2, 2, 3);
assert(array_compare(array, array2) == false);
//////////////////////////////////////////////////////////////////////////
array_at(array3, 2) = 5;
assert(array_compare(array, array3) == false);
//////////////////////////////////////////////////////////////////////////
array_destroy(array);
array_destroy(array2);
array_destroy(array3);
return 0;
}
可以看出,首先要確定 CArray 的具體類型,以 int 型為例,動態數組具有可變的容量(capacity,已分配空間)和 實際大小(size,已使用的空間),而malloc等函數的參數要求都是指針,因此,可以把 CArray 定義為結構體:
// defination of CArray
typedef struct CArray
{
int* arrayhead;
int size;
int capacity;
}CArray;
注意,在結構體中(c++中,結構體可以看做是簡單的類),是不允許初始化普通成員的,因為上述代碼只是給出此類型的定義,而沒有定義實際的變量。能初始化的只有不隨變量變化的靜態成員。
array_initial函數
考慮 array_initial 函數,需要對 CArray 成員進行初始化,但是注意到測試文件(LibArray.cpp)中,是先定義了一個 CArray型的變量 array,再將其作為參數傳入 array_initial 中,因此函數的參數應當是引用,如果設置為傳值調用,則相當於沒有對實參作出初始化,就賦值給形參,編譯器將報錯。
我實現的 array_initial 定義為
void array_initial(CArray &array)
{
array.arrayhead = NULL;
array.size = 0;
array.capacity = 0;
}
array_recap函數##
考慮第一個 assert 斷言(關於斷言的作用可以參考assert()函數用法總結),由函數名可見, array_recap 需要給 array 分配十個單位長度的空間(在此,單位長度即為sizeof(int));
為了函數的通用性而非只針對測試文件,需要考慮要求分配的空間 capacity 與實際已有容量 array.capacity 的大小關系,考慮到已存放的數據的長度array.size 可能會變,需要進一步設置。核心代碼如下:
array.capacity = capacity;
array.size = array.size > capacity ? capacity : array.size;
int* buffer = NULL;
buffer= (int*) realloc(buffer, sizeof(int) * capacity);
for (int i = 0; i < array.size; i++)
buffer[i] = array.arrayhead[i];
free(array.arrayhead);
array.arrayhead = buffer;
其中, array.size 取決於分配后的實際長度。
array_append函數##
顧名思義,此函數需要向已分配空間中追加,由下文的array_at函數可以看到, append函數不僅要做到空間的追加分配,還要同時向已有空間賦值,且下標即為對應空間的值。
注意到我們不必寫出append的具體方式,而可以調用已有的recap函數進行空間的分配。 具體代碼如下:
void array_append(CArray &array, int num)
{
if (num+1 > array.capacity)
{
array_recap(array, num+1);
}
array.arrayhead[array.size++] = num` `
}
array_capacity 和 array_size函數##
array_size 和 array_capacity 函數則只需返回結構體相應的數值即可。不多贅述。
int array_capacity(const CArray &array)
{
return array.capacity;
}
int array_size(const CArray& array)
{
return array.size;
}
array_at函數##
這個函數比較特殊,從兩個方面來討論:
- LibArray.cpp中第一次調用此函數的代碼是這樣的:
for (int i = 0; i < array_size(array); ++i)
{
assert(array_at(array, i) == i);
}
可見,函數的返回值應當是 int 型的值(或者至少是整型,在C語言中),才能與 i 進行比較。從這段代碼也可以看出,之前在 array_append 函數中,應當在 下標為 i 的地方賦值為 i。
2.問題出在第二次調用:
array_at(array3, 2) = 5;
注意,如果返回值是一個 int 類型的值,那么它無法作為“可修改的左值”來參與賦值,也就是說, 無法實現 2 = 3 這樣的操作。然而在上一小節可以看出,其返回值確實是一個整型量。
如何解決這個問題? 可能有朋友會猜想使用指針,但是 由於測試文件 LibArray 不可修改,因此能做的就是尋找到一種合適的返回值。在 C++ 中就有這樣一種“神器”:引用。
關於引用的具體用法,可以參考這篇文章:C++中引用(&)的用法和應用實例
另外,筆者在《C++ Primer Plus》中,也讀到了關於引用作為返回值的有關內容:
//《C++ Primer Plus》(第6版) 中文版
// P449 - P450頁
// 返回指向非 const 對象的引用
String s1("Good stuff");
String s2, s3;
s3 = s2 =s1;
在上述代碼中,s2.operator=() 的返回值被賦給 s3。 為此, 返回 String 對象或 String 對象的引用都是可行的。但與 Vector 示例中一樣, 通過使用 引用, 可避免該函數調用 String 的復制構造函數來創建一個新的 String 對象。 在這個例子中, 返回類型不是 const ,因為方法 operator=() 返回一個指向 s2 的引用, 可以對對象進行修改。
可見,返回值如果是一個引用(注意這個引用不能是在函數體內新定義的變量, 否則根據 C語言 變量的生存期規則,函數執行結束時,變量的內存會被釋放,因此無法使用這塊內存, 引用也就成了非法),那么既可以讀取它的值,也可以對內存中的值進行再賦值。代碼如下:
TypeName& array_at(const CArray &a, int num)
{
return a.arrayhead[num];
}
array_copy函數##
要對 CArray 結構進行整體的復制(從array 到 array2),必須要保證函數代碼執行結束后,兩個變量的所有參數都是一致的。具體實現時,可以先對array2(也就是形參中的 b 結構)進行內存分配, 大小與 array(形參中的 a 結構)的容量capacity是一致的。
其次,再將 array 中已有賦值的區域逐個復制給 array2.代碼如下:
void array_copy(const CArray &a, CArray &b)
{
array_recap(b, a.capacity);
for (int i = 0; i < a.size; i++)
b.arrayhead[i] = a.arrayhead[i];
b.size = a.size;
}
array_compare函數##
要比較兩個 CArray 結構是否完全一致,必須先確定其 capacity 和 size 的大小是否相同,最后再逐個比較內存中元素的值。若完全相同,返回值為 true,否則為 false。函數的返回值為 bool 類型,若編譯器不支持這種類型, 可以修改為 int 類型的變量,並定義特殊值為 true 、 false。具體代碼如下:
bool array_compare(const CArray a, const CArray b)
{
if (a.size != b.size)
{
printf("Their size are not equal, check out in array_compare().\n");
return false;
}
if (a.capacity != b.capacity)
{
printf("Their capacity are not equal, check out in array_compare().\n");
return false;
}
for (int i = 0; i < a.size; i++)
{
if (a.arrayhead[i] != b.arrayhead[i])
{
printf("They are not equal in the NO.%d place\n", i);
return false;
}
}
return true;
}
array_insert函數##
根據 LibArray.cpp中調用代碼:
array_insert(array2, 2, 3);
可見,第一個參數是要插入的 CArray 結構, 第二和第三個參數則是插入的位序 num 及其值 value(順序無關緊要)。
要實現此函數,就必須先確定位序和待插入結構的capacity大小關系。如果 位序大於其容量,則插入無從談起。若小於,則首先應重新分配 動態數組的大小應將動態數組中從 num 開始 一直到 capacity 的值后移一位,最后再將 第 num 位賦值為 value。代碼如下:
void array_insert(CArray &array, int num, TypeName value)
{
if(num > array.capacity)
{
printf("Cannot insert, the num is larger than capacity, check out in array_insert().\n");
exit(0);
}
else
{
array_recap(array, array.capacity + 1);
for (int i = array.capacity - 1; i >= num; i--)
array.arrayhead[i] = array.arrayhead[i - 1];
array.arrayhead[num - 1] = value;
array.size += 1;
}
}
array_destroy函數##
在 C語言和 C++中,內存的管理是十分重要的,如果沒有用free函數釋放 由 malloc 等函數分配的內存,就會造成內存泄漏。(C++中, 必須用 delete 來刪除對應的由 new 分配的內存)。因此在程序結束前(或某個變量使用完成后),有必要釋放內存空間,並將其參數置為合適的值(一般為零)。具體代碼如下:
void array_destroy(CArray &array)
{
free(array.arrayhead);
array.arrayhead = NULL;
array.capacity = 0;
array.size = 0;
}
