☰
C语言顺序表从零实现:动态扩容与增删改查详解
2026/10/9 12:42:24 网站建设 项目流程

1. 为什么数据结构要从顺序表开始

很多自学C语言的朋友都有个共同的困惑:指针还没整明白,结构体刚有点感觉,突然冒出个“数据结构”,一看教材就是线性表、链表、栈、队列,瞬间头大。我当年学到这里的时候也一样,翻了几本经典教材,前言都在说“数据结构是编程的内功”,但翻开第一章就是一堆抽象定义,配合那些手写草稿图,完全不知道这东西到底拿来干嘛。

先说结论:如果你正在学C语言,顺序表是你接触“数据结构”这四个字最平滑的一个切入点。它不涉及指针绕来绕去的技巧,也不要求你先掌握动态内存的复杂用法,本质上就是把数组包装成一个“可以自动管理长度、提供增删改查接口”的容器。你完全可以把它理解为“升级版数组”——数组用起来最痛苦的地方,长度固定、无法插入、删除要手动搬移元素,顺序表把这些痛点一个一个解决掉。

这篇文章的内容,是我在实际写代码、调试、甚至应对笔试面试过程中反复打磨出来的顺序表专题。从底层结构设计到每一步操作的实现,再到常见的崩溃点和面试高频问题,都会拆开讲清楚。适合正在学C语言、准备开始啃数据结构的初学者;如果你是考研党,顺序表在408里也属于必须熟练掌握的基础内容,后面学链表、栈、队列、图,很多思路都是从这里延伸出去的。

我会用最简单直白的方式,把顺序表从0到1写一遍,每一段代码都告诉你为什么这么写,坑在哪里,怎么排查。看完之后,你自己能独立完成顺序表的增删改查、动态扩容和排序,这就达到目的了。

2. 顺序表的设计思路与核心概念

2.1 数组的痛点与顺序表的解决思路

先做个简单的对比。一个普通数组:

int arr[10];

它的缺陷很明显:长度是编译期定死的,一旦声明为10个元素,存满了就无能为力;想要在中间插入一个元素,必须手动把后面所有元素往后搬;想要删除中间某个元素,又得手动往前搬;数组本身也不知道当前到底存了几个有效元素,只能靠你额外用一个变量去记录。

顺序表的思路是:用一块连续的内存空间存储元素(本质上还是数组),但额外封装三个关键信息——存储空间的起始地址、当前有效元素个数、当前分配的容量。所有对数据的操作都通过函数来完成,而不是让使用者在外面裸操作数组。

#define MAX_SIZE 100 typedef struct { int data[MAX_SIZE]; int length; } SeqList;

这种静态版本是最容易理解的:data数组负责存数据,length记录当前有多少个有效元素。顺序表这个名字,“顺序”指的就是逻辑上相邻的元素在物理存储上也相邻,所有元素按顺序排在一整块连续内存里。

2.2 静态分配与动态分配的选择

上面那种写法,data数组的大小写死在代码里,一旦MAX_SIZE定义小了,想存更多数据就崩了;定义大了,又浪费内存。实际工程里很少用这种静态大小写死的方式,更常见的是动态分配。

