DS:顺序表的实现

感谢各位友友的支持!目前我的博客进行到了DS阶段,在此阶段首先会介绍一些数据结构相关的知识,然后再进行顺序表的学习。学习数据结构是为后面的通讯录项目打基础。

在学习数据结构之前,需要友友们掌握一些储备知识——结构体、指针、动态内存管理

一、数据结构相关概念

    什么是数据结构?从字面意义上讲,就是“数据”和“结构”。

1.1 什么是数据?

    常见的数值1、2、3、4.....、教务系统里保存的用户信息(姓名、性别、年龄、学历等
等)、网页里肉眼可以看到的信息(⽂字、图⽚、视频等等),这些都是数据。

1.2 什么是结构?

       当我们想要使用大量使用同⼀类型的数据时,通过手动定义⼤量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在⼀起,结构也可以理解为组织数据的方式。比如说,在一个山坡上寻找一头叫做贝蒂的羊,就很难查找,但是如果是从一号羊圈里寻找到某一头羊就很容易,这是因为羊圈将数据进行了有效的组织管理。这里的羊圈就相当于“结构”,羊圈里的羊就相当于“数据”。

简而言之,我们为了方便组织管理数据,使用了一个可以有效组织数据的方式——数据结构。

1.3 什么是数据结构?

        数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由哪部分构成,以什么方式构成,以及数据元素之间呈现的结构。

      就比如在一个生意兴隆的餐厅中,如果不借助排队的⽅式来管理客⼾,会导致客⼾就餐感受差、等餐时间⻓、餐厅营业混乱等情况。同理,程序中如果不对数据进⾏管理,可能会导致数据丢失、操作数据困难、野指针等情况。通过数据结构,能够有效将数据组织和管理在⼀起,按照我们的⽅式任意对数据进⾏增删改查等操作。

1.4 数据结构到底有什么用?

(1)能够存储数据(如顺序表、链表等结构);

(2)存储后的数据方便我们进行数据的增删查改操作。

1.5 数组

       在没接触数据结构之前,如果要创建100个数据,我们会选择使用数组的方式创建这100个变量。这样就将同类型的数据进行了有效的管理。因此,数组是最基础的数据结构。

既然有了数组,为什么还要学习其他的数据结构(如顺序表)?

       先看下面的情形:假定数组有100个空间,已经使用了5个,向数组中插⼊数据步骤:求数组的长度,求数组的有效数据个数,向下标为数据有效个数的位置插⼊数据(注意:这里是否要判断数组是否满了,满了还能继续插入吗)……假设数据量非常庞大,频繁的获取数组有效数据个数会影响程序执行效率。
结论:最基础的数据结构能够提供的操作已经不能完全满足复杂算法实现。

二、顺序表

2.1 线性表

        线性表( Linear list )是n个具有相同特性的数据元素的有限序列,或者是具有部分相同特性的⼀类数据结构的集合。

       线性表是⼀种在实际中⼴泛使⽤的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串……线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

举例:根据相同特性,蔬菜分为绿叶类、⽠类、菌菇类等;水果分为热带水果、亚热带水果、温带水果等。

2.2 如何理解逻辑结构和物理结构?

       前面说,线性表是具有相同特性的数据结构的集合。这种相同特性体现在:

逻辑结构: 在线性表中,逻辑结构是连续的。

物理结构:数据在内存中存储时,它的结构是物理结构。

       线性表是一种抽象的、逻辑结构上连续,物理结构上不一定连续的结构。

       数组在逻辑结构和物理结构上都是连续的(因为数组在内存中是连续存放的),因此 以数组为底层的 顺序表在物理结构和逻辑结构上都是连续的。

2.3 顺序表的分类

        对于一份素菜汤来说,他的底层就是青菜,但是在加入牛肉、水进行烹煮摆盘等一系列操作后,就变成了一份更加高级的西湖牛肉羹。

       对于顺序表来说,顺序表的底层是数组,但是它提供了很多现成的方法(增删查改等方法),但是它通过对数组进行一系列的增删查改等操作,就升级变成了一个新的数据结构,即线性表。

      数组申请空间有两种方式:1. 定长数组(如:int arr[100]={0};)2. 动态数组:动态内存开辟数组的空间,确定大小后再去动态申请内内存。

      由于顺序表的底层是数组,而数组又可以分为定长数组和动态数组,因此顺序表也可以分为两类: 静态顺序表和动态顺序表 。

       通过对数组进行封装,实现了常用的增删改查等接口操作,将数组升级为了所谓的顺序表。封装过程就需要用到结构体。

