c语言实现数据结构中的顺序表

一.顺序表的概念

.在实现顺序表之前我们先了解一下孙旭表的概念是什么?我们说顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。那么看了这个概念我们首先知道的一件事就是数据在外面的顺序表中是连续储存的,不能跳跃式的存储,比如说1,2,3,4,5,6这样连续的存储是对的但是像1,2,3, ,4, ,5这样跳跃式的存储那就是不可以的。那么我们的顺序表一般可以分为两个状态:

  • 静态顺序表:使用定长数组存储。
  • 动态顺序表:使用动态开辟的数组存储。

那么我们这里首先来看看静态的顺序表。

二.静态顺序表的实现

静态顺序表是依靠静态的数组来实现数据的存储,所以我们这里肯定得有一个数组来储存我们的数据,但是我们这里不单单只有一个数组,我们这里还得用一个变量来记录我们此时顺序表中已经存储数据的个数,以方便我们后面的增删查改的操作,因为柘林有多个数据,所以我们这里就得采用结构体的形式来实现我们静态的顺序表,那么有些小伙伴们就写出了如下的代码:

#include<stdio.h>
struct SeqList
{
	int a[10];
	int size;
};
int main()
{
	//...
	return 0;
}

那么这里大家有没有发现一个问题就是我们这样写代码的话,在以后就会遇到两个问题就是我们以后要是像修改数据的时候就很麻烦啊,比如说我想把这里的10改成20,是不是就得找到这里创建顺序表的结构体啊,那么我们这里是代码很少,所以找起来特别的快,但是我们未来工作的时候遇到的代码是特别多的,那么我们这找起来的时候就很费劲,所以我们这里就可以在文件的开头定义一个标识符,这个标识符所对应的就是我们这里数组的大小,比如说这样:#define SLcapacity 10,那么以后我们要是像修改这个数组大小的时候是不是就直接在开头修改这里标识符所对应的值就可以了啊对吧,那么同样的道理我们这里的int是不是也会有着同样的道理,我们这里创建的顺序表是用来存储int类型的数据的,那要是以后我们这里要进行修改改成存储浮点型的数据,那么我们这里是不是就有得一个一个的找啊,所以为了解决这个问题我们也可以在文件的开头定义这么一个标识符来表示我们这里的数据类型比如说这样:#define SLDatatype int这样我们要修改存储的数据类型的时候就只用将这里表示符所对应的类型改成其他的类型就可以了,当然我们这里的命名也是有一定的心思在里面的,因为我们不同的表具有不同的特性,所以我们以后在写工程的时候就会用到多种表,所以我们这里在进行定义标识符的时候我们就在这个前面加上一个SL那么这个表示的就是顺序表的内容SLDatatype就表示的是顺序表的数据类型,SLcapacity表示的就是顺序表的容量,这个点大家可以学一下。

三.静态顺序表的缺陷

按道理来说我们这里应该将静态顺序表的增删查改的,但是这里大家就没必要学习这方面的内容了,因为我们静态顺序表有一个非常大的缺点就是我们不知道这里的容量到底给多大?因为我们这个静态顺序表跟我们的名字是一样的他是静止不能改变的 ,所以我们在程序设计的时候就不知道到底得给多大,你把这个容量给成1000,但是我们在使用的时候可能只要存储数据,那么这是不是就浪费了很多的空间,要是你把这里的容量给成100,但是我们在使用的时候要存储1000个数据,那么这里是不是就又不够用又会报错啊,所以我们这就是静态顺序表的一个非常大的缺陷,因为这个缺陷我们在以后的工程里面几乎不会用到静态顺序表,所以我们这里就没必要再往下了解了,那么看到这个问题大家肯定想到我们c语言有这么一个知识点,就是动态内存分配,那么我们用这个内容里面的知识点是不是就可以解决这个问题,我我们用malloc来开辟一个动态的空间,再用realloc来对我们开辟的空间进行扩容,所以我们就可以用两个函数来改进我们这里的静态顺序表,这里改进的结果就是我们上面说的动态顺序表。

