顺序表详解|顺序表常见错误并调试分析

news2025/1/12 12:23:58

前言:

        今天我们开始学习基础的数据结构——顺序表,数据结构就是将数据在内存存储起来,在内存管理数据。


一、线性表

        1、线性表(Linear list)是n个具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串……

        2、线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

顺序表:用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置表示。 

链表:用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系由指针表示。 

二、顺序表

1、概念即结构

        顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

        顺序表一般可以分为:

(1)静态顺序表:使用定长数组存储元素。

(2)动态顺序表:使用动态开辟的数组存储

        顺序表的本质:

顺序表的本质就是数组,但是在数组的基础上,它还要求数据是从开始位置连续存储的,不能跳跃间隔。

(1)静态顺序表:使用定长数组存储元素

//顺序表的静态存储——开少了不够用,开多了浪费
#include<stdio.h>

#define N 7//定义宏常量N为顺序表中数组的大小(优点后续能做到一改全改)
typedef int SLDataType;//重定义顺序表中元素的类型(优点后续能做到一改全改)

typedef struct SeqList
{
	SLDataType array[N];//定长数组
	size_t size;//有效数据的个数
}SL;

        顺序表最大的缺点:顺序表的空间,在编译阶段就已经确定了,不能修改。所以就会产生空间开少了不够,开多了浪费的问题。

(2)动态顺序表:使用动态开辟的数组存储

//顺序表的动态存储——按需申请
#include<stdio.h>

typedef int SLDataType;//重定义顺序表中元素的类型
typedef struct SeqList
{
	SLDataType* array;//指向动态开辟的数组
	size_t size;//有效数据的个数
	size_t capacity;//容量空间的大小
}SL;

        动态顺序表改良了静态顺序表的缺点,空间按需申请,系统根据程序需要即时分配。

 2、接口实现

        静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N在编译阶段就已经确定好了,N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

        接口函数:接口函数就是某个模块写了给其他模块用的函数,简单的说接口函数就是类中的公有函数。

        我们实现顺序表,也是尽量将其模块化(即通过接口实现),写一部分调试一部分,防止最后代码bug过多。

项目名称:SeqList

项目分为三个模块:

        (1)顺序表的测试模块(调试模块):Test.c

        (2)顺序表的实现模块(接口函数的实现):SeqList.c

        (3)顺序表的声明模块(头文件、顺序表结构体、接口函数的声明):SeqList.h

(1)声明模块:SeqList.h


//预处理,包含后续用到的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>

//宏常量——优点能做到一改全改
#define INIT_CAPACITY 4 //初始化顺序表是,开辟空间的大小

//顺序表的动态存储——按需申请
typedef int SLDataType;//重定义顺序表中元素的类型(优点:能做到一改全改)
typedef struct SeqList
{
	SLDataType* array;//指向动态开辟的数组
	int size;//有效数据的个数
	int capacity;//容量空间的大小
}SL;

//数据的管理常用的无非就四种:增删查改
//基本增删查改接口

//顺序表初始化
void SLInit(SL* ps);

//检查空间,如果满了,进行增容
void CheckCapacity(SL* ps);

//顺序表尾插
void SLPushBack(SL* ps, SLDataType x);

//顺序表尾删
void SLPopBack(SL* ps);

//顺序表头插
void SLPushFront(SL* ps, SLDataType x);

//顺序表头删
void SLPopFront(SL* ps);

//顺序表查找
int SLFind(SL* ps, SLDataType x);

//顺序表在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x);

//顺序表删除pos位置的值
void SLErase(SL* ps, int pos);

//顺序表销毁
void SLDestory(SL* ps);

//顺序表打印
void SLPrint(SL* ps);

        接口函数:命名风格是跟着STL走的,建议大家也是一样,方便后期学习。

0X01顺序表初始化(SLInit)

        顺序表初始化模块①实现顺序表初始化array指向开辟数组空间起始位置;②实现初始化有几个有效数据个数;③实现初始化容量空间的大小。(注意:参数的传递为传址调用——形参的改变要影响实参的改变)

        SeqList.c文件实现:

//顺序表初始化
void SLInit(SL* ps)
{
	//函数参数为指针且要求不能为NULL,则一定要断言指针的有效性(方便我们查错)
	assert(ps);
	//①给数组初始化开辟INIT_CAPACITY个SLDataType类型的空间
	ps->array = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
	//判断malloc是否开辟成功
	if (NULL == ps->array)
	{
		//打印错误信息
		perror("SLInit::malloc");
		//退出
		return;
	}
	//②有效数据个数的初始化
	ps->size = 0;
	//③容量空间大小的初始化
	ps->capacity = INIT_CAPACITY;
}

        Test.c文件调试:

//实现测试顺序表的初始化功能
void TestSeqList1()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);
}

int main()
{
	TestSeqList1();
	return 0;
}

        F10调试观察是否初始化成功:如下图

