动态顺序表 单链表 通讯录管理系统C 语言数据结构入门项目线性表全实现本仓库为 C 语言数据结构「线性存储结构」专项学习的完整产出包含从基础概念梳理、接口代码实现、项目落地开发到问题调试优化的全流程记录。 仓库内附完整可运行源码、编译好的可执行文件以及完整的学习交流记录适合刚完成 C 语言基础学习、初次接触数据结构的同学参考学习。重要提示1.本文章主要由三部分组成小标题中已显示目录也可快速定位2.了解详细学习过程请到数据结构专题https://blog.csdn.net/luminousone/category_13167841.html?fromshareblogcolumnsharetypeblogcolumnsharerId13167841sharereferPCsharesourceLuminousonesharefromfrom_link3.所有配套代码请访问Luminous/Luminousbegin一、项目学习目标本次专项学习围绕数据结构中的线性存储结构展开在 C 语言语法基础上系统掌握顺序存储与链式存储的原理、实现与落地应用同时练习借助工具高效自主学习的方法。核心目标包括理解线性表的基本概念与逻辑特点掌握动态顺序表的定义、初始化、增删改查、销毁等全套操作掌握单链表的结点定义、创建、插入、删除、查找、遍历、销毁等全套操作能够将数据结构落地到实际项目中完成小型管理系统的开发掌握代码调试、问题定位的基本方法提升工程化代码能力学会梳理完整的学习过程形成可复用的学习记录。核心学习内容本次学习覆盖线性表两大存储结构的全部核心知识点线性表基础线性表定义、线性结构特点、逻辑结构、基本操作、顺序存储与链式存储的核心区别顺序存储动态顺序表存储特点、结构体设计、初始化、扩容、插入 / 删除 / 查找 / 遍历、优缺点分析链式存储单链表存储思想、结点结构、头指针与头结点、头插 / 尾插、插入 / 删除 / 查找 / 遍历、优缺点分析第一部分整体学习总结通过本次 C 语言专项学习我围绕数据结构中的线性存储结构进行了系统学习与代码实践重点掌握了线性表、顺序表和单链表的基本概念、存储特点及常用操作。学习初期我先理解了线性表的逻辑结构明确了顺序存储和链式存储的区别随后完成了动态顺序表的初始化、扩容、插入、删除、查找、修改和销毁等全套接口。在顺序表学习的基础上我完成了基于动态顺序表的 C 语言通讯录项目。通讯录使用结构体保存联系人信息包括姓名、性别、年龄、电话和住址等内容并通过顺序表统一管理联系人。项目实现了添加、删除、查找、修改、显示、保存和加载等功能让顺序表的相关操作真正落地到实际项目中。在调试过程中我遇到了结构体打印格式不匹配、姓名查找失败、输入异常导致菜单死循环、scanf 和 fgets 混用造成输入错乱、修改联系人信息保存异常等问题。通过逐行分析代码逻辑我加入了清空输入缓冲区、去除字符串换行符、判断输入返回值、优化修改函数等处理方式使程序运行更加稳定。后续我继续学习了单链表理解了结点、头指针和 next 指针的作用并练习了头插、尾插、查找、删除、遍历和销毁等操作。通过对比总结我认识到顺序表适合下标访问方便、数据较稳定的场景而链表更适合频繁插入和删除的场景。通过本次学习我巩固了结构体、指针、数组、函数、动态内存分配、字符串处理和文件操作等 C 语言核心知识也提高了代码调试和问题分析能力。后续我会继续加强链表和指针相关练习进一步提升程序设计能力。二、完整学习交流记录第二部分按学习进度整理以下为本次学习过程中借助 AI 工具辅助理解概念、排查代码问题、梳理知识点的完整记录按学习阶段完整保留还原从入门到踩坑再到解决的全流程。记录 1认识线性表与整体学习方向提问 本次 C 语言线性表学习应该先理解哪些核心内容线性表、顺序表、链表之间是什么关系AI 回答整理 线性表是一种最基本的数据结构它的特点是数据元素之间存在一对一的线性关系。除了第一个元素没有直接前驱、最后一个元素没有直接后继外其余元素都有且只有一个直接前驱和一个直接后继。顺序表和链表都属于线性表它们的逻辑结构相同都是线性关系但存储结构不同。顺序表使用连续空间存储数据链表使用指针把不同位置的结点连接起来。学习补充 我先从线性表的概念入手理解线性表的逻辑结构再继续学习顺序表和链表两种存储方式。顺序表对应顺序存储链表对应链式存储两者都是线性表的具体实现。个人理解与处理 通过这次交流我明确了本次学习的核心内容是线性表。顺序表和链表都是线性表的实现方式区别主要体现在存储结构上。后续学习可以先从顺序表入手完成基础接口再把顺序表应用到通讯录项目中最后再学习单链表并思考如何优化通讯录底层结构。记录 2理解顺序表的基本思想提问 什么是顺序表它和普通数组有什么区别为什么做通讯录项目之前要先学顺序表AI 回答整理 顺序表可以理解为用数组实现的线性表特点是数据在内存中连续存放可以通过下标快速访问元素。普通数组大小固定而动态顺序表可以通过动态内存申请和扩容来改变容量。顺序表通常需要记录三个信息数据存放的位置、当前有效数据个数、当前容量。通讯录中每个联系人可以看作一个数据元素多个联系人组成一个线性表所以可以用顺序表作为通讯录的底层存储结构。学习补充 我把动态顺序表理解为 “动态数组 size capacity” 的封装。size 表示当前有效数据个数capacity 表示当前容量数组指针负责指向动态申请的空间。个人理解与处理 我先整理了线性表和顺序表的基础概念明确顺序表的核心不是简单数组而是对数组进行管理和封装。这样后面写通讯录时联系人信息就可以作为顺序表中的元素进行统一管理。记录 3设计动态顺序表结构体提问 如果用 C 语言实现动态顺序表结构体应该怎么写size 和 capacity 分别代表什么AI 回答整理 动态顺序表一般可以定义一个结构体里面包含数据指针、有效元素个数 size 和容量 capacity。数据指针用于指向动态申请的空间size 表示当前顺序表中已经存放了多少个有效数据capacity 表示当前申请的空间最多能存放多少个数据。插入数据时要看 size 是否等于 capacity如果相等就需要扩容。遍历和删除时主要依赖 size不能把 capacity 当作有效数据个数。学习补充 顺序表结构体主要包含三个成员数据指针、有效数据个数和容量。后续初始化、扩容、插入、删除、查找和销毁等操作都围绕这三个成员展开。个人理解与处理 我根据 AI 解释写出了动态顺序表结构体并理解了 size 和 capacity 的区别。size 控制有效数据范围capacity 控制当前空间大小。如果混淆这两个变量就容易出现访问无效空间、越界访问或扩容错误等问题。记录 4实现顺序表初始化、扩容和销毁提问 顺序表初始化、扩容和销毁应该怎么写动态内存管理有什么要注意的AI 回答整理 初始化时需要把数据指针置为空把 size 和 capacity 置为 0。扩容时要先判断当前容量是否已满如果 size 等于 capacity就申请更大的空间。第一次扩容可以申请 4 个元素空间之后可以按照 2 倍容量增长。使用 realloc 时最好用临时指针接收返回值判断申请成功后再赋给原指针避免扩容失败导致原空间丢失。销毁时需要释放动态申请的空间并将指针置为空size 和 capacity 也要清零。学习补充 我实现了初始化、扩容和销毁函数。初始化让顺序表从空表状态开始扩容用于解决容量不足问题销毁函数用于释放动态申请的空间。个人理解与处理 我在顺序表代码中实现了 SLInit、SLExp 和 Destroy 等函数并注意了 assert 断言和 realloc 失败判断。通过这一步我理解了动态顺序表比普通数组灵活的原因也进一步熟悉了 malloc、realloc、free 等动态内存操作。记录 5实现顺序表基础增删操作提问 顺序表的尾插、头插、尾删、头删应该怎么实现元素移动时有什么注意事项AI 回答整理 尾插最简单只需要判断是否需要扩容然后把新元素放到下标 size 的位置再让 size 加 1。头插需要从后向前移动数据为第一个位置腾出空间再把新元素放到下标 0 处。尾删只需要判断顺序表是否为空然后 size 减 1。头删需要从前向后移动数据用后一个元素覆盖前一个元素最后 size 减 1。插入和删除时都要注意顺序表是否为空、位置是否合法以及移动方向。学习补充 我完成了尾插、头插、尾删和头删等基础接口。尾部操作主要改变 size头部操作需要移动数组元素。个人理解与处理 我完成了 PushBack、PushFront、SLPopBack、SLPopFront 等接口。通过调试我发现头插必须从后往前移动否则前面的数据会覆盖后面的数据头删则需要从前往后覆盖。这个过程让我加深了对数组元素移动的理解。记录 6实现顺序表指定位置插入、删除、查找和修改提问 顺序表进阶接口怎么写指定位置插入和指定位置删除的边界有什么区别AI 回答整理 指定位置插入时合法范围是 0 到 size 之间包括 size因为 pos 等于 size 时相当于尾插。插入时需要从最后一个元素开始向后移动直到 pos 位置腾出来。指定位置删除时合法范围是 0 到 size-1因为删除只能删除已经存在的元素。查找可以通过遍历顺序表实现找到返回下标找不到返回 - 1。修改指定位置元素时需要先判断下标是否合法然后直接修改对应位置的数据。学习补充 我补充了指定位置插入、指定位置删除、按值查找和按位置修改等接口使顺序表的增删改查功能更加完整。个人理解与处理 我在顺序表中补充了 PushSpecify、SLDeleteSpecify、FindSpecify 和 Modifyspecified 等接口。通过这个过程我进一步理解了顺序表的增删改查也认识到边界判断非常重要。插入可以在末尾之后插入但删除不能删除不存在的位置这一点在写代码时很容易出错。记录 7从顺序表过渡到通讯录项目提问 我已经完成了动态顺序表想用它做一个 C 语言通讯录应该怎么设计AI 回答整理 通讯录可以看作顺序表的实际应用。每个联系人可以定义为一个结构体包含姓名、性别、年龄、电话、地址等信息。多个联系人可以存储在动态顺序表中。顺序表负责底层存储通讯录函数负责具体业务功能例如添加联系人、删除联系人、查找联系人、修改联系人、显示联系人、保存联系人等。可以把联系人结构体作为顺序表的数据类型让顺序表管理联系人数组。学习补充 我把联系人设计成结构体包含姓名、性别、年龄、电话、住址等字段。然后把顺序表的数据类型从普通数据改为联系人结构体使顺序表可以存储多个联系人。个人理解与处理 我把原来顺序表中存储普通数据的思路改成存储联系人结构体并开始编写通讯录项目。这个过程让我认识到数据结构不是孤立的理论顺序表可以直接作为小型管理系统的底层结构。记录 8通讯录多文件结构设计提问 通讯录项目分成多个文件写头文件和源文件之间应该怎么组织为什么容易出现头文件包含混乱AI 回答整理 多文件项目中要注意文件职责清晰。顺序表相关声明和实现可以放在 SeqList.h 和 SeqList.c 中通讯录相关声明和实现可以放在 Contact.h 和 Contact.c 中主函数和菜单可以放在 test.c 或 main.c 中。头文件要用 #pragma once 或者 #ifndef 防止重复包含。顺序表和通讯录之间如果互相包含容易出现循环依赖。可以让通讯录使用顺序表结构但尽量避免两个头文件互相依赖。学习补充 我按照五个文件组织代码分别负责顺序表声明、顺序表实现、通讯录声明、通讯录实现和主函数测试。这样比所有代码写在一个文件里更清晰。个人理解与处理 我按照五个文件的结构整理通讯录项目包括 SeqList.h、SeqList.c、Contact.h、Contact.c 和 test.c。通过 AI 提示我注意到头文件要尽量减少互相包含并让各个文件职责更清晰。这样后续调试时也更容易定位问题。记录 9通讯录基础功能实现提问 通讯录基础功能应该先实现哪些添加、删除、查找、修改、展示之间应该怎么联系AI 回答整理 基础版通讯录可以先实现菜单循环再实现添加联系人、删除联系人、显示联系人、查找联系人、修改联系人和退出保存等功能。添加联系人本质上是向顺序表尾插一个联系人结构体删除联系人需要先查找联系人位置再调用顺序表指定位置删除修改联系人需要先查找联系人再对结构体字段进行修改显示联系人就是遍历顺序表把每个联系人信息打印出来。学习补充 我先实现了添加、删除、展示、查找、修改和退出系统等基础功能。其中添加联系人对应顺序表尾插删除联系人对应顺序表指定位置删除展示联系人对应顺序表遍历。个人理解与处理 我完成了通讯录的基础功能把顺序表的接口用于联系人管理。添加联系人时使用 PushBack删除联系人时使用 SLDeleteSpecify展示联系人时遍历 Con-a 数组。这个阶段项目已经可以运行但输入、查找和修改体验还比较粗糙。记录 10查找功能出错定位顺序表打印格式和类型不匹配提问 我把顺序表改成存储联系人结构体后查找时出现崩溃、乱码或打印错误应该怎么排查AI 回答整理 需要先检查顺序表的数据类型和打印格式是否匹配。如果原来的顺序表是存整数打印格式可能定义为 % d但现在顺序表中存的是联系人结构体不能再用 % d 打印结构体。通讯录应该单独写展示函数按照联系人字段分别打印姓名、性别、年龄、电话和地址而不是继续使用原来顺序表的通用打印格式。查找联系人时也应使用 strcmp 比较字符串字段而不能直接用普通整数比较方式。出错代码与修改方向#define SLTYPE_PRINT_FMT %d这个格式适合打印整数但通讯录中存储的是联系人结构体因此不能继续按整数格式打印。通讯录中应当单独按字段输出联系人信息例如printf(%-10s %-10s %-10s %-15s %-20s\n, Con-a[index].name, Con-a[index].Gender, Con-a[index].age, Con-a[index].Tel, Con-a[index].Address);个人理解与处理 我检查后发现SeqList.h 中原来有类似 SLTYPE_PRINT_FMT “% d” 的设计但通讯录实际存储的是结构体。于是我把通讯录打印逻辑独立出来在 ShoCon 和 FinCon 中分别打印联系人字段避免用整数格式输出结构体。这次问题让我认识到顺序表可以写成通用结构但当数据类型改变后打印、查找和比较方式也必须同步调整。记录 11查找菜单输入逻辑问题提问 我的查找菜单提示 “默认按姓名查找输入 1 切换方式”但是我直接输入姓名时程序提示输入错误这是为什么AI 回答整理 这是因为代码中使用 scanf (”% d”, choose) 读取数字所以程序一开始期待用户输入数字。如果用户直接输入姓名例如 “王一”scanf 读取失败就会进入输入错误分支。提示语和实际代码逻辑不一致。要么修改提示让用户先输入数字选择要么修改逻辑让程序真正支持直接输入姓名。如果想实现 “默认按姓名查找”可以先设计清楚输入规则避免既想接收数字又想直接接收字符串导致混乱。出错代码与修改方向printf(默认按姓名查找输入 1 切换方式); if (scanf(%d, choose) ! 1) { ClearBuff(); printf(输入错误\n); return -1; }这段代码本质上要求用户必须先输入数字但提示语容易让用户直接输入姓名。后来我把查找功能改成明确的二级菜单让用户先选择按姓名、电话或住址查找。个人理解与处理 我根据 AI 解释认识到问题不是 strcmp 本身而是查找菜单的输入逻辑和提示不一致。后来我调整了 Menu2 函数让用户可以明确选择按姓名、电话、地址查找或者让默认姓名查找逻辑与提示保持一致。这个问题让我明白交互提示和代码读取方式必须对应。记录 12姓名查找失败处理换行符和输入缓冲区提问 我按姓名查找时明明添加了 “王一”但是查找还是 Not found可能是什么原因AI 回答整理 如果使用 fgets 读取字符串可能会把末尾的换行符一起读进去。例如存储的是 “王一”查找时输入的可能是 “王一 \n”strcmp 比较时就会判定不相等。因此需要在读取后使用 strcspn 去掉换行符。同时如果前面使用 scanf 读取数字输入缓冲区中可能残留换行符后面使用 fgets 时可能直接读到空行所以在 scanf 后要根据情况清理缓冲区。出错代码与修改方向fgets(search, sizeof(search), stdin);如果只这样写输入末尾可能带有换行符导致 strcmp 比较失败。修改时需要增加search[strcspn(search, \n)] \0;查找函数中也可以对传入的字符串再次处理name[strcspn(name, \n)] \0;个人理解与处理 我把查找函数和 Menu2 中的字符串输入逻辑进行了调整在读取姓名、电话、地址后去掉末尾换行符并在必要位置调用 ClearBuff 清理输入缓冲区。修复后按姓名、电话、地址查找都更加稳定。这个问题让我理解了 scanf 和 fgets 混用时为什么容易出错。记录 13优化输入异常解决菜单死循环提问 通讯录菜单要求输入数字如果用户输入汉字或字母程序会一直死循环怎么解决AI 回答整理 这是因为 scanf 读取数字失败后错误输入仍然留在输入缓冲区中。下一次循环时scanf 会继续读取同样的错误内容于是不断失败形成死循环。解决办法是在 scanf 返回值不为 1 时调用清空输入缓冲区的函数把当前行的错误输入清理掉再提示用户重新输入。可以写一个 ClearBuff 函数循环读取字符直到遇到换行符或 EOF。出错代码与修改方向scanf(%d, input);如果用户输入汉字或字母scanf 读取失败错误内容留在缓冲区中。修改后加入返回值判断if (scanf(%d, input) ! 1) { ClearBuff(); printf(输入错误请输入数字\n); continue; }同时封装清空输入缓冲区函数void ClearBuff() { int c; while ((c getchar()) ! \n c ! EOF); }个人理解与处理 我在菜单输入中加入了 scanf 返回值判断如果输入不是数字就调用 ClearBuff 清理缓冲区并提示 “输入错误请输入数字”。这样即使输入汉字、字母或其他非法内容程序也不会卡死而是重新等待用户输入。记录 14修改联系人时实现 “回车跳过不修改”提问 修改联系人时如果每一项都必须重新输入很麻烦。能不能实现不想修改的字段直接回车跳过AI 回答整理 可以使用 fgets 读取一整行输入。如果用户直接按回车说明不修改当前字段保留原来的值如果用户输入了新内容就去掉换行符并覆盖原字段。可以封装一个 InputSkip 函数传入目标字段、字段长度和原值。函数内部读取输入后判断是否为空如果为空就复制旧值否则复制新值。这样可以让修改功能更灵活。关键实现思路fgets(temp, read_len, stdin); temp[strcspn(temp, \n)] \0; if (strlen(temp) 0) { strncpy(buf, oldVal, len - 1); } else { strncpy(buf, temp, len - 1); }个人理解与处理 我根据这个思路写了 InputSkip 函数并把修改姓名、年龄、电话、地址等操作改成 “直接回车保留旧值”。这样通讯录修改功能从原来的全部重输优化成只修改需要变动的字段使用体验更好。记录 15修改姓名输入错乱问题提问 修改联系人姓名时出现输入错乱有时候姓名修改失败后面的字段也会受影响这是为什么AI 回答整理 这类问题通常和输入缓冲区处理有关尤其是 scanf 和 fgets 混用时更容易出现。需要检查在进入修改函数前是否已经清理过缓冲区如果工具函数内部又重复清理可能会把用户刚输入的内容提前读走导致真正的 fgets 读不到数据。清空缓冲区不能随便放在每个输入函数里而应该根据调用流程确定是否需要清理。输入函数最好只负责读取输入和判断是否保留旧值缓冲区清理单独放在必要位置。出错原因与修改方向 修改姓名、年龄、电话、住址时统一交给 InputSkip 处理避免每个地方都重复清空缓冲区。修改时主要保留如下调用方式printf(请输入新的姓名); InputSkip(Con-a[index].name, NAME_Max, Con-a[index].name); printf(请输入电话); InputSkip(Con-a[index].Tel, TEL_Max, Con-a[index].Tel);个人理解与处理 我重新检查了修改联系人时的调用流程发现有些地方清理缓冲区的位置不合适容易造成输入被提前消耗。后来我调整了 InputSkip 函数和相关调用逻辑让输入函数主要负责读取和判断减少重复清理缓冲区的情况。这个过程让我认识到调试输入问题不能只看单个函数还要看整个函数调用顺序。记录 16修改性别时自定义内容无法保存提问 我修改联系人性别时输入 0 代表女、输入 1 代表男但是输入其他内容有时候改不了这是为什么AI 回答整理 需要检查自定义输入是否真正赋值到联系人结构体中。性别修改逻辑应该分几种情况如果用户直接回车就保留旧值如果输入 0就保存为 “女”如果输入 1就保存为 “男”如果输入其他内容例如 “保密” 或 “未知”就直接把输入内容复制到 Gender 字段中。不能只读取到临时 buf 里却没有正确写回结构体字段。出错代码与修改方向InputSkip(buf, sizeof(buf), Con-a[index].Gender);这一步只是读取到临时数组 buf 中后面还需要根据 buf 内容写回结构体字段if (strlen(buf) 0) { // 保留原性别 } else if (strcmp(buf, 0) 0) { strcpy(Con-a[index].Gender, 女); } else if (strcmp(buf, 1) 0) { strcpy(Con-a[index].Gender, 男); } else { strcpy(Con-a[index].Gender, buf); }个人理解与处理 我修改了 ModDat 函数中的性别处理逻辑。现在输入 0 会保存 “女”输入 1 会保存 “男”输入其他文字也会保存为自定义性别直接回车则保留原来的值。这个 Bug 让我认识到输入到临时变量并不代表数据已经更新必须明确写回结构体成员。记录 17通讯录文件保存和加载提问 通讯录退出后数据丢失应该怎么保存第一次运行文件不存在怎么办AI 回答整理 可以使用文件操作保存联系人数据。退出通讯录时以二进制写入方式打开文件把顺序表中的每个联系人结构体写入文件。程序启动时以二进制读取方式打开文件如果文件存在就循环读取联系人并插入到通讯录中如果文件不存在说明是第一次运行可以提示没有历史数据并创建新的空通讯录。文件操作时必须判断 fopen 返回值避免文件打开失败后继续读写导致错误。学习补充 我加入了文件保存和加载功能。程序启动时尝试读取历史联系人数据退出时把当前通讯录内容保存到文件中。个人理解与处理 我实现了 SaveCon 和 LoadCon 函数。程序启动时加载历史数据退出时保存当前通讯录。第一次运行没有文件时程序不会崩溃而是创建新的通讯录。这让我进一步练习了 C 语言文件读写和异常判断。记录 18通讯录终版功能整理提问 我的通讯录已经多次修改终版应该怎么总结哪些功能算已经完成AI 回答整理 可以从底层结构、基础功能、输入优化、查找优化、修改优化、文件保存和 Bug 修复几个方面总结。底层使用动态顺序表存储联系人基础功能包括添加、删除、查找、修改、显示、保存和退出。输入优化包括处理非法输入、清理缓冲区、使用 fgets 读取字符串。查找优化包括按姓名、电话、地址查找。修改优化包括回车跳过不修改、性别快捷输入和自定义输入。Bug 修复包括解决查找乱码、姓名查找失败、菜单死循环、修改姓名输入错乱、性别自定义无法保存等问题。学习补充 学习记录从顺序表基础接口开始到通讯录基础版、优化版、终版和补坑篇记录了从实现功能到修复 Bug 的完整过程。终版通讯录已经具备添加、删除、查找、修改、展示、保存、加载和异常输入处理等功能。个人理解与处理 我根据学习记录和调试过程把通讯录项目整理成了一个完整的顺序表应用案例。相比一开始只会写简单接口后面我能根据实际使用中的问题不断修改程序说明自己对顺序表和 C 语言项目的理解更深入了。记录 19学习单链表理解链式存储思想提问 顺序表通讯录完成后为什么还要学习单链表单链表和顺序表最大的区别是什么AI 回答整理 顺序表使用连续空间存储数据访问方便但插入和删除时可能需要移动大量元素并且需要考虑容量和扩容问题。单链表使用结点存储数据每个结点包含数据域和指针域指针指向下一个结点。链表不要求物理空间连续插入和删除时主要修改指针关系不需要整体移动元素也不需要像顺序表那样扩容。但是链表不能通过下标直接访问某个元素查找时通常要从头遍历。学习补充 我学习了结点结构、头指针、头插法、尾插法、查找、插入、删除、遍历和销毁等操作。相比顺序表单链表更强调指针之间的连接关系。个人理解与处理 我在顺序表通讯录的基础上继续学习单链表重点理解结点结构、头指针、头插法、尾插法、查找、插入、删除和释放空间等内容。通过对比我认识到顺序表和链表各有适用场景不能简单说谁更好。记录 20单链表结点设计提问 如果用单链表存储数据结点应该怎么设计和顺序表结构体有什么区别AI 回答整理 单链表结点一般由数据域和指针域组成。数据域用于存放具体数据指针域用于保存下一个结点的地址。如果是普通整数链表数据域可以是 int如果是通讯录链表数据域可以是联系人结构体。顺序表结构体中通常有数组指针、size 和 capacity而单链表不需要 capacity因为它不是一次性申请连续空间而是需要一个结点就申请一个结点。链表主要依赖头指针找到第一个结点再通过 next 指针依次访问后续结点。学习补充 单链表中每个结点都保存数据和指向下一个结点的指针。链表的整体访问依赖头指针不像顺序表那样通过数组下标直接访问。个人理解与处理 我理解了顺序表和链表在结构体设计上的区别。顺序表重点管理数组空间和有效数据个数链表重点管理每个结点之间的指针关系。后续如果把通讯录改成链表结构就要把联系人信息放到结点的数据域中。记录 21单链表基本操作学习提问 单链表应该先实现哪些基本操作为什么有些函数要用二级指针AI 回答整理 单链表基本操作包括创建结点、头插、尾插、查找、删除、遍历和销毁。头插和头删可能会改变头指针本身如果只传一级指针函数内部修改的只是形参副本外部头指针不会改变。因此在可能改变头指针的操作中需要使用二级指针。尾插时需要找到最后一个结点再把新结点连接到链表尾部。销毁链表时要先保存下一个结点地址再释放当前结点避免释放后找不到后续结点。学习补充 我重点练习了头插、尾插、查找、遍历和销毁。链表和顺序表最大的不同是链表操作经常会改变指针指向尤其是涉及头结点变化时需要特别注意参数传递。个人理解与处理 我根据 AI 提示练习了单链表的基本接口理解了为什么链表操作中经常使用二级指针。尤其是头插、头删和销毁操作都需要特别注意头指针变化和内存释放顺序。记录 22单链表插入和删除的指针顺序提问 单链表插入和删除时最容易错在哪里为什么有时候会断链或内存泄漏AI 回答整理 链表插入时要注意连接顺序。比如在 pos 结点后插入新结点应先让新结点的 next 指向 pos 原来的下一个结点再让 pos 的 next 指向新结点。如果顺序反了可能导致后面的链表丢失。删除结点时要先保存待删除结点地址再修改前驱结点的 next 指针最后释放待删除结点。如果没有保存地址就修改指针可能找不到要释放的结点造成内存泄漏。销毁链表时也要先保存 next再释放当前结点。关键代码补充newnode-next pos-next; pos-next newnode;删除结点时需要先保存待删除结点SLTNode* del pos-next; pos-next del-next; free(del);个人理解与处理 我用 “先保存、再连接、后释放” 的思路记忆链表操作。插入时重点防止断链删除时重点防止丢失待释放结点。通过练习我对链表指针操作的顺序有了更清楚的认识。记录 23用单链表思路优化通讯录底层结构提问 如果不用顺序表而是用单链表优化通讯录整体结构应该怎么改AI 回答整理 可以把联系人信息作为链表结点的数据域每个结点中保存一个联系人结构体和一个 next 指针。通讯录不再使用动态数组、size 和 capacity而是使用头指针指向第一个联系人结点。添加联系人时可以使用尾插法把新联系人放到链表末尾删除联系人时先按姓名、电话或地址找到目标结点再修改前驱结点的 next 指针并释放目标结点查找联系人时从头指针开始遍历修改联系人时找到目标结点后直接修改结点中的联系人数据展示联系人时遍历整个链表退出时遍历释放所有结点。学习补充 如果把通讯录底层改为链表可以把联系人结构体作为链表结点的数据域。每个结点保存一个联系人结点之间通过 next 指针连接。这样添加联系人时不需要扩容删除联系人时也不需要移动数组元素。个人理解与处理 我把顺序表通讯录和单链表通讯录进行对比思考如何把原来依赖数组下标的操作改成依赖指针遍历。顺序表版本删除联系人时根据下标调用 SLDeleteSpecify而链表版本删除联系人时需要记录前驱结点和当前结点。通过这个过程我对链式存储的插入、删除和遍历有了更清楚的认识。记录 24顺序表通讯录和链表通讯录添加功能对比提问 顺序表通讯录和链表通讯录在添加联系人时有什么区别AI 回答整理 顺序表通讯录添加联系人时通常是判断容量是否足够如果容量满了就扩容然后把联系人放到数组末尾。链表通讯录添加联系人时不需要判断容量也不需要扩容而是申请一个新结点把联系人信息存入结点中再把新结点连接到链表末尾。顺序表添加时主要关注容量问题链表添加时主要关注结点申请和指针连接问题。个人理解与处理 我理解了两种添加方式的区别。顺序表版本添加联系人比较简单但要考虑扩容链表版本不需要扩容但要处理新结点申请和尾部连接。这样可以从实际功能角度体会顺序存储和链式存储的差异。记录 25顺序表通讯录和链表通讯录删除功能对比提问 顺序表通讯录和链表通讯录在删除联系人时有什么区别AI 回答整理 顺序表通讯录删除联系人时通常先找到联系人下标然后把后面的元素依次向前移动最后 size 减 1。链表通讯录删除联系人时通常需要同时记录当前结点和前驱结点找到目标结点后让前驱结点的 next 指向目标结点的下一个结点再释放目标结点。如果删除的是第一个结点还需要单独修改头指针。关键对比补充 顺序表删除依赖下标SLDeleteSpecify(Con, index);链表删除依赖指针prev-next cur-next; free(cur);个人理解与处理 我认识到删除操作最能体现顺序表和链表的区别。顺序表删除需要移动数据链表删除需要修改指针。链表删除虽然不用移动大量元素但要特别注意头结点、空链表、只有一个结点等特殊情况。记录 26链表通讯录查找和修改功能设计提问 如果通讯录底层改成链表查找和修改功能应该怎么写AI 回答整理 链表通讯录查找联系人时需要定义一个临时指针 cur从头结点开始遍历。每访问一个结点就比较该结点中的姓名、电话或地址是否与输入内容匹配。如果匹配就返回该结点指针或直接打印联系人信息。如果要修改联系人可以先通过查找函数找到目标结点然后直接修改该结点数据域中的姓名、性别、年龄、电话、地址等字段。链表中没有数组下标所以操作重点从 “下标” 转为 “结点指针”。个人理解与处理 我把顺序表中的按下标访问思路转为链表中的按指针遍历思路。原来修改联系人是Con-a[index]链表中则应该是cur-data。这个变化让我更清楚地理解了数组和链表在访问方式上的区别。记录 27顺序表通讯录和链表通讯录的对比总结提问 顺序表通讯录和链表通讯录各有什么优缺点总结里应该怎么写AI 回答整理 顺序表通讯录结构简单联系人连续存放按位置访问方便适合数据量不大、主要进行展示和查找的小型通讯录。但顺序表删除中间元素时需要移动后面的元素容量不足时还需要扩容。链表通讯录不需要连续空间也不需要扩容插入和删除时只需要修改指针更适合频繁增删联系人或数据量变化较大的情况。但链表不能通过下标直接访问联系人查找时需要从头遍历而且每个结点还需要额外的指针空间。个人理解与处理 我在总结中把顺序表和单链表的特点进行了对比。顺序表版本适合作为基础项目便于理解动态数组和增删改查链表版本则更能体现链式存储的优势尤其是插入删除时不需要移动大量元素。通过两个版本对比我对 “线性表逻辑结构相同但存储结构不同” 这一点理解更深。记录 28整理学习过程和交流记录提问 我已经把线性表、顺序表、通讯录、单链表学习过程写成学习记录交流记录应该怎么整理才不像单独拼凑AI 回答整理 交流记录不应该只是一堆零散问答也不应该把学习笔记和对话完全分开。可以按照学习路线整理先认识线性表再学习顺序表接着实现动态顺序表接口然后把顺序表用于通讯录项目在项目中遇到查找、输入、修改、保存等问题再通过工具辅助调试最后学习单链表并思考用链表优化通讯录。这样既能体现学习过程也能体现工具在每个阶段的辅助作用。个人理解与处理 我按「学习概念→实现接口→完成项目→发现问题→调试优化→拓展链表→总结对比」的顺序整理交流记录把调试内容融入整个项目迭代过程中而不是单独放成一个模块。这样更能完整呈现从学习顺序表到完成通讯录再到学习单链表优化通讯录的全过程。三、工具使用与学习总结3.1 工具使用心得本次学习过程中我主要使用 AI 工具辅助学习线性表、动态顺序表、通讯录项目和单链表相关内容。工具在学习过程中主要起到了解释概念、梳理思路、分析代码、辅助调试和总结知识点的作用。基础学习阶段帮助理清线性表、顺序表和单链表之间的关系明确顺序存储和链式存储的核心区别梳理各接口的实现思路。代码实现阶段提示元素移动方向、边界判断、动态内存管理的注意事项减少基础语法和逻辑错误。项目开发阶段指导多文件结构设计、头文件包含规则、结构体类型适配、文件读写的实现方法。调试优化阶段帮助定位输入缓冲区、字符串处理、函数调用逻辑等隐蔽问题给出排查方向和解决思路。拓展对比阶段梳理两种存储结构的差异整理对比维度深化对数据结构选型的理解。注工具仅作为辅助学习手段所有代码均经过手动编写、调试与验证确保完全理解逻辑后再落地到项目中。3.2 学习完成成果通过本次专项学习与代码实践最终完成以下内容掌握线性表的基本概念和核心特点理解顺序存储和链式存储的本质区别与适用场景完整实现动态顺序表的初始化、扩容、插入、删除、查找、修改和销毁全套接口基于动态顺序表实现完整的 C 语言通讯录项目完成通讯录添加、删除、查找、修改、显示、保存和加载全功能修复类型不匹配、输入缓冲区残留、换行符匹配失败等常见字符串问题优化修改体验实现回车跳过不修改、性别快捷输入等交互细节掌握单链表的结点定义、头插、尾插、查找、删除、遍历和销毁全套操作梳理单链表版本通讯录的实现思路完成两种存储结构的优缺点对比。3.3 后续学习方向目前对链表复杂操作的熟练度还有提升空间指针边界情况和内存释放顺序仍需加强练习代码工程化和异常处理能力也有优化空间。后续会继续通过手写代码、运行调试、知识点复盘和习题练习巩固数据结构基础并进一步学习栈、队列、二叉树等更多数据结构内容。四、仓库文件说明第三部分Luminous/Luminousbegin代码仓库地址点击获取文件4.1 文件结构├── 1.通讯录源代码 ├── SeqList.h # 动态顺序表接口声明 ├── SeqList.c # 动态顺序表接口实现 ├── Contact.h # 通讯录功能声明 ├── Contact.c # 通讯录功能实现 ├── test.c # 主函数、菜单与程序入口 ├── 2.通讯录可执行程序Debug ├──通讯录.exe # 编译好的可执行文件Windows直接运行 ├── 3.通讯录VSSt2022完整文件 # 所有文件4.2 运行方式直接使用Windows 系统下双击通讯录.exe即可运行数据会自动保存在同目录下的二进制文件中源码编译使用 gcc 等 C 语言编译器编译所有.c 文件即可生成可执行程序bashgcc SeqList.c Contact.c test.c -o contact.exe4.3 功能说明通讯录核心功能联系人增删改查全功能支持按姓名 / 电话 / 地址多条件查找修改支持回车跳过保留原值性别支持 0/1 快捷输入与自定义输入退出自动保存启动自动加载历史数据非法输入容错不会因输入字符导致程序死循环顺序表 / 单链表接口完整实现增删改查、初始化、销毁等标准接口代码注释清晰适合入门学习参考本项目仅作为学习参考资料供大家理解数据结构逻辑与 C 语言项目开发思路。请同学们独立完成自己的学习任务哦