四.动态顺序表的准备工作

根据我们之前函数的知识点,我们这里将顺序表分为三个文件,分别是SeqList.h / SeqList.c / Test.c这三个文件其中SeqList.h 是专门用来放函数的声明, SeqList.c 这个文件就专门用来存放我们函数的具体实现, Test.c这个文件就专门用来测试我们这里的函数写的是否真确。

五.动态顺序表的实现

我们c语言里面学过这么一个函数:realloc我们来看看这个函数的具体介绍:
请添加图片描述
请添加图片描述
我们看看这个函数对参数的介绍当中有这么一句话说当我们这里的传过来的是一个空指针的话,那么我们这个函数的功能就和malloc函数的功能一样,也就是说我们这个函数既有扩容的功能也有动态内存开辟的功能,那么我们这里再看看这个函数的返回值,他说我们这个函数的返回值是一个指针,这个指针指向的就是开辟的内存的地址,然后如果是扩容的话可能会返回一个新的地址也可能返回的是原来的地址,如果扩容失败的话那么这个函数返回的就是一个空指针,那么看了这个函数介绍我们知道了这个函数的返回值不管怎么样都是一个指针,那么我们这里就得将我们上面的结构体进行一下修改加上一点东西进去,然后我们的结构体就成了这样:

typedef struct SeqList
{
	SLDataType* a;//指针指向的开辟的内存的地址
	int size;//当前链表中含有的元素个数
	int capacity;//链表的容量
}SL;

我们这里的结构体改好了,但是我们这里还有一个问题没有解决就是我们这里的扩容是扩多大呢?我们是1个元素1个元素的扩容的吗?容量从11扩大到12,12不够了再扩容到13,是这样子的扩容吗?那么我们这里想一下这样子扩容有什么不好的地方,首先我们知道我们用realloc进行扩容的时候他是有代价的,他是先调用操作系统里面的接口再来内存中开辟空间,那么我们这里不停的调用realloc的话就会不停的调用操作系统的接口,那么这样的话就就会导致我们程序的效率降低,而且我们的扩容还分为两种不同的情况一个是原地扩容,另外一个就是异地扩容,如果是原地扩容的话我们系统在扩容的时候消耗的效率就非常的低,而如果是异地扩容的话那么我们这里就得在其他地方开辟一个扩容后的大小的空间,并且还得将之前的数据拷贝到这个内存里面,那么这样的话就会导致效率地下,因为我们每次只多扩容一个空间,并且扩容的概率还很高,那么这样的话我们异地扩容的次数就会变得更多,所以我们这里的效率就会更进一步的低下,所以这种扩容的方式肯定是不可取的,那么我们这里就得换一种方法,我们可以改成每次扩容的空间都是之前的两倍,比如我第一开辟的空间能够装下4个元素,如果我们这时要扩容的话我们这里就扩大两倍就变成装下8个元素,等不够用了就再进行扩容容量就变成16个元素等等,当然我们这里不一定非得扩容两倍我们也可以采用4倍,1.5倍这样的倍率来进行增长,这就好比一个胖子吃一碗饭可能吃不饱,吃三碗饭吃不下,两碗饭刚刚好的这种情况,我们可以大致的根据实际情况来修改我们这里的扩大的倍率。

1.动态顺序表的初始化

我们在创建一个顺序表之前首先得将这个顺序表进行一下初始化我们将这个结构体里面的指针初始化为空指针,将size和capacity的值全部都初始化为0,因为这里是对结构体的本身进行操作,所以我们这里的参数就得是结构体类型的指针,因为这里的是指针,所以在进行初始化的时候我们还得来判断一下这里传过来的指针是不是空指针,那么我们这里的代码实现就如下:

void SLInit(SL* psl)
{
	assert(psl);
	psl->a = NULL;
	psl->capacity = psl->size = 0;
}

那么我们这里就可以在Test.c里面进行测试:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	return 0;
}

