线性表:表中的数据元素关系是一对一的关系

顺序表:将表中的数据依次存放在计算机内存中

一组地址连续的存储单元中的一种数据结构

基本操作:增删改查,数组是顺序组的一种表现,物理位置连续,逻辑位置连续。

 1.增:

腾出位置用来存放插入的数据,如果空间不够的情况下,要做插入,会导致后面的数据溢出。

2.删

在尾部删除

 在中部或者头部删除:

改与查过于简易,这里不做赘述。

顺序表的实现:(可变化)

对于该顺序表的创建过程:结合了结构体,指针,函数等多方面的内容,需要仔细推敲。意味着pArr->pData=NULL;

parr->length==NULL

销毁:

函数内部操作的是实参的副本,不是实参本身。如果想通过函数修改外部变量(包括指针变量)的本身值,就需要传递它的地址

一、为什么创建 / 初始化函数(createArr、initArrData)不需要二级指针?

这两个函数的目标不是修改外部指针变量本身,而是操作「指针指向的结构体对象」,用一级指针就足够了:

  1. 初始化函数(initArrData):传递一级指针Arr* pArr

    • 主函数中传递的是myArr(一级指针,指向结构体对象),函数内部的pArrmyArr副本,但这两个指针(myArrpArr指向同一块结构体内存
    • 我们在函数中操作的是 pArr->arrpArr->length(结构体成员),本质是通过指针副本修改「共同指向的结构体对象的内容」,不是修改myArr这个指针变量本身的值。
    • 简单说:我只是想给 “指针指向的房子装修”,不是想 “更换房子的门牌号(指针本身的值)”,一级指针足够。
  2. 创建函数(createArr):直接返回Arr*指针

    • 这个函数是新建一块结构体内存,把内存地址返回给主函数,主函数用myArr(一级指针)接收这个地址即可,不需要通过函数修改myArr本身,自然不需要二级指针。

二、为什么销毁函数(destroyArr)必须用二级指针Arr** pArrPtr

销毁函数的目标之一是修改外部指针变量本身(把主函数的myArr置为NULL,这时候必须传递外部指针的地址(即二级指针):

  1. 我们的需求:销毁后,主函数中的myArr必须变成NULL(避免野指针),也就是要修改myArr这个指针变量本身的值(不是修改它指向的内容)。
  2. 如果用一级指针Arr* pArr传递:
    • 函数内部的pArrmyArr的副本,此时执行pArr = NULL,修改的只是副本的值,主函数中的myArr仍然指向原来被free的无效内存,还是野指针。
    • 简单说:想修改 “门牌号(指针本身)”,但只拿到了 “门牌号的复印件(指针副本)”,修改复印件毫无意义。
  3. 用二级指针Arr** pArrPtr传递:
    • 传递的是&myArr(主函数中myArr指针变量的地址),函数内部的pArrPtr指向myArr
    • 执行*pArrPtr = NULL,本质是通过 “指针地址” 直接修改主函数中myArr本身的值,让它变成NULL,达到避免野指针的目的。
    • 简单说:拿到了 “门牌号的存放地址”,可以直接修改门牌号本身,让它变成无效(NULL)。

总结:一级指针 vs 二级指针的使用边界(针对结构体操作)

操作目标所需指针类型核心原因
修改结构体成员(arrlength一级指针(Arr*只需访问指针指向的结构体内存,无需修改指针本身
修改外部指针变量本身(置为NULL二级指针(Arr**需通过指针地址修改外部指针的值,克服值传递的副本限制

简单类比:

  • 一级指针:你有一把家门钥匙(myArr),复制了一把(pArr),用复制的钥匙也能打开家门、修改家里的东西(结构体成员)。
  • 二级指针:你想把原来的家门钥匙作废(置为NULL),必须找到存放原钥匙的盒子(&myArr),二级指针就是打开这个盒子的钥匙。

为什么必须单独释放(*pArr)->pData

(*pArr)->pData动态分配的内存(比如通过malloc申请的数组),它的内存和结构体*pArr本身的内存是分开的

  • *pArr的内存:是结构体对象的内存(比如用malloc(sizeof(ArrStruct))申请的);
  • (*pArr)->pData的内存:是结构体内部指针单独申请的内存(比如(*pArr)->pData = malloc(10 * sizeof(int)))。

如果只释放*pArr(结构体本身),pData指向的内存会变成无人管理的 “内存泄漏”—— 因为结构体释放后,再也找不到pData的地址,没法释放这部分内存了。

为什么length不用单独释放?

length是结构体的普通成员变量(不是指针),它的内存是包含在结构体*pArr的内存里的。当你执行free(*pArr)释放结构体本身时,length占用的内存会随着结构体一起被系统回收,不需要单独用freefree只能用来释放动态分配的内存,普通成员变量不需要)。

因为函数型参与实参的区别,用二级指针的话可以实现对于实参的真正销毁。 

且两者都有释放的意义; 

   create函数的返回类型是Arr*,但你写了return -1;-1int类型),这是类型不匹配,编译会报错。应该改成return NULL;

清除:与销毁不同,只是把原来赋予的值清除掉。

判断数据表是否为空:

添加数据:

1.尾部追加函数

你这个需求用realloc是更高效的(不用新申请 + 拷贝)

realloc更好!不会造成内存的浪费,使用realloc的一些注意事项:

  • 必须用临时指针接收返回值:如果realloc失败,会返回NULL,如果直接用p->arr = realloc(...),失败时会导致原p->arr的地址丢失(内存泄漏);
  • 原指针为NULL的兼容:如果p->arrNULL(比如首次添加元素),realloc会自动等价于malloc,不用单独写malloc的逻辑;
  • 内存扩充的逻辑顺序:先扩内存→检查是否成功→再更新指针 / 长度 / 添加元素,避免 “长度先增加但内存没扩成” 的逻辑错误。

2.插入位置函数

 

3.删除数据

A:尾部删除

B:头部/中部删除

查找

补:笔记有误,在return 后自行添加-1;

改写:

以下是完整代码示例

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
_CRT_SECURE_NO_WARNINGS
typedef struct Myarr
{
	int* arr;
	int length;
}Arr;
//创建结构体
Arr* creat(void)
{
	Arr* p = (Arr*)malloc(sizeof(Arr));
	if (p == NULL)
	{
		printf("创建失败");
		return NULL;
	}
	return p;
}
void creatarr(Arr* p, int length)
{
	p->arr = (int*)malloc(sizeof(int) * length);
	p->length = length;

}//子函数的赋值函数的封装;
//创建函数的封装,为其赋予内存和内容;d
void destroy(Arr** p)
{
	if (NULL == (*p))
	{
		printf("本身不存在,无需销毁");
		return;
	}
	if (NULL == (*p)->arr)
		printf("无需销毁,数组不存在");
	free(*p);
	*p = NULL;
	free((*p)->arr);
	(*p)->arr = NULL;

}
//创建函数的销毁,从根本上清除,需要二级指针。
void clear(Arr* p)
{
	if (NULL == p)
	{
		printf("本身不存在,无法清除");
		return;
	}
	if (NULL == p->arr)
	{
		printf("数组为空,无需要清除");
		return;
	}
	free(p->arr);
	p->arr = NULL;
	p->length = NULL;
}
//创建函数的清除
void  iskong(Arr* p)
{

	if (NULL == p)
	{
		printf("本身不存在");
	
	}
	if (NULL == p->arr)
	{
		printf("该数组为空");
		
	}
	printf("不为空\n");
}
//判断数组是否为空;
void putlast(Arr* p, int target)
{
	if (NULL == p)
	{
		printf("本身不存在");
		return;
	}
	int* newarr = realloc(p->arr, sizeof(int) * (p->length + 1));
	if (newarr == NULL)
	{
		printf("扩充内存失败");
		return;
	}
	p->arr = newarr;
	p->arr[p->length] = target;
	p->length++;

}//在数组的末尾添加函数;
void putmid(Arr* p, int ip, int target)
{
	if (NULL == p)
	{
		printf("本身不存在");
		return;
	}
	if (ip<0 || ip>p->length)
	{

		printf("ip不合法");
		return;
	}

	int* newarr = (int*)realloc(p->arr, sizeof(int) * (p->length + 1));
	if (newarr == NULL)
	{
		printf("扩充内存失败");
		return;
	}
	p->arr = newarr;
	for (int i = p->length; i >= ip;i--)
		p->arr[i] = p->arr[i - 1];
	p->arr[ip] = target;
	p->length++;

}//在数组中间增删改,其实有这个就够了,包含在尾部更好操作。

int  search(Arr* p, int target)
{
	if (NULL == p)
	{
		printf("本身不存在");
		return  -1;
	}
	for (int i = 0;i < p->length;i++)
	{
		if (p->arr[i] == target)
			return i;
	}
	

}
//查找函数,并返回其ip下标;
void delete(Arr* p, int target)
{
	if (NULL == p)
	{
		printf("本身不存在");
		return;
	}
	int r = search(p, target);
	if (r == -1)
	{
		printf("不存在该值,无法删除");
		return;
	}

	for (int i = r;i < p->length - 1;i++)
		p->arr[i] = p->arr[i + 1];

	int* newarr = (int*)realloc(p->arr, sizeof(int) * (p->length - 1));
	if (newarr == NULL)
	{
		printf("收缩内存失败");
		return;
	}
	p->arr = newarr;
	p->length--;
}//在中部删减数据,其实在末尾也适用,所以末尾的就先不写了,🤫;
void change(Arr* p, int from, int target)
{
	if (NULL == p)
	{
		printf("本身不存在");
		return;
	}
	int r = search(p, from);
	if (r == -1)
	{
		printf("不存在该值,无法修改");
		return;
	}
	p->arr[r] = target;

}//对数组中数值的改写;
void printd(Arr* p)
{
	for (int i = 0;i < p->length;i++)
	

		printf("%d", p->arr[i]);
	printf("\n");


}
//接下来是应用时刻;
int main()
{
	Arr *p = creat();
	creatarr(p, 5);
	for (int i = 0;i < p->length;i++)
		p->arr[i] = i + 1;
	/*destroy(p);*/
	/*clear(p);*/
	putlast(p, 6);
	iskong(p);
	putmid(p, 0, 0);

	delete(p, 1);
	printd(p);
	int s = search(p, 0);
	printf("%d", s);
	
	return 0;
}

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