浅析动态数组

一、原始动态数组

#include 
#include 
#include 

// 动态数组结构体
typedef struct _stu_dynamicArray
{
	void** data;		// 堆区真实数组指针(由于不知道动态数组中要存储何种类型数据,因此用万能指针void*来代表所有数据类型包括自定义类型来作为数组中的元素。而外面就得用二级指针来指向一级指针了)
	int capacity;		// 数组最大容量
	int size;			// 数组元素大小
} stu_dynamicArray;

// 动态数组初始化
stu_dynamicArray* dynamicArrayInit(int capacity)
{
	if (capacity < 1) {return NULL;}
	
	// 在堆区开辟动态数组结构
	stu_dynamicArray* pDA = (stu_dynamicArray*)malloc(sizeof(stu_dynamicArray));
	if (pDA == NULL) { return NULL; }	// 开辟失败返回空

	// 设置初始大小
	pDA->capacity = capacity;
	pDA->size = 0;

	// 在堆区开辟真实数组指针
	pDA->data = (void*)malloc(sizeof(void*) * capacity);
	if (pDA->data == NULL)	// 开辟失败返回空
	{
		free(pDA);
		return NULL;
	}

	return pDA;
}

// 动态数组指定位置插入
void dynamicArrayInsert(stu_dynamicArray* da, int pos, void* val)
{
	if (da == NULL) { return; }
	if (val == NULL) { return; }
	if (pos < 0 || pos > da->size) { pos = da->size; }	// 位置不正确则默认尾插

	// 判断数组元素是否已经达到最大容量
	if (da->size >= da->capacity)
	{
		// 在堆区重新开辟新空间
		int newCapacity = da->capacity * 2;
		void** newData = (void*)malloc(sizeof(void*) * newCapacity);
		if (newData == NULL) { return; }
		// 将原数据拷贝到新空间
		memcpy(newData, da->data, sizeof(void*) * da->capacity);
		// 释放原空间
		free(da->data);
		// 更改指针指向
		da->data = newData;
		// 更新容量
		da->capacity = newCapacity;
	}

	// 开始插入
	for (int i = da->size - 1; i >= pos; i--)	// 从后往前移
	{
		da->data[i + 1] = da->data[i];
	}
	da->data[pos] = val;

	// 更新元素大小
	da->size++;
}

// 动态数组尾插法
void dynamicArrayPushBack(stu_dynamicArray* da, void* val)
{
	dynamicArrayInsert(da, da->size, val);
}

// 动态数组指定位置删除
void dynamicArrayErase(stu_dynamicArray* da, int pos)
{
	if (da == NULL) { return; }
	if (pos < 0 || pos > da->size - 1) { return; }	// 位置不正确则返回

	// 开始删除
	for (int i = pos; i < da->size - 1; i++)	// da->size - 1这个减1很重要啊
	{
		da->data[i] = da->data[i + 1];
	}

	// 更新元素大小
	da->size--;
}

// 动态数组尾删法
void dynamicArrayPopBack(stu_dynamicArray* da)
{
	if (da == NULL) { return; }
	if (da->size > 0)
	{
		da->size--;
	}
}

// 动态数组销毁
void dynamicArrayDestroy(stu_dynamicArray* da)
{
	if (da == NULL) { return; }
	if (da->data != NULL)
	{
		free(da->data);
		da->data = NULL;
	}
	free(da);
	da = NULL;
}

// 动态数组遍历(利用回调函数)
void dynamicArrayForEach(stu_dynamicArray* da,void(*myForEach)(void*))
{
	if (da == NULL) { return; }
	if (myForEach == NULL) { return; }
	for (int i = 0; i < da->size; i++)
	{
		myForEach(da->data[i]);
	}
}

// 测试用结构体
struct _stu_person
{
	char name[31];
	int age;
};

// 测试用回调函数
void myPrintPerson(void* val)
{
	struct _stu_person* p = val;
	printf("姓名:%s 年龄:%d\n",p->name,p->age);
}