我们来看看在没有初始化之前我们这个结构体里面的数据是什么样的:
在这里插入图片描述
等我们初始化完之后我们的结构体里面的内容就成了这样:
在这里插入图片描述
我们看到指针a的内容变成了空指针,size和capacity的值也全部都变成了0,那么这就说明我们这个函数的实现是正确的,我们接着往下看。

2.动态内存的头插尾插

好我们现在创建了一个顺序表,再对其初始化,那么我们这里就得往这个顺序表中插入数据了,那么我们这里的插入数据就可以分为往顺序表中的头部和尾部插入数据,那么我们这里首先来看个简单的往顺序表的尾部插入数据。

尾插

那么这里的插入数据就可以类比成我们往衣柜里面放进一件一件衣服,但是在放衣服之前我们得做的一件事就是先见看看我们这个衣柜放不放下对吧,要是放不下的话,那么我们这个衣服就暂时不能放进去,那么我们这里的插入数据也是同样的道理,我们在插入数据之前我们得看看这个顺序表里面还有没有剩余的空间,那我们这里该如何来看呢?大家看看我们这个结构体里面的元素,一个是容量一个当前顺序表中的元素个数,那么当我们这里的元素个数等于我们容量的时候我们就不能先插入数据因为这样子会越界访问,我们得先用realloc来进行扩容使得容量变为原来的两倍再来插入数据,但是我们这里还有一种情况就是我们第一次插入数据的时候我们这里的容量本来就是为0的,所以我们扩容的时候就不能简单的乘以一个2吗,而是得先对其赋值,而且我们发现一开始的时候容量和里面元素的个数也是一摸一样呢的,那么我们这里就可以将其作为一个条件放到if语句里面,如果我们这里的两个值相等的话,就进入这个if语句里面再来判断我们这里的容量是否是0,如果为0的话我们这里就赋值,如果不为0的话我们这里就将容量乘以,这个我们就可以用三目操作符来进行实现,然后再使用realloc把新的容量作为其中的参数来扩容,因为我们这里的扩容可能会失败所以我们这里得再创建一个变量来防止开辟失败而导致我们原来的那块空间找不到了,所以我们这里检查的代码就如下:

void SLCheckCapacity(SL* psl)
{
	assert(psl);
	if (psl->capacity == psl->size)
	{
		psl->capacity = psl->capacity == 0 ? 4: psl->capacity * 2;
		SLDataType* p1 = realloc(psl->a, (psl->capacity)*sizeof(SLDataType));
		//注意这里是容量乘以元素的大小,不是单独的一个容量
		if (p1 == NULL)
		{
			perror("realloc fail");
		}
		psl->a = p1;
	}
}

那么我们这里的扩容就完成了,因为我们这里在很多地方都会用到扩容的这个功能所以我们直接将其分装成一个函数叫SLCheckCapacity,那么我们就将接下来我们就可以来插入数据,那么这个就很简单了,虽然我们这里的size记录的是当前顺序表中的元素个数,但是他还具有另外一个特性就是标记着当前尾插的位置,你可以想一下当我们的size为0的时候是不是一个元素都没有,如果一个元素都没有的话,我们插入的第一个元素的位置 是不是就是0,而0就是我们size的值,如果我们这里有5个元素size的值就是5,而第5个元素对应的下标是4所以我们再插入数据的时候是不是就要插到4的后面一位,也就是对应下标为5的位置, 那么这里也就是我们size的值,所以我们这里就可以根据我们size的值来进行尾插,我们每次插完之后就得将我们的size的值加1,表示我们这里顺序表中的元素个数又多了一个,那么我们这里尾插的完整的代码就如下:

void SLInsertBuck(SL* psl, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;
}

那么我们这里的尾插就写完了我们可以在Test.c这个文件里面测试一下我们这个函数对不对,那么我们这里测试的代码就如下:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 1);
	SLInsertBuck(&p, 2);
	SLInsertBuck(&p, 3);
	SLInsertBuck(&p, 4);
	SLInsertBuck(&p, 5);
	return 0;
}