typedef struct { int *data; // 指向动态申请的内存 int length; // 当前有效元素个数 int capacity; // 当前容量,即最多能存多少个 } SeqList;

使用动态版本,你可以做到:容量不够时自动扩容,用多少申请多少,不浪费内存。这也是我推荐初学者直接上手的方式,因为它在思维上跟静态版本差别不大,却提前让你接触了动态内存管理,后面学链表会顺手很多。

具体来说,结构体里存的是指针data,初始化时用malloc申请一块容量为capacity的内存,让data指向它。length表示“已经用了多少”,capacity表示“最多能放多少”。data[0]到data[length-1]是有效元素,data[length]到data[capacity-1]是还没用的空位。

注意:动态顺序表里,data指向的内存最终必须用free释放,否则就是内存泄漏。C语言不像Java有垃圾回收,这个责任得自己担起来。

2.3 顺序表的三个核心指标

聊顺序表,绕不开时间复杂度。很多人觉得这就是应付考试的概念,但实际上它直接决定了你的代码在数据量变大之后会不会卡死。

操作时间复杂度说明
按下标访问(查找第i个元素)O(1)因为是连续内存,直接首地址+偏移量就算出位置
按值查找O(n)最坏情况要遍历所有元素
在尾部插入/删除O(1)不需要搬移其他元素
在中间或头部插入/删除O(n)后续元素要整体后移或前移

这就可以解释为什么顺序表适合“存了之后频繁按下标读”的场景,而不适合“频繁在中间插入删除”的场景。如果你要处理的是一个随时在头部插入的任务队列,顺序表每次插入都要把后面的元素全往后挪,效率很低,这种时候链表更合适。但链表按下标访问又得从头一个个跳,各有各的适用场景。

理解时间复杂度一定要落到“搬移元素”这个操作上。顺序表插入的核心代价不在于malloc,而在于内存中整片数据的搬移。想象一下一排人站在电影院的连排座位上,新来的人要坐到第3个位置,后面所有人必须整体往后挪一个座位,这就是顺序表插入的真实代价。

3. 顺序表的完整实现:从初始化到销毁

3.1 结构定义与宏准备

先搭出完整的头文件结构。我用的是动态版本,支持任意元素个数,不用提前写死大小。

#include <stdio.h> #include <stdlib.h> typedef int ElemType; // 方便以后改类型,想存double就改成double #define INIT_CAPACITY 8 // 初始容量 typedef struct { ElemType *data; int length; // 当前元素个数 int capacity; // 当前最大容量 } SeqList;

定义ElemType这个别名很重要。写数据结构的时候,你要把这份代码当成一个“通用的容器”来设计,而不是专门为int服务。以后你想存学生成绩、存坐标点、存自定义结构体,只需要把ElemType改成对应的类型,其他代码基本不用动。

常用操作清单如下:初始化、销毁、清空、判空、求长度、按下标取值、按值查找、在指定位置插入、删除指定位置元素、打印输出。这十个函数覆盖了顺序表95%的日常使用场景。

3.2 初始化与销毁

void initList(SeqList *list) { list->data = (ElemType *)malloc(INIT_CAPACITY * sizeof(ElemType)); if (list->data == NULL) { printf("内存分配失败\n"); exit(1); } list->length = 0; list->capacity = INIT_CAPACITY; }

malloc之后一定检查返回值,这是保命习惯。C语言里malloc失败会返回NULL,如果你不检查直接往下写,就是往空地址写数据,轻则段错误,重则整个程序崩溃,而且极难排查。

销毁函数:

void destroyList(SeqList *list) { free(list->data); list->data = NULL; list->length = 0; list->capacity = 0; }

free完之后置NULL是第二个保命习惯。如果不置NULL,这个指针就成了“悬空指针”,你心里知道它已经失效了,但地址值还在那里,万一后面不小心又访问它,会读到已经被释放的内存,这种bug特别隐蔽,调试半天都看不出来。

3.3 扩容机制:什么时候扩大,怎么扩大

插入元素之前必须先检查容量。如果length等于capacity,说明满了,需要扩容。扩容的经典做法是每次扩大到原来的2倍。

void ensureCapacity(SeqList *list) { if (list->length < list->capacity) { return; } int newCapacity = list->capacity * 2; ElemType *newData = (ElemType *)realloc(list->data, newCapacity * sizeof(ElemType)); if (newData == NULL) { printf("扩容失败\n"); exit(1); } list->data = newData; list->capacity = newCapacity; }

为什么是2倍而不是capacity + 10或者3倍?这里面有讲究。如果每次只多申请固定大小,假设初始容量8,之后每次加10,那么插入第9、19、29个元素时都要扩容,总共需要的时间复杂度会累积成O(n²)。而每次扩容翻倍,扩容的次数大约是log n次,总的时间复杂度是O(n)。翻倍这个策略,在均摊分析下,每次插入操作的平均时间复杂度是O(1)。

简单说就是:扩容太频繁浪费时间和内存,扩容倍数太大会浪费空间,2倍是工程实践里最均衡的选择。

这里我特意用realloc而不是malloc。realloc可以在原有内存块的基础上调整大小,如果后面有足够的连续空间,它会原地扩展,直接返回原指针;如果后面空间不够,它会重新找一块更大的区域,把旧数据自动拷贝过去,再释放旧内存。这比“手动malloc新内存、自己memcpy、再free旧内存”简单得多。

3.4 插入操作:先挪数据,再放新值

插入是顺序表最核心的算法操作。假设要在位置pos(从1开始计数,教材惯例)插入一个新元素value,步骤分三步:

  1. 校验pos是否合法:pos必须满足1 <= pos <= length+1。注意长度为0时只能在第1个位置插入。
  2. 将pos及其后面所有元素依次后移一位,从最后一个元素开始往前搬移。
  3. 在data[pos-1]处放入value,length加1。

代码实现:

void insertElem(SeqList *list, int pos, ElemType value) { if (pos < 1 || pos > list->length + 1) { printf("插入位置不合法\n"); return; } ensureCapacity(list); for (int i = list->length - 1; i >= pos - 1; i--) { list->data[i + 1] = list->data[i]; } list->data[pos - 1] = value; list->length++; }

这里最容易踩坑的是搬移的方向。必须从后往前搬,先搬最后那个元素,再逐步往前。如果你从前往后搬,第一个元素会覆盖掉第二个元素,然后第二个又覆盖第三个,数据直接乱套。

我当初第一次写这段代码就犯过这个错。当时想着“把元素往后移嘛,从第pos个开始不就行了”,结果pos后面的元素全被前面的覆盖了,整个顺序表只剩一个值重复多遍。从那以后我总结出一个口诀:后移要从尾巴开始,前移要从头部开始。

3.5 删除操作:从前往后覆盖

删除第pos个元素(1 <= pos <= length),步骤:把pos位置后面的元素依次前移一位,长度减1。

void deleteElem(SeqList *list, int pos) { if (pos < 1 || pos > list->length) { printf("删除位置不合法\n"); return; } for (int i = pos; i < list->length; i++) { list->data[i - 1] = list->data[i]; } list->length--; }

移动方向跟插入相反,从pos开始往最后一个元素遍历,把后面的往前覆盖。删除操作不需要真的清空最后一个位置的内存,只需要把length减1,后面再插入新值时自然会覆盖掉。

很多初学者问:删掉之后最后一个元素的位置还残留着旧值,会不会有影响?答案是不会。因为length已经变成了新长度,所有接口都只认下标0到length-1这个范围,残留值属于“逻辑死亡”,不在有效数据范围内。这个设计很像Java里ArrayList的size,length一减,多出来的槽位就自动被忽略了。

3.6 查找与访问

按下标访问是顺序表的强项,O(1)时间就能定位。为什么能这么快?因为数组的内存是连续的,data[i]等价于*(data + i),编译器帮你算好了偏移量。

ElemType getElem(SeqList *list, int pos) { if (pos < 1 || pos > list->length) { printf("访问位置不合法\n"); return -1; } return list->data[pos - 1]; }

按值查找则是另一回事。假设你想找到第一个值为value的元素的下标:

int locateElem(SeqList *list, ElemType value) { for (int i = 0; i < list->length; i++) { if (list->data[i] == value) { return i + 1; // 返回逻辑位置 } } return 0; // 0表示没找到 }

这个函数返回值为0的设计是考研教材里的经典写法,因为合法的位置最小是1,所以0可以当作特殊标记。如果你直接返回物理下标i,那“没找到”就不知道该返回什么了,-1可以,但0这个标记更有“教材味”,也好处理。

3.7 打印与销毁收尾

void printList(SeqList *list) { if (list->length == 0) { printf("空表\n"); return; } for (int i = 0; i < list->length; i++) { printf("%d ", list->data[i]); } printf("\n"); }

测试的时候打印函数是调试神器,每做一步操作就打印一次,马上就能看出问题在哪。很多新手埋头调错,其实问题可能早就出现了,只是你没把它打出来看。

完整的内存释放流程,在主函数退出前调用destroyList即可。养成这个习惯,以后写完程序用Valgrind检测内存泄漏,会非常干净。

4. 完整测试用例与运行结果分析

4.1 一个覆盖全操作的测试主函数

光有函数定义不够,得写一个能跑通的测试程序。以下测试覆盖初始化、插入、删除、查找、扩容全流程:

int main() { SeqList list; initList(&list); // 插入5个元素 for (int i = 1; i <= 5; i++) { insertElem(&list, i, i * 10); } printList(&list); // 在位置3插入新值 insertElem(&list, 3, 99); printList(&list); // 删除位置2的元素 deleteElem(&list, 2); printList(&list); // 查找值99 int pos = locateElem(&list, 99); printf("99 在位置 %d\n", pos); // 按下标访问 ElemType val = getElem(&list, 3); printf("第3个元素是 %d\n", val); // 扩容测试:插入到超过初始容量 for (int i = 6; i <= 20; i++) { insertElem(&list, i, i * 100); } printf("扩容后的长度:%d,容量:%d\n", list.length, list.capacity); printList(&list); destroyList(&list); return 0; }

运行结果:

10 20 30 40 50 10 20 99 30 40 50 10 99 30 40 50 99 在位置 2 第3个元素是 30 扩容后的长度:15,容量:32 10 99 30 40 50 600 700 800 900 1000 1100 1200 1300 1400 1500

这里注意一个问题:删除位置2后,原来位置3的99变成了新的位置2,所以查找99返回的是2而不是3。这个结果看着简单,但初学者经常在这里搞混——删除之后,所有元素的位置都重新洗牌了。

容量变化也验证了扩容逻辑:初始容量8,插入第9个元素时扩容到16,插入第17个元素时再次扩容到32,最终capacity是32,正好是2的幂。你可以观察到,length在扩容后并没有立刻填满,而是留出了一部分冗余空间,这就是“用空间换时间”的经典体现。

4.2 边界条件测试不可省略

上面那个测试覆盖了正常情况,但一份健壮的顺序表代码,边界测试才是真正见功底的地方。建议你至少跑这几类用例:

  • 在空表中插入第一个元素。
  • 在length+1的位置插入元素(合法,追尾插入)。
  • 在length+2的位置插入元素(非法,应报错)。
  • 删除第1个元素(头删)。
  • 删除最后一个元素(尾删)。
  • 查找一个不存在的值。
  • 反复插入、删除、再插入,看length是否混乱。
// 专门测试边界 void boundaryTest() { SeqList list; initList(&list); insertElem(&list, 1, 100); // 空表插入第一个 printList(&list); insertElem(&list, 2, 200); // 尾部插入 printList(&list); insertElem(&list, 4, 300); // 非法位置,应该报错 printList(&list); deleteElem(&list, 1); // 头删 printList(&list); deleteElem(&list, 1); // 再删,只剩一个元素 printList(&list); int pos = locateElem(&list, 999); if (pos == 0) { printf("999 未找到,查找函数正常\n"); } destroyList(&list); }

输出应该是:正常插入两次,第三次插入因为位置4超出了length+1=3的范围而打印“插入位置不合法”,然后两次头删把列表删空。整个过程没有崩溃、没有段错误,才算合格。

4.3 为什么一定要用指针传参

注意看,我所有函数的第一个参数都是SeqList *list,而不是SeqList list。这里面有个很重要的C语言基础:结构体作为参数传递时是值传递,函数内部拿到的是结构体的一个副本,你修改length、capacity,外面的结构体完全感知不到。

如果写成:

void insertElem(SeqList list, int pos, ElemType value) { // 修改的是list副本 }

那么插入操作后,list.length不会增加,data里的值改了也存不回去。具体来说,data指针本身是存在结构体里的,传递副本时指针的地址值被复制了一份,你通过这个指针能修改堆上的内存数据,但修改不了外面的length和capacity。这就导致:元素确实写进内存了,但外面不知道有多少个,打印的时候length还是0。这种bug折磨过无数初学者。

传指针就完全不一样了。通过list->data访问的是堆上真正的数组,list->length++直接修改的是原结构体的成员。函数内的一切操作直接作用于调用者的那份数据。

经验之谈:如果你在调试中发现“插入函数执行了,但外面打印还是空”,90%是你用了值传递。先检查函数签名,再看函数体里有没有写成list.xxx而不是list->xxx。

5. 常见报错与调试经验速查

5.1 段错误(Segmentation Fault)的几种典型场景

段错误是C语言初学阶段遇到最多的崩溃问题,顺序表相关的段错误一般就这几类:

**场景一:没有初始化就调用插入。**你定义了一个SeqList list;,没调用initList,直接insertElem(&list, 1, 10)。此时list.data的值是垃圾值(未初始化局部变量),你以为自己在往一个合法的数组里写数据,实际是在乱写地址,运气好没崩,运气不好直接段错误。

解决办法就是强制自己牢记使用顺序表的步骤:定义 -> initList -> 操作 -> destroyList。有些教材会建议在定义结构体时把data初始化为NULL,这可以作为保险:

SeqList list = {NULL, 0, 0};

这样即使你忘了初始化,函数里检查list->data == NULL也能及时发现。

**场景二:扩容失败或忘记扩容。**如果你的代码里插入前没有检查capacity,数据存到第8个(初始容量8)时再插入第9个,就写到数组边界之外了。C语言数组访问不会自动报错,而是“未定义行为”,可能当时不崩,但会在某个随机时刻崩掉,或者覆盖掉其他变量。这种越界写bug最有迷惑性,因为你不一定能当场复现。

**场景三:销毁后还继续使用。**调用了destroyList,又调用insertElem或者printList。destroy之后data指向NULL(如果你按照我前面的写法置了NULL),再访问list->data[i]就是空指针解引用,必崩。从写代码的第一天起就要养成“内存生命周期”意识:内存不是你用完随手一丢就完事的,free之后它就跟你无关了,再碰就是非法操作。

5.2 逻辑错误的排查步骤

如果程序没崩,但输出不对,多半是逻辑层面的问题。我总结了一套排查顺序,按这个顺序来,能省一半时间:

  1. 打印length和capacity。开头先确认这两个值是否符合预期。绝大多数逻辑错误都体现在length不对上。
  2. 打印整个表。每次插入、删除后都调用printList,逐步观察数据变化,很快就能定位是哪一步出了问题。
  3. 检查循环边界。插入后移的循环条件是i >= pos-1,很多新手写成i > pos-1,导致pos位置后面的元素漏搬。删除前移是i < length,写成i <= length就把最后一个空位的前一个垃圾值也搬进来了。
  4. 检查位置换算。用户眼中的“第1个元素”对应物理下标0,任何函数签名里的pos你都要在心里换算成pos-1才能访问数组。

我见过一个特别典型的案例:有同学写插入函数时,搬移循环的边界条件用的是i > pos-1而不是i >= pos-1,结果插入位置上的旧值没有被覆盖,插入的新值放进去之后,后面元素位置整体错了一位。打印出来看似没什么问题,但仔细一对比就知道,插入位置的值变成了原来pos位置的值。这种错误用眼睛看代码很难发现,把“插入后打印”和“期望输出”放一起对比,一眼就暴露了。

5.3 用GDB调试顺序表

如果你已经在用VS Code或者Linux环境,学会GDB的基本操作会极大提升调试效率。编译时用-g参数保留调试信息,然后运行gdb:

gcc -g seqlist.c -o seqlist gdb ./seqlist

在gdb里可以打断点查看结构体内容:

break insertElem run print *list print list->length print list->capacity next

当你觉得“插入后length没变”时,直接在函数结束前print *list,就能看到length和capacity的真实值。如果发现data数组里值写进去了,但length没变,基本就是值传递问题;如果length变了但数组数据乱了,基本就是搬移方向或边界条件问题。

学会用调试器跟学会写代码同样重要。顺序表这种小型数据结构,正是练习调试技巧的最佳训练场。

6. 顺序表的进阶应用与面试考点

6.1 经典题目:合并两个有序顺序表

顺序表学了不能白学,它直接对应一批笔试面试里的经典算法题。比如合并两个非递减有序的顺序表为一个新的有序顺序表,要求不破坏原表。

思路:双指针法,两个表各用一个指针从头扫描,比较两个指针指向的元素,谁小先放进新表,然后对应指针后移。某一方走完后,把剩下的元素全部拷贝进新表。

SeqList mergeList(SeqList *a, SeqList *b) { SeqList c; initList(&c); int i = 0, j = 0; while (i < a->length && j < b->length) { if (a->data[i] <= b->data[j]) { insertElem(&c, c.length + 1, a->data[i]); i++; } else { insertElem(&c, c.length + 1, b->data[j]); j++; } } while (i < a->length) { insertElem(&c, c.length + 1, a->data[i]); i++; } while (j < b->length) { insertElem(&c, c.length + 1, b->data[j]); j++; } return c; }

效率上面,insertElem在尾部插入,分摊下来O(1),整个合并是O(m+n)。这段代码考察的不仅仅是“你会不会写循环”,更重要的是你是否理解“逻辑位置”和“物理下标”的关系——我这里插入用的是c.length + 1,每次插入后length变了,所以这个位置始终是尾部,不要写成固定值。

6.2 经典题目:原地删除重复元素

另一个高频题:给定一个有序顺序表,删除所有重复元素,要求不额外开辟数组,空间复杂度O(1)。

思路:两个下标i和j,j遍历整个表,i记录“下一个不重复元素应该放的位置”。遇到跟上一个不同的元素,就把它移到i位置,然后i加1。最后length设置为i。

void removeDuplicates(SeqList *list) { if (list->length < 2) return; int i = 1; // 新表长度计数 for (int j = 1; j < list->length; j++) { if (list->data[j] != list->data[i - 1]) { list->data[i] = list->data[j]; i++; } } list->length = i; }

这个算法在原数组上原地修改,不需要第二个数组,空间复杂度O(1)。它的核心是“用覆盖代替删除”,因为顺序表的删除操作本质就是覆盖,这道题只是让你一次性地把该覆盖的地方覆盖掉,而不是逐次调用delete函数,避免O(n²)的复杂度。

6.3 面试里的隐藏考点:结构体对齐与内存布局

还有一个面试官爱问的隐藏考点:顺序表结构体的内存占用。以我之前定义的结构体为例:

typedef struct { int *data; int length; int capacity; } SeqList;

在64位系统上,int *占8字节,length和capacity各占4字节,合起来16字节。由于结构体对齐规则,这三个成员正好凑成16,没有额外padding。如果你把成员顺序换成:

typedef struct { int length; int *data; int capacity; } SeqList;

那么8字节的指针会被对齐到8字节边界,length后面会有4字节的padding,结构体仍然16字节,但如果再加一个char型成员,不同顺序可能导致大小不同。这块属于C语言和结构的交叉知识点,面试时考顺序表经常顺带考,值得留意。

另外,如果你用malloc给data分配内存,malloc返回的地址通常是16字节对齐的,这保证了后续可以对data做任何类型转换都不会出问题。

6.4 从顺序表演化到其他数据结构

学完顺序表之后,你的认知就能串起来了:

  • 栈其实可以看作“只允许在尾部插入和删除的顺序表”。
  • 队列可以看作“尾部插入、头部删除的顺序表”,但为了效率一般用循环数组实现。
  • 字符串可以看作“元素类型是char的顺序表”。
  • 图论里的邻接矩阵,本质上就是二维的顺序表。

理解了这个脉络,你再看后面的内容会非常有底气。很多人觉得数据结构是一堆零散知识点,实际上它有一条主线:连续存储和链式存储是两条最基本的物理存储方式,所有抽象的数据结构(栈、队列、树、图)都是在这两种物理结构之上构建逻辑规则。顺序表就是“连续存储”这条主线的源头。

7. 我的实操体会与工程建议

写到这里,说几句实在的。顺序表这段内容,我前前后后用过不下几十遍,每次给初学者讲数据结构,我都会让他们先把顺序表手写一遍,再跑一遍完整的测试。这个过程看起来简单,实际写下来却有几个很深的体会。

第一,C语言写数据结构,最忌讳的就是“背代码”。网上一抓一大把的顺序表代码,你背下来默写出来,语法没错,但运行起来一改需求就崩。真正有效的练习方式是这样的:先自己独立设计结构体,再自己写插入和删除函数,最后用测试用例验证。哪怕第一次写得稀烂,只要你认真调试过一次,理解就比看十遍代码深刻得多。

第二,强烈建议在写完基础版之后,再实现一个支持泛型的版本。C语言没有模板,但你可以用void *指针存储任意类型的数据,配合memcpy来搬移内存。这样写出来的顺序表,可以同时存int、double、结构体指针等不同类型。这个进阶版本能让你把指针和内存操作练得滚瓜烂熟。

第三,不管代码多简单,一定要养成写完就测的习惯。我自己的流程是:写完一个函数,立刻写一个两三行的main函数调用它,看输出是否符合预期,然后再写下一个。不要憋到全部写完再统一测试,那样一旦出错,排查范围会扩大到整个文件,调试成本高好几倍。

补充一个很实用的小技巧:在开发阶段,可以给结构体增加一个char debugFlag成员,或者干脆写一个宏开关。当调试开关打开时,每次插入删除后自动打印整个表;关闭时则静默执行。这比你在各个地方手动加print语句要优雅得多,代码发布前统一把宏关掉就行。

最后说一个很多人忽略的细节:顺序表里插入和删除时,校验参数的顺序有讲究。必须先判位置合法性,再判是否扩容。如果你先扩容再判位置,一个非法位置的插入也会触发扩容,白白浪费内存。虽然这个浪费很小,但代码的严谨性就是在这种细节里体现出来的。

顺序表只是数据结构的起点,但它决定了你后续所有数据结构学习的深度。把我的经验和踩坑记录分享出来,希望你能少走一些我走过的弯路。接下来你可以试着自己动手,把今天代码里的static版本改成动态版本,或者把int改成double,又或者增加一个“按区间删除”的功能。每加一个功能,你对这块内容的理解就更深一层。

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

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

立即咨询