C语言数据结构一:动态数组

这篇具有很好参考价值的文章主要介绍了C语言数据结构一:动态数组。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、概念认知

先说一个概念:数组是一段连续的内存空间。存储相同的数据类型。

数组的两个关键点:

  1. 连续内存;
  2. 相同类型。

第一个特征:起点。

首先连续内存:所以为了找到动态数组我们必须找到一个首元素地址。(内存首地址。)

如果不知道首地址,那无法找到并操作内存空间。

第二个特征:容量。

知道首地址了,我们需要知道元素的个数。动态数组:动态,可以动态插入、删除、扩展容量。所以我们必须有个初始容量。开辟多大空间,放多少个元素。除了容量,最好还有一个状态量:表示当前的数据量:也就是说长度。

  • 容量说的是:可以放多少数据。
  • 长度说的是:当前有多少数据。

我的钱包(=银行)(容量)无限大,而我当前拥有的钱有限(长度)。

第三个特征:类型。

我们虽然有了首地址,也有了元素个数,但是我们还是不知道开辟多大空间。因为我们还需要元素的大小。元素所占内存的大小,其实就是元素的类型。可是:

C语言无法动态获取数据的类型。

好在我们还有void*类型可以指向任意类型的指针。

所以我们虽然不知道具体元素的类型,但是我们可以定义一个通用的指针类型,指向我们的元素的地址即可。所以我们的元素就是地址。

所以我们的元素类型就是void*。所以我们的数组指针就是void**类型。


 二、实现

动态数组其实就是一种结构体。 一种自定义类型。

2.1定义数据结构。

先写数据信息结构。

 首地址,就像存储字符串数组一样。

char * arr[] = {"aaa", "bbb", "ccc"};

 arr数组中每个元素都是char*类型,那么arr如果写入结构体中,可以定义为:

struct DemoStruct
{
    char ** arr;
    int b;
}

 同样道理:元素类型为void*。那么定义数组则为void** arr

//1. 先把所需要的数据信息结构定义下来
struct DynamicArray
{
	//数组存储元素的空间的首地址
	void **addr;
	//存储数据的内存中最大能够容纳多少元素
	int capacity; //容量
	//当前存储数据的内存中有多少个元素
	int size; //大小
};

2.2 头文件

#pragma once

#include<stdlib.h>
#include<stdio.h>
#include<string.h>

#ifdef __cplusplus
extern "C"{
#endif

	//1. 先把所需要的数据信息结构定义下来
	struct DynamicArray
	{
		//数组存储元素的空间的首地址
		void **addr;
		//存储数据的内存中最大能够容纳多少元素
		int capacity; //容量
		//当前存储数据的内存中有多少个元素
		int size; //大小
	};

	//初始化数组
	struct DynamicArray *Init_DynamicArray(int capacity);
	//插入元素
	void Insert_DynamicArray(struct DynamicArray *arr, int pos, void *data);
	//遍历
	void Foreach_DynamicArray(struct DynamicArray *arr, void(*_callback)(void *));
	//位置删除
	void RemoveByPos_DynamicArray(struct DynamicArray *arr, int pos);
	//按值删除
	void RemoveByValue_DynamicArray(struct DynamicArray *arr, void *data, int(*compare)(void*, void *));
	//销毁数组
	void Destroy_DynamicArray(struct DynamicArray *arr);

#ifdef __cplusplus
}
#endif

头文件中,我们要写入关于这个数组结构体的一些方法声明,然后去.c源文件中去实现。


2.3初始化

首先是初始化数组。给数组分配内存,也就是给这个结构体分配内存

我们的动态数组初始化也是:struct DynamicArray * arr = malloc(sizeof(struct DynamicArray));

arr这个指针指向这个数组的结构体:即动态数组。

动态数组其实就是一种结构体。 

然后这个结构体的一个属性:首地址:首地址指向整个数组地址。为整个数组开辟空间。就像是char * arr = malloc(sizeof(char*)*Num)一样操作。

arr->addr = malloc(sizeof(元素类型)* Num);这里Num为Capacity。元素类型为void*由使用方指定。

arr->addr = malloc(sizeof(void*)*arr->capacity);

此时没有插入值,所以size为零。

//初始化数组
struct DynamicArray *Init_DynamicArray(int capacity)
{
	if (capacity <= 0)
	{
		return NULL;
	}

	struct DynamicArray *arr = malloc(sizeof(struct DynamicArray));
	if (NULL == arr)
	{
		return NULL;
	}
	arr->capacity = capacity;
	arr->addr = malloc(sizeof(void *)*arr->capacity);
	arr->size = 0;

	return arr;
}

2.4插入元素 

此时注意我们输入函数的是结构体指针