我们这里就可以通过监视来看看我们这里内存中的变化:
在这里插入图片描述
然后我们这里再按一下F10的话我们的程序就应该会进行扩容,那么这时我们的capacity就会变成8,而我们的size就会变成5,下标为5的位置数据就为5,那么我们来看看这里的变化如何:
在这里插入图片描述
我们看到我们这里确实尾插成功了,那么我们接下来就来看看头插。

头插

我们上面学习如何实现尾插,那么我们再来看看头插是如何来实现的,在实现实现插入数据之前我们还是得先干一件事就是看看我们这里的容量是否够,所以我们先调用一下SLCheckCapacity,然后我们这里就要插入数据,但是我们这里与尾插不一样,因为尾插的位置是该顺序表的最后一个位置,而我们这里的头插插的地方是我们这里的第一个位置,又因为我们这里不是覆盖,所以我们得将之前的数据整体的往后挪动一个位置,比如说一共有5个元素,这时候你要头插的话你就得先将下标为4的元素(就是第五个元素)移动到下标为5的地方去,再将下标为3的元素(就是第4个元素)移动到下标为4的位置上去,这样依次类推,直到我们将这个顺序表的第一个元素移动到下标为1的位置上去,那么这样的话我们就将这个下标为0的位置空了出来,然后我们再将要插入的元素放到下标为0的地方去,这样我们就实现了尾插,那么我们这里就可以创建一个变量叫end一开始将这个end的值赋值为size-1然后将其放到一个while循环里面这个循环的条件就是end<0,然后循环体里面的内容就是a[end+1]=a[end]
然后我们再让end的值减减,那么我们这里的代码就如下:

void SLInsertFront(SL* psl, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[0] = x;
	psl->size++;
}

我们来测试一下这个代码,我们首先尾插两个数据进去假如这两个数据是10和20吧,然后我们再头插三个数据进去假如这三个数据是30 40 50 ,那么我们最终数据存储情况就是50 40 30 10 20 ,我们来看看顺序表的内部构成:
在这里插入图片描述
那么我们这里的头插就完成了,但是我们这里每次想看看顺序表里面的内容都得调试的话,是不是就有点麻烦啊,那么我们这里我们就可以写一个函数来专门打印我们这里的顺序表的内容,我们这里就可以先创建好一个变量,我们这里就叫这个变量为begin用来记录开始的位置,然后再用循环来打印这个位置对应的值,然后我们每经历一次循环我们就使begin的值加一,当我们这里的begin的值等于size-1的时候我们就跳出循环因为我们这里的打印已经结束了,那么我们的代码就如下:

void SLPrint(SL* psl)
{
	assert(psl);
	int begin = 0;
	while (begin <= psl->size - 1)
	{
		printf("%d ", psl->a[begin]);
		begin++;
	}
}

我们来看看这里的打印结果:
在这里插入图片描述

3.顺序表的头删尾删

尾删

那么我们这里的尾删就非常的简单了,我们这里只用先判断一下这个传过来的地址是不是一个空指针,然后我们就直接将我们的size减减就可以了,因为减减之后我们是无法通过size来访问到这个位置的值的,而且我们在插入新的数据的时候是还会覆盖掉原来我们删除数据的那个值,所以我们这里就直接可以直接减减,那么我们的代码就是这样:

void SLPopBack(SL* psl)
{
	assert(psl);
	psl->size--;
}

是不是感觉非常的简单啊,那么我们可以来测试一下:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertFront(&p, 30);
	SLPrint(&p);
	printf("\n");
	SLPopBack(&p);
	SLPopBack(&p);
	SLInsertFront(&p, 40);
	SLInsertFront(&p, 50);
	SLPrint(&p);
	return 0;
}

然后我们再来看看这个代码打印的结果:
在这里插入图片描述
那么我们发现我们这里确实是尾删成功了吗,但是我们这里来看看下面的代码:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLPopBack(&p);
	SLPopBack(&p);
	SLPopBack(&p);
	SLPopBack(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertBuck(&p, 30);
	free(p.a);
	return 0;
}

