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

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函数##

这个函数比较特殊,从两个方面来讨论:

  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++中引用(&)的用法和应用实例

另外,笔者在《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 类型的变量,并定义特殊值为 truefalse。具体代码如下:

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;
}
posted @ 2017-09-27 17:27  largerthanlife  阅读(9237)  评论(0编辑  收藏  举报