//插入元素
void Insert_DynamicArray(struct DynamicArray *arr, int pos, void *data)
{

	if (NULL == arr)
	{
		return;
	}

	if (NULL == data)
	{
		return;
	}

	if (pos < 0 || pos > arr->size)
	{
        // 如果插入时,pos大于当前的长度,则直接把数据放在数组最后面。
		pos = arr->size;
	}

	//判断空间是否足够
	if (arr->size >= arr->capacity)
	{

		//1. 申请一块更大的内存空间
		int newcapacity = arr->capacity * 2;   // C#中动态数组也是如此操作的。
		void **newspace = malloc(sizeof(void *)* newcapacity);

		//2. 将原来空间的数据拷贝到新空间
		memcpy(newspace, arr->addr, sizeof(void *)* arr->capacity);

		//3. 释放原来空间的内存
		free(arr->addr);

		//4. 更新addr指向
		arr->addr = newspace;
		arr->capacity = newcapacity;

	}


	//移动元素,给pos位置空出位置来
	for (int i = arr->size - 1; i >= pos; --i)
	{
		arr->addr[i + 1] = arr->addr[i];
	}

	//将新元素插入到pos位置
	arr->addr[pos] = data;
	arr->size++;
}

2.5遍历查看 

遍历时,我们不知道元素的内部结构(数据结构),所以这个打印查看函数,需要使用方提供,

使用方调用我们这个方法,我们这个方法反向回调使用方的提供的函数。因为我们不知道使用方使用什么类型,使用方自己知道如何打印自己的数据结构(数据信息)。

回调函数定义方法:

返回值类型(*回调函数名称)(参数列表)

//遍历
void Foreach_DynamicArray(struct DynamicArray *arr, void(*_callback)(void *))
{
	if (NULL == arr)
	{
		return;
	}

	if (NULL == _callback)
	{
		return;
	}

	for (int i = 0; i < arr->size; ++i)
	{
		_callback(arr->addr[i]);
	}


}

2.6删除

//位置删除
void RemoveByPos_DynamicArray(struct DynamicArray *arr, int pos)
{

	if (NULL == arr)
	{
		return;
	}

	if (pos < 0 || pos > arr->size - 1)
	{
		return;
	}


	for (int i = pos; i < arr->size - 1; ++i)
	{
		arr->addr[i] = arr->addr[i + 1];
	}


	arr->size--;
}
//按值删除
void RemoveByValue_DynamicArray(struct DynamicArray *arr, void *data, int(*compare)(void*, void *))
{
	if (NULL == arr)
	{
		return;
	}

	if (NULL == data)
	{
		return;
	}

	if (NULL == compare)
	{
		return;
	}


	for (int i = 0; i < arr->size; ++i)
	{
		if (compare(arr->addr[i], data))
		{
			RemoveByPos_DynamicArray(arr, i);
			break;
		}
	}

}

2.7销毁

//销毁数组
void Destroy_DynamicArray(struct DynamicArray *arr)
{
	if (NULL == arr)
	{
		return;
	}

	if (arr->addr != NULL)
	{
		free(arr->addr);
		arr->addr = NULL;
	}

	free(arr);
	arr = NULL;
}

原则上,谁molloc的,谁free,但是具体元素数据指向哪里,我们并不知道,且使用方知道,使用方也必须开辟,使用方开辟,那就由使用方释放。我们只free我们自己开辟的开辟从外到内,释放是从内到外:先释放内部属性的指针:arr->addr的内存,然后再释放整个结构体指针。

使用方测试:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"DynamicArray.h"


struct Person
{
	char name[64];
	int age;
};

void myPrint(void *data)
{
	struct Person *person = (struct Person *)data;
	printf("Name:%s Age:%d\n", person->name,person->age);
}

int myCompare(void *d1,void *d2)
{
	struct Person *p1 = (struct Person *)d1;
	struct Person *p2 = (struct Person *)d2;

	return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}


void test()
{
	//创建动态数组
	struct DynamicArray *da = Init_DynamicArray(5);
	//动态数组添加元素
	struct Person p1 = { "aaa", 10 };
	struct Person p2 = { "bbb", 20 };
	struct Person p3 = { "ccc", 30 };
	struct Person p4 = { "ddd", 40 };
	struct Person p5 = { "eee", 50 };
	struct Person p6 = { "fff", 60 };
	
	Insert_DynamicArray(da, 0, &p1);
	Insert_DynamicArray(da, 0, &p2);
	Insert_DynamicArray(da, 0, &p3);
	Insert_DynamicArray(da, 1, &p4);
	Insert_DynamicArray(da, 1, &p5);

	printf("capacity:%d\n",da->capacity);
	Insert_DynamicArray(da, 100, &p6);  //3 5 4 2 1 6
	printf("capacity:%d\n", da->capacity);

	Foreach_DynamicArray(da, myPrint);

	printf("---------------\n");
	RemoveByPos_DynamicArray(da,2);//3 5 2 1 6
	Foreach_DynamicArray(da, myPrint);

	printf("---------------\n");
	struct Person pDel = { "aaa", 10 };
	RemoveByValue_DynamicArray(da, &pDel, myCompare);

	//da->addr = NULL;

	Foreach_DynamicArray(da, myPrint);

	//销毁
	Destroy_DynamicArray(da);
}