0X02顺序表的扩容&尾插&打印&销毁

        顺序表的扩容模块:每一次添加新数据的时候我们都要调用该模块是否进行扩容,如果顺序表的有效数据个数等于容量空间大小时即要扩容,一般扩容我们扩容2倍(其他的也可以)。在扩容时我们常常遇到一些问题,今天我来为大家演示一遍。

        SeqList.c文件实现:扩容的错误代码演示

//检查空间,如果满了,进行增容
void CheckCapacity(SL* ps)
{
	//ps不能为空,所以一般先断言指针的有效性
	assert(ps);
	//判断是否扩容,即有效数据个数等于容量空间时扩容
	if (ps->size == ps->capacity)
	{
		//防止扩容失败,先用一个临时指针变量存储申请空间的起始地址
		SLDataType* tmp = (SLDataType*)realloc(ps->array, ps->capacity * 2);
		//判断是否扩容成功,扩容成功仍用ps->array指向这块空间,否则提示扩容失败并退出
		if (tmp != NULL)
		{
			ps->array = tmp;
			//扩容成功,顺序表的容量空间也要更新
			ps->capacity *= 2;
		}
		else
		{
			//打印错误信息
			perror("CheckCapacity::realloc");
			exit(-1);
		}
	}
}

        Test.c文件调试:

void TestSeqList1()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//尾插7个新数据
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPushBack(&s, 5);
	SLPushBack(&s, 6);
	SLPushBack(&s, 7);

	//打印顺序表观察
	SLPrint(&s);

	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList1();
	return 0;
}

        运行结果有一个奇怪的值:如下图

        F10调试之后,发现是free出错:如下图

free出错一般有两个可能:

        ①释放指针为野指针或者释放指针没有指向开辟空间的起始地址 ;

        ②对动态开辟的空间越界访问。

        根据我们调试和free可能出错的原因,我们将错误锁定在扩容,仔细查错后发现是越界访问了,因为realloc等动态开辟申请空间大小的单位是字节,所以扩容之后空间大小为8字节,不是我们认为的32个字节。

使用realloc需要注意的几点:

        1、realloc开辟空间有三种情况:①原地扩容;②另找一块空间扩容;③扩容失败。所以为了防止扩容失败之后原来的空间也不能使用,一定要先使用临时变量来指向开辟的空间。

        2、所有动态开辟的函数malloc、realloc申请空间的大小都是字节,要注意别造成越界访问。

对于该知识点还有问题的可以去复习下该知识点,链接如下:动态内存管理(1)_从前慢,现在也慢的博客-CSDN博客

        SeqList.c文件实现:扩容的正确代码演示

void CheckCapacity(SL* ps)
{
	//ps不能为空,所以一般先断言指针的有效性
	assert(ps);
	//判断是否扩容,即有效数据个数等于容量空间时扩容
	if (ps->size == ps->capacity)
	{
		//防止扩容失败,先用一个临时指针变量存储申请空间的起始地址
		SLDataType* tmp = (SLDataType*)realloc(ps->array, sizeof(SLDataType) * ps->capacity * 2);
		//判断是否扩容成功,扩容成功仍用ps->array指向这块空间,否则提示扩容失败并退出
		if (tmp != NULL)
		{
			ps->array = tmp;
			//扩容成功,顺序表的容量空间也要更新
			ps->capacity *= 2;
		}
		else
		{
			//打印错误信息
			perror("CheckCapacity::realloc");
			exit(-1);
		}
	}
}

        顺序表的尾插模块:尾插就是在最后面添加一个新数据,每一次添加数据都要调用CheckCapacity函数判断是否扩容,顺序表是从头开始连续存储的结构,所以直接ps->array[ps->size] = x即添加成功,添加成功后注意有效数据个数+1。

        图示:

        SeqList.c文件实现:顺序表的尾插

void SLPushBack(SL* ps, SLDataType x)
{
	//断言指针的有效性
	assert(ps);
	//添加新数据,先调用CheckCapacity函数,判断是否扩容
	CheckCapacity(ps);
	//尾插——顺序表是从头开始连续存储size个有效数据,且数组下标从0开始
	ps->array[ps->size] = x;
	//尾插之后,有效数据个数更新
	ps->size++;
}

        顺序表的打印模块:顺序表是从开始位置连续存储size个数据,所以我们直接for循环,从第一个开始遍历到size个即可。

        SeqList.c文件实现:顺序表的打印

void SLPrint(SL* ps)
{
	//ps不能为NULL,先断言
	assert(ps);
	//for循环遍历顺序表
	size_t i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->array[i]);
	}
	printf("\n");
}

        顺序表的销毁模块:因为顺序表我们是动态开辟的内存,当我们不再使用顺序表,如果服务器一直运行,就会造成内存泄漏,所以我们要有一个手动销毁的接口(注意销毁之后有效数据和容量空间都为0)。

         SeqList.c文件实现:顺序表的销毁

