在C/C++中,原生数组的空间大小是静态的。例如,声明int a[10]之后,数组a的大小就已经固定为10,因此在使用时,常常以数据量的上限作为数组的长度,比如最多有可能要保存100,000个元素时,可以创建一个长度为的数组。这样做的显著缺点就是,当实际数据量较小时,会造成大量的空间浪费。更合理的方法是因地制宜地对数组空间进行动态分配。这里给出一种最简单的动态数组管理策略:
- 使用用户分配的一段连续的内存空间表示数组;
- 初始时,数组的容量为16;
- (扩展) 当插入元素时,如果空间已满,将数组容量增加至原有的2倍,也就是将原有的空间释放,分配一个大小为原有大小2倍的内存空间,并将原来的元素复制到新的空间中,例如原来的空间大小为16,现在增加到32;
- (压缩) 删除数组中元素后,如果剩余元素的数量小于容量的1/4(向下取整)且数组的容量大于16时,删除掉原有的空间,分配一个为原有容量一半的新的内存空间,并将原来的元素复制到新的空间中,例如原来的容量为64,但是删除元素后,数组中只有15个元素,那么将容量压缩到32。
根据以上策略,实现下面的接口。这些接口以C++中容器std::vector为蓝本,接口的具体功能请看函数前的注释。通过实现这些接口,你或许能够受到一些关于面向对象编程(OOP)的启发。
动态数组类型定义:
typedef int Elem;
typedef enum bool
{
false,
true
} bool;
typedef struct DynamicArray
{
size_t capacity; // 数组的容量,初始值和最小值均为16u
size_t size; // 数组中元素的个数
Elem* data; // 用于保存数组中元素的内存空间
} DynamicArray;
函数接口定义:
// 创建数组,初始容量为16。
DynamicArray* DynamicArray_new();
// 本题中,在位置pos前插入元素*v,也就是*v插入后,它是数组中的第pos个元素,下标从0开始。
// 注意,插入后的数组可能发生容量的倍增,假设内存分配总是合法的,不需要针对内存不足抛出异常。
// 例如:
// 1. pos = 10 ,表示在第10个元素前插入元素*v,插入后它前面有10个元素,原来的第10个元素在它后面
// 2. pos = 0 ,表示在数组的起始位置插入元素*v
// 3. pos = size,表示在数组的末尾插入元素*v
void DynamicArray_insert(DynamicArray* this, size_t pos, const Elem* v);
// 删除位置pos的元素,可以假定pos总是合法的,即pos=0,1,...,size - 1,删除后的数组不应该有空档。
// 注意,删除数组元素可能会导致容量变更。
// 例如:
// size = 6, 数组中的元素为0,1,2,3,4,5。删除了pos=3的元素3之后,数组的元素变为0,1,2,4,5,size=5,数组第3个元素为4
Elem DynamicArray_erase(DynamicArray* this, size_t pos);
// 获取位置为pos的元素,假设pos总是合法的。
Elem DynamicArray_get(const DynamicArray* this, size_t pos);
// 将位置为pos的元素设置为*v,假设pos总是合法的。
void DynamicArray_set(DynamicArray* this, size_t pos, const Elem* v);
// 返回数组的容量。
size_t DynamicArray_capacity(const DynamicArray* this);
// 返回数组的元素个数。
size_t DynamicArray_size(const DynamicArray* this);
// 返回数组元素是否为空,为空则返回true,否则返回false。
bool DynamicArray_empty(const DynamicArray* this);
// 重设数组元素个数为new_size,注意,容量可能因此发生变更。
// 新的数组元素个数如果比原来的数组更多,将原有数组的元素复制到新数组对应位置即可。
// 新的数组元素个数如果比原来的数组更少,超出的部分截断即可。
// 例如:
// 1. 原数组为0,1,2,3,4,5,新数组大小为4,那么新数组变为0,1,2,3,size=4
// 2. 原数组为0,1,2,3,新数组大小为6,那么数组变为0,1,2,3,?,?,size=6,其中?表示任意值,即这些多出的元素
// 不需要初始化
void DynamicArray_resize(DynamicArray* this, size_t new_size);
// 删除数组。不要忘记释放原有数组的空间,否则会造成内存泄漏。
void DynamicArray_delete(DynamicArray* this);
裁判测试程序样例:
int main()
{
char line[100];
DynamicArray* arr = NULL;
size_t size;
size_t pos;
Elem v;
while (gets(line))
{
switch (line[0])
{
case 'C': // 创建数组
arr = DynamicArray_new();
break;
case 'D': // 删除数组
DynamicArray_delete(arr);
arr = NULL;
break;
case 'R': // resize
sscanf(line + 2, "%lu", &size);
DynamicArray_resize(arr, size);
break;
case 'c': // 输出capacity
printf("%lu\n", DynamicArray_capacity(arr));
break;
case 's': // 输出size
printf("%lu\n", DynamicArray_size(arr));
break;
case 'i': // 插入元素
sscanf(line + 2, "%lu %d", &pos, &v);
DynamicArray_insert(arr, pos, &v);
break;
case 'd': // 删除元素并输出
sscanf(line + 2, "%lu", &pos);
printf("%d\n", DynamicArray_erase(arr, pos));
break;
case 'G': // get元素并输出
sscanf(line + 2, "%lu", &pos);
printf("%d\n", DynamicArray_get(arr, pos));
break;
case 'S': // set元素
sscanf(line + 2, "%lu %d", &pos, &v);
DynamicArray_set(arr, pos, &v);
break;
case 'e': // 输出数组是否为empty
printf("%s\n", DynamicArray_empty(arr) == true ? "true" : "false");
break;
default:
break;
}
}
return 0;
}
/* 请在这里填写答案 */
输入输出介绍:
输入输出均由裁判程序完成,作答时通常不需要关注。
以下为裁判程序的输入输出介绍,以单个字母表示命令,它们是大小写敏感的,随后跟随若干个参数,命令列表如下:
| 命令 | 含义 | 示例 | 示例含义 |
|---|---|---|---|
| C | 创建数组 | C | |
| D | 删除数组 | D | |
| R $0 | 重设数组元素个数为$0 | R 20 | 重设数组元素个数为20 |
| c | 输出数组容量capacity | c | |
| s | 输出数组元素个数size | s | |
| i 01 | 在位置0前插入元素1 | i 10 5 | 在第10个位置前插入5 |
| d $0 | 删除位置$0的元素并输出 | d 10 | 删除位置为10的元素并输出 |
| G $0 | 获取位置$0的元素并输出 | G 5 | 获取位置为5的元素并输出 |
| S 01 | 将位置0的元素赋值为1 | S 5 10 | 将数组位置为5的元素设为10 |
| e | 输出数组是否为空,为空输出true,反之输出false | e |
更具体的信息请参考裁判测试程序样例。
可以保证,同一时间,程序中至多只有一个被创建的数组。在创建数组(C)后,每一个操作均在刚刚创建的数组中进行。当数组被删除且没有新的数组被创建时,不会有除创建(C)外的指令。
输入样例:
C
R 10
s
c
S 9 10
i 0 -1
G 0
G 10
d 0
G 9
e
c
R 20
s
c
R 60
s
c
R 5
s
c
D
结尾无空行
输出格式:
10
16
-1
10
-1
10
false
16
20
32
60
64
5
16
结尾无空行
代码:
DynamicArray* DynamicArray_new()
{
DynamicArray* vec;
vec = (DynamicArray*)malloc(sizeof(struct DynamicArray));
vec->capacity = 16 ;
vec->data = (int *)malloc(sizeof(int) * vec->capacity);
vec->size = 0 ;
return vec ;
}
void DynamicArray_insert(DynamicArray* this, size_t pos, const Elem* v){
if(this == NULL)
return ;
if(this->size == this->capacity)
{
this->capacity *= 2 ;
this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
}
int t = pos ;
for(int i = this->size-1 ; i >= t ; i-- )
this->data[i+1] = this->data[i];
this->data[t] = *v ;
this->size ++;
}
Elem DynamicArray_erase(DynamicArray* this, size_t pos){
if(this == NULL)
return 0;
if(pos < 0 || pos > this->size)
return 0;
Elem t =this->data[pos];
for(size_t i = pos ; i < this->size-1 ; i ++ )
this->data[i] = this->data[i+1];
this->size -- ;
while(this->size < this->capacity / 4 && this->capacity > 16)
this->capacity /= 2 ;
this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
return t ;
}
Elem DynamicArray_get(const DynamicArray* this, size_t pos)
{
return this->data[pos];
}
void DynamicArray_set(DynamicArray* this, size_t pos, const Elem* v)
{
this->data[pos] = *v ;
}
size_t DynamicArray_capacity(const DynamicArray* this)
{
return this->capacity;
}
size_t DynamicArray_size(const DynamicArray* this)
{
return this->size;
}
bool DynamicArray_empty(const DynamicArray* this)
{
if(this->size == 0)
return true;
else
return false;
}
void DynamicArray_resize(DynamicArray* this, size_t new_size){
if(new_size > this->capacity)
{
while(new_size > this->capacity)
this->capacity *= 2;
this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
this->size = new_size;
}
else
{
while(new_size < this->capacity / 4 && this->capacity > 16)
this->capacity /= 2 ;
this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
this->size = new_size;
}
}
void DynamicArray_delete(DynamicArray* this){
free(this->data);
free(this);
}