二手手表网站网络爬虫需要自己做网站吗
2026/4/18 13:21:13 网站建设 项目流程
二手手表网站,网络爬虫需要自己做网站吗,宁波专业网站推广制作服务,优化大师电脑版官方免费下载#x1f3e0;个人主页#xff1a;黎雁 #x1f3ac;作者简介#xff1a;C/C/JAVA后端开发学习者 ❄️个人专栏#xff1a;C语言、数据结构#xff08;C语言#xff09;、EasyX、游戏、规划、程序人生 ✨ 从来绝巘须孤往#xff0c;万里同尘即玉京 文章目录【线性表系列…个人主页黎雁作者简介C/C/JAVA后端开发学习者❄️个人专栏C语言、数据结构C语言、EasyX、游戏、规划、程序人生✨ 从来绝巘须孤往万里同尘即玉京文章目录【线性表系列进阶篇】手搓单向链表从指针迷宫到代码实现一、核心概念回顾二、工程结构规划三、第一步定义链表节点与函数声明SList.h四、第二步实现链表核心操作函数SList.c1. 辅助函数创建新节点2. 打印链表3. 尾插操作4. 头插操作5. 尾删操作6. 头删操作7. 查找操作8. 指定位置前插入9. 指定位置删除五、第三步编写测试用例Test.c编译与运行指令Windows MinGW预期输出结果六、常见问题与避坑指南 七、进阶篇小结【线性表系列进阶篇】手搓单向链表从指针迷宫到代码实现你好欢迎来到线性表系列的进阶篇。在上一篇的入门篇中我们了解了链表诞生的背景和核心思想。今天我们将不再纸上谈兵而是亲自动手用C语言实现一个完整的单向不带头非循环链表。这不仅是对理论知识的巩固更是一次绝佳的指针实战演练。我们将从定义节点开始一步步实现头插、尾插、头删、尾删、查找、任意位置插入和删除等所有核心操作。准备好了吗系好安全带我们要进入指针的世界了一、核心概念回顾【线性表系列入门篇】从顺序表到链表解锁数据结构的进化密码在开始编码之前我们先来回顾一下单向不带头非循环链表的核心特征这是我们实现代码的理论基础单向链表每个节点只有一个指向下一个节点的指针 (next)只能从前往后遍历无法反向查找。不带头链表链表的第一个节点就存储有效数据我们用一个指针phead来指向它链表为空时phead NULL。非循环链表链表的最后一个节点的next指针指向NULLNULL是链表的结束标志。学习提示单向不带头非循环链表是链表学习的重中之重它的操作逻辑最能体现指针的精髓也是笔试面试的高频考点。二、工程结构规划在实际开发中一个清晰的工程结构能大大提升代码的可读性和可维护性。我们将采用头文件源文件测试文件的分离模式文件名称作用SList.h存放节点结构体定义、函数声明、必要的头文件包含SList.c存放链表所有操作函数的具体实现Test.c编写测试用例验证链表功能的正确性这种分离模式的优势接口与实现分离调用者只需关注SList.h中的函数声明便于多人协作开发避免函数重复定义提高代码复用性修改实现逻辑时无需改动测试代码三、第一步定义链表节点与函数声明SList.h链表的基本单元是节点Node。我们用结构体来定义节点同时在头文件中声明所有操作函数。#pragmaonce// 防止头文件被重复包含#includestdio.h#includestdlib.h#includeassert.h// 定义链表存储的数据类型方便后续修改typedefintSLTDataType;// 定义单向链表节点结构体typedefstructSListNode{SLTDataType data;// 节点存储的数据域structSListNode*next;// 节点的指针域指向下一个节点}SLTNode;// 函数声明// 辅助函数创建一个新节点SLTNode*BuySListNode(SLTDataType x);// 打印链表voidSListPrint(SLTNode*phead);// 尾插在链表尾部插入元素voidSListPushBack(SLTNode**pphead,SLTDataType x);// 头插在链表头部插入元素voidSListPushFront(SLTNode**pphead,SLTDataType x);// 尾删删除链表尾部元素voidSListPopBack(SLTNode**pphead);// 头删删除链表头部元素voidSListPopFront(SLTNode**pphead);// 查找查找值为x的节点找到返回节点地址否则返回NULLSLTNode*SListFind(SLTNode*phead,SLTDataType x);// 在pos节点之前插入值为x的节点voidSListInsert(SLTNode**pphead,SLTNode*pos,SLTDataType x);// 删除pos位置的节点voidSListErase(SLTNode**pphead,SLTNode*pos);代码解析typedef int SLTDataType使用类型重定义后续如果要存储double类型只需修改这一行代码。struct SListNode包含data数据域和next指针域两个成员指针域的类型必须是struct SListNode*才能指向同类型节点。#pragma onceC语言的预处理指令避免头文件被多次包含导致的重复定义错误。assert.h提供断言函数assert用于在调试阶段检查非法参数比如传入的pos节点不能为NULL。四、第二步实现链表核心操作函数SList.c所有函数的具体实现都放在SList.c中我们逐个分析每个操作的实现逻辑、关键点和注意事项。1. 辅助函数创建新节点#includeSList.h// 辅助函数创建一个新节点并初始化SLTNode*BuySListNode(SLTDataType x){// 动态分配内存SLTNode*newnode(SLTNode*)malloc(sizeof(SLTNode));// 检查内存分配是否成功if(newnodeNULL){perror(malloc fail);// 打印错误原因exit(1);// 异常退出程序}// 初始化节点newnode-datax;newnode-nextNULL;// 新节点默认指向NULL避免野指针returnnewnode;}关键点动态内存分配后必须检查是否成功否则会导致程序崩溃新节点的next指针必须初始化为NULL防止出现野指针2. 打印链表// 打印链表遍历链表打印每个节点的数据voidSListPrint(SLTNode*phead){SLTNode*curphead;// cur指针用于遍历链表while(cur!NULL)// 遍历到NULL时停止{printf(%d-,cur-data);curcur-next;// cur指针后移}printf(NULL\n);// 打印链表结束标志}关键点使用cur指针遍历避免修改原链表的头指针phead打印格式%d-清晰展示链表结构结尾的NULL明确链表的结束位置3. 尾插操作// 尾插在链表尾部插入元素voidSListPushBack(SLTNode**pphead,SLTDataType x){SLTNode*newnodeBuySListNode(x);// 情况1链表为空if(*ppheadNULL){*ppheadnewnode;}// 情况2链表不为空else{// 找到尾节点next为NULL的节点SLTNode*tail*pphead;while(tail-next!NULL){tailtail-next;}// 尾节点的next指向新节点tail-nextnewnode;}}核心思考为什么参数是二级指针SLTNode** pphead当链表为空时需要修改main函数中头指针plist的值让它指向新节点在C语言中要修改一个变量的值必须传递它的地址plist是一级指针它的地址就是二级指针pphead4. 头插操作// 头插在链表头部插入元素voidSListPushFront(SLTNode**pphead,SLTDataType x){SLTNode*newnodeBuySListNode(x);// 新节点的next指向原来的头节点newnode-next*pphead;// 头指针指向新节点新节点成为新的头*ppheadnewnode;}关键点头插操作无需遍历链表时间复杂度为O(1)效率极高操作顺序不能颠倒必须先让新节点指向原头节点再更新头指针5. 尾删操作// 尾删删除链表尾部元素voidSListPopBack(SLTNode**pphead){// 情况1链表为空直接返回if(*ppheadNULL){return;}// 情况2链表只有一个节点elseif((*pphead)-nextNULL){free(*pphead);// 释放节点内存*ppheadNULL;// 头指针置空避免野指针}// 情况3链表有多个节点else{// 找到倒数第二个节点SLTNode*prev*pphead;while(prev-next-next!NULL){prevprev-next;}// 释放尾节点free(prev-next);// 倒数第二个节点的next置空成为新的尾节点prev-nextNULL;}}关键点必须处理三种边界情况空链表、单节点链表、多节点链表释放内存后必须将指针置空否则会出现野指针问题单节点链表删除后头指针必须置空6. 头删操作// 头删删除链表头部元素voidSListPopFront(SLTNode**pphead){// 链表为空直接返回if(*ppheadNULL){return;}// 保存下一个节点的地址防止链表断裂SLTNode*next(*pphead)-next;// 释放头节点free(*pphead);// 头指针指向原头节点的下一个节点*ppheadnext;}关键点删除前必须保存原头节点的下一个节点地址否则会丢失整个链表时间复杂度为O(1)效率高于尾删7. 查找操作// 查找查找值为x的节点SLTNode*SListFind(SLTNode*phead,SLTDataType x){SLTNode*curphead;while(cur!NULL){if(cur-datax){returncur;// 找到返回节点地址}curcur-next;}returnNULL;// 未找到返回NULL}关键点查找成功返回节点地址方便后续在该节点附近进行插入/删除操作查找失败返回NULL调用者可以根据返回值判断是否找到目标节点8. 指定位置前插入// 在pos节点之前插入值为x的节点voidSListInsert(SLTNode**pphead,SLTNode*pos,SLTDataType x){assert(pos!NULL);// 断言pos不为空非法参数直接终止程序// 情况1pos是头节点等价于头插if(pos*pphead){SListPushFront(pphead,x);}// 情况2pos在链表中间或尾部else{// 找到pos的前一个节点prevSLTNode*prev*pphead;while(prev!NULLprev-next!pos){prevprev-next;}assert(prev!NULL);// 确保pos在链表中// 创建新节点并插入SLTNode*newnodeBuySListNode(x);prev-nextnewnode;newnode-nextpos;}}关键点使用assert检查pos的合法性调试阶段快速发现问题当pos是头节点时直接复用头插函数减少代码冗余必须找到pos的前驱节点prev才能完成插入操作9. 指定位置删除// 删除pos位置的节点voidSListErase(SLTNode**pphead,SLTNode*pos){assert(pos!NULL);// 情况1pos是头节点等价于头删if(pos*pphead){SListPopFront(pphead);}// 情况2pos在链表中间或尾部else{// 找到pos的前驱节点prevSLTNode*prev*pphead;while(prev!NULLprev-next!pos){prevprev-next;}assert(prev!NULL);// 前驱节点的next指向pos的下一个节点prev-nextpos-next;// 释放pos节点的内存free(pos);}}关键点删除操作的核心是让前驱节点跳过pos节点指向pos的下一个节点释放pos节点的内存避免内存泄漏同样需要处理pos是头节点的特殊情况五、第三步编写测试用例Test.c测试是验证代码正确性的关键步骤。我们编写一个完整的测试函数依次调用所有链表操作函数观察输出结果是否符合预期。#includeSList.hvoidTestSList1(){SLTNode*plistNULL;// 链表头指针初始化为NULL表示空链表printf( 尾插操作 \n);SListPushBack(plist,1);SListPushBack(plist,2);SListPushBack(plist,3);SListPrint(plist);// 预期输出1-2-3-NULLprintf( 头插操作 \n);SListPushFront(plist,0);SListPrint(plist);// 预期输出0-1-2-3-NULLprintf( 查找操作 \n);SLTNode*posSListFind(plist,2);if(pos){printf(找到节点值为%d\n,pos-data);// 预期输出找到节点值为2}printf( 指定位置前插入 \n);SListInsert(plist,pos,20);SListPrint(plist);// 预期输出0-1-20-2-3-NULLprintf( 指定位置删除 \n);posSListFind(plist,1);SListErase(plist,pos);SListPrint(plist);// 预期输出0-20-2-3-NULLprintf( 尾删操作 \n);SListPopBack(plist);SListPrint(plist);// 预期输出0-20-2-NULLprintf( 头删操作 \n);SListPopFront(plist);SListPrint(plist);// 预期输出20-2-NULL}intmain(){TestSList1();return0;}编译与运行指令Windows MinGW在命令行中输入以下指令编译并运行程序gcc SList.c Test.c-oSListTest SListTest.exe预期输出结果 尾插操作 1-2-3-NULL 头插操作 0-1-2-3-NULL 查找操作 找到节点值为2 指定位置前插入 0-1-20-2-3-NULL 指定位置删除 0-20-2-3-NULL 尾删操作 0-20-2-NULL 头删操作 20-2-NULL六、常见问题与避坑指南 在实现单向链表的过程中新手很容易遇到以下问题我们提前总结并给出解决方案常见问题产生原因解决方案野指针问题释放内存后未将指针置空未初始化指针就使用1. 释放内存后立即将指针置空2. 指针声明时初始化为NULL链表断裂头插/头删时未保存原节点的指针尾删时未找到前驱节点1. 头删前保存原头节点的下一个节点2. 尾删时遍历到倒数第二个节点二级指针使用错误不理解为什么需要二级指针传递一级指针导致修改无效牢记要修改一级指针的值必须传递它的地址二级指针内存泄漏只删除节点但不释放内存忘记释放链表所有节点1. 删除节点时调用free函数2. 程序结束前遍历链表释放所有节点边界情况处理不全忽略空链表、单节点链表的情况编写代码时先处理空链表、单节点链表的特殊情况再处理通用情况七、进阶篇小结恭喜你在这篇进阶篇中你已经成功地掌握了链表的工程化实现方法采用头文件源文件测试文件的分离模式规范代码结构。理解了单向链表的核心操作逻辑深入掌握头插、尾插、头删、尾删、查找、指定位置插入/删除的实现细节。攻克了二级指针的使用难点明白为什么需要二级指针以及如何正确使用二级指针修改头指针。学会了编写测试用例验证代码通过测试用例快速发现并修复代码中的问题。规避了链表实现的常见陷阱掌握野指针、链表断裂、内存泄漏等问题的解决方案。通过这次实战你对指针的理解和C语言的编程能力都得到了一次质的飞跃。在下一篇高阶篇中我们将挑战结构最复杂但操作最简单的双向带头循环链表并通过几道经典的OJ题目如反转链表、合并有序链表、判断环形链表等来检验和升华我们所学的知识。敬请期待点赞收藏关注后续内容不迷路~ 你的支持是我创作的最大动力

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询