// 测试动态数组
void testDynamicArray()
{
	// 创建动态数组
	stu_dynamicArray* arr = dynamicArrayInit(4);

	// 测试数据
	struct _stu_person p1 = { "刘备",39 };
	struct _stu_person p2 = { "关羽",34 };
	struct _stu_person p3 = { "张飞",32 };
	struct _stu_person p4 = { "赵云",28 };
	struct _stu_person p5 = { "吕布",30 };

	// 开始插入
	dynamicArrayPushBack(arr ,&p1);
	dynamicArrayInsert(arr, 10, &p2);
	dynamicArrayInsert(arr, 1, &p3);

	// 数据遍历
	printf("=====当前容量:%d,元素个数:%d=====\n", arr->capacity,arr->size);
	dynamicArrayForEach(arr, myPrintPerson);

	// 扩容测试
	dynamicArrayPushBack(arr, &p4);
	dynamicArrayInsert(arr, 0, &p5);

	// 重新遍历(不用遍历函数)
	printf("\n=====扩容后容量:%d,元素个数:%d=====\n", arr->capacity, arr->size);
	for (int i = 0; i < arr->size; i++)
	{
		struct _stu_person* p = arr->data[i];	// arr->data是2级指针,而arr->data[i]是1级指针
		printf("姓名:%s 年龄:%d\n", p->name, p->age);
	}

	// 删除数据
	dynamicArrayErase(arr,1);
	printf("\n=====删除第一个位置数据后的容量:%d,元素个数:%d=====\n", arr->capacity, arr->size);
	dynamicArrayForEach(arr, myPrintPerson);
	dynamicArrayPopBack(arr);
	printf("\n=====删除最后位置数据后的容量:%d,元素个数:%d=====\n", arr->capacity, arr->size);
	dynamicArrayForEach(arr, myPrintPerson);

	// 销毁数组
	dynamicArrayDestroy(arr);
	printf("\n=====数组已经销毁=====\n");

}

// 主函数
int main()
{
	testDynamicArray();
	return 0;
}

二、封装后的动态数组

        上面的动态数组已经能满足用户的大部分要求了,但这里有一个很大的问题没解决,就是这个动态数组结构赤果果的呈现在用户面前,那么用户就可以任意修改其中的数据了,比如说数组元素大小,数组最大容量等。这是让人无法容忍的,必须封装,必须保持神秘感,必须不让用户接触到这些东西。那么封装的关键点在哪呢?毋容置疑,当然是【stu_dynamicArray* dynamicArrayInit(int capacity)】这个用户第一个接触到的动态数组初始化函数了。在该函数中返回的直接就是stu_dynamicArray这个结构体,这绝对不行,改为返回void*这个万能指针,然后我们在具体函数中再将其偷偷强制转换为stu_dynamicArray这个结构体,不要让用户知道不就行了。

#include 
#include 
#include 

// 动态数组结构体
typedef struct _stu_dynamicArray
{
	void** data;		// 堆区真实数组指针(由于不知道动态数组中要存储何种类型数据,因此用万能指针void*来代表所有数据类型包括自定义类型来作为数组中的元素。而外面就得用二级指针来指向一级指针了)
	int capacity;		// 数组最大容量
	int size;			// 数组元素大小
} stu_dynamicArray;

// 用万能指针来代替动态数组结构体,这是封装的关键
typedef void* dynamicArray;

// 动态数组初始化
dynamicArray dynamicArrayInit(int capacity)
{
	if (capacity < 1) {return NULL;}
	
	// 在堆区开辟动态数组结构
	stu_dynamicArray* pDA = (stu_dynamicArray*)malloc(sizeof(stu_dynamicArray));
	if (pDA == NULL) { return NULL; }	// 开辟失败返回空

	// 设置初始大小
	pDA->capacity = capacity;
	pDA->size = 0;

	// 在堆区开辟真实数组指针
	pDA->data = (void*)malloc(sizeof(void*) * capacity);
	if (pDA->data == NULL)	// 开辟失败返回空
	{
		free(pDA);
		pDA = NULL;
		return NULL;
	}

	return pDA;
}

