【数据结构】顺序表详解 从零开始步步解读 画图理解并调试分析_根据自己的理解,画一幅图来表示一个空的顺序表

💬 SeqList.h:

#pragma once
#include <stdio.h>
#include <stdlib.h>

typedef int SLDataType;

/* 动态顺序表 */
typedef struct SeqList {
	SLDataType* array;
	int size;            //有效数据个数
	int capacity;        //数组实际能存数据的空间容量是多大
}SL;

/* 接口函数 */
void SeqListInit(SL* psl);

🔑 解读:

为了避免同一个头文件被包含多次我们可以使用 #pragma once(在C语言系列预处理章节提到过,为了加深印象我在这里不得不再重申一下)。

为了方便后续修改数据类型,我们可以使用 typedef 定义一个新的数据类型,这里我们把它取名为 SLDataType(顺序表数据类型)。

我们为了让定义的结构体使用时更方便,我们同样可以使用 typedef 将其定义为 SL(此时 SL = struct SeqList,后续在使用时可以更加方便)。

最后声明我们要实现的接口函数——顺序表初始化函数并取名为 SeqListInit ,参数部分为 SL* psl(这里的 SL 就是之前被 typedef 定义的结构体 struct SeqList ),考虑到形参是只是实参的临时拷贝的问题,为了能够伤其内部,这里我们会使用址传递。所以,为了能够接收 “这个” 地址,我们使用指针 psl 来接收。

💬 SeqList.c:

#include "SeqList.h"

/* 初始化 */
void SeqListInit(SL* psl) {
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

🔑 解读:

首先引入我们自己创建的头文件 #include “SeqList.h” ,我们就可以开始动手实现顺序表初始化函数了。

首先通过 psl 指向 array,将数组为空。因为是初始化,所以将有效数据个数和数组时即能存数据的空间容量一并置为0。

💬 Test.c:

#include "SeqList.h"

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);
}

int main() {
	TestSeqList1();

	return 0;
}

🔑 解读:测试代码部分,我们不在 main 函数内直接测试而是通过函数来测试,这样的好处是可以方便我们选择性的测试一部分的代码。

为了测试,TestSeqList1 函数中首先创建一个结构体,这里取名为 sl ,随后将 sl 址传递传入 SeqListInit 函数中进行测试。

我们运行代码发现代码可以成功运行,我们下面进入调试来看一下 SeqListInit 函数是否起作用了。

🐞 调试代码:

至此,SeqListInit 函数就写好了。

0x02 尾插(SeqListPushBack)

📚 说明:所谓的尾插,就是在后面进行插入。

💡 出现三种情况:

① 第一种情况是顺序表压根就没有空间。

② 第二种情况就是我们创建的 capacity 空间满了。

③ 第三种情况是空间足够,直接插入数据即可。

💬 SeqList.h:

...
void SeqListPushBack(SL* psl, SLDataType x);  //尾插

🔑 解读:SLDataType x 是要添加到顺序表的元素。

💬 SeqList.c:

...
/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {		
	// 首先判断有没有空间,如果没有空间或者空间不足,那么我们就扩容
	if (psl->size == psl->capacity) {
        // 如果容量是0(第一次)就赋4,如果不是0,就把容量翻一倍
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity*2;
		// 这里使用realloc,因为如果原空间为空,就等于malloc。调整为 new_capacity个SLDataType大小的空间
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
        // 检测是否realloc成功
		if (tmp_array == NULL) {
			printf("realloc fail {扩容失败}\n");
			exit(-1);
		}
		// 更新它们的大小
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}

	//插入
	psl->array[psl->size] = x;
	psl->size++;
}

🔑 解读:

根据我们刚才分析的三种情况,首先我们需要判断是空间是否足够。判断思路如下:如果 size == capacity(有效数据个数等于实际能存的最大空间容量),我们进行扩容操作。

如果空间不足,我们就创建一个变量 new_capacity 用来存放 “新容量” ,int new_capacity = psl->capacity 首先把 capacity 的值赋值给这个 “新容量” ,因为考虑到第一次使用 capacity 大小为0,翻倍会出问题(0乘以任何数都是0),这里巧妙地使用三目操作符:int new_capacity = psl->capacity == 0 ? 4 : psl->capacity*2 , 如果 capacity 为 0 (第一次使用大小是0),就把4赋值给它;如果不为0,就把 capacity 的值翻一倍(x2)。

❓ 这里为什么要x2(翻一倍)?

💡 随便你,你想一次扩容多少就扩容多少,乘2只是一个比较折中的扩容容量方案。