2.3.1 静态顺序表 

定义:使用定长数组存储元素

(1)静态顺序表的一般结构
#define N 1000  //定义一个宏,方便根据需要修改定长数组的大小,这样就不用频繁改动数组的大小
typedef int SLDataType;
//因为顺序表根据需要可能需要存储不同的数据类型,所以将其进行重命名
//如果想改成char和float,可以直接在这里修改,不需要去改动后面的内容
typedef struct Seqlist
{
	SLDataType a[N];//定长数组,可以通过修改#define定义的N来改变数组大小
	int size;
	//定长数组开辟了N个空间,但不代表里面有N个有效数个数,所以需要size来记录有效数个数。
}SL;//将名字修改得简短一点

1. 为什么命名为 SeqList和SLDataType?

       将类型用typedef定义为SLDataType,因为顺序表可以用来存储各种类型的数据如内置类型、自定义类型等,使用SLDataType可以灵活修改数据的类型,而不用大量的修改数据类型的代码。

SeqList是sequence (顺序)和List(表)的组合,即为顺序表。命名要求简洁明了。

2. 初始化要注意传值调用和传址调用

3. 使用size有什么作用?

       例如int  arr[10] = {1 , 2 , 3 , 4 , 5 } ; 这里的有效数据个数就是5,size用来记录空间中使用的有效数据的个数,虽然申请了这么多的空间,但不是所有空间都存储了有效的数据。 

4. 为什么要使用宏定义,而不直接使用arr[100]?

       便于后续对空间大小进行修改,如果代码写到中途返现数组大小不合适,就可以直接使用宏定义进行一键替换,而不需要,一个一个的去修改,这样也就不会出现误改漏改的情况,写代码的效率进一步提升!

(2) 静态顺序表的劣势

a. 如果给定的数组大小不合适会出现空间浪费和数据丢失的情况。

       给定的数组长度如果大了,那么空间分配就大了,造成空间的浪费;空间分配小 了,不够用,导致后续的数据保存失败,造成数据丢失。(例如,存储用户信息的空间如果分配小了,就会出现用户信息丢失的情况;用户信息丢失得小,面临失业;如果用户信息丢失大了,则会面临被整个行业通报的情况。总而言之,丢失用户信息,是一件非常严重的技术事故,同时这也体现了一位程序员的代码素养)

b. 此外,静态顺序表对于未来可能存储的数据量是未知的。因此,更加推荐使用顺序表,但不代表静态顺序表就没用了。

2.3.2 动态顺序表

       根据前面对静态顺序表的分析,我们为了减少问题的产生,选择使用动态顺序表,动态顺序表结构灵活,程序猿可以根据需要灵活地开辟空间。

定义:使用动态数组进行动态增容

typedef int SLDataType;
//顺序表可能需要存储不同的数据类型,将其命名为SLDataType,然后通过typedef根据需要修改数据类型
//而且,修改数据,不一定得一键替换,有时只需要改变一部分数据类型
//一键替换是宏定义,比如:#define SLDataType int
typedef struct Seqlist
{
	SLDataType* arr;//动态数组,一开始不确定大小,程序员可以根据过程中的需求去合理开辟
	int size;//开辟了相应的空间,用size来记录有效数个数。	
    int capacity;//空间容量,若有扩容,用其记录扩容后动态数组的大小。
}SL;//(名称修改方式1)

//typedef struct SeqList SL;//(名称修改方式2)

       与静态顺序表相比,动态顺序表不仅底层的数组不同,还增加了capacity这一变量,这是因为动态顺序表在最开始并不知道要申请多大的空间,且在为顺序表进行动态内存开辟时,顺序表的空间是在变化的。

三、动态顺序表的实现

        在前面我们就知道了相比动态顺序表来说静态顺序表的问题更多,因此更推荐使用动态顺序表,下面是动态顺序表的实现过程介绍。

准备工作:在 SeqList.h文件中进行顺序表的声明,但不实现;在 SeqList.c文件中写实现顺序表的方法;在test.c文件中进行代码的测试,一定要写一部分再测一部分。

3.1 初始化和销毁

3.1.1 初始化

代码1:

     在这段代码中出现了“未初始化的局部变量'sl' ”,是因为传值调用与传址调用的混淆。

1. 行参传值中,行参是实参的一份临时拷贝,如果我们创建的变量参数sl没有进行能初始化,那就不能进行值的传递的;

2. 即使sl初始化了,由于行参是实参的临时拷贝,sl的值就不会被改变。可行的办法就是传址调用,使用指针来调用。

3. 此外,  .  操作符要改为 -> 操作符

