顺序表的内部结构的实现
主要目标:
实现顺序表的基本骨架,为以后实现其它功能做铺垫
思路整理:
-
创建顺序表,先定义声明一个顺序表。
-
初始化顺序表,创建销毁顺序表
-
大致框架形成,便向框架里塞各种各样的功能,如增删查改
条件实现
SqList.h: 实现类型定义和函数声明等的功能
SqList.c: 作为函数的实现
test.c
: 对函数进行测试等功能
代码实现操作
SqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h> //为后面扩容所用的realloc函数引入的头文件
#include<string.h>
#include<assert.h>
typedef int SLDateType; //用typedef重新定义int,给int重新取名字,是为了在后续更换到其它数据类型的时候,不用将代码里的int改掉,直接改此处即可,从而实现改一,而改全部。
typedef struct SqList {
SLDateType* a ;
int size; //初步定义的时候不用赋值,定义size是为了记录,数组a的元素个数,当然只是记录,并不会改变a里面的值
int capacity; //初步定义的时候不用赋值,定义size是为了检测内存空间,从而判断是否需要扩容等问题
}SL; //一样用typedef重新声明一个结构体名,方便代码编写,不用处处写SqList.
void SLInit(SL*ps); //初始化顺序表接口声明
void SLDestory(SL* ps); //销毁顺序表接口声明
void SLPushBack(SL* ps, SLDateType x); //后插顺序表接声明
void SLPushFront(SL* ps, SLDateType x); //前插顺序表声明
void SLPopBack(SL* ps); //后删顺序表声明
void SLPopFront(SL* ps); //前删顺序表声明
void SLInsert(SL* ps, int pos, SLDateType x); //任意位置插入顺序表声明
void SLDelete(SL* ps, int pos); //任意位置删除顺序表声明
void SLPrint(SL* ps); //展示顺序表声明
void SLCheck(SL* ps); //检查顺序表声明
SqList.c:
对其初始化函数:
void SLInit(SL* ps) {
assert(ps);
ps->a = NULL; //犯过错误
ps->size = 0;
ps->capacity = 0;
}
对函数进行初始化时,一定要是ps(ps是一个在测试界面是一个struct SL的一个变量 sl传进来的一个指针)指向的a size capacity
不能够自己又在重新定义,就像int* a int size int capacity
eg:
void SLInit(SL* ps) {
assert(ps);
int*a = NULL; //犯过错误
ps->size = 0;
ps->capacity = 0;
}
因为前面已经定义了struct SqList类型的指针a,size , capacity类型数值了,如果在初始化时不是写成ps->a, ps->size ps->capacity 等数据,而是毫无理由的写成 int* a int size int capacity的话,系统会认为你对其进行了重定义,此刻,特别是int* a就是一个野指针,没有指向,所以会出现上图,当我想进行扩容的时候发生错误。
所以初始化的数据一定要跟定义的数据类型匹配。
对其销毁接口
void SLDestory(SL* ps) {
assert(ps);
if (ps->a = NULL) {
return;
}
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->size = 0;
}
当然也可以
void SLDestroy(SL* ps) //销毁主要是判断ps->a是否还存在,存在则释放内存,并且,并且,指针置空,其余赋予初始值
{
assert(psl);
if (psl->a != NULL)
{
free(psl->a); //释放空间
psl->a = NULL; //置空指针
psl->size = 0;
psl->capacity = 0;
}
}
打印顺序表函数:
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
后插元素
void SLPushBack(SL* ps, SLDateType x) {
assert(ps);
if (ps->size == ps->capacity) {
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//数据结构没有规定固定要扩容的量,此处的乘以2是因为,每次增长两倍是比较合适的一个量
SLDateType* tmp = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));
if (tmp == NULL) {
perror("realloc failed");
return;
}
//用tmp承担ps->a的扩容任务是因为,扩容可能会失败,一旦扩容失败,将会导致ps->a中的原有数据的丢失,所以先用一个替身来保护真身
ps->a = tmp; //归还空间
ps->capacity = newcapacity; //这是新的空间含量
}
ps->a[ps->size] = x;
ps->size++; //新增了数据要++
}
正如前边所说,ps->size是用来监测顺序表ps->a的数据,当其ps->size = ps->capacity时,就是说,空间已满,需要扩容。
扩容所需的函数主要是realloc, 特别强调的是,realloc的第二参数是扩容后的大小
进行realloc时,如果一开始空间为0的话,那么此刻,realloc的作用便跟malloc的功能一样了
效果展示:
void test1() {
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPrint(&sl);
}
前插元素
void SLPushFront(SL* ps, SLDateType x) {
assert(ps);
if (ps->size == ps->capacity) {
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//数据结构没有规定固定要扩容的量,此处的乘以2是因为,每次增长两倍是比较合适的一个量
SLDateType* tmp = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));
if (tmp == NULL) {
perror("realloc failed");
return;
}
//用tmp承担ps->a的扩容任务是因为,扩容可能会失败,一旦扩容失败,将会导致ps->a中的原有数据的丢失,所以先用一个替身来保护真身
ps->a = tmp; //归还空间
ps->capacity = newcapacity; //这是新的空间含量
}
int end = ps->size - 1; //由下图可知,size的位置是在最后一个具体元素的下一个位置,所以要找到最后一个元素,需要ps->size-1,所以给end赋值ps->size-1
while (end >= 0) { //犯过错,将end>0,其要>=0
//如果end最后不加等于0的话,ps->a[end]就无法识别到0号位置,
//导致0号位置的元素无法传递给一号位置,最后导致前插的准备工作失败
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
根据前插和后插的两个函数的编写,便会发现,其都会在进行插入元素的时候检查空间是否足够,是否要考虑扩容,因此,我们便会将是否需要扩容重新拎出来,单独作为一个函数,以此来增强代码的易读性
void SLCheck(SL* ps) {
if (ps->size == ps->capacity) {
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDateType* tmp = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));
if (tmp == NULL) {
perror("realloc failed");
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
void SLPushBack(SL* ps, SLDateType x) {
assert(ps);
SLCheck(ps);
ps->a[ps->size] = x;
ps->size++;
}
void SLPushFront(SL* ps, SLDateType x) {
assert(ps);
SLCheck(ps);
int end = ps->size - 1;
while (end >= 0) { //犯过错,将end>0,其要>=0
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
效果展示
void test1() {
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPrint(&sl);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
SLPushFront(&sl, 6);
SLPrint(&sl);
}
任意位置插入函数:
对于任意位置插入函数,可以简单理解为其前插函数的0号位置改为任意位置即可,但需额外增加函数参数
void SLInsert(SL* ps, int pos, SLDateType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size); //遗漏,就是超过两端后就不能再让他继续增加了,不然数组访问不到,因为size始终只是监测功能
SLCheck(ps);
int i = ps->size - 1;
for (; i >= pos; i--) {
ps->a[i + 1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
效果展示
void test1() {
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPrint(&sl);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
SLPushFront(&sl, 6);
SLPrint(&sl);
SLInsert(&sl, 2, 5);
SLInsert(&sl, 4, 6);
SLInsert(&sl, 5, 7);
SLPrint(&sl);
}
后删元素(删除的位置都是数组的位置,而不是元素的位置)## 标题
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size > 0);
ps->size--;
}
直接把监控size–, 就能将原有的数据隐藏,注意只是隐藏起来,就看不见了
效果展示:
void test1() {
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPrint(&sl);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
SLPushFront(&sl, 6);
SLPrint(&sl);
SLInsert(&sl, 2, 5);
SLInsert(&sl, 4, 6);
SLInsert(&sl, 5, 7);
SLPrint(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPrint(&sl);
}
前删元素
void SLPopFront(SL* ps) {
assert(ps);
int end = 1;
while (end<ps->size) {
ps->a[end-1] = ps->a[end]; //从前往后,数据一个个往前移动
end++;
}
ps->size--;
}
效果展示:
void test1() {
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPrint(&sl);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
SLPushFront(&sl, 6);
SLPrint(&sl);
SLInsert(&sl, 2, 5);
SLInsert(&sl, 4, 6);
SLInsert(&sl, 5, 7);
SLPrint(&sl);
SLPopBack(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLDestory(&sl);
}
任意位置删除元素
void SLDelete(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos <= ps->size); //遗漏
int del = pos;
for (int i = del; i < ps->size; i++) {
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
其原理跟前删元素也差不多,在此便不做展示
不要频繁的去调用头插,因为此调用时间复杂太长哩
还有就是,popback(后删)要及时判断,空了就不要删了
以上便是我写的第一篇博客,还望各位大佬提出建议,如果有错误,请大佬们即使斧正,谢谢支持!