void SLDestory(SL* ps)
{
	//使用完销毁
	free(ps->array);
	//释放之后,ps->array不改变,防止非法访问置为NULL
	ps->array = NULL;
	//释放之后,有效数据个数和容量空间都为0
	ps->capacity = ps->size = 0;
}

        Test.c文件调试:

void TestSeqList1()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//尾插7个新数据
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPushBack(&s, 5);
	SLPushBack(&s, 6);
	SLPushBack(&s, 7);

	//打印顺序表观察
	SLPrint(&s);

	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList1();
	return 0;
}

        运行结果:成功

0X03顺序表的尾删

        顺序表的尾删模块:因为顺序表的数据是以有效数据的个数size为标准的,所以我们直接size减1即可。

        SeqList.c文件实现:顺序表的错误尾删演示

void SLPopBack(SL* ps)
{
	//ps->array[ps->size - 1] = 0;//没有意义,因为遍历顺序表是按照size为条件的
	ps->size--;
}

          Test.c文件调试:

void TestSeqList1()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//尾插4个新数据
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);

	//打印顺序表观察
	SLPrint(&s);

	//尾删5个数据,并尾删后每次打印
	SLPopBack(&s);
	SLPrint(&s);
	
	SLPopBack(&s);
	SLPrint(&s); 
	
	SLPopBack(&s);
	SLPrint(&s);

	SLPopBack(&s);
	SLPrint(&s);

	SLPopBack(&s);
	SLPrint(&s);

	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList1();
	return 0;
}

        运行结果:(奇怪)

        为什么呢?我们F10调试观察:

         我们发现尾删到第五个数据时,size变成了一个0xffffffff数,这是因为有效数据只有4个,减到第五个时为-1,-1的补码就是0xffffffff,又因为打印时i是无符号类型所以size也是无符号整形就变成了一个超级大的正数。

        SeqList.c文件实现:顺序表的正确尾删演示

void SLPopBack(SL* ps)
{
	//断言指针的有效性
	assert(ps);
	//①暴力检查,断言——表达式为假,直接结束程序并提示错误位置
	assert(ps->size > 0);
	//②温柔的检查
	/*if (ps->size == 0)
	{
		return;
	}*/
	//ps->array[ps->size - 1] = 0;//没有意义,因为遍历顺序表是按照size为条件的
	ps->size--;
}

        运行结果:

        assert提示我们错误在哪里,我们直接改错即可。

0X04顺序表的头插

        顺序表的头插模块:顺序表的头插就是要在起始位置插入一个数据,添加一个新数据先调用CheckCapacity函数判断是否扩容。因为顺序表是从头开始连续存储的,所以要将以前的数据都向后挪动,才在起始位置添加新数据,注意添加数据之后有效数据个数+1.

        图示:

         SeqList.c文件实现:顺序表的头插演示

void SLPushFront(SL* ps, SLDataType x)
{
	//断言指针的有效性
	assert(ps);
	//添加新数据判断是否扩容
	CheckCapacity(ps);
	//将以前的数据向后挪动,从最后一个开始
	int end = ps->size - 1;//数组下标从0开始
	while (end >= 0)
	{
		ps->array[end + 1] = ps->array[end];
		end--;
	}
	//挪动完之后,在起始位置添加新数据
	ps->array[0] = x;
	//添加完数据,有效数据个数+1
	ps->size++;
}

        Test文件调试:

void TestSeqList2()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//头插4个新数据
	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 4);

	//打印顺序表观察
	SLPrint(&s);

	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList2();
	return 0;
}

        运行结果:

0X05顺序表的头删

        顺序表的头删模块:头删就是将起始位置的数据删除,我们while循环每一次只需将后一个数据向前挪动一位,挪动到begin < ps->size结束,最后再将有效数据个数-1即可。

        图示:

          SeqList.c文件实现:顺序表的头删演示

void SLPopFront(SL* ps)
{
	//断言指针的有效性
	assert(ps);
	//断言顺序表是否为空为空,直接报错并提示
	assert(ps->size > 0);
	//while循环一次将后一个数据向前挪动一位
	int begin = 1;
	while (begin < ps->size)
	{
		ps->array[begin - 1] = ps->array[begin];
		begin++;
	}
	//删除之后,有效数据个数-1
	ps->size--;
}

        Test文件调试:

void TestSeqList2()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//头插4个新数据
	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 4);

	//打印顺序表观察
	SLPrint(&s);

	//头删
	SLPopFront(&s);
	SLPrint(&s);

	SLPopFront(&s);
	SLPrint(&s);

	SLPopFront(&s);
	SLPrint(&s);

	SLPopFront(&s);
	SLPrint(&s);

	SLPopFront(&s);
	SLPrint(&s);
	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList2();
	return 0;
}

        运行结果:

assert断言是一个非常好的工具,如果表达式为假直接报错并提示错误信息的准确位置。 

0X06顺序表在指定位置插入x

        顺序表在指定位置插入x模块:顺序表是连续存储的,要在指定位置下标pos插入数据时,我们要注意插入下标pos是否合理。

        图示:

         SeqList.c文件实现:顺序表在指定位置插入x