所以,正确的代码为:

3.1.2 销毁

代码如下:

注意:

1. 为什么要使用assert断言?首先,必须要保证是动态内存开辟的空间,才能释放,所以释放空间前要先判断ps是否为空;其次,如果传入的是NULL,说明没有对顺序表的动态数组开辟过空间,也就不需要通过free释放空间了;

2. 为什么ps->arr被置为NULL?a.  动态申请开辟的空间被释放了,但是这块空间只是失去了使用权限,该空间仍然存在;b.  ps指针指向的空间虽然被释放了,但是指针的值并没有被改变,因此我们无法通过指针自身来判断它指向的空间是否被释放了;c.  把指针置空可以提醒我们该指针指向的空间已经释放,因为程序猿在写了大量的代码后很容易忘记指针指向的空间是否释放,如果误用了已经被释放的空间,程序将会崩溃,而如果ps->arr是一个空指针,那么编译器会通过报错来提醒你。

3.2 增容

      在增加、插入数据等操作之前,都要先判断空间是否足够,如果空间不够,就需要动态申请空间。

增容代码如下:

void SLCheckCapacity(SL* ps)
{
	//插入数据之前先看空间够不够
	if (ps->capacity == ps->size)
	{
		//申请空间
		//malloc calloc realloc  int arr[100] --->增容realloc
		//三目表达式
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大的空间
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}

1. 增容使用哪个函数?

       使用realloc,因为它有增容的概念,而且可以进行多次增容;malloc和calloc都可以用来申请 一段连续的空间,但是它们都没有增容的概念。//复习realloc的空间处理方法;

      值得注意的是:(1)realloc增容的第二个参数单位是字节,所以代码中的newCapacity需要乘以sizeof(SLDataType);(2)使用realloc申请空间可能会申请失败,realloc返回 EOF,但是不能用ps->arr接收返回值,因为arr数组空间变为NULL,会使得arr空间原本可能会有数据消失,出现数据丢失的情况,因此我们创建一个新的临时变量tmp来接收开辟空间返回的地址;(3)realloc的返回值类型是void*,因此需要tmp需要强制类型转换为SLDataType*。
2. 增容需要申请多大的空间?(增容的原则)

        增容规则:增容通常来说,成倍数增加,一般是2、3倍。这个规律涉及概率论(补充:为什么增容需要以倍数增加?)比如,插入数据如果 是一个一个进行插入的,每插入一个数据就申请一块空间,当需要插入的数据很多时,就会出现频繁增容的情况,造成程序性能低下。最好的解决办法就是空间一次增加许多,但又不能增加太大,避免空间浪费;也不能增加太小了,所以2、3倍增加。如:4-8-16-32-64-128-256-512-1T……(2倍增加的)
        如果插入的数据量不大,前期就能表现出来,因为数据个数和空间大小成正比。如果前期的数据量不确定,先少一点申请空间,若发现插入的数据比较多,就逐步扩大空间。

3. 使用三目操作符 int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;有什么作用?

      起初在对顺序表进行初始化的时候,对capacity赋的值就是0,0无论乘以多少倍的容量值都是0;此外 ,如果capacity不等于0,那么就给它赋值4,这样newCapacity就等于2 * ps->capacity;如果capacity等于0,说明,ps指向的空间的空间容量为0。

4. 为什么还要判断tmp==NULL?

判断tmp得到的返回值是否为NULL,也就是判断动态申请空间是否成功

5. exit(1)和return 1的区别?

(1)exit(): 关闭所有文件,终止正在执行的进程。

a.  exit是系统调用级别的 ,它表示了一个进程的结束,用于在程序运行过程中随时结束程序, exit的参数是返回给os操作系统的,exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程;通常情况:在整个程序中,只要调用exit就结束(当前进程或者在main时候为整个程序)。

b.  exit 是一个函数,exit是操作系统提供的(系统函数库中给出的)。

c.  exit() 则会立即结束整个程序的执行,且不会返回到调用者。

(2)return()是返回函数值并退出当前函数

a.  return是语言级别的,它表示了调用堆栈的返回; return()是返回函数值并退出当前函数,当然如果是在主函数main, 自然也就结束当前进程了,如果不是,那就是退回上一层调用。在多个进程时。如果有时要检测上个进程是否正常退出。就要用到上个进程的返回值,依次类推。

b.  return返回函数值,是关键字 ,是C语言提供的。

c.  return 只会结束当前的函数,且如果是在子函数中使用,程序其余部分还会继续执行。 

总的来说,exit(1)和return 1在这里都是差不多的效果,只是exit会比return更加暴力一些

3.3 打印

       后续我们会实现很多个函数的封装,包括尾插、头插、在指定位置插入数据等,在写完这些代码后我们都会进行调试来改正代码中的错误,但是代码写完后的调试操作太麻烦,而且难度也加大了,因此,我们选择再封装一个函数,可以对每一个封装的函数进行验证,便于我们及时找到错误并改正,提高我们编程的效率。

3.4 增删查改

3.4.1 插入(增)

插入有两种情况:尾插和头插。

(1)尾插

(ps->size)指向最后一个数据结尾 ,在size的位置插入数据,size++,

正确代码如下::

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	//if (ps==NULL)//
	//{
	//	return;//直接退出
	//}//比较温柔的洁柔的解决方式
    //先判断空间够不够
	assert(ps);//相较于if比较暴力,需要在SeqList.h中添加assert.h
	SLCheckCapacity(ps);//判断空间不够,就会增容
    //开始插入数据
	ps->arr[ps->size] = x;
	ps->size++;
}// ps避免为NULL

 1. assert断言:为了判断ps是否为空。对指针解引用的前提是该指针不能为空指针(NULL),这也是为了避免该函数被滥用!不能是你想传什么就传什么,后面的很多函数接口也要考虑这个情况!

