【内卷数据结构】顺序表超详细解析 从零开始步步解读 画图理解+调试分析 菜单制作_内卷信息架构

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注网络安全)
img

正文

	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 的效果:


![](https://img-blog.csdnimg.cn/d0d41a05e6dc4af1a6b993eb13d3e670.png)(摘自MSDN)


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


![](https://img-blog.csdnimg.cn/0da68a41469c406f9b52c2922db53aaf.png)



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


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


![](https://img-blog.csdnimg.cn/1102872727b846a199b590aaeb25014d.png)


如果没有问题,就可以进行赋值操作了。将 new\_capacity 交给 psl->**capacity**,将 tmp\_array 交给 psl->**array**:


![](https://img-blog.csdnimg.cn/fb019aa4f8294b7fb4e9e276f08c88f9.png)


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


![](https://img-blog.csdnimg.cn/b66ac4a72dd242b58501a0b1af6564e1.png)



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


![](https://img-blog.csdnimg.cn/5e58838c8fe1416cab16d9f6c10e6d0f.png)



💬 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 型,所以我们传入数字进行测试。


![](https://img-blog.csdnimg.cn/7ddea25334964aa183cdbd8a4b1556dd.png)


🐞 调试:


![](https://img-blog.csdnimg.cn/2613629725a9418496415a3569da083e.png)


  至此,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);

}



> 
> 🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/6b22fbbc7eb9448e8bd233dd300a4f8d.png)



#### 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** 之后那块开辟的内存空间已经不在了,为了防止野指针,我们还需要手动把它置为空指针。最后再把 **capacity** 和 **size** 全部设为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-- 即可实现。这就是尾删:


![](https://img-blog.csdnimg.cn/fd55e34e7e2942ff810f6f0168719038.png)



💬 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); //销毁

}


🚩 运行结果:


![](https://img-blog.csdnimg.cn/3fcc9417213c4450b4a1fd54449c5d79.png)


![](https://img-blog.csdnimg.cn/f222d62ae39c4c9eb243b82094194e65.png)


🤔 不知道你有没有发现,其实我们只需要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); //销毁

}



> 
>  🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/d1f8fba69a1d4ae289022ef9a1726b89.png)


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


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


![](https://img-blog.csdnimg.cn/ac7b328ab5074b408545b4c5572576fc.png)



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


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


💬 SeqList.c:



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


![](https://img-blog.csdnimg.cn/ab2040b780dd43b48a9e379a8b540d62.png)



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


💬 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--;

}


![](https://img-blog.csdnimg.cn/82399615f9a747eaa4eb520ccacec95a.png)


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




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


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


![](https://img-blog.csdnimg.cn/c1b229682c99490891b611e1361cb1b8.png)



💬 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)
> 
> 
> 


![](https://img-blog.csdnimg.cn/d60456cfff2c47d0b555d00e5407b6b5.png)


❓ 我们是不是少了点啥?


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



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


![](https://img-blog.csdnimg.cn/e08cb350f9334097a8bfab20f602fd11.png)


 💬 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); //销毁

}


![](https://img-blog.csdnimg.cn/863a934e4b1941328a7f7dc63f9a2868.png)




####  0x07 头删(SeqListPopFront)


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


![](https://img-blog.csdnimg.cn/ab2ecea72ff1423cb3cec879f20e1b8f.png)



💬 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 。此时,就实现了头删。


![](https://img-blog.csdnimg.cn/0358f93fe3b54bfca48f814c7fc98749.png)



💬 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); //销毁

}



> 
> 🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/2d29360908184838883d652a15a13967.png)




#### 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;

}



> 
> 🚩 运行结果如下
> 
> 
> 


![](https://img-blog.csdnimg.cn/917a085ca45b44038d5e843520f1b66b.png)




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


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


![](https://img-blog.csdnimg.cn/a9162db1fada400fa990f54843408927.png)


💬 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;

}


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



> 
> 🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/eb9ed68754504e3e8b295f2bc92d7211.png)



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


我们都把功能这么强的 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); //打印


SeqListDestory(&sl); //销毁

}

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

return 0;

}



> 
> 🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/cd9e69fd25144f1f92371677c9cb7d87.png)



🔺 我们把 Insert  写好了,就可以直接复用到前面的写的尾插和头插了。其实只要写好 SeqListInsert 任意位置插入,头尾就都能控制了。




#### 0x10 删除指定位置的数据(SeqListErase)


📚 删除指定位置的数据,我们仍然要限制 **pos** 的位置。限制条件部分和 SeqListInsert 不同的是,因为 psl->**size** 这个位置没有效数据,所以删除的位置不能是 psl->**size**!



💬 SeqList.h:



···
int SeqListEarse(SL* psl, int pos); //指定位置删除



💬 SeqList.c:



/* 指定位置删除 /
int SeqListEarse(SL
psl, int pos) {
assert(pos >= 0 && pos < psl->size);

int begin = pos + 1;
while (begin < psl->size) {
	psl->array[begin - 1] = psl->array[begin];
	begin++;
}

psl->size--;

}



💬 Test.c:测试下代码



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); //打印

SeqListEarse(&sl, 2);
SeqListPrint(&sl); //打印


SeqListDestory(&sl); //销毁

}

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

return 0;

}



> 
> 🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/44e640d675784a028d9d5e81a90ed895.png)



⚡ 对应的,和上面一样。头删尾删都可以复用 SeqListEarse 了


💬 SeqList.c:头删复用 SeqListEarse



/* 头删 /
void SeqListPopFront(SL
psl) {
SeqListEarse(psl, 0);
}


💬 SeqList.c:尾删复用 SeqListEarse



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

assert(psl->size > 0);
psl->size--;
*/

SeqListEarse(psl, psl->size - 1);

}


