一、顺序表有什么功能?
这就像是大纲一样,只要我们明确了要实现的功能,之后就是努力地实现它们就好了,众所周知,顺序表是在计算机内存中以数组的形式保存我们需要的数据。既然数据被保存了,那么我们接下来就是对这些保存好的数据进行对应的操作,增删改查是必须要有的,接着,我们想将顺序表中的内容打印出来,看看到底有没有正确的存储我们需要的数据,我们可以再设计一个打印函数,想对顺序表的内容排序也可以再设计一个排序函数,反正根据自己所需设计,但是增删改查是最基本的,必须要有的。
二、实现顺序表的各个功能
1.前置准备
在实现顺序表的各个功能之前我们得先有点前置准备内容,顺序表的成员类型,顺序表这个结构体的设计,头文件的引用......这些操作笔者推荐都放在一个头文件中进行,这样在调用的时候仅需引用一个头文件即可完成我们需要的功能。
// SeqList.h //将所需函数和所需头文件的引用放在一个头文件中,那么在使用的时候就只用引用一个头文件 #pragma once//防止头文件被重复引用 #include <stdio.h> #include <assert.h> #include <stdlib.h>//可能要用到的头文件 typedef int SlDateType; //将int typedef成SlDateType这样就可以区分int和顺序表成员 // 虽然它们的类型本质上都是int但是它们的字面含义已经不同 //当然了,可以把int换成别的类型 //这样子设计其实更多的是为了到时顺序表成员类型想更换直接从这换就全换了,不用一个一个换 typedef struct seqlist { SlDateType* a; //创建一个指针类型的顺序表成员数据,为什么是指针? //这样做是为了到时能够使用malloc和realloc对空间的大小进行开辟与修改 //相当于柔性数组 int sz;//已经存放了多少个成员 int capacity;//容量大小,以后判定空间是否够用可以通过这个来判定 }seqlist;//将结构体名字命名为seqlist,使用时更加方便
2.初始化顺序表
记得在书写代码之前将之前的顺序表头文件引用进来,这样就可以用到顺序表头文件的内容。
//顺序表.c #include"顺序表.h" void init_seqlist(seqlist* s1)//通过指针的形式访问,便能够对内容进行修改 { s1->capacity = 3;//将容量大小初始化为3,为了更好地测试到时的扩容效果 s1->sz = 0;//将成员数量初始化为0,代表着此时没有存放成员 s1->a = (SlDateType*)malloc(sizeof(SlDateType) * s1->capacity); //将顺序表的成员数组大小初始化和容量一样的大小 if (s1->a == NULL)//开辟失败的话直接退出程序 { exit(-1); } }
在顺序表.c中书写完函数的内容后别忘了在顺序表.h中引用它,这样别人才能够只引用一个头文件就能够使用对应函数。
3.顺序表扩容
在存放顺序表成员之前我们应该要注意的一点就是,我们的容量是有限的,那么在触及容量的时候,也就是顺序表已经被装满的时候,我们应该要扩容处理,避免出现装不下内容的情况出现。
void if_enough(seqlist* s1) { if (s1->sz == s1->capacity) //当容量和成员个数相当时,显然就已经存满了,需要扩容 { s1->a = realloc(s1->a,sizeof(SlDateType)*s1->capacity*2); //将容量扩大到原来容量的两倍 if (s1->a == NULL) { perror("if_enough");//错误提示 return;//中止程序 } s1->capacity *= 2;//扩容成功,容量翻倍 printf("扩容成功,当前容量为%d\n",s1->capacity);//扩容成功给个提示 } }
4.打印顺序表
在增加之前,我们就会意识到这样一个问题,倘若我增加了顺序表成员,但我又该如何分辨出我成功增加了顺序表成员呢,听着是不是很绕?其实就是我们不能够直接的看到顺序表的内容,因此我们可以使用打印的方式将顺序表的内容打印出来。
void print_seqlist(const seqlist* s1) //将内容打印出来,但内容是不会被改变的,因此用const修饰,避免内容被修改 { if (s1->sz == 0) { printf("顺序表为空,操作失败\n"); return; } int i = 0; for (i = 0; i < s1->sz; i++) { printf("%d ", s1->a[i]);//将内容通过循环的方式打印出来 } printf("\n");//打印完所有内容之后最好换行 }
5.增加顺序表成员
5.1尾增
什么是尾增?顾名思义,就是从最后面开始增加,即在顺序表的最后进行成员的添加,注意:在进行增加之前我们需要判断顺序表是否被放满,这个时候就可以使用我们之前创建的if_enough函数来判断是否需要进行扩容处理,如果需要则扩容,不需要则无事发生。
void seqlist_pushback(seqlist*s1,SlDateType x) { if_enough(s1); s1->a[s1->sz] = x;//在顺序表的最后插入一个数据x s1->sz++; }
创建一个新的文件,测试一下我们之前写的代码
//test.c #include"顺序表.h" int main() { seqlist s1; init_seqlist(&s1);//初始化顺序表 print_seqlist(&s1); //将顺序表内容打印,但此时是空,故不能打印 seqlist_pushback(&s1, 1);//依次将1,2,3放入顺序表中 seqlist_pushback(&s1, 2); seqlist_pushback(&s1, 3); print_seqlist(&s1);//打印顺序表 seqlist_pushback(&s1, 2);//依次将2,3放入顺序表中 seqlist_pushback(&s1, 3); print_seqlist(&s1);//打印顺序表 }
5.2头增
顾名思义,在顺序表的最开始进行成员的增加,我们不难想象,若是这个顺序表中已经有成员了这么办?难不成直接将这个成员替换成我们的目标?如果这样做就会少一个成员,根据数组的经验,我们只能够通过覆盖的方式先将所有的成员往后挪一个单位,再将最前面的成员替换成我们需要的成员。同样在开始之前我们应该要判断容量是否足够。
这里挪动是核心,同样也是一门学问,笔者在这画副图给大家,大家就懂得如何挪动了
由图可知,我们要先将最后面的成员往后挪动到下一个空间中,也就是sz对应的空间内容,得是sz-1的空间内容,sz-1的内容得是sz-2的内容,那么就可以通过循环的方式实现,sz-i指向的内容等于sz-i-1指向的内容,i实现一步步的覆盖,
这里面比较难想的就是i的范围,由目标分析可知,当sz-i-1=0的时候结束循环,为什么?,因为当sz-i-1=0的时候,sz-i等于1,也就是1对应的目标,等于0对应的目标,完成这一步之后,所有的覆盖就已经结束,根据计算可知,i=sz-1,故i<sz便可以实现所有的覆盖。
void seqlist_pushfront(seqlist* s1, SlDateType x) { if_enough(s1); int i = 0; for (i=0;i<s1->sz;i++)//通过循环从后往前覆盖 { s1->a[s1->sz - i] = s1->a[s1->sz-i-1]; } s1->a[0] = x;//将首元素替换成x s1->sz++; }
同样可以测试一下
//test.c #include"顺序表.h" int main() { seqlist s1; init_seqlist(&s1);//初始化顺序表 print_seqlist(&s1); //将顺序表内容打印,但此时是空,故不能打印 seqlist_pushback(&s1, 1);//依次将1,2,3放入顺序表中 seqlist_pushback(&s1, 2); seqlist_pushback(&s1, 3); print_seqlist(&s1);//打印顺序表 seqlist_pushfront(&s1,520);//在最前面放入520 print_seqlist(&s1);//打印顺序表 }