C、C++之动态数组的实现
本篇博客基于笔者本人正在学习的C++上机课程作业,主要代码由C语言构成。由于C语言没有 string 、vector、valarray等完善的类,所以在实现动态数组时,需要自行考虑内存的分配和管理,C语言中,对内存管理的函数如malloc、realloc、free等被包括在 < malloc .h >头文件中。关于这些函数使用的具体实例,可以参考这篇文章:[ C语言动态内存管理malloc、calloc、realloc、free的用法和注意事项 ](http://blog.csdn.net/autumn_summer/article/details/17919713)
具体实现时,使用了某些 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()函数用法总结](http://www.cnblogs.com/ggzss/archive/2011/08/18/2145017.html)),由函数名可见, 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函数
这个函数比较特殊,从两个方面来讨论:
1. 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++中引用(&)的用法和应用实例 ](http://www.cnblogs.com/Mr-xu/archive/2012/08/07/2626973.html)
另外,笔者在《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;
}