前言
在C/C++编程中,数组是最基础的数据结构之一。然而,C/C++中的原生数组有一个限制:数组的大小在声明后是固定的,无法在运行时根据需要调整。这给处理可变长度数据带来了不便。为了克服这个限制,实现一个“动态数组”,它允许我们在需要时动态增减元素,并提供增、删、查、改四种基本操作。
题目要求
实现一个动态数组,支持以下操作:
- 增:在数组末尾添加一个元素。
- 删:删除指定位置的元素,这里我使用指定位置后面的数据逐步向前覆盖的操作来删除指定位置的元素。
- 查:获取指定位置的元素。
- 改:更新指定位置的元素。
思路分析
在实现动态数组的过程中,几个关键问题:
- 如何动态管理数组大小:原生数组大小固定,但可以通过动态内存分配函数来解决这个问题。常用的方法是使用malloc / realloc/free(C语言)或new / delete(C++),来管理数组空间。
- 动态扩容策略:每当数组容量不足时,我们可以将其容量翻倍,以减少频繁的内存分配。
- 实现增删查改:设计简单的接口,完成添加、删除、查找、修改的操作。
知识点
- 动态内存管理:通过malloc/free和realloc(C语言)或new/delete(C++)来动态管理内存。
- 指针操作:在C/C++中,指针用于操作动态分配的内存区域。
- 扩容策略:当容量不足时,通常将数组容量扩大到两倍,以优化性能,减少内存重分配的频率。
代码实现
C语言实现
#include <stdio.h> // 包含标准输入输出库,用于输入输出功能
#include <stdlib.h> // 包含标准库,用于动态内存分配等功能
// 定义动态数组的结构体
typedef struct {
int* data; // 指向存储数据的指针,用于动态分配内存
int size; // 当前元素个数,表示数组中实际存储的元素数量
int capacity; // 当前容量,表示数组能够存储的最大元素数量
} DynamicArray;
// 初始化动态数组
void initArray(DynamicArray* arr, int initialCapacity) {
arr->data = (int*)malloc(initialCapacity * sizeof(int)); // 分配初始内存,用于存储整数
arr->size = 0; // 初始化元素个数为0
arr->capacity = initialCapacity; // 设置初始容量
}
// 动态扩容,当数组空间不足时调用
void resizeArray(DynamicArray* arr) {
arr->capacity *= 2; // 将容量翻倍
arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int)); // 根据新的容量重新分配内存
}
// 增加元素到数组末尾
void addElement(DynamicArray* arr, int value) {
if (arr->size >= arr->capacity) { // 检查是否需要扩容
resizeArray(arr); // 当空间不足时扩容
}
arr->data[arr->size++] = value; // 在数组末尾插入元素,并增加元素个数
}
// 删除指定位置的元素
void removeElement(DynamicArray* arr, int index) {
if (index < 0 || index >= arr->size) { // 检查索引是否越界
printf("Index out of bounds\n");
return;
}
for (int i = index; i < arr->size - 1; i++) {
arr->data[i] = arr->data[i + 1]; // 将后面的元素向前移动一位,覆盖掉要删除的元素
}
arr->size--; // 调整数组大小,减少元素个数
}
// 获取指定位置的元素
int getElement(DynamicArray* arr, int index) {
if (index < 0 || index >= arr->size) { // 检查索引是否越界
printf("Index out of bounds\n");
return -1; // 返回-1表示索引越界
}
return arr->data[index]; // 返回指定位置的元素
}
// 修改指定位置的元素
void setElement(DynamicArray* arr, int index, int value) {
if (index < 0 || index >= arr->size) { // 检查索引是否越界
printf("Index out of bounds\n");
return;
}
arr->data[index] = value; // 修改指定位置的元素为新的值
}
// 释放动态数组,防止内存泄漏
void freeArray(DynamicArray* arr) {
free(arr->data); // 释放动态分配的内存
arr->data = NULL; // 将指针置为空,避免野指针
arr->size = 0; // 重置元素个数为0
arr->capacity = 0; // 重置容量为0
}
int main()
{
DynamicArray arr; // 声明动态数组变量
initArray(&arr, 2); // 初始化容量为2的数组
addElement(&arr, 10); // 添加元素10到数组
addElement(&arr, 20); // 添加元素20到数组
addElement(&arr, 30); // 添加元素30到数组,此时触发扩容
printf("Element at index 1: %d\n", getElement(&arr, 1)); // 输出索引1处的元素,应为20
setElement(&arr, 1, 40); // 将索引1处的元素修改为40
printf("Element at index 1 after update: %d\n", getElement(&arr, 1)); // 输出修改后的元素,应为40
removeElement(&arr, 1); // 删除索引1处的元素
printf("Array size after deletion: %d\n", arr.size); // 输出删除后的数组大小,应为2
freeArray(&arr); // 释放动态数组,避免内存泄漏
return 0;
}
注意: malloc() 和 realloc() 可能会因为内存不足而返回NULL,可以为initArray() 和 resizeArray() 添加内存分配失败的检测代码,避免程序因空指针操作而崩溃或导致原始内存块泄漏。下面是修改后的代码:
// 初始化动态数组
void initArray(DynamicArray *arr, int initialCapacity) {
arr->data = (int*)malloc(initialCapacity * sizeof(int)); // 分配初始内存
if (arr->data == NULL) { // 检查内存分配是否成功
printf("Failed to allocate memory for the dynamic array.\n");
exit(1); // 内存分配失败,退出程序
}
arr->size = 0;
arr->capacity = initialCapacity;
}
// 动态扩容
void resizeArray(DynamicArray *arr) {
arr->capacity *= 2; // 将容量翻倍
int *newData = (int*)realloc(arr->data, arr->capacity * sizeof(int)); // 重新分配内存
if (newData == NULL) { // 检查内存重新分配是否成功
printf("Failed to reallocate memory for the dynamic array.\n");
free(arr->data); // 内存扩容失败,释放旧数据
exit(1); // 退出程序
}
arr->data = newData; // 更新数据指针
}
C++实现
#include <iostream> // 包含标准输入输出流库
using namespace std; // 使用标准命名空间,简化代码中的std::前缀
// 定义一个DynamicArray类,用于动态数组的管理
class DynamicArray {
private:
int *data; // 指向动态分配数组的指针
int size; // 当前数组中存储的元素个数
int capacity; // 当前数组的容量(即已分配的空间大小)
// 动态扩容函数,当数组容量不足时调用
void resize() {
capacity *= 2; // 将容量翻倍
int *newData = new int[capacity]; // 分配新的内存空间
for (int i = 0; i < size; i++) {
newData[i] = data[i]; // 将旧数据复制到新空间
}
delete[] data; // 释放旧内存空间
data = newData; // 更新指针,指向新空间
}
public:
// 构造函数,初始化动态数组
DynamicArray(int initialCapacity = 2) {
data = new int[initialCapacity]; // 分配初始内存空间
size = 0; // 初始化元素个数为0
capacity = initialCapacity; // 初始化容量为传入值或默认值2
}
// 添加元素到动态数组末尾
void add(int value) {
if (size >= capacity) {
resize(); // 如果当前元素个数达到容量上限,则进行扩容
}
data[size++] = value; // 在数组末尾添加新元素,并更新元素个数
}
// 删除指定位置的元素
void remove(int index) {
if (index < 0 || index >= size) {
cout << "Index out of bounds" << endl; // 如果索引超出范围,输出错误信息
return;
}
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1]; // 将指定位置后的元素前移
}
size--; // 更新元素个数
}
// 获取指定位置的元素值
int get(int index) const {
if (index < 0 || index >= size) {
cout << "Index out of bounds" << endl; // 如果索引超出范围,输出错误信息
return -1; // 返回-1表示错误
}
return data[index]; // 返回指定位置的元素值
}
// 修改指定位置的元素值
void set(int index, int value) {
if (index < 0 || index >= size) {
cout << "Index out of bounds" << endl; // 如果索引超出范围,输出错误信息
return;
}
data[index] = value; // 修改指定位置的元素值
}
// 获取当前数组的元素个数
int getSize() const {
return size;
}
// 析构函数,释放动态分配的内存空间
~DynamicArray() {
delete[] data; // 释放数组内存
}
};
// 主函数
int main()
{
DynamicArray arr; // 创建DynamicArray对象arr,使用默认初始容量2
arr.add(10); // 向arr中添加元素10
arr.add(20); // 向arr中添加元素20
arr.add(30); // 向arr中添加元素30
cout << "Element at index 1: " << arr.get(1) << endl; // 输出arr中索引1位置的元素值(20)
arr.set(1, 40); // 将arr中索引1位置的元素值修改为40
cout << "Element at index 1 after update: " << arr.get(1) << endl; // 输出修改后的元素值(40)
arr.remove(1); // 从arr中删除索引1位置的元素
cout << "Array size after deletion: " << arr.getSize() << endl; // 输出删除元素后的数组大小(2)
return 0; // 主函数返回0,表示程序正常结束
}
补充
return;和return -1;的区别
在编程中,return; 和 return -1; 是两种不同的返回语句,它们具有不同的含义和用途。以下是它们的主要区别:
- 返回值类型:
- return;:如果函数没有指定返回类型(即 void 类型),那么 return; 表示结束函数的执行并返回到调用该函数的地方。它不会返回任何值。
- return -1;:如果函数有指定的返回类型(例如 int),那么 return -1; 表示结束函数的执行并返回 -1 这个值。-1 通常用于表示某种错误或特殊情况,但具体含义取决于函数的文档或上下文。
- 使用场景:
- return;:通常用于 void 类型的函数,表示函数执行完毕,不需要返回任何值。
- return -1;:通常用于有返回值的函数,特别是当函数需要返回一个错误码或状态码时。例如,在操作系统或文件操作中,
-1
常用于表示失败或错误。
- 语义含义:
- return
;
:没有特定的语义含义,只是表示函数结束。 - return -1;:具体的语义含义取决于函数的上下文。例如,在某些函数中,
-1
可能表示文件未找到、内存分配失败、无效参数等。
- return
- 代码示例:
void 类型的函数使用 return;
:
void printMessage() {
printf("Hello, World!\n");
return; // 结束函数执行
}
有返回值的函数使用 return -1;:
int openFile(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
return -1; // 返回 -1 表示文件打开失败
}
// 其他操作...
return 0; // 返回 0 表示成功
}
在动态数组中间插入一个数据
上面代码里没有实现这一操作,这里提供一个示例(仅供参考):
// 插入元素到指定位置
void insertElement(DynamicArray *arr, int index, int value) {
if (index < 0 || index > arr->size) {
printf("Index out of bounds\n");
return;
}
// 如果数组已满,先扩容
if (arr->size >= arr->capacity) {
resizeArray(arr);
}
// 将指定位置及之后的元素向后移动
for (int i = arr->size; i > index; i--) {
arr->data[i] = arr->data[i - 1];
}
// 插入新元素
arr->data[index] = value;
arr->size++; // 更新大小
}