int main(){

	test();

	system("pause");
	return EXIT_SUCCESS;
}

反思:

这样的一个写法有个问题:我们直接把我们的结构体暴露了在外面,任何人都可以修改访问。这样是不安全的。

我们初始化函数时,返回的是我们的结构体指针。直接把结构体返回了。

解决方案是我们的函数体返回时,返回void*,无类型指针。这样其他函数插入参数时,也是void*类型。然后在函数里面做个强制类型转换即可。避免别人在外面直接调用结构指针。

此时考虑面向对象的封装特性。文章来源地址https://www.toymoban.com/news/detail-742511.html

到了这里,关于C语言数据结构一:动态数组的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言 数据结构】数组与对称矩阵的压缩存储

    提到数组,大家首先会想到的是:很多编程语言中都提供有数组这种数据类型,比如 C/C++、Java、Go、C# 等。但本节我要讲解的不是作为数据类型的数组,而是数据结构中提供的一种叫数组的存储结构。 和线性存储结构相比,数组最大的不同是:它存储的数据可以包含多种“一

    2024年02月04日
    浏览(15)
  • 头歌(C语言)-数据结构与算法-数组(共7关)

    任务描述 本关任务:将十个数进行从大到小的顺序进行排列。 相关知识(略) 编程要求 根据提示,在右侧编辑器 Begin-End 处补充代码。 输入 输入十个整数。 输出 以从大到小的顺序输出这个十个数。 测试说明 样例输入: 1 2 3 4 5 6 7 8 9 10 样例输出: 10 9 8 7 6 5 4 3 2 1 代码:

    2024年02月11日
    浏览(12)
  • C语言自定义数据类型(二)使用结构体数组

    一个结构体变量中可以存放一组有关联的数据(如一个学生的学号、姓名、成绩等数据)。如果有 10 个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数值型数组的不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别

    2024年01月19日
    浏览(8)
  • 【JavaSE专栏48】Java集合类ArrayList解析,这个动态数组数据结构你了解吗?

    作者主页 :Designer 小郑 作者简介 :3年JAVA全栈开发经验,专注JAVA技术、系统定制、远程指导,致力于企业数字化转型,CSDN学院、蓝桥云课认证讲师。 主打方向 :Vue、SpringBoot、微信小程序 本文讲解了 Java 中集合类 ArrayList 的语法、使用说明和应用场景,并给出了样例代码。

    2024年02月16日
    浏览(14)
  • 数据结构与算法教程,数据结构C语言版教程!(第五部分、数组和广义表详解)五

    数组和广义表,都用于存储逻辑关系为“一对一”的数据。 数组存储结构,99% 的编程语言都包含的存储结构,用于存储不可再分的单一数据;而广义表不同,它还可以存储子广义表。 本章重点从矩阵的角度讨论二维数组的存储,同时讲解广义表的存储结构以及有关其广度和

    2024年01月23日
    浏览(14)
  • 数据结构与算法教程,数据结构C语言版教程!(第五部分、数组和广义表详解)三

    数组和广义表,都用于存储逻辑关系为“一对一”的数据。 数组存储结构,99% 的编程语言都包含的存储结构,用于存储不可再分的单一数据;而广义表不同,它还可以存储子广义表。 本章重点从矩阵的角度讨论二维数组的存储,同时讲解广义表的存储结构以及有关其广度和

    2024年01月21日
    浏览(18)
  • 数据结构 | 寻找二维数组的最大值和对应下标 | C语言代码

    题目:         本题目要求读入M(最大为10)行N(最大为15)列个元素,找出其中最大的元素,并输出其行列值。 输入格式:         输入在第一行中给出行数m和列数n。接下来输入m*n个整数。 输出格式:         输出最大值的行号,列号,值。 输入样例: 2 3 1 2 3 4 5 6 输

    2024年02月05日
    浏览(12)
  • 追梦之旅【数据结构篇】——详解C语言动态实现顺序表

        😎博客昵称:博客小梦 😊最喜欢的座右铭:全神贯注的上吧!!! 😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主! 😘博主小留言:哈喽! 😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不

    2024年01月24日
    浏览(11)
  • 追梦之旅【数据结构篇】——详解C语言动态实现带头结点的双向循环链表结构

        😎博客昵称:博客小梦 😊最喜欢的座右铭:全神贯注的上吧!!! 😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主! 😘博主小留言:哈喽! 😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不

    2024年01月17日
    浏览(20)
  • 『初阶数据结构 • C语言』⑧ - 动态顺序表详解(附完整源码)

    本章内容 写在前面 1.静态与动态是指什么? 2.动态顺序表结构的定义 3.动态顺序表的函数接口实现 4.动态顺序表的问题及思考 5.关于顺序表的OJ题 6.OJ答案及解析 1.移除元素 2.删除有序数组中的重复项 ​3.合并两个有序数组 7.动态顺序表完整源码 1.SeqList.h 2.SeqList.c     上一章

    2024年02月16日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包