我们来看看这个代码运行的结果:
在这里插入图片描述
这里报错了,这是为什么呢?我们可以通过调试来发现一下问题:首先我们这里的尾插是没有问题的
在这里插入图片描述
然后我们这里再经历两个尾删,然后我们的size就等于了0.
在这里插入图片描述
但是我们发现我们下面的代码还有尾删的代码,那么我们这里再调试一下看看会发生什么?
在这里插入图片描述
我们发现这里的size变成了-2,而我们知道我们插入数据都是根据size的值来判断插入地方的,如果我们的size变成负数再插入数据的话是不是就越界访问了啊,那么这就是我们的问题所在,所以我们在删除数据的时候就得先判断我们的值当我们size等于0的时候就不准再删除数据了,那么我们的代码就如下:

void SLPopBack(SL* psl)
{
	assert(psl);
	if (psl->size == 0)
	{
		return;
	}
	psl->size--;
}

这样当我们的size等于0 的时候就算又调用了这个函数也不会使得我们这里的size的值减一了。

头删

相对于尾删我们这里就会麻烦一些,因为我们这里在删除数据之后得将后面的数据往前面挪动一个位置,那么我们这里就可以先创建一个变量叫begin将他的值赋值为0表示顺序表的开头,然后我们就创建你一个循环这个循环里面的功能就是将下标为begin+1的值赋值到下标为begin的位置上去,然后再将begin的值加1,那么我们再将begin的值加一,那么这就是这个循环体的内容,那么这个循环结束的条件就是当begin的值等于size-1的时候那么这时我们地 循环就结束了,最后再将我们地size地值减1,因为我们这里也会存在跟尾插同样地问题,那么我们这里也得加以防范,所以最终我们地代码为:

void SLPopFront(SL* psl)
{
	assert(psl);
	if (psl->size == 0)
	{
		return;
	}
	int begin = 0;
	while (begin < psl->size - 1)
	{
		psl->a[begin] = psl->a[begin + 1];
		begin++;
	}
	psl->size--;
}

那么我们这里再来测试一下看看我们这个函数实现地是否是对的:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertBuck(&p, 30);
	SLInsertBuck(&p, 40);
	SLInsertBuck(&p, 50);
	SLPopFront(&p);
	SLPrint(&p);
	return 0;
}

如果我们的函数实现没有问题的话我们这里代码的运行结果为:20 30 40 50我们来看看这个运行的结果如何:
在这里插入图片描述
那么这里跟我们预测的一模一样,那么这就说明我们的这个头删是正确的,但是有些小伙伴看到这里可能会有那么一点点的疑问就是如果我们这里的容量是1w个的话,我们删除了5000个数据,那么我们这里要不要将我们这里的内存进行缩小呢?对吧?这样的话我们就可以多余出来很多的空间啊,我们看那个realloc这个函数的介绍的时候也发现了这个函数确实还有缩小空间的功能啊,那么这里我们说啊没必要,并且坚决不采取缩容的手法,因为我们采用缩容的话这里会在堆上开辟另外的一个空间来存放我们的这里的数据,那么这就意味着我们有得一个一个的复制数据这就会导致我们的效率变低,而且很可能在以后的使用过程中我们的数据又多了起来,你有得扩容而这个扩容基本上也是异地扩容所以这就又会导致我们要一个一个的复制数据又会导致我们的效率降低,而且随着我们现在的科技的发展我们的内存那是越来越大,而时间就显得越来越宝贵,这就好比你买手机的时候是想要一个超大内存2TB的老人机还是一个只有128G但是搭载了麒麟9000的智能手机了,所以我们的顺序表这里就完全不采用缩容这行为。

4.顺序表的查找

我们这里的顺序表中有很多的数据,那么我要是想从中找到一个数据的位置,那该如何去做呢?那么我们这里就可以来创建一个函数,这个函数的作用就是找到该数据并且返回该数据的下标,当然这里也有没有找到的情况,所以我们这里就规定如果没有找到的话我们这里就返回-1,那么我们如何来实现这个函数呢?那这就很简单了嘛,我们直接遍历整个数据不就好了吗?就像这样:

