C、C++之动态数组的实现

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;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容