目录
1.顺序表中的头文件 (每一种函数方法)
#define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDataType* a;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//指定位置之前插⼊/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
我的讲述方法是对这几行代码一一进行讲解,建议复制一份在VS中,边看博客边看头文件,不然真的看不懂。
2.关于typedef 的用法
1.typedef用来给类型重命名,这里为什么要把int 重命名改为SLDataType呢?
因为如果用int的话后续的代码如果要更改,那就得把全部的int 都更改,如果提前typedef了就可以直接改 上面的类型就好了
2.下面把struct SeqList 改为SL仅仅是因为前面那一串太长了,给他缩短一点,增加代码的可读性
3.初始化和销毁表
3.1初始化表
初始化表,用于初始化变量。变量的使用前都是要初始化的。
a的指针的意思是指向 要填充的变量
capacity的意思是目前开辟的内存的量,size表示现在被占用的内存的量
void SLInit(SL* ps)
{
ps->a = NULL;
ps->capacity = ps->size = 0;
}
3.2销毁表
判断ps->a的原因是,判断这个指针是不是为空,如果为空,会不执行任何操作直接返回。
然后再给ps a 置空,capacity和size 置为0;
void SLDestory(SL* ps)
{
if (ps->a)
{
free(ps->a);
}
ps->a = NULL;
ps->capacity = ps->size = 0;
}
4.打印表
打印表很简单,就用for循环遍历 realloc开辟的空间就好了(在下一部分自动扩容表讲解)
注意打印结束后换行,避免下次打印连在一起。
ps->a[ i ] 的意思就和数组一样。用来表示每一块开辟空间里的存储的值。
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
5.自动扩容表!!!(重点)
1. 首先扩容表的前提是,你的capacity(现在总共开辟的内存量)和size(已有数据的内存)这两个变量已经相等,说明这时候的内存已经不够了,需要添加。
2.所以判断语句是 ps capacity == ps size(这里不写箭头了)。
3.看到下面的int newcapacity 其实是用来判断 ps->capacity是否为0.如果为0则就直接等于4
不等于就等于 2 * ps->capacity, (0 ?4 :2*ps capacity就是判断是否为0,真就为前,假就为后)。
4.为什么要判断是否为0呢?因为如果为0,你下面开辟的代码 realloc开辟出来的空间还是0
5.之和在判断是否为NULL开辟失败的情况。
6.最后将开辟的空间 再次赋予 ps a。
7.ps capacity = newcapacity。
void SLCheckCapacity(SL* ps)
{
if (ps->capacity == ps->size)
{
/*if (ps->capacity == 0) 判断capacity的情况,和下面newcapacity意思相同
{
ps->capacity == 4;
}
else
{
ps->capacity == ps->capacity * 2;
}*/
int newCapacity = ps->capacity == 0 ? 4 : 2*ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc失败\n");
return;
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
6.头部插入表和尾部插入表
6.1尾部插入表
很简单,1.首先需要断言 ps 是否为空 NULL,因为为空指针,进行解引用的时候会变成野指针。所以要保证传进来的指针不为空。
2.给一个上面的自动扩容表的函数,用来判断空间是否足够。
3.如果足够 则将 ps size 这个空间的位置赋值为x 。(因为是数组,所以第一个数的下标是0.所以ps size 表示被占用的空间数量,比如说有三个空间被占用,ps size =3,而被占空间的下标是0 1 2,所以3这个空间是没数值的,直接赋值即可。)
4.记得每次插入表给一个size ++;
void SLPushBack(SL* ps,SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
6.2头部插入表
一样的先断言看看ps是否为空。
1.与尾部插入不同的点就是,在头部插入,怎么在头部插入呢?
可以用for循环从后往前遍历一遍,将每一个数值都往后移一个内存。
2.然后再将 0 下标位置的内存赋值为 x。
3.插入表一定要size++
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
7.头部删除表和尾部删除表
7.1判断表是否为空
为什么要进行多一步判断表为空?
可以想象如果一个表都为空了,那它还删除什么?那肯定报错啊
所以在进行删除表的时候要进行判断表的有无。
bool 表达式只有两个值 真就是为true 为1,假就是 false 为0.
所以return的值要是一个表达式,用来判断是否为真或假。
bool SLIsEmpty(SL* ps)
{
assert(ps);
return ps->size == 0;
}
7.2尾部删除表
超简单的删除尾表。
1.一样的判断ps是否为空
2.判断表是否为空
3.对于一个开辟的表的内存,如果size--就说明有数据的空间-1. 下次在插入表数据的时候,直接使用这个空间,这样就是类似一个抽象的删除,因为下次添加数据还是用的这块内存,所以你不用给这块空间的数据清空也可以使用。
可以想象一个二手汽车,前一个拥有人想把它卖了,这时候这车就是无主车,等下一个车主来了,直接把前一个车主的名字改为下一个车主的名字即可。
void SLPopBack(SL* ps)
{
assert(ps);
assert(!(SLIsEmpty(ps)));
ps->size--;
}
(!SLIsEmpty( ps )) 这个表达式的意思就是 上面用bool函数判断 表中有没有数据,
如果表中没有数据 就返回了一个1 ,用!来取反 所以这里就是 如果为1则断言程序终止。
如果为0就说明 有数据 程序继续进行。
7.3头部删除表
1.首先还是判断ps是否为空,和表是否为空
2.用for循环遍历把后一个的值赋予前一个。
为什么是 i < ps size - 1.因为我们只要有数据的空间,如果是 ps size不减一 那就会把 有一个空着的数据传到 最后一个位置。这是不需要的,因为是删除表,最后一个位置要被放弃的。
3.删除表 要 ps size --
void SLPopFront(SL* ps)
{
assert(ps);
assert(!(SLIsEmpty(ps)));
for (int i = 0; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
8. 指定位置的插入和删除
8.1指定位置的插入
1.首先判断 ps 是否为空,判断 pos 是否超出表的范围。
2.因为是插入表所以要判断空间是否足够
3.用for循环 从后往前遍历 将前一个的数值赋值给后一个
为什么是 i = ps size -1,因为 ps size 的值 是表示 有数据的空间个数,而对于空间的下标来说,ps size 这个数字的空间下表指向的反而是下一个无数据的空间。我们看第一组的转换
ps a【i + 1】= ps a【i】如果把i 换了 就变成了ps a 【ps size】 = ps a 【ps size -1】。
这样就很好理解了,把下一个空数据的内存赋值为 最后一个内存的值。
pos -1,和上面类似,可以自己思考一下。(不懂可以私聊我,看到必回)
4.最后在pos 那个位置插入数据 x
5.插入表 ps size++
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i > pos - 1; i--)
{
ps->a[i + 1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
8.2指定位置的删除
1.因为是删除首先判断 ps 是否为空 表是否为空,在判断 要查找的数字是否在表数据的范围内。
2.这个删除和上面类似,就是要把前一个数据的值改为下一个数据的值即可,从pos的位置开始,因为要把pos位置的值给占领了。
3.ps size--
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(!SLIsEmpty(ps));
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}