2. 写入异常:从图中可以看出,capacity空间容量为0,因此不能插入数据,在此之前要先判断空间够不够,如果不够就要申请空间。(因此代码中用到增容代码:SLCheckCapacity,可以先判断)

3. 只要是插入数据,一定不要忘记最后要size++ 

(2)头插

从下标为0的位置插入数据,移动的顺序是从后向前进行的,移动的方向是向后移动,结构是整体数据向后移一位。移动数据的顺序如果错了,就会覆盖原来的数据,造成数据丢失更改。

正确代码:

//头插
void SLPushFront(SL* ps,SLDataType x)
{
	assert(ps);//判断传入的指针是否为空
	//先判断空间够不够
	SLCheckCapacity(ps);
	//先让数据表中已有的数据整体往后移动
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//前面的数据向后挪,arr[1]=arr[0](最后一次),
		//得到结束条件i>0,而且arr[0]位置空出来,可以插入数据了
	}
	ps->arr[0] = x;//直接在arr[0]处插入数据
	ps->size++;
}

判断循环结束的条件方法:看最后一次循环是怎样的,然后根据它来进行调整。

3.4.2 删除(删)

(1)尾删

尾删有两种情况,

       ps->arr[ps->size-1]=-1;中-1是我为了举例随便写的, 其实无论这里是否有值,只要size--了,那么他就不是有效的元素了,即使后面又插入了新元素,插入的新元素都是直接覆盖掉原数据,所以没必要特意地去赋值。(只要不是访问size之外的数据,size有数据不影响对顺序表的增删查改操作)

       此外,顺序表不能为空,如果为空就不能执行删除操作。

正确代码:

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//判断顺序表是否为空
	//ps->arr[ps->size - 1] = 0;这段代码有没有都可以,加入它感觉还有点画蛇添足
	--ps->size;
}

 尾删测试:

(2)头删

代码如下:

//头删:
void SLPopFront(SL* ps)
{
	assert(ps);//判断指针是否为空
	assert(ps->size);//判断顺序表是否为空
	//数据整体往前挪动一位
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
		//最后一个循环arr[0] = arr[1];ps->arr[ps->size - 2] = ps->arr[ps->size - 1];
	}
	ps->size--;
}

头删测试:

由上图可知代码正确。

       删除了首元素之后,要将后面的元素依次往前挪,如果是从后往前挪,那么3一旦覆盖2,就找不到2了,所以必须从前往后挪。

3.4.3 指定位置的操作

(1)指定位置插入数据

代码如下:

//指定位置之前插⼊数据,即在pos之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)//在ps指向的顺序表里插入,插入位置的下标,插入的数据
{
	assert(ps);//判断指针是否为空
	assert(pos >= 0 && pos <= ps->size);//pos是指顺序表的数组下标,有范围,0到有效数据个数之间
	//开始插入数据,判断空间够不够
	SLCheckCapacity(ps);
	//让下标为pos的位置后面的数据整体向后移动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

在这里综合了头插和尾插,测试如下:

(2)指定位置删除数据

指定位置删除之后,要把后面的元素往前挪!

 代码如下:

//指定位置之前删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//size位置上并没有有效的数据,因此,pos不能等于size
	//整个顺序表的数据少了一个,size--
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//循环条件判断:ps->a[i-2] = ps->a[i-1];
        //size处没有数据,所以i不能等于size-1
	}
	ps->size--;
}
//测试一定要注意边界上的数据,再加上对中间位置的数据的测试

