目录
一、简述:
顺序表的创建和功能实现(插入,删除,排序等等)和通讯录的实现十分相似,换言之,你会通讯录了,顺序表就是小菜一碟,所以不知道通讯录的实现的话,可以先看一下我写的通讯录的实现过程,这个顺序表只是在通讯录的逻辑上稍加修改就实现了,可以说顺序表是通讯录的副本
我们分3个文件:
seqlist.h文件放——头文件的包含和函数声明
seqlist.c文件放——函数的定义和实现
test.c文件——用来测试写的顺序表的相关逻辑
而且这个程序不建议先写菜单,因为写菜单后你调试麻烦,你每次都需要选择菜单的选项,才能往下调试,所以菜单的实现可以放在最后,并且调试时断点可以移动,但是已经调试的代码不能恢复到没执行的状态,什么时候用?比如我们动态开辟内存成功了,我就想看看开辟没成功的时候,我们就可以拖动断点,看看开辟失败程序是怎么走的
二、顺序表的概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。换句话说,顺序表就是数组,但是在数组的基础上,他还要求数据是连续存储的,不能跳跃间隔,即比如a是个数组,存a[5] =0,然后再存a[9] =0这对数组来说是可以的,但对于顺序表,你只能在存完a[5] =0后,存a[6] = 0。
三、顺序表的创建
分为静态顺序表和动态顺序表,但是静态顺序表大小有些固定,所以我们用动态开辟内存函数来创建动态顺序表
//静态版本
//N给小了不够用,给大了会浪费
#define N 10000
typedef int SLDataType;
//静态顺序表
typedef struct SeqList
{
SLDataType a[N];
int sz;//数组中存储了多少个数据
}SL;
//动态版本
//和通讯录中通讯录的结构体创建十分相似
#include<stdlib.h>
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* a;
int sz;//当前顺序表存的有效数据的个数
int capacity;//当前顺序表的最大容量的个数
}SL;
接下来是函数的相关实现细节,这里说的函数也可以叫做接口函数,接口函数就是某个模块写了主要给其他模块用的函数
1、初始化
//顺序表初始化的实现
void SeqListInit(SL* pc)
{
pc->a = NULL;
pc->sz = pc->capacity = 0;//连续赋值
}
2、尾插数据和检查容量
增加数据就要考虑顺序表容量是否够。从顺序表结尾插入数据也属于添加数据,所以要考虑这个顺序表的容量问题,因为我是动态开辟的,先写一个检查容量函数CheckCapacity
有必要说的一点是:越界并不会报错,而是在free释放内存的时候会报错
void CheckCapacity(SL* pc)
{
if (pc->sz == pc->capacity)
{
int newcapacity = pc->capacity == 0 ? 4 : pc->capacity * 2;
//因为==的优先级高于?: 所以先判断是否容量为0
//这个操作的成功性在于了解运算符的优先级性
SLDataType* ptr = (SLDataType*)realloc(pc->a, newcapacity * sizeof(SLDataType));
if (ptr != NULL)
{
pc->a = ptr;
pc->capacity = newcapacity;
}
else
{
printf("realloc fail\n");
exit(-1);
//开辟都失败了,这个程序就没必要往下走了
//直接一个exit,终止程序
}
}
}
//顺序表尾插数据的实现
void SeqListPushBack(SL* pc, SLDataType x)
{
//先检查容量,不够就进行扩容
CheckCapacity(pc);
pc->a[pc->sz] = x;
pc->sz++;
}
3、打印顺序表数据的函数
这个操作很简单,我们只需要遍历到当前顺序表有效数据个数(sz),打印即可
//顺序表打印数据的函数的实现
void SeqListPrint(SL* pc)
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%d ", pc->a[i]);
}
printf("\n");
}
4、尾删数据
//顺序表尾删数据的实现
void SeqListPopBack(SL* pc)
{
//删除的前提是顺序表有元素
//assert条件为真,往下执行,为假,直接程序报错
//这是一个比较粗暴的方式,也可以用if条件判断
//assert记得引头文件
assert(pc->sz > 0);
pc->sz--;
}
5、头插数据
//顺序表头插数据的函数实现
void SeqListPushFront(SL* pc,SLDataType x)
{
CheckCapacity(pc);
//挪动数据
int end = pc->sz - 1;
while (end >= 0)
{
pc->a[end + 1] = pc->a[end];
end--;
}
pc->a[0] = x;
pc->sz++;
}
6、头删数据
//顺序表头删数据的函数实现
void SeqListPopFront(SL* pc)
{
assert(pc->sz > 0);
//挪动数据
int begin = 0;
while (begin < pc->sz-1)
{
pc->a[begin] = pc->a[begin + 1];
begin++;
}
pc->sz--;
}
7、查找顺序表元素
如果找到了这个元素返回这个元素对应的下标,没找到则返回-1
//查找顺序表中数据的函数实现
//规定找到范围数组元素下标
//没找到返回-1
int SeqListFind(SL* pc, SLDataType x)
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (pc->a[i] == x)
{
return i;
}
}
return -1;
}
8、顺序表的指定位置插入
这个函数你需要传你想在哪个位置,插入什么元素,这个位置指的是数组的元素下标,指定位置插入,那么你指定的这个元素下标肯定不能越界,所以先要判断一下并且还要考虑容量问题
//顺序表中插入指定位置(数组下标为pos)的数据的函数
void SeqListInsert(SL* pc,int pos,SLDataType x)
{ //温柔的处理方式
//插入位置的检查,不能越界
//if (pos > pc->sz || pos < 0)
//{
// //这里为什么不用perror直接打印动态开辟的错误信息?
// //因为perror是调用c语言的一些库函数失败的时候去打印的
// //他会打印库函数调用失败的原因,比如main,fopen函数等
// printf("pos invalid\n");
// //这里不用perror是因为pos可能是因为传参失败
// //不是库函数调用失败问题,所以不用
// return;
//}
//粗暴的处理方式
assert(pos <= pc->sz && pos >= 0);
CheckCapacity(pc);
int end = pc->sz;
while (end > pos)
{
pc->a[end] = pc->a[end-1];
end--;
}
pc->a[pos] = x;
pc->sz++;
}
9、顺序表中删除指定位置的数据
这个函数你需要传你想在哪个位置,删除什么元素,这个位置指的是数组的元素下标,指定位置删除,那么你指定的这个元素下标肯定不能越界,所以先要判断一下这个元素是否存在,我给定的范围是否越界
//顺序表中删除指定位置的数据的函数
void SeqListErase(SL* pc, int pos)
{
assert(pos >= 0 && pos < pc->sz);
int begin = pos;
while (begin < pc->sz-1)
{
pc->a[begin] = pc->a[begin + 1];
begin++;
}
pc->sz--;
}
10、销毁顺序表
销毁其实就是因为动态开辟的内存,需要手动返还给操作系统,不然会导致内存泄漏的问题
//销毁顺序表的函数实现
void SeqListDestroy(SL* pc)
{
free(pc->a);
pc->a = NULL;
pc->capacity = 0;
pc->sz = 0;
}
最后,源码如下,相关实现逻辑均在代码的注释中!
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#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("*** 0、退出 ***\n");
printf("************************\n");
}
int main()
{
int input = 0;
SL ps = { 0 };
int pos = 0;
SLDataType x = 0;
//顺序表的初始化
SeqListInit(&ps);
do
{
menu();
printf("请输入你想进行的操作:>\n");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出\n");
//销毁顺序表
SeqListDestroy(&ps);
break;
case 1:
//头插数据
printf("请输入你要头插的数据\n");
while(scanf("%d", &x) != EOF)
SeqListPushFront(&ps, x);
break;
case 2:
//头删数据
SeqListPopFront(&ps);
break;
case 3:
//尾插数据
printf("请输入你要尾插的数据\n");
while (scanf("%d", &x) != EOF)
SeqListPushBack(&ps, x);
break;
case 4:
//尾删数据
SeqListPopBack(&ps);
break;
case 5:
//打印顺序表
SeqListPrint(&ps);
break;
case 6:
//查找顺序表中的某个元素,pos是数组下标
printf("请输入你要查找的数据\n");
scanf("%d", &x);
pos = SeqListFind(&ps,x);
if (pos == -1)
printf("你要查找的元素不存在\n");
else
printf("该元素下标为%d\n",pos);
break;
case 7:
//指定位置插入数据
printf("请输入你要插入位置的下标和插入的数据\n");
while (scanf("%d %d",&pos, &x) != EOF)
SeqListInsert(&ps, pos, x);
break;
case 8:
//指定位置删除数据
printf("请输入你要删除位置的下标\n");
while (scanf("%d", &pos) != EOF)
SeqListErase(&ps, pos);
break;
default:
printf("无此选项,请重新输入!\n");
}
} while (input);
return 0;
}
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"seqlist.h"
//顺序表初始化的实现
void SeqListInit(SL* pc)
{
pc->a = NULL;
pc->sz = pc->capacity = 0;//连续赋值
}
//顺序表检查容量的函数
void CheckCapacity(SL* pc)
{
if (pc->sz == pc->capacity)
{
int newcapacity = pc->capacity == 0 ? 4 : pc->capacity * 2;
//因为==的优先级高于?: 所以先判断是否容量为0
//这个操作的成功性在于了解运算符的优先级性
SLDataType* ptr = (SLDataType*)realloc(pc->a, newcapacity * sizeof(SLDataType));
if (ptr != NULL)
{
pc->a = ptr;
pc->capacity = newcapacity;
}
else
{
printf("realloc fail\n");
exit(-1);
//开辟都失败了,这个程序就没必要往下走了
//直接一个exit,终止程序
//exit正常退出返回0 exit(0)
//exit异常退出,写个-1就行 exit(-1)
}
}
}
//顺序表尾插数据的实现
void SeqListPushBack(SL* pc, SLDataType x)
{
//先检查容量,不够就进行扩容
CheckCapacity(pc);
pc->a[pc->sz] = x;
pc->sz++;
}
//顺序表尾删数据的实现
void SeqListPopBack(SL* pc)
{
//删除的前提是顺序表有元素
//assert条件为真,往下执行,为假,直接程序报错
//这是一个比较粗暴的方式,也可以用if条件判断
//assert记得引头文件
//可以直接pc->sz--是因为顺序表是连续存储不间断的
//--后一定是把最后一个元素删掉的,而头删肯定就不能这么做
assert(pc->sz > 0);
pc->sz--;
}
//顺序表打印数据的函数的实现
void SeqListPrint(SL* pc)
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%d ", pc->a[i]);
}
printf("\n");
}
//销毁顺序表的函数实现
void SeqListDestroy(SL* pc)
{
free(pc->a);
pc->a = NULL;
pc->capacity = 0;
pc->sz = 0;
}
//顺序表头插数据的函数实现
void SeqListPushFront(SL* pc,SLDataType x)
{
CheckCapacity(pc);
//挪动数据
int end = pc->sz - 1;
while (end >= 0)
{
pc->a[end + 1] = pc->a[end];
end--;
}
pc->a[0] = x;
pc->sz++;
}
//顺序表头删数据的函数实现
void SeqListPopFront(SL* pc)
{
assert(pc->sz > 0);
//挪动数据
int begin = 0;
while (begin < pc->sz-1)
{
pc->a[begin] = pc->a[begin + 1];
begin++;
}
pc->sz--;
}
//查找顺序表中数据的函数实现
//规定找到范围数组元素下标
//没找到返回-1
int SeqListFind(SL* pc, SLDataType x)
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (pc->a[i] == x)
{
return i;
}
}
return -1;
}
//顺序表中插入指定位置(数组下标为pos)的数据的函数
void SeqListInsert(SL* pc,int pos,SLDataType x)
{ //温柔的处理方式
//插入位置的检查,不能越界
//if (pos > pc->sz || pos < 0)
//{
// //这里为什么不用perror直接打印动态开辟的错误信息?
// //因为perror是调用c语言的一些库函数失败的时候去打印的
// //他会打印库函数调用失败的原因,比如main,fopen函数等
// printf("pos invalid\n");
// //这里不用perror是因为pos可能是因为传参失败
// //不是库函数调用失败问题,所以不用
// return;
//}
//粗暴的处理方式
assert(pos <= pc->sz && pos >= 0);
CheckCapacity(pc);
int end = pc->sz;
while (end > pos)
{
pc->a[end] = pc->a[end-1];
end--;
}
pc->a[pos] = x;
pc->sz++;
}
//顺序表中删除指定位置的数据的函数
void SeqListErase(SL* pc, int pos)
{
assert(pos >= 0 && pos < pc->sz);
int begin = pos;
while (begin < pc->sz-1)
{
pc->a[begin] = pc->a[begin + 1];
begin++;
}
pc->sz--;
}
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* a;
int sz;//当前顺序表存的有效数据的个数
int capacity;//当前顺序表的最大容量的个数
}SL;
//顺序表初始化的声明
void SeqListInit(SL* pc);
//顺序表尾插函数的声明
void SeqListPushBack(SL* pc, SLDataType x);
//顺序表尾删函数的声明
void SeqListPopBack(SL* pc);
//顺序表打印数据的函数声明
void SeqListPrint(SL* pc);
//顺序表销毁函数的声明(因为是动态开辟的,内存需要释放)
void SeqListDestroy(SL* pc);
//顺序表头插数据的函数声明
void SeqListPushFront(SL* pc,SLDataType x);
//顺序表头删数据的函数声明
void SeqListPopFront(SL* pc);
//找顺序表中数据的函数声明
int SeqListFind(SL* pc, SLDataType x);
//顺序表中插入指定位置的数据的函数声明
void SeqListInsert(SL* pc, int pos, SLDataType x);
//顺序表中删除指定位置的数据的函数声明
void SeqListErase(SL* pc, int pos);