💬 Test.c:测试下代码



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); //打印

SeqListEarse(&sl, 2);
SeqListPrint(&sl); //打印

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

SeqListDestory(&sl); //销毁

}

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

return 0;

}



> 
> 🚩 运行结果如下:
> 
> 
> 


![](https://img-blog.csdnimg.cn/6be97be4925f45a7baa4935e6568617f.png)




### 四、菜单的制作


#### 0x00 前言


📚 数据结构的本质意义是在项目中存储数据,所以做菜单的意义并不是很大。但是鉴于一些教材上是写菜单的,有些学校也是特别喜欢教菜单,用于展示所做的成果。其实菜单的制作都差不多,在我以前写的文章中的猜数字、三子棋、扫雷等菜单实现其实都是差不多的,所以在这里我简单粗糙地实现一下。


📜 不建议一上来就写点菜单!写菜单不容易调试,最好先写单元测试。等你把程序函数接口测试得没问题了,再写菜单。



#### 0x01 菜单代码演示



void menu() {
printf(“\n\n”);
printf(“#######################\n”);
printf(“# 1. 头部位置插入 #\n”);
printf(“# 2. 头部位置删除 #\n”);
printf(“# 3. 尾部位置插入 #\n”);
printf(“# 4. 尾部位置删除 #\n”);
printf(“# 5. 顺序表打印 #\n”);
printf(“# 6. 查找数据位置 #\n”);
printf(“# 7. 指定位置插入 #\n”);
printf(“# 8. 指定位置删除 #\n”);
printf(“# 0. 退出程序 #\n”);
printf(“#######################\n”);
}

enum SQ {
CLOSE, //0 把CLOSE放在第一个,正好顺势推下去
PUSH_FRONT, //1
POP_FRONT, //2
PUSH_BACK, //3
POP_BACK, //4
PRINT, //5
SEARCH, //6
INSERT, //7
EARSE, //8
};

void MenuTest() {
SL sl;
SeqListInit(&sl);
int input = 0;
int x = 0;
int pos = 0;

do {
	menu();
	printf("[信息] 请选择操作: ");
	scanf("%d", &input);

	switch (input) {
		case CLOSE: //退出(0)
			printf("[信息] 已退出!\n");
			break;
		case PUSH_FRONT: //头插(1)
			printf("【请输入你要头插的数据,以-404结束】\n>>> ");
			scanf("%d", &x);
			while (x != -404) {
				SeqListPushFront(&sl, x);
				scanf("%d", &x);
			}
			break;
		case POP_FRONT: //头删(2)
			SeqListPopFront(&sl);
			printf("【已删除】\n");
			break;

		case PUSH_BACK: //尾插(3)
			printf("【请输入你要尾插的数据,以-404结束】\n>>> ");
			scanf("%d", &x);
			while (x != -404) {
				SeqListPushBack(&sl, x);
				scanf("%d", &x);
			}
			break;
		case POP_BACK: //尾删(4)
			SeqListPopBack(&sl);
			printf("【已删除】\n");
			break;
		case PRINT: //打印(5)
			printf("* 顺序表:");
			SeqListPrint(&sl);
			break;
		case SEARCH: //查找(6)
			printf("【请输入查找的数据】>> ");
			scanf("%d", &x);
			int ret = SeqListFind(&sl, x);
			if (ret != -1) {
				printf("找到了,下标为 %d\n", ret);
			} else {
				printf("找不到\n");
			}
			break;
		case INSERT: //指定位置插入 (7)
			printf("【请输入你要插入的位置】>> ");
			scanf("%d", &pos);
			printf("【请输入你要插入的数据,以-404结束】\n>>> ");
			scanf("%d", &x);
			while (x != -404) {
				SeqListInsert(&sl, pos, x);

如何自学黑客&网络安全

黑客零基础入门学习路线&规划

初级黑客
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(一周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(一周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(一周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k

到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?

如果你想要入坑黑客&网络安全,笔者给大家准备了一份:282G全网最全的网络安全资料包评论区留言即可领取!

7、脚本编程(初级/中级/高级)
在网络安全领域。是否具备编程能力是“脚本小子”和真正黑客的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力.

如果你零基础入门,笔者建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习;搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP, IDE强烈推荐Sublime;·Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;·用Python编写漏洞的exp,然后写一个简单的网络爬虫;·PHP基本语法学习并书写一个简单的博客系统;熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选);·了解Bootstrap的布局或者CSS。

8、超级黑客
这部分内容对零基础的同学来说还比较遥远,就不展开细说了,附上学习路线。
img

网络安全工程师企业级学习路线

img
如图片过大被平台压缩导致看不清的话,评论区点赞和评论区留言获取吧。我都会回复的

视频配套资料&国内外网安书籍、文档&工具

当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

img
一些笔者自己买的、其他平台白嫖不到的视频教程。
img

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
img

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

在网络安全领域。是否具备编程能力是“脚本小子”和真正黑客的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力.

如果你零基础入门,笔者建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习;搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP, IDE强烈推荐Sublime;·Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;·用Python编写漏洞的exp,然后写一个简单的网络爬虫;·PHP基本语法学习并书写一个简单的博客系统;熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选);·了解Bootstrap的布局或者CSS。

8、超级黑客
这部分内容对零基础的同学来说还比较遥远,就不展开细说了,附上学习路线。
img

网络安全工程师企业级学习路线

img
如图片过大被平台压缩导致看不清的话,评论区点赞和评论区留言获取吧。我都会回复的

视频配套资料&国内外网安书籍、文档&工具

当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

img
一些笔者自己买的、其他平台白嫖不到的视频教程。
img

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
[外链图片转存中…(img-9ZgAzeen-1713465432392)]

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

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值