void SLInsert(SL* ps, int pos, SLDataType x)
{
	//断言ps指针的有效性
	assert(ps);
	//断言指定位置下标pos是否合理
	assert(pos >= 0 && pos <= ps->size);
	//添加新数据调用CheckCapacity函数判断是否扩容
	CheckCapacity(ps);
	//从最后一个数据开始挪到数据
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->array[end + 1] = ps->array[end];
		end--;
	}
	//挪动完之后插入数据,并有效数据个数+1
	ps->array[pos] = x;
	ps->size++;
}

        该函数可以实现在指定位置插入数据,所以前面的头插尾插可以复用该函数实现。

        SeqList.c文件实现:复用SLInsert函数的尾插

void SLPushBack(SL* ps, SLDataType x)
{
	//断言指针的有效性
	assert(ps);
	添加新数据,先调用CheckCapacity函数,判断是否扩容
	//CheckCapacity(ps);
	尾插——顺序表是从头开始连续存储size个有效数据,且数组下标从0开始
	//ps->array[ps->size] = x;
	尾插之后,有效数据个数更新
	//ps->size++;

	//有了SLInsert函数,我们可以直接复用该函数进行尾插x
	SLInsert(ps, ps->size, x);
}

         SeqList.c文件实现:复用SLInsert函数的头插

void SLPushFront(SL* ps, SLDataType x)
{
	//断言指针的有效性
	assert(ps);
	添加新数据判断是否扩容
	//CheckCapacity(ps);
	将以前的数据向后挪动,从最后一个开始
	//int end = ps->size - 1;//数组下标从0开始
	//while (end >= 0)
	//{
	//	ps->array[end + 1] = ps->array[end];
	//	end--;
	//}
	挪动完之后,在起始位置添加新数据
	//ps->array[0] = x;
	添加完数据,有效数据个数+1
	//ps->size++;

	//有了SLInsert函数,我们可以直接复用该函数进行头插x
	SLInsert(ps, 0, x);
}

        Test文件调试:

void TestSeqList3()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//头插4个新数据
	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 4);

	//打印顺序表观察
	SLPrint(&s);

	//在下标为2的位置插入10
	SLInsert(&s, 2, 10);
	SLPrint(&s);

	//在下标为5的位置插入20即尾插
	SLInsert(&s, 5, 20);
	SLPrint(&s);

	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList3();
	return 0;
}

        运行结果:

0X07删除指定位置下标pos的数据

        删除指定位置pos的数据,我们也要注意pos的位置,注意因为数组下标从0开始,所以ps->size位置没有有效数据。删除指定位置pos思想和头删一样,从后面一个数据开始向前挪动直到begin < ps->size 。

        SeqList.c文件实现:删除指定位置pos的数据

void SLErase(SL* ps, int pos)
{
	//断言指针的有效性
	assert(ps);
	//断言位置的有效性,注意因为数组下标从0开始所以ps->size位置无有效数据(间接判断了size>0)
	assert(pos >= 0 && pos < ps->size);
	//在指定下标pos位置删除值,类似头删,将pos后面的数据向前挪动
	int begin = pos + 1;
	while (begin < ps->size)
	{
		//将后一个数据向前挪动
		ps->array[begin - 1] = ps->array[begin];
		begin++;
	}
	//删除之后有效数据个数-1
	ps->size--;
}

        该函数可以实现在指定位置删除数据,所以前面的头删尾删可以复用该函数实现

        SeqList.c文件实现:复用SLErase函数的头删

void SLPopFront(SL* ps)
{
	//断言指针的有效性
	assert(ps);
	//断言顺序表是否为空为空,直接报错并提示
	//assert(ps->size > 0);
	while循环一次将后一个数据向前挪动一位
	//int begin = 1;
	//while (begin < ps->size)
	//{
	//	ps->array[begin - 1] = ps->array[begin];
	//	begin++;
	//}
	删除之后,有效数据个数-1
	//ps->size--;

	//有了SLErase函数,我们可以直接复用该函数进行头删
	SLErase(ps, 0);
}

        SeqList.c文件实现:复用SLErase函数的尾删

void SLPopBack(SL* ps)
{
	//断言指针的有效性
	assert(ps);
	//①暴力检查,断言——表达式为假,直接结束程序并提示错误位置
	//assert(ps->size > 0);
	//②温柔的检查
	/*if (ps->size == 0)
	{
		return;
	}*/
	//ps->array[ps->size - 1] = 0;//没有意义,因为遍历顺序表是按照size为条件的
	//ps->size--;
	
	//有了SLErase函数,我们可以直接复用该函数进行尾删
	SLErase(ps, ps->size - 1);
}

         Test文件调试:

void TestSeqList3()
{
	SL s;
	//注意SLInit为传址调用,形参的改变要影响实参
	SLInit(&s);

	//头插4个新数据
	SLPushFront(&s, 1);
	SLPushFront(&s, 2);
	SLPushFront(&s, 3);
	SLPushFront(&s, 4);

	//打印顺序表观察
	SLPrint(&s);

	//在下标为2的位置插入10
	SLInsert(&s, 2, 10);
	SLPrint(&s);

	//在下标为5的位置插入20即尾插
	SLInsert(&s, 5, 20);
	SLPrint(&s);

	//删除下标3的数据
	SLErase(&s, 3);
	SLPrint(&s);

	//销毁顺序表
	SLDestory(&s);
}

int main()
{
	TestSeqList3();
	return 0;
}

        运行结果:

我们删除该数据的同时可以将该空间还给操作系统吗,即可以缩容吗?

        答案是:不可以, 缩容——释放一部分空间。操作系统不支持,因为它是整块申请,整块释放的。realloc函数一般都是扩容,不会将其缩小,因为防止被别人占用了,后期想扩容后续空间不足,发生异地扩容。

0X08顺序表查找

        顺序表查找模块:我们只需遍历顺序表,如果找到返回该值下标,没有找到返回-1.

        SeqList.c文件实现:

int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	//遍历顺序表,如果找到就返回该值的下标,没有找到就返回-1
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->array[i] == x)
		{
			return i;
		}
	}
	//没找到
	return -1;
}

三、总代码(菜单测试)

        我们最后可以做一个菜单来测试一下我们所有的功能,但是数据结构的本质意义是存储数据的,所以做菜单的意义并不大。(不建议,一开始就做菜单,做菜单不易调试,最好先写好接口函数调试完了,在写菜单)

        声明模块:SeqList.h

//预处理,包含后续用到的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>

//宏常量——优点能做到一改全改
#define INIT_CAPACITY 4 //初始化顺序表是,开辟空间的大小

//顺序表的动态存储——按需申请
typedef int SLDataType;//重定义顺序表中元素的类型(优点:能做到一改全改)
typedef struct SeqList
{
	SLDataType* array;//指向动态开辟的数组
	int size;//有效数据的个数
	int capacity;//容量空间的大小
}SL;

//数据的管理常用的无非就四种:增删查改
//基本增删查改接口

//顺序表初始化
void SLInit(SL* ps);

//检查空间,如果满了,进行增容
void CheckCapacity(SL* ps);

//顺序表尾插
void SLPushBack(SL* ps, SLDataType x);

//顺序表尾删
void SLPopBack(SL* ps);

//顺序表头插
void SLPushFront(SL* ps, SLDataType x);

//顺序表头删
void SLPopFront(SL* ps);

//顺序表查找
int SLFind(SL* ps, SLDataType x);

//顺序表在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x);

//顺序表删除pos位置的值
void SLErase(SL* ps, int pos);

//顺序表销毁
void SLDestory(SL* ps);

//顺序表打印
void SLPrint(SL* ps);

       实现模块: SeqList.c

//接口函数的实现
#include"SeqList.h"

//顺序表初始化
void SLInit(SL* ps)
{
	//函数参数为指针且要求不能为NULL,则一定要断言指针的有效性(方便我们查错)
	assert(ps);
	//①给数组初始化开辟INIT_CAPACITY个SLDataType类型的空间
	ps->array = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
	//判断malloc是否开辟成功
	if (NULL == ps->array)
	{
		//打印错误信息
		perror("SLInit::malloc");
		//退出
		return;
	}
	//②有效数据个数的初始化
	ps->size = 0;
	//③容量空间大小的初始化
	ps->capacity = INIT_CAPACITY;
}

//检查空间,如果满了,进行增容
void CheckCapacity(SL* ps)
{
	//ps不能为空,所以一般先断言指针的有效性
	assert(ps);
	//判断是否扩容,即有效数据个数等于容量空间时扩容
	if (ps->size == ps->capacity)
	{
		//防止扩容失败,先用一个临时指针变量存储申请空间的起始地址
		SLDataType* tmp = (SLDataType*)realloc(ps->array, sizeof(SLDataType) * ps->capacity * 2);
		//判断是否扩容成功,扩容成功仍用ps->array指向这块空间,否则提示扩容失败并退出
		if (tmp != NULL)
		{
			ps->array = tmp;
			//扩容成功,顺序表的容量空间也要更新
			ps->capacity *= 2;
		}
		else
		{
			//打印错误信息
			perror("CheckCapacity::realloc");
			exit(-1);
		}
	}
}

//顺序表尾插
void SLPushBack(SL* ps, SLDataType x)
{
	//断言指针的有效性
	assert(ps);
	添加新数据,先调用CheckCapacity函数,判断是否扩容
	//CheckCapacity(ps);
	尾插——顺序表是从头开始连续存储size个有效数据,且数组下标从0开始
	//ps->array[ps->size] = x;
	尾插之后,有效数据个数更新
	//ps->size++;

	//有了SLInsert函数,我们可以直接复用该函数进行尾插x
	SLInsert(ps, ps->size, x);
}