int SLFind(SL* psl,SLDataType x)
{
	assert(psl);
	int begin = 0;
	while (begin <= psl->size - 1)
	{
		if (psl->a[begin] == x)
		{
			return begin;
		}
		begin++;
	}
	return -1;
}

我们来看看这个函数的正确性如何,我们用下面的代码来进行一下测试:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertBuck(&p, 30);
	SLInsertBuck(&p, 40);
	SLInsertBuck(&p, 50);
	int c = SLFind(&p, 30);
	if (c != -1)
	{
		printf("%d ", p.a[c]);
	}
	else
	{
		printf("没找到\n");
	}
	return 0;
}

我们这个代码的作用就是将这个查找这顺序表中的数据,并且测试这个返回值是否是真确的,那么我们这个代码的打印结果为:
在这里插入图片描述
确实找到了那么这也就说明我们这个函数实现的没有毛病。但是有小伙伴要说了,啊你这个方法太行啊效率低下啊,我之前学过一个方法叫二分查找法这个快多了,但是我们二分查找得有一个前提就是我们的数据得是有序的啊,如果你想使用二分查找法的话你得先对数据进行一下排序,然后再使用二分查找法,那么这样的话效率就会低很多。

5.顺序表指定位置的删除

那么我们知道头删尾删,但是实际上我们发现这个有一个小小的问题就是,我们在使用的过程中要是想删除一个中间的数据该怎么办呢?不会说我们先不停的尾删直到把那个数据也删除掉再来不停的尾插我们删除掉的数据吧,那么这个肯定是不切实际的所以我们这里就得写一个函数来专门的实现我们这里的指定位置的删除,那么这里我们就得传过来一个你给的要删除的位置,但是在我们的函数里面我们肯定得先对这个函数的位置进行判断,判断他是否小于我们的size的,那有小伙伴说那是不是还要大于等于0啊,那么我们这里其实就可以在接收参数的时候以无符号整型的形式来接收他,这样的话就不会出现负数的情况,那么我们的参数接收完了我们就得来删除,我们就创建一个变量来记录我们这里传过来的值,我们这里就用begin来记录,我们再创建一个循环,在循环的内部我们就可以让a[begin]的值等于a[begin+1]的值,然后每次循环我们都是得begin++,那么我们这里循环的条件就是begin的值小于size-1,这样当我们出循环的时候我们就完成数据的挪动,最后我们在让size的值–,那么这里有个问题我们这里需要跟上面的删除一样判断一下我们这里的删除是否合理吗?答案是不用的,因为我们这里在前面的一步就有了类似的功能就是判断传过来的位置是否小于我们的size的,当我们没有数据的时候size等于0,又因为size是无符号整型不能存在比0还小的值了,所以我们这里的就可以用这个来进行判断,那么我们的代码就如下:

void SLErase(SL* psl, size_t pos)
{
	assert(psl);
	assert(pos < (size_t)psl->size);
	size_t begin = pos;
	while (begin < (size_t)psl->size - 1)
	{
		psl->a[begin] = psl->a[begin + 1];
		begin++;
	}
	psl->size--;
}

那么我们这里就可以对其来进行一下测试测试的代码如下:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertBuck(&p, 30);
	SLInsertBuck(&p, 40);
	SLInsertBuck(&p, 50);
	int c = SLFind(&p, 30);
	SLErase(&p, c);
	SLPrint(&p);
	return 0;
}

我们来看看这个代码运行的结果:
在这里插入图片描述
那么这就说明我们这里的函数实现是正确的。

6.顺序表指定位置的插入