(3)顺序表的查找
//查找数据
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);//判断指针是否为空
	for (int i = 0; i < ps->size; i++)//遍历数组
	{
		if (ps->arr[i] == x)
		{
			//找到了
			return i;//返回下标
		}
	}
	//没找到
	return -1;//返回无效的下标整数
}

四、顺序表的所有代码

       下面是我写的整个有关顺序表实现的代码,值得一提的是,代码不是只有一种写法,我的代码也不一定是非常好的代码,因此,下面的代码作为参考,希望友友们作为参考,适当发挥!

SeqList.h

#pragma once

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

typedef int	SLDataType;//方便后续类型的统一替换

//动态顺序表
typedef struct SeqList
{
	SLDataType* arr;
	int size;//有效数据个数
	int capacity;
}SL;//重命名

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

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

//顺序表的打印,不用传入指针了,只需传值
void  SLPrint(SL s);


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


//顺序表的删除
void SLPopBack(SL* ps);//尾删
void SLPopFront(SL* ps);//头删

//指定位置之前插⼊数据
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置之前删除数据
void SLErase(SL* ps, int pos);
//查找数据
int SLFind(SL* ps, SLDataType x);

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
//次文件用于声明各种方法:

#include "SeqList.h"//可以引用到


void SLInit(SL* ps)//s是结构体变量
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

//顺序表的销毁
void SLDestroy(SL* ps)
{

	if (ps->arr)//先判断arr数组中是否有申请空间,等价于if(ps->arr!=null)
	{
		free(ps->arr);
	}
	ps->arr = NULL;//销毁空间后指针要及时置为空(NULL)
	ps->size = ps->capacity = 0;//避免他们变成随机值(变成其他值)
} 
void SLCheckCapacity(SL* ps)
{
	//插入数据之前先判断空间够不够
	if (ps->capacity == ps->size)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//如果capacity不等于0,那么newCapacity等于2 * ps->capacity
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//但是,capacity之前就被初始化为0了
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);//exit作用用法:直接退出,程序不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}
//顺序表的打印
void  SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);//暴力,在SeqList.h中添加assert.h
	SLCheckCapacity(ps);

	ps->arr[ps->size] = x;//也可以这样写
	ps->size++;
}

//头插
void SLPushFront(SL* ps,SLDataType x)
{
	assert(ps);
	//先判断空间够不够
	SLCheckCapacity(ps);
	//先让数据表中已有的数据整体往后移动
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//前面的数据向后挪,arr[1]=arr[0](最后一次),
		//得到结束条件i>0,而且arr[0]位置空出来,可以插入数据了
	}
	ps->arr[0] = x;//直接在arr[0]处插入数据
	ps->size++;
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//判断顺序表是否为空
	//ps->arr[ps->size - 1] = 0;这段代码有没有都可以
	--ps->size;
}

//头删:
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//数据整体往前挪动一位
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
		//最后一个循环arr[0] = arr[1];ps->arr[ps->size - 2] = ps->arr[ps->size - 1];
	}
	ps->size--;
}

//指定位置之前插⼊数据,即在pos之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)//在ps指向的顺序表里插入,插入位置的下标,插入的数据
{
	assert(ps);//判断指针是否为空
	assert(pos >= 0 && pos <= ps->size);//pos是指顺序表的数组下标,有范围,0到有效数据个数之间
	//开始插入数据,判断空间够不够
	SLCheckCapacity(ps);
	//让下标为pos的位置后面的数据整体向后移动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}


//指定位置之前删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);//加入一个断言:顺序表不能为空,这样代码更加健壮
	assert(pos >= 0 && pos < ps->size);//size位置上并没有有效的数据,因此,pos不能等于size
	assert(ps->size);//确保里面有元素,否则不执行 
	//整个顺序表的数据少了一个,size--
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//边界判断:ps->a[i-2] = ps->a[i-1];
		//size处没有数据,所以i不能等于size-1
	}
	ps->size--;
}
//测试一定要注意边界上的数据,再加上对中间位置的数据的测试

//查找数据
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);//判断指针是否为空
	for (int i = 0; i < ps->size; i++)//遍历数组
	{
		if (ps->arr[i] == x)
		{
			//找到了
			return i;//返回下标
		}
	}
	//没找到
	return -1;//返回无效的下标整数
}

友友们,码字不易哎,三连支持一波呗~ 


  • 67
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 42
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值