//顺序表尾删
void SLPopBack(SL* ps)
{
	//断言指针的有效性
	assert(ps);
	//①暴力检查,断言——表达式为假,直接结束程序并提示错误位置
	//assert(ps->size > 0);
	//②温柔的检查
	/*if (ps->size == 0)
	{
		return;
	}*/
	//ps->array[ps->size - 1] = 0;//没有意义,因为遍历顺序表是按照size为条件的
	//ps->size--;
	
	//有了SLErase函数,我们可以直接复用该函数进行尾删
	SLErase(ps, ps->size - 1);
}

//顺序表头插
void SLPushFront(SL* ps, SLDataType x)
{
	//断言指针的有效性
	assert(ps);
	添加新数据判断是否扩容
	//CheckCapacity(ps);
	将以前的数据向后挪动,从最后一个开始
	//int end = ps->size - 1;//数组下标从0开始
	//while (end >= 0)
	//{
	//	ps->array[end + 1] = ps->array[end];
	//	end--;
	//}
	挪动完之后,在起始位置添加新数据
	//ps->array[0] = x;
	添加完数据,有效数据个数+1
	//ps->size++;

	//有了SLInsert函数,我们可以直接复用该函数进行头插x
	SLInsert(ps, 0, x);
}

//顺序表头删
void SLPopFront(SL* ps)
{
	//断言指针的有效性
	assert(ps);
	//断言顺序表是否为空为空,直接报错并提示
	//assert(ps->size > 0);
	while循环一次将后一个数据向前挪动一位
	//int begin = 1;
	//while (begin < ps->size)
	//{
	//	ps->array[begin - 1] = ps->array[begin];
	//	begin++;
	//}
	删除之后,有效数据个数-1
	//ps->size--;

	//有了SLErase函数,我们可以直接复用该函数进行头删
	SLErase(ps, 0);
}

//顺序表查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	//遍历顺序表,如果找到就返回该值的下标,没有找到就返回-1
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->array[i] == x)
		{
			return i;
		}
	}
	//没找到
	return -1;
}

//顺序表在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{
	//断言ps指针的有效性
	assert(ps);
	//断言指定位置下标pos是否合理
	assert(pos >= 0 && pos <= ps->size);
	//添加新数据调用CheckCapacity函数判断是否扩容
	CheckCapacity(ps);
	//从最后一个数据开始挪到数据
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->array[end + 1] = ps->array[end];
		end--;
	}
	//挪动完之后插入数据,并有效数据个数+1
	ps->array[pos] = x;
	ps->size++;
}

//顺序表删除pos位置的值
void SLErase(SL* ps, int pos)
{
	//断言指针的有效性
	assert(ps);
	//断言位置的有效性,注意因为数组下标从0开始所以ps->size位置无有效数据(间接判断了size>0)
	assert(pos >= 0 && pos < ps->size);
	//在指定下标pos位置删除值,类似头删,将pos后面的数据向前挪动
	int begin = pos + 1;
	while (begin < ps->size)
	{
		//将后一个数据向前挪动
		ps->array[begin - 1] = ps->array[begin];
		begin++;
	}
	//删除之后有效数据个数-1
	ps->size--;
}

//顺序表销毁
void SLDestory(SL* ps)
{
	//使用完销毁
	free(ps->array);
	//释放之后,ps->array不改变,防止非法访问置为NULL
	ps->array = NULL;
	//释放之后,有效数据个数和容量空间都为0
	ps->capacity = ps->size = 0;
}

//顺序表打印
void SLPrint(SL* ps)
{
	//ps不能为NULL,先断言
	assert(ps);
	//for循环遍历顺序表
	size_t i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->array[i]);
	}
	printf("\n");
}

        测试模块: Test.c

#include"SeqList.h"
//自定义函数:实现菜单功能
void menu()
{
	printf("*************************************\n");
	printf("*      1.尾插         2.尾删        *\n");
	printf("*      3.头插         4.头删        *\n");
	printf("*      5.打印         6.查找        *\n");
	printf("*          7.指定位置插入           *\n");
	printf("*          8.指定位置删除           *\n");
	printf("*      9.销毁         0.退出        *\n");
	printf("*************************************\n");
}

//枚举顺序表的功能:增加代码的可读性和维护性
enum SQ
{
	CLOSE,//0——退出,枚举默认从0开始,一次递增1
	PUSH_BACK,
	POP_BACK,
	PUSH_FRONT,
	POP_FRONT,
	PRINT,
	SEARCH,
	INSERT,
	EARSE,
	DESTORY,
};