此时,new_capacity 中就存放了扩容的新容量(如果第一次使用为容量为4,容量扩容后为capacity的二倍)。

确定好新的容量后,我们可以对数组动刀子了。为了防止翻车,我们先创建一个临时数组来扩容。这里要进行动态内存开辟,我们选择使用 realloc 而不是 malloc 函数,因为 realloc 可以调整并且如果是第一次用自带 malloc 的效果:

(摘自MSDN)

下面我们来看是如何扩容的:

🔗 如果还是比较懵,建议详细复习: 《维生素C语言》第十三章 - 动态内存管理

这里我们还加了一段判断,如果你动态内存管理学的比较好这里就不难理解:

如果没有问题,就可以进行赋值操作了。将 new_capacity 交给 psl->capacity,将 tmp_array 交给 psl->array

扩容成功后就可以插入了,插入相对来说就很很简单了。

🔺 当然,如果一开始空间足够就不需要扩容,就可以直接跳到这里:

💬 Test.c:

#include "SeqList.h"

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
}

int main() {
	TestSeqList1();

	return 0;
}

🔑 解读:我们来测试下 SeqListPushBack 尾插功能是否实现成功了。我们之前定义的 SLDataType 是 int 型,所以我们传入数字进行测试。

🐞 调试:

至此,SeqListPushBack 函数就写好了。

0x03 顺序表打印(SeqListPrint)

📚 顺序表打印就是打印顺序表内数据,我们可以利用循环解决。

💬 SeqList.h:

···
void SeqListPrint(SL* psl);        //打印

🔑 解读:因为是单纯的打印,所以只需要把顺序表传过去就行。

💬 SeqList.c:

/* 打印 */
void SeqListPrint(SL* psl) {
	int i = 0;
	for (i = 0; i < psl->size; i++) {
		printf("%d ", psl->array[i]);
	}
	printf("\n");
}

🔑 解读:遍历整个顺序表,利用 for 循环就可以轻松解决,把他们都打印出来就可以了。

💬 Test.c:

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);

	SeqListPrint(&sl);
}

🚩 运行结果如下:

0x04 销毁(SeqListDestroy)

📚 因为是动态开辟的,所以如果空间不用我们就需要销毁掉。如果不销毁会存在内存泄漏的风险,所以与之对应的我们写一个销毁的接口函数。

💬 SeqList.h:

...
void SeqListDestory(SL* psl);		//销毁

💬 SeqList.c:

/* 销毁 */
void SeqListDestory(SL* psl) {
	//首先把空间还给操作系统
	free(psl->array);
	psl->array = NULL; //置空防止空指针
	psl->capacity = psl->size = 0; //空间置0
}

🔑 解读:首先使用 free 函数把动态开辟的内存空间释放掉,因为free 之后那块开辟的内存空间已经不在了,为了防止野指针,我们还需要手动把它置为空指针。最后再把 capacitysize 全部设为0,做到空着手来,空着手去,销毁部分就实现好了。

💬 Test.c:

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);

	SeqListPrint(&sl);

	SeqListDestory(&sl);
}
0x05 尾删(SeqListPopBack)

📚 如果我们想把第一个数据删除,最简单的思路是把要尾删的地方置为 0,然后再把 size-- 即可实现。这就是尾删:

💬 SeqList.h:

void SeqListPopBack(SL* psl);                    //尾删

💬 SeqList.c:

/* 尾删 */
void SeqListPopBack(SL* psl) {
	psl->array[psl->size - 1] = 0;		//因为是下标,所以size-1才能对的上
	psl->size--;	//实际有效个数-1
}

🔑 解读:首先 psl->array[psl -> size - 1] 这里 -1 ,是因为下标的原因,减1才可以对的上。这里把末尾的元素设为了0,此时再 size– 让实际有效个数减1。

💬 Test.c:我们来删除两个数据测试一下

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);  //打印

    SeqListDestory(&sl); //销毁
}

🚩 运行结果:

🤔 不知道你有没有发现,其实我们只需要psl -> size– 就可以了…… 找到尾部的目标然后把他置为0其实没有意义,因为 size 一减,货一拉,就什么痕迹都没有了。是 size 标识了我们存入了多少个有效数据,比如有5个数据,size– 后就只有前4个数据有意义了。所以,尾删我们只需要 size-- ,就可以达到目的:

/* 尾删 */
void SeqListPopBack(SL* psl) {
	// psl->array[psl->size - 1] = 0;
	psl->size--;						//实际有效个数-1
}

❓ 尾删就这么简单?