那么我们这里的插入就得分为前插和后插,但是根据我们的生活习惯来看的话一般都是前插,那么这里我们就来实现前插,首先我们的参数就相对删除就会多了一个就是你想要插入的数据是什么,那么我们这个函数的实现就是首先判断一下容量够不够吧,对吧,然后我们在把你要插入地位置的后面的数据全部都往后面挪动一位,那么我们这里就创建一个变量叫end他的值就等于size-1,然后我们就创建一个循环在循环里面就执行这样的代码:a[end]=a[end-1],然后我们再让end的值减减,那么我们这个循环结束的标志就是end的值小于我们给的位置的时候就停止循环了,最后再让size的值加加,那么我们的代码如下:

void SLErase(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[pos] = x;
	psl->size++;
}

我们来测试一下这个代码的正确性:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertBuck(&p, 30);
	SLInsertBuck(&p, 40);
	SLInsertBuck(&p, 50);
	int c = SLFind(&p, 30);
	SLInsert(&p, c,300);
	SLPrint(&p);
	return 0;
}

我们来看看这个代码的运行结果为:
在这里插入图片描述
那么这个能不能说明我们这里的代码是真确的呢?我们再来看看下面的代码:

#include"SeqList.h"
int main()
{
	SL p;
	SLInit(&p);
	SLInsertBuck(&p, 10);
	SLInsertBuck(&p, 20);
	SLInsertBuck(&p, 30);
	SLInsertBuck(&p, 40);
	SLInsertBuck(&p, 50);
	SLInsert(&p, 0, 300);
	SLPrint(&p);
	return 0;
}

我们来看看这个代码的运行结果为:
在这里插入图片描述
我们发现这个啥也打印不出来,这是为什么呢?那么我们这里就可以通过调试来看看问题出在了哪,那么我们这里首先知道的是肯定是出在了SLInsert这个函数里面,那么我们这里就可以来看看这个函数的内部调试:
在这里插入图片描述
首先我们的end的值为4,那么我们这里经历的就是讲数据往后移,我们来看看往后移的结果:
在这里插入图片描述
我们可以看到我们这里确实是将pos后面的数据全部都往后挪动了一格,那么我们接着就应该是插入数据了,那么我们再来看:
在这里插入图片描述
那么我们这里问题就出现了我们这里的end的值为-1,但是我们依然进入了循环这是为什么呢?既然我负数都能进入循环的话,那么这里的判断条件就应该是出现问题的原因了吧,所以我们来看一下这里的判断条件我们的pos是无符号整型,但是我们的end确实有符号的整型,那么我们这里对其进行运算的话是不是就会发生整型提升啊,如果整型提升的话我们的end就是一个无符号整型,那么他的-1就会被认为是一个正数也就是
在这里插入图片描述
那么这样的话我们这里就是一个死循环,所以我们这里的解决方法就是在end的前面加一个强制类型转化,将其变为有符号的整型,那么我们这里的代码如下:

void SLInsert(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= (int)pos)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[pos] = x;
	psl->size++;
}

我们再来看看上面的代码运行的结果:
在这里插入图片描述
我们发现这里就可以完美的实现。那么这里我们就实现成功了,当然解决这个问题的方法不止一种大家可以自己去摸索摸索。

7.动态顺序表的销毁

既然我们可以创建初始化了一个动态顺序表,那么我们这里经过一段时间的使用达到目的了之后肯定就不再使用这个动态顺序表了,那么我们这里就得写一个函数专门来销毁这个顺序表,那么我们这里就来想想这个如何来销毁呢?首先我们这个函数的参数肯定得是一个结构体的指针,因为得对实体进行修改,既然这里的参数使指针的话我们就得先判断这个指针是否为空指针,然后又因为我们这里是在动态空间中开辟了一个空间,所以我们这里就可以用free函数将这个空间进行释放,然后再将指向这个空间的指针的内容初始化为空指针,防止其成为野指针,最后再将这里的size和capacity的值初始化为0,这就是我们这个顺序表销毁的过程,那么我们的代码就如下:

void SLDestory(SL* psl)
{
	assert(psl);
	free(psl->a);
	psl->a = NULL;
	psl->capacity = psl->size = 0;
}

看到这里我们的顺序表就结束了。

点击此处获取代码

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶超凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值