//测试菜单功能函数
void MenuTest()
{
	//定义一个顺序表变量
	SL s1;
	//调用初始化函数,初始化顺序表
	SLInit(&s1);
	//定义功能选择变量
	int input = 0;
	//定义插入变量存储插入数据
	SLDataType x = 0;
	//定义插入位置变量
	int pos = 0;
	//do……while——至少执行一次
	do
	{
		//调用菜单
		menu();
		printf("请选择功能:>");
		scanf("%d", &input);
		//switch多分支语句
		switch (input)
		{
		case CLOSE://退出(0)
			printf("已退出\n");
			break;
		case PUSH_BACK://尾插(1)
			printf("请输入你要尾插的数据,以-404结束!\n");
			scanf("%d", &x);
			while (x != -404)
			{
				SLPushBack(&s1, x);
				scanf("%d", &x);
			}
			break;
		case POP_BACK://尾删(2)
			SLPopBack(&s1);
			printf("删除成功!\n");
			break;
		case PUSH_FRONT://头插(3)
			printf("请输入你要头插的数据,以-404结束!\n");
			scanf("%d", &x);
			while (x != -404)
			{
				SLPushFront(&s1, x);
				scanf("%d", &x);
			}
			break;
		case POP_FRONT://头删(4)
			SLPopFront(&s1);
			printf("删除成功!\n");
			break;
		case PRINT://打印(5)
			SLPrint(&s1);
			break;
		case SEARCH://查找(6)
			printf("请输入要查找的数据:>");
			scanf("%d", &x);
			int ret = SLFind(&s1, x);
			if (ret != -1)
			{
				printf("找到了,下标为%d\n", ret);
			}
			else
			{
				printf("找不到\n");
			}
			break;
		case INSERT://指定位置插入(7)
			printf("请输入你要插入的位置:>");
			scanf("%d", &pos);
			printf("请输入你要插入的数据,以-404结束!\n");
			scanf("%d", &x);
			while (x != -404)
			{
				SLInsert(&s1, pos, x);
				scanf("%d", &x);
			}
			break;
		case EARSE://指定位置删除(8)
			printf("请输入你要删除的位置:>");
			scanf("%d", &pos);
			SLErase(&s1, pos);
			printf("删除成功!\n");
			break;
		case DESTORY://销毁(9)
			SLDestory(&s1);
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
	

int main()
{
	MenuTest();
	return 0;
}

顺序表我们就大致学完,下期进行单链表的学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/922894.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美国访问学者签证好办吗?

近年来&#xff0c;随着国际交流与合作的不断深入&#xff0c;许多人对于美国访问学者签证的办理情况产生了浓厚的兴趣。那么&#xff0c;美国访问学者签证到底好办吗&#xff1f;让知识人网小编带您一起了解一下。 首先&#xff0c;美国作为世界上的科研、教育和创新中心之一&…

springBoot的启动

自动配置注解的逻辑: 在启动类的基础上,导入了springboot的大量自动配置类,以至于自己不用关心配置实现过程(约定大于配置) 大量自动配置类是如何导入的? Spring提供了一个SpringFactories功能(SPI: service provider interface ),读取固定文件META-INF/spring.factories,按照…

keepalived双机热备,keepalived+lvs(DR)

本节主要学习了keepalivedlvs的作用和配置方法主要配置调度器和web节点&#xff0c;还有keepalived的双击热备&#xff0c;主要内容有概述&#xff0c;安装&#xff0c;功能模块&#xff0c;配置双击热备&#xff0c;验证方法&#xff0c;双击热备的脑裂现象和VIP无法通信。 目…

❤ windows 安装后台java开发环境JDK 、MySQL 、Redis

❤ windows 安装后台java开发环境 1、windows 安装 JDK. 下载地址&#xff1a; http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 1、下载安装 官网点击下载安装 网盘 jdk安装包 链接&#xff1a;https://pan.baidu.com/s/1sdxA6B…

Axure设计之日期选择器(年月选择)

在系统中&#xff0c;日期选择器经常会用到&#xff0c;包括日历日期的选择、日期时间的选择和日期范围的选择&#xff0c;一般是下拉列表的形式进行选择。Axure没有自带的日期选择器&#xff0c;下面教大家如何在Axure中制作真实日期选择&#xff08;年月选择&#xff09;效果…

首席执行官Adam Selipsky解读“亚马逊云科技的技术产品差异化”

迄今为止&#xff0c;亚马逊云科技已经参与了21世纪几乎所有的大型计算变革&#xff0c;亚马逊云科技是一个很传奇的故事&#xff0c;它始于大约20年前的一项实验&#xff0c;当时亚马逊试图出售其过剩的服务器。人们确实对此表示怀疑。为什么在线书店试图销售云服务&#xff1…

js逆向实战之某书protobuf反序列化

什么是Protobuf&#xff1f; \qquad Protobuf&#xff08;Protocol Buffer&#xff09;是 Google 开发的一套数据存储传输协议&#xff0c;作用就是将数据进行序列化后再传输&#xff0c;Protobuf 编码是二进制的&#xff0c;它不是可读的&#xff0c;也不容易手动修改&#xf…

抓包工具Charles的安装及代理设置(Windows浏览器代理、安卓代理)

1、下载Charles 官网地址&#xff1a;https://www.charlesproxy.com/download/&#xff0c;下载对应的安装包。安装完成后按照以下步骤进行代理配置。 2、配置Charles证书 按照以下截图步骤进行配置即可。 3、Charles代理设置 这里的端口号根据自己情况设置&#xff0c;这里…

17.4 【Linux】systemctl 针对 timer 的配置文件

有时候&#xff0c;某些服务你想要定期执行&#xff0c;或者是开机后执行&#xff0c;或者是什么服务启动多久后执行等等的。在过去&#xff0c;我们大概都是使用 crond 这个服务来定期处理&#xff0c; 不过&#xff0c;既然现在有一直常驻在内存当中的 systemd 这个好用的东西…

visio三维格式、三维旋转导出图模糊解决方案

问题描述 visio中元素经过三维格式或三维旋转&#xff08;也可能包括其他的特殊操作&#xff09;后&#xff0c;导出.emf格式的图会模糊&#xff0c;如下图所示。 解决方案 借助其他软件 在ppt中将对应的元素旋转后再导入visio&#xff0c;此时对于visio来说该元素相当于一…

RunnerGo:一款高效且易用的性能测试工具

在软件开发过程中&#xff0c;性能测试是确保应用程序能够高效运行的关键步骤。为了提供高质量的测试服务&#xff0c;许多企业正在寻求功能强大且易用的性能测试工具。RunnerGo是一个基于Go语言开发的性能测试平台&#xff0c;具有简单易用、高效稳定等特性&#xff0c;适用于…

Dubbo重启服务提供者或先启动服务消费者后启动服务提供者,消费者有时候会出现找不到服务的问题及解决

文章目录 [toc] 1.环境2.版本3.pom依赖3.1父工程的pom3.2子模块的pom 4.问题5.根本原因5.1根本原因说明5.2总入口5.3servletWeb容器初始化5.4 nacos服务注册监听点5.5 dubbo启动服务注册监听点 6.解决办法6.1降低springBoot版本为2.2.x6.2 修改源码6.2.1修改源码方式一6.2.2修改…

【RPC框架】RPC与Dubbo(让你一文搞懂,超级详细好理解!)

目录 什么是RPC框架&#xff0c;Dubbo又是什么&#xff0c;二者之间有什么联系 是不是说的有些抽象&#xff0c;那我们来说的通俗易懂点吧&#xff0c;这次你一定能听懂 简单举例 实际例子 真实场景demo&#xff08;说了这么多&#xff0c;实际体会一下代码吧&…

(6)(6.3) 自动任务中的相机控制

文章目录 前言 6.3.1 概述 6.3.2 自动任务类型 6.3.3 创建合成图像 前言 本文介绍 ArduPilot 的相机和云台命令&#xff0c;并说明如何在 Mission Planner 中使用这些命令来定义相机勘测任务。这些说明假定已经连接并配置了相机触发器和云台(camera trigger and gimbal ha…

iis服务web页面 localhost可以访问 ip不能访问

1、修改C:\Windows\System32\drivers\etc\下面hosts文件&#xff1b;需要重启电脑查看效果&#xff1b; 2、通过internet选项-》安全-》站点-》添加对应http://127.0.0.1和对应电能IP&#xff1b;

Android SDK 上手指南||第六章 用户交互

第六章 用户交互 在这篇教程中&#xff0c;我们将对之前所添加的Button元素进行设置以实现对用户点击的检测与响应。为了达成这一目标&#xff0c;我们需要在应用程序的主 Activity类中略微涉及Java编程内容。如果大家在Java开发方面的经验不太丰富也没必要担心&#xff0c;只…

Module not found: Error: Can‘t resolve ‘vue-pdf‘ in ‘xxx‘

使用命令npm run serve时vue项目报错&#xff1a; Module not found: Error: Cant resolve vue-pdf in xxx 解决方案&#xff1a; 运行命令&#xff1a; npm install vue-pdf --save --legacy-peer-deps 即可解决。 再次顺利执行npm run serve

032 - 位值类型-BIT

数据BIT类型用于存储位值。一种类型 允许存储-位值。 范围从 1 到 64。 BIT(M)MM 为了指定位值&#xff0c; 可以使用符号。是使用零和一编写的二进制值。例如&#xff0c; 和 分别代表7和128。请参见 第 9.1.5 节“位值文字”。 bvaluevalueb111b10000000 如果将值分配给 长…

ChatGPT提示与技巧分享:如何作出更好的提示2023年8月

​对ChatGPT的一些酷炫技巧感兴趣吗?这里提供了一些可以帮助你充分利用ChatGPT&#xff0c;成为AI工具专家的技巧。 毫无疑问&#xff0c;ChatGPT是目前最广泛使用的人工智能工具之一。它不仅毫不留情地取代了一些特定领域常用的软件小工具&#xff08;如智能对联、经典语录生…

2023.8 - java - Java 异常处理

异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 比如说&#xff0c;你的代码少了一个分号&#xff0c;那么运行出来结果是提示是错误 java.lang.Error&#xff1b;如果你用System.out.println(11/0)&#xff0c;那么…