当然不是,我们还需要考虑一些情况的发生。如果我们一直删,删到没有数据可以删了,是不是会出现问题呢?我们试着删完5个数据后继续删,然后再插入输入打印看看:


void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl); //打印
	
	SeqListPushBack(&sl, 10);
	SeqListPushBack(&sl, 20);
	SeqListPrint(&sl); //打印

	SeqListDestory(&sl); //销毁
}

🚩 运行结果如下:

(吐槽:这时候一打印,就爆炸了。以为写的很完美的尾插,一直以为它是不会翻车的,就,就,爆炸了,碎成了片片,我同情它甚至超过了同情我自己。)

🐞 不慌,我们来调试看看!

💡 解决方案:添加 SeqListPopBack 限制条件

方式一:如果没有数据了,使调用 SeqListPopBack 没效果(儒雅随和的方式)

💬 SeqList.c:

/* 尾删 */
void SeqListPopBack(SL* psl) {
	if (psl->size > 0) {
		// psl->array[psl->size - 1] = 0;
		psl->size--;	//实际有效个数-1
	}
}

方式二:使用断言,直接不允许你做类似这样的动作。你在调用这个接口之前你必须确定它是有数据的,如果没有数据就不能调了,调了就给你报错。

💬 SeqList.h:使用断言需引入 assert.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
···

💬 SeqList.c:

/* 尾删 */
void SeqListPopBack(SL* psl) {
	//if (psl->size > 0) {
	//	// psl->array[psl->size - 1] = 0;
	//	psl->size--;	//实际有效个数-1
	//}

	assert(psl->size > 0);
	psl->size--;
}

🔺 至于选择儒雅随和的方式还是暴力解决的方式,就看你自己喜好了。

0x06 头插(SeqListPushFront)和检查是否需要增容(SeqListCheckCapacity)

📚 顺序表要求数据是连续存储的,且必须是从头开始存储。所以,对于顺序表而言如果要实现头插,就需要把数据往后挪动。不能从前往后挪,如果从前往后挪就挪就会把后面的数据覆盖掉。

💬 SeqList.h:

void SeqListPushFront(SL* psl, SLDataType x);    //头插

💬 SeqList.c:

/* 头插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	// 挪动数据
	int end = psl->size - 1; // 因为指向的是数据的下标,所以要 -1
	while (end >= 0) { 
		psl->array[end + 1] = psl->array[end];
		end--; //让end指向下一个要移的数据
	}

	// 此时第一个位置已经被腾出来了,可以插入了
	psl->array[0] = x;
	psl->size++;
}

🔑 解读:首先创建一个 end 变量用来指向要移动的数据,因为指向的是数据的下标,所以是 size 要减 1 。随后进入 while 循环,如果 end >= 0 说明还没有移动完,就会进入循环。循环体内利用下标,进行向后移动操作,移动结束后再 end-- ,进行下一个数据的向后移动。挪动数据成功后,就可以插入了。此时顺序表第一个位置就被腾出来了,就可以在下标0位置插入欲插入的数据 x 了。最后记得 size++ 。

💬 Test.c:测试头插

我们之前用的 TestSeqList1 测试函数里东西太多了很乱,现在我们再创建一个 TestSeqList2 函数来继续测试。现在就能体现出不在 main 函数内直接测试而是通过函数来测试的好处了,我们可以选择性地测试一部分代码,互相之间不干扰。

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPrint(&sl); //打印

	SeqListDestory(&sl); //销毁
}

int main() {
	// TestSeqList1();
	TestSeqList2();

	return 0;
}

🚩 运行结果如下:(先插入了12345,又在12345前面头插了 -1、-2、-3)

❓ 我们是不是少了点啥?

💡 我们还需要检查是否需要扩容,和尾插一样。

📚 我们之前在完成 SeqListPushBack 尾插的时候就已经写好了,我们不妨把它抽取出来写成一个函数,方便以后多次调用:

💬 SeqList.c:检查是否需要增容函数

/* 检查是否需要扩容 */
void SeqListCheckCapacity(SL* psl) {
	// 首先判断有没有空间,如果没有空间或者空间不足,那么我们就扩容
	if (psl->size == psl->capacity) {
		// 如果容量是0(第一次)就赋4,如果不是0,就把容量翻一倍
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		// 这里使用realloc,因为如果原空间为空,就等于malloc。调整为 new_capacity个SLDataType大小的空间
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
		// 检测是否realloc成功
		if (tmp_array == NULL) {
			printf("realloc fail {扩容失败}\n");
			exit(-1);
		}
		// 更新它们的大小
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}
}