// 动态数组指定位置插入
void dynamicArrayInsert(dynamicArray arr, int pos, void* val)
{
	if (arr == NULL) { return; }
	if (val == NULL) { return; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	if (pos < 0 || pos > da->size) { pos = da->size; }	// 位置不正确则默认尾插

	// 判断数组元素是否已经达到最大容量
	if (da->size >= da->capacity)
	{
		// 在堆区重新开辟新空间
		int newCapacity = da->capacity * 2;
		void** newData = (void*)malloc(sizeof(void*) * newCapacity);
		if (newData == NULL) { return; }
		// 将原数据拷贝到新空间
		memcpy(newData, da->data, sizeof(void*) * da->capacity);
		// 释放原空间
		free(da->data);
		// 更改指针指向
		da->data = newData;
		// 更新容量
		da->capacity = newCapacity;
	}

	// 开始插入
	for (int i = da->size - 1; i >= pos; i--)			// 从后往前移
	{
		da->data[i + 1] = da->data[i];
	}
	da->data[pos] = val;

	// 更新元素大小
	da->size++;
}

// 动态数组尾插法
void dynamicArrayPushBack(dynamicArray arr, void* val)
{
	if (arr == NULL) { return; }
	if (val == NULL) { return; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	dynamicArrayInsert(arr, da->size, val);
}

// 动态数组指定位置删除
void dynamicArrayErase(dynamicArray arr, int pos)
{
	if (arr == NULL) { return; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	if (pos < 0 || pos > da->size - 1) { return; }		// 位置不正确则返回

	// 开始删除
	for (int i = pos; i < da->size - 1; i++)			// da->size - 1这个减1很重要啊
	{
		da->data[i] = da->data[i + 1];
	}

	// 更新元素大小
	da->size--;
}

// 动态数组尾删法
void dynamicArrayPopBack(dynamicArray arr)
{
	if (arr == NULL) { return; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	if (da->size > 0)
	{
		da->size--;
	}
}

// 动态数组销毁
void dynamicArrayDestroy(dynamicArray arr)
{
	if (arr == NULL) { return; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	if (da->data != NULL)
	{
		free(da->data);
		da->data = NULL;
	}
	free(da);
	da = NULL;
}

// 得到动态数组最大容量
int dynamicArrayCapacity(dynamicArray arr)
{
	if (arr == NULL) { return -1; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	return da->capacity;
}

// 得到动态数组中元素大小
int dynamicArraySize(dynamicArray arr)
{
	if (arr == NULL) { return -1; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	return da->size;
}

// 动态数组遍历(利用回调函数)
void dynamicArrayForEach(dynamicArray arr,int(*myForEach)(void*))
{
	if (arr == NULL) { return; }
	stu_dynamicArray* da = (stu_dynamicArray*)arr;		// 强制转换
	if (myForEach == NULL) { return; }
	for (int i = 0; i < da->size; i++)
	{
		if (myForEach(da->data[i]) == -1) { break; }	// 根据返回值判断是否中途退出遍历
	}
}

// 测试用结构体
struct _stu_person
{
	char name[31];
	int age;
};

// 测试用回调函数(返回-1则退出遍历)
int myPrintPerson(void* val)
{
	struct _stu_person* p = val;
	printf("姓名:%s 年龄:%d\n",p->name,p->age);
	return 0;
}

// 测试动态数组
void testDynamicArray()
{
	// 创建动态数组
	dynamicArray arr = dynamicArrayInit(4);

	// 测试数据
	struct _stu_person p1 = { "刘备",39 };
	struct _stu_person p2 = { "关羽",34 };
	struct _stu_person p3 = { "张飞",32 };
	struct _stu_person p4 = { "赵云",28 };
	struct _stu_person p5 = { "吕布",30 };

	// 开始插入
	dynamicArrayPushBack(arr ,&p1);
	dynamicArrayInsert(arr, 10, &p2);
	dynamicArrayInsert(arr, 1, &p3);

	// 数据遍历
	printf("=====当前容量:%d,元素个数:%d=====\n", dynamicArrayCapacity(arr), dynamicArraySize(arr));
	dynamicArrayForEach(arr, myPrintPerson);

	// 扩容测试
	dynamicArrayPushBack(arr, &p4);
	dynamicArrayInsert(arr, 0, &p5);

	// 重新遍历
	printf("\n=====扩容后容量:%d,元素个数:%d=====\n", dynamicArrayCapacity(arr), dynamicArraySize(arr));
	dynamicArrayForEach(arr, myPrintPerson);

	// 删除数据
	dynamicArrayErase(arr,1);
	printf("\n=====删除第一个位置数据后的容量:%d,元素个数:%d=====\n", dynamicArrayCapacity(arr), dynamicArraySize(arr));
	dynamicArrayForEach(arr, myPrintPerson);
	dynamicArrayPopBack(arr);
	printf("\n=====删除最后位置数据后的容量:%d,元素个数:%d=====\n", dynamicArrayCapacity(arr), dynamicArraySize(arr));
	dynamicArrayForEach(arr, myPrintPerson);

	// 销毁数组
	dynamicArrayDestroy(arr);
	printf("\n=====数组已经销毁=====\n");

}

// 主函数
int main()
{
	testDynamicArray();
	return 0;
}

你可能感兴趣的:(c语言)