🔑 解读:我们把之前写在 SeqListPushBack 中的检查增容的代码直接复制粘贴过来就可以了,这样一来,我们需要检查是否需要增容时直接调用 SeqListCheckCapacity 函数就可以了!

💬 SeqList.c:更新下尾插函数

/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {
	//检查增容
	SeqListCheckCapacity(psl); 

	//插入
	psl->array[psl->size] = x;
	psl->size++;
}

💬 SeqList.c:更新下头插函数

/* 头插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	//检查增容
	SeqListCheckCapacity(psl); 

	// 挪动数据
	int end = psl->size - 1; // 因为指向的是数据的下标,所以要 -1
	while (end >= 0) { 
		psl->array[end + 1] = psl->array[end];
		end--; //让end指向下一个要移的数据
	}

	// 此时第一个位置已经被腾出来了,可以插入了
	psl->array[0] = x;
	psl->size++;
}

📌 如果不扩容,继续往下添加会造成的后果:【不感兴趣可跳过】

💬 Test.c:把尾插函数内的  SeqListCheckCapacity(psl);  注释掉

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPushFront(&sl, -4); //继续添加
	SeqListPrint(&sl); //打印

	SeqListDestory(&sl); //销毁
}

0x07 头删(SeqListPopFront)

📚 如果我们想把第一个数据删除,用尾删的方法直接把 size-- 显然是没有用的了。因为顺序表数据是从头开始存且有顺序的, size-- 无效的也只是最后一个数据。所以要想实现头删,我们不得不把数据先往前挪动,然后再 --size 。

💬 SeqList.h:

···
void SeqListPopFront(SL* psl);                  //头删

💬 SeqList.c:

/* 头删 */
void SeqListPopFront(SL* psl) {
	assert(psl->size > 0);

	//挪动数据
	int begin = 1;
	while (begin < psl->size) {
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}
    
    //int begin = 0;
	//while (begin < psl->size - 1) {
	//	psl->array[begin] = psl->array[begin + 1];
	//	begin++;
	//}

	psl->size--; //实际有效个数-1
}

🔑 解读:首先断言顺序表有数据,这个前面讲过了这里就不再赘述了。然后开始挪动数据,创建一个 begin 变量并赋个 1,然后进入循环。只要 size 大于 begin 就会进入循环。每次进入循环后将下标 begin -1 上的数据赋给下标 begin 上的数据,这样就达到了右向左移动的目的。最后 begin++ 移动下一个(如果满足条件的话)。移动完毕后,第一个数据就被第二个数据覆盖掉了,而第二个数据被第三个数据覆盖掉了……最后多出来的一块我们再用 size-- 解决掉,实际容量减1 。此时,就实现了头删。

💬 Test.c:测试代码

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPushFront(&sl, -4);
	SeqListPrint(&sl); //打印

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl); //打印

	SeqListDestory(&sl); //销毁
}

🚩 运行结果如下:

0x08 查找位置(SeqListFind)

📚 查找顺序表中某值的位置,如果找到了就返回该值的下标,没有找到我们就返回 -1 。

💬 SeqList.h:

···
int SeqListFind(SL* psl, SLDataType x);				   //查找

💬 SeqList.c:查找

/* 查找 */
int SeqListFind(SL* psl, SLDataType x) {
	int i = 0;
	for (i = 0; i < psl->size; i++) {
		if (psl->array[i] == x) {
			return i;
		}
	}
	// 没找到
	return -1;
}

🔑 解读:首先遍历整个顺序表,如果 psl->array[i] == x 就返回 i 接,没找到就返回 -1。这里我们用的方法就是简单粗暴的 O(N) Find 暴力求解,当然你还可以试着用二分查找做,排序一下,写一个二分查找的接口。

💬 Test.c:

void TestSeqList3() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	int ret = SeqListFind(&sl, 3);
	if (ret != -1)
		printf("找到了,下标为%d\n", ret);
	else
		printf("没找到!\n");


	SeqListDestory(&sl); //销毁
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	TestSeqList3();

	return 0;
}

🚩 运行结果如下

0x09 在指定的下标位置插入(SeqListInsert)

📚 顺序表要求数据从第一个开始放并且数据是连续存储的,所以我们就要注意下指定指定的下标位置 pos 的位置了!有些位置并不可以,所以我们需要进行一些检查。

💬 SeqList.h:

int SeqListInsert(SL* psl, int pos, SLDataType x);  //指定位置插入

💬 SeqList.c:

/* 指定位置插入 */
int SeqListInsert(SL* psl, int pos, SLDataType x) {
	//if (pos > psl->size || pos < 0) {
	//	printf("pos invalid {pos非法}\n");
	//	return;
	//}

	assert(pos >= 0 && pos <= psl->size);

	//检查增容
	SeqListCheckCapacity(psl);

	int end = psl->size - 1;
	while (end >= pos) {
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	 
	//插入
	psl->array[pos] = x;
	psl->size++;
}

🔑 解读:首先添加 pos 位置的限定条件,可以根据自己的喜好选择是儒雅随和的处理方式,还是暴力处理方式。这里我的选择是简单粗暴地使用断言解决,限定 pos>= 0 并且 pos <= psl->size 从而保证 pos 合法。然后,因为是插入所以免不了要检查增容,直接调用之前写好的检查增容的函数即可。检查完后就可以开始移动了,和头插差不多,我们创建一个变量 end 记录最后一个的下标(psl->size-1),并通过它来指向要移动的数据。最后进入 while 循环,以 end >= pos 作为条件。移动完后,x 的位置就腾出来了,再把 x 插入进去,最后再 size++,就完成了。

💬 Test.c:

void TestSeqList3() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListInsert(&sl, 2, 30);
	SeqListPrint(&sl); //打印

	int pos = SeqListFind(&sl, 4);
	if (pos != -1) {
		SeqListInsert(&sl, pos, 40);
	}
	SeqListPrint(&sl); //打印
	
	SeqListInsert(&sl, 0, -10);
	SeqListPrint(&sl); //打印

	SeqListInsert(&sl, 8, 80);
	SeqListPrint(&sl); //打印

	    
	SeqListDestory(&sl); //销毁
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	TestSeqList3();

	return 0;
}

🔑 解读:这里为了防止有问题,我们不妨多测测试。

🚩 运行结果如下:

⚡ 头插函数和尾插函数的修改:

我们都把功能这么强的 SeqListInsert 写出来了,我们之前写的头插和尾插接口函数是不是可以直接复用下 SeqListInsert ?

💬 SeqList.c:尾插复用 SeqListInsert

/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {
	SeqListInsert(psl, psl->size, x);
}

💬 SeqList.c:头插复用 SeqListInsert

/* 头插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	SeqListInsert(psl, 0, x);
}

💬 Test.c:测试下有没有问题,先尾插12345,再头插12345

void TestSeqList4() {
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //打印

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); //打印



还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!


王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。


对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!


【完整版领取方式在文末!!】


***93道网络安全面试题***


![](https://img-blog.csdnimg.cn/img_convert/6679c89ccd849f9504c48bb02882ef8d.png)








![](https://img-blog.csdnimg.cn/img_convert/07ce1a919614bde78921fb2f8ddf0c2f.png)





![](https://img-blog.csdnimg.cn/img_convert/44238619c3ba2d672b5b8dc4a529b01d.png)





内容实在太多,不一一截图了


### 黑客学习资源推荐


最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


😝朋友们如果有需要的话,可以联系领取~

#### 1️⃣零基础入门


##### ① 学习路线


对于从来没有接触过网络安全的同学,我们帮你准备了详细的**学习成长路线图**。可以说是**最科学最系统的学习路线**,大家跟着这个大的方向学习准没问题。


![image](https://img-blog.csdnimg.cn/img_convert/acb3c4714e29498573a58a3c79c775da.gif#pic_center)


##### ② 路线对应学习视频


同时每个成长路线对应的板块都有配套的视频提供:


![image-20231025112050764](https://img-blog.csdnimg.cn/874ad4fd3dbe4f6bb3bff17885655014.png#pic_center)


#### 2️⃣视频配套工具&国内外网安书籍、文档


##### ① 工具


![](https://img-blog.csdnimg.cn/img_convert/d3f08d9a26927e48b1332a38401b3369.png#pic_center)


##### ② 视频


![image1](https://img-blog.csdnimg.cn/img_convert/f18acc028dc224b7ace77f2e260ba222.png#pic_center)


##### ③ 书籍


![image2](https://img-blog.csdnimg.cn/img_convert/769b7e13b39771b3a6e4397753dab12e.png#pic_center)

资源较为敏感,未展示全面,需要的最下面获取

![在这里插入图片描述](https://img-blog.csdnimg.cn/e4f9ac066e8c485f8407a99619f9c5b5.png#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/111f5462e7df433b981dc2430bb9ad39.png#pic_center)


##### ② 简历模板


![在这里插入图片描述](https://img-blog.csdnimg.cn/504b8be96bfa4dfb8befc2af49aabfa2.png#pic_center)

 **因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆**




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值