手写C语言顺序表:动态扩容与内存管理全解析

1. 为什么教程都在让你手写顺序表——我理解的三个真实理由

很多人第一次接触数据结构,看到“手写顺序表”这个练习时,第一反应往往是:C语言数组不是已经能存数据了吗?为什么非要自己封装一层?甚至有人直接把int arr[100]玩得溜熟,觉得顺序表就是个换皮数组。这个想法非常正常,但只对了一半。

我先说个结论:数组是顺序表的底层存储手段,但数组自己不负责“管理容量”这件事。你申请一个int arr[100],从那一刻起它的上限就被写死了。你要是存到第101个元素,C语言不会报错,恭喜你,你踩到了“缓冲区溢出”的地雷——程序可能当时没事,然后在你完全不相关的地方突然崩溃,这种问题最难排查。顺序表就是给原始数组包了一层“管家”:它记住当前存了多少个元素、总容量是多少,存满了就自动开辟一块更大的内存,把旧数据搬过去,然后把旧内存释放干净。

我理解手写顺序表的第一个真实理由,是让你亲眼看到“扩容”是怎么发生的。很多人背八股文能背出“动态扩容”“搬移元素”“时间复杂度 O(n)”,但你问他realloc到底干了啥、为什么扩容后旧指针不能继续用,他就卡住了。手写一遍,这些问题就再也不需要背。

第二个理由,是把“逻辑结构”和“物理存储”之间的差别钉进脑子里。顺序表在逻辑上是连续的、有序的序列,在物理上就是一段连续内存加一个计数器。你亲手维护这个计数器,你会比任何时候都清楚“数组越界”不是编译器的错,而是你对边界的理解有漏洞。

第三个理由比较现实——笔试和面试真考这个。某公司的笔试环节就考过一道题:实现一个支持任意插入位置的顺序表,要求插入第 n 个元素时时间复杂度 O(1)。基本功不扎实的人当场就懵了。不是说题目多难,而是你平时不是真的自己写过,你对“尾部插入”和“头部插入”的代价没有肌肉记忆。

所以这篇我打算拿一个最典型的场景做主线:从零实现一个支持动态扩容的 Seqlist,用它存整型数据,实现初始化、尾插、任意位置插入、删除、查找、清空、销毁,并配一组完整的测试。我会把容量判断、扩容倍率、内存搬移、数据丢失这些细节全部掰开讲。

2. 扩容逻辑的源头设计:容量判断、扩容时机与增长倍率

手写顺序表之前,先想清楚几个关键问题。不然代码写一半你会发现全是 patch:这里忘了判断满、那里忘了释放、扩容倍率拍脑袋定了个 1.5 倍结果频繁扩容性能稀烂。这些决策都应该在动手前做掉。

2.1 为什么必须记录“容量”和“长度”两个字段

一个合格的结构体至少要有三个成员:

typedef struct { int *data; // 指向底层数组的指针 int size; // 当前元素个数,逻辑长度 int capacity; // 当前能容纳的最大元素个数,物理容量 } Seqlist;

初学者最容易犯的错误是只留一个length,每次要判断“是否满”时就去sizeof(arr)/sizeof(int)——这招对真实指针完全无效。data是指针,不是数组名,sizeof(data)返回的是指针大小,在 64 位机器上恒等于 8,你算出来的容量永远是 2。所以capacity这个字段不是可有可无的,它让程序在任意时刻都知道“我有多少空间”,而不是去猜。

size和capacity的语义也必须分清楚:

  • size表示“已用”,即逻辑上有多少个有效元素,它决定了遍历、查找、删除的边界。
  • capacity表示“可用”,即内存块最多能装多少元素,它决定了什么时候需要触发扩容。

两者相等时,就是“满了”。这是扩容的唯一触发条件。

2.2 扩容的两种策略:增量扩容与倍数扩容

扩容不是“每次加 10 个”或者“每次翻倍”拍脑袋决定的,它背后是“摊还分析”的权衡。

增量扩容:每次扩容固定增加E个元素,比如 capacity += 10。这种策略的优点是内存利用率高,几乎不浪费空间;缺点是如果插入次数特别多,每次扩容都要搬移一批元素,累计搬移次数约为n^2 / (2E),数据量大了以后非常吃亏。比如你插了 10000 个元素,每次加 10,你可能要扩容 1000 次,每次搬移几百上千个元素,耗时呈二次增长。

倍数扩容:每次扩容后 capacity 变成原来的k倍,常见倍率是 1.5 或 2。这种策略的优点是搬移总次数大幅下降:插入 n 个元素累计搬移约(k/(k-1))*n次,摊到每个元素上是常数级,所以均摊时间复杂度是 O(1)。缺点是如果你只插入了 11 个元素,capacity 却翻到了 16,浪费了 5 个“空位”。这一点点浪费对现代机器完全不是事,所以主流动态数组的实现基本都用倍数扩容。

我自己的实践建议是:初学阶段用 2 倍扩容,生产级代码建议用 1.5 倍。2 倍逻辑简单,好想好写;1.5 倍是把 capacity 和已释放的旧内存复用率之间做平衡——翻倍扩容时,你新申请的内存永远比所有旧内存加起来还大,导致旧内存几乎不可能被新分配复用,内存碎片问题更明显。1.5 倍则给系统留了复用旧内存块的机会,整体内存利用率更高。这个细节初学不用太纠结,但要知道不同倍率背后的取舍。

2.3 何时分配、何时搬移、何时释放

确定了倍率,还得敲定扩容动作本身。我的expand函数内部逻辑是这样设计的:

static int seqlist_expand(Seqlist *list) { int new_capacity = list->capacity * 2; int *new_data = (int *)realloc(list->data, new_capacity * sizeof(int)); if (new_data == NULL) { return -1; } list->data = new_data; list->capacity = new_capacity; return 0; }

这里我用的是realloc,不是先malloc再memcpy再free。原因在于realloc在底层优先尝试在原有内存块后面“续租”,如果后面空间够,它直接扩大并返回原地址,数据都不用搬;只有后面不够时才申请新内存、搬数据、释放旧内存。你手动写三步也能实现同样的效果,但多了一轮无意义的数据搬运和两次内存操作,代码也啰嗦。

但realloc有个陷阱:它可能返回新地址并把旧地址释放掉。所以你不能写成:

list->data = (int *)realloc(list->data, new_capacity * sizeof(int));

一旦realloc失败,它会返回 NULL,但同时保留旧内存块不释放,问题来了——你原来的list->data已经被 NULL 覆盖了,旧内存的地址也丢了,这叫做“内存泄漏 + 数据丢失”双杀。正确写法就是先存到临时变量new_data,判断非空后再赋给list->data。

说完扩容,插入前的判断逻辑也清晰了:

if (list->size >= list->capacity) { seqlist_expand(list); }

这段代码要出现在每次插元素之前,而不是某个统一入口。否则你可能会漏掉“中间插入”这条路径。

3. 手写一份可运行还带容量保护的 Seqlist 实现

理论铺垫完,直接上完整代码。这一段我尽量把注释写清楚,但读的时候不要只抄注释,要想清楚每句话对应的就是前面讨论的哪个决策。

3.1 结构体与基础 api 的完整代码

#include <stdio.h> #include <stdlib.h> #include <string.h> #define INIT_CAPACITY 4 typedef struct { int *data; int size; int capacity; } Seqlist; // 初始化:分配初始内存,size 清零 void seqlist_init(Seqlist *list) { list->data = (int *)malloc(INIT_CAPACITY * sizeof(int)); if (list->data == NULL) { fprintf(stderr, "init: malloc failed\n"); exit(EXIT_FAILURE); } list->size = 0; list->capacity = INIT_CAPACITY; } // 扩容:容量翻倍,失败返回 -1 int seqlist_expand(Seqlist *list) { int new_capacity = list->capacity * 2; int *new_data = (int *)realloc(list->data, new_capacity * sizeof(int)); if (new_data == NULL) { return -1; } list->data = new_data; list->capacity = new_capacity; return 0; } // 尾插:最常用、均摊 O(1) int seqlist_push_back(Seqlist *list, int value) { if (list->size >= list->capacity) { if (seqlist_expand(list) != 0) { return -1; } } list->data[list->size] = value; list->size++; return 0; } // 任意位置插入,pos 从 0 开始,[0, size] 合法 int seqlist_insert(Seqlist *list, int pos, int value) { if (pos < 0 || pos > list->size) { return -1; } if (list->size >= list->capacity) { if (seqlist_expand(list) != 0) { return -1; } } // 从后往前搬移,避免覆盖未处理数据 for (int i = list->size; i > pos; i--) { list->data[i] = list->data[i - 1]; } list->data[pos] = value; list->size++; return 0; } // 删除指定位置元素,pos 范围 [0, size-1] int seqlist_erase(Seqlist *list, int pos) { if (pos < 0 || pos >= list->size) { return -1; } // 从前往后覆盖,把 pos 后面的元素整体前移 for (int i = pos; i < list->size - 1; i++) { list->data[i] = list->data[i + 1]; } list->size--; return 0; } // 查找:返回下标,找不到返回 -1 int seqlist_find(Seqlist *list, int value) { for (int i = 0; i < list->size; i++) { if (list->data[i] == value) { return i; } } return -1; } // 按位置访问 int seqlist_at(Seqlist *list, int pos, int *out) { if (pos < 0 || pos >= list->size) { return -1; } *out = list->data[pos]; return 0; } // 清空:只把 size 归零,不释放内存,方便复用 void seqlist_clear(Seqlist *list) { list->size = 0; } // 销毁:释放底层内存,指针置空,防止野指针 void seqlist_destroy(Seqlist *list) { free(list->data); list->data = NULL; list->size = 0; list->capacity = 0; } // 打印当前状态,调试利器 void seqlist_print(Seqlist *list) { printf("size=%d, capacity=%d\n", list->size, list->capacity); for (int i = 0; i < list->size; i++) { printf("%d ", list->data[i]); } printf("\n"); }

代码量不长,但每一段都有讲究。seqlist_insert里的 for 循环从list->size往pos+1方向倒着搬移,这个顺序如果反了,正着从前往后搬,你会把data[i]的值写到data[i+1]上,接着再用data[i+1]去覆盖data[i+2]——所有元素都会变成同一个值,数据直接毁掉。删除时则必须从前往后搬,因为覆盖方向是向前的,从后往前搬反而会丢数据。这个“方向感”就是顺序表最核心的基本功。

3.2 为什么初始化容量设为 4 而不是 1 或 100

容量初始值是个好问题。设成 1,第一次插就扩容,能省内存但这省法毫无意义;设成 100,如果实际只用 5 个元素,白占 95 个int的空间。我见过不少用 100 起步的教程代码,那只是图省事,谈不上设计。

选 4 的理由是:第一,它足够小,任何场景都不会浪费太多;第二,它很快触达“第一次扩容”,让你做实验时立刻能看到扩容日志;第三,倍增后依次变成 8、16、32、64,内存增长速度平滑,不会因为初始值太大而掩盖了扩容代码的 bug。初学阶段建议就按 4 来,后续想调大一点改成 8 也无妨。

3.3 从 main 函数跑通一个完整流程

写完 API 还不算完,你得真的跑起来。下面这段主函数演示了用法:

int main(void) { Seqlist list; seqlist_init(&list); // 尾插 10 个元素,故意超过初始容量 4,触发至少两次扩容 for (int i = 0; i < 10; i++) { seqlist_push_back(&list, i); } seqlist_print(&list); // size=10, capacity=16 // 在第 2 个位置插 99 seqlist_insert(&list, 2, 99); seqlist_print(&list); // 0 1 99 2 3 4 5 6 7 8 9 // 删除第一个元素 seqlist_erase(&list, 0); seqlist_print(&list); // 1 99 2 3 4 5 6 7 8 9 // 查找 6 int idx = seqlist_find(&list, 6); printf("index of 6 = %d\n", idx); seqlist_destroy(&list); return 0; }

跑这个程序的时候注意观察每次seqlist_print打印的capacity。你会发现第一次是size=4, capacity=4,插入第 5 个元素时 capacity 变成 8;插入第 9 个元素时 capacity 变成 16。这个变化过程就是“动态扩容”最直观的证据。

4. 把动态扩容完整跑一遍:插入、越界、缩容怎么看

代码能跑了,但我还是建议你亲手做几件事“折腾”一下,因为这些折腾最能暴露理解漏洞。

4.1 扩容前后地址变化:亲眼看看 realloc 做了什么

在seqlist_expand里加一行打印:

printf("expand: old=%p, new=%p, old_cap=%d, new_cap=%d\n", (void *)list->data, (void *)new_data, list->capacity, new_capacity);

然后重新编译运行。你会看到两种情况:

  • 某些扩容后新旧地址相同。这是realloc在原内存块后面直接扩展成功的表现,数据根本没搬。
  • 某些扩容后新旧地址不同。这是系统当前内存碎片化,原内存后面空间不够,只能另找一块更大的地方,然后把旧数据搬过去。

不管是哪种,扩容完成后旧指针都不能再用了。如果你的代码里某个地方偷偷保存了一份Seqlist里data的旧地址,扩容之后那个旧地址指向的内存可能已经被释放,你再去访问它属于典型的 use-after-free 错误,崩溃和乱数据都算轻的。

这也能解释为什么很多人写着写着突然发现“数组越界还能撑一会儿”:因为内存没有立刻被覆盖,旧地址里的数据还残留着一份副本,程序就跑得看似正常,实际上全是指向已释放内存的悬垂指针。所以我自己的习惯是——扩容后马上在注释里标注“旧指针作废,所有指向 data 的临时变量都需要重新获取”。

4.2 越界插入、越界删除、空表操作的统一拦截

写进 API 的参数校验不是摆设。我把三种典型情况都列在这里,方便你自己测试:

操作错误示例预期结果
中间插入seqlist_insert(&list, -1, 10)返回 -1,不修改 size,不修改数组
尾部越界插入seqlist_insert(&list, list.size + 1, 10)返回 -1,拒绝插入
删除空表seqlist_erase(&list, 0)且 size == 0返回 -1,不崩不退出
访问越界seqlist_at(&list, list.size, &x)返回 -1,out不被写入

这里最需要强调的是访问越界:如果seqlist_at不校验pos >= list->size,它可能会返回一块未初始化的内存里的“垃圾值”。表面上看程序没崩,但实际上你读了不该读的内存。C 语言不像其它语言会给你抛异常,它是无声的,这比崩溃更危险。

所以,我要求自己的所有顺序表查询接口必须带返回值表示成功与否,把结果写到out指针里。这样调用方可以通过返回值判断是否安全,而不是直接拿返回值当有效数据。

4.3 缩容:该做还是不该做

删除了大量元素之后,capacity 还是 16,但 size 只有 2,这种“内存闲置”的情况要不要缩容?我的答案分两派。

如果是教学练习,不做缩容。理由是:缩容本身也是一次 O(n) 的搬移操作,如果你的程序接下来又要插入新数据,缩完还得扩容,一缩一扩之间白搬两次数据,性能反而更差,这叫“抖动”。教学场景里,让 size 和 capacity 的差距保持可见,反而能让你意识到动态数组的内存管理是“只增不减”的惯性设计。

如果是实际项目,规定一个“缩容阈值”是可以的,比如:size < capacity / 4时才缩容到一半。而且缩容也必须用realloc,缩容失败时要容忍——因为缩容只是优化,不是功能。缩容失败不应该让你认为的操作本身失败,所以返回码要和真正的错误路径区分清楚。

int seqlist_shrink(Seqlist *list) { if (list->size >= list->capacity / 4) { return 0; // 还没到缩容线,不动作 } int new_capacity = list->capacity / 2; int *new_data = (int *)realloc(list->data, new_capacity * sizeof(int)); if (new_data == NULL) { return -1; // 缩容失败,最坏情况是继续用原内存 } list->data = new_data; list->capacity = new_capacity; return 0; }

注意缩容里面的“阈值”既划在了capacity / 4,缩到capacity / 2,而不是一掉到一半立刻缩到一半,这就是为了防抖动。你能想到这一步,才算真的理解了动态扩容背后的性能逻辑。

5. 实测中最容易翻车的三个隐蔽问题

代码跑通只是起点。我在给别人 review 代码的时候,以下三个问题出现的频率最高,而且个个隐蔽,编译不报错,跑小数据也不崩,数据量大点才暴露。

5.1 插入元素的数量超过 INT_MAX / 2 时的溢出风险

倍增扩容有个隐藏 bug:如果 capacity 已经很大了,new_capacity = list->capacity * 2可能会溢出 int,变成负数。realloc收到负数长度后会认为参数非法,直接返回 NULL,程序就卡在扩容失败上。正常场景里没人会往顺序表里塞 10 亿个元素,但作为健壮性设计,这一步最好加上:

if (list->capacity > 1024 * 1024 * 1024) { return -1; // 防止溢出,也防止无意义扩容 }

或者直接把 capacity 改成size_t类型,从根上消除溢出问题。初学阶段用int做容量字段更方便打印,但要知道它是有天花板的。

5.2seqlist_destroy被连续调用两次的崩溃

free一个已经释放的指针是未定义行为,多数情况下直接崩溃。很多初学者在写完seqlist_destroy之后没有把data置 NULL,导致销毁后没有任何标记表示这块内存“没了”。

正确做法就是我代码里写的:data = NULL; size = 0; capacity = 0;。这样你再调一次free(list->data)时,free(NULL)是合法的空操作,不会崩。顺便也把capacity置零,这样后续如果误操作插入,也会在扩容前因为 capacity=0 触发异常路径,而不会悄悄写内存。

5.3 扩容失败但调用方直接忽略返回值

看这段代码:

if (seqlist_expand(&list) != 0) { return -1; }

问题是有些人在插入函数里把seqlist_expand的返回值丢了,直接继续在后面写list->data[list->size] = value。万一内存不足扩容失败,data还是原来的容量,这一写就越界了。

C 语言没有自动帮你处理“扩容失败应该怎么办”的机制,所以返回码检查绝对不能省。我的统一约定是:所有可能失败的操作一律返回-1表示失败,成功返回0。调用方至少要做一次判断。

5.4 用 valgrind 验证字节级的正确性

光靠肉眼和测试用例还不够,我强烈建议你跑一遍内存检测工具。Linux 下可以用valgrind:

gcc -g -o seqlist_demo seqlist.c valgrind --leak-check=full --show-leak-kinds=all ./seqlist_demo

正常的输出应该是All heap blocks were freed -- no leaks are possible。如果它报出Invalid write、Invalid read或者definitely lost,那就说明某个位置越界了或者内存没释放干净。我见过有人自信满满地写完代码,一查valgrind,发现destroy之前某个分支直接 return 了,内存泄漏,这种问题代码 review 很难看出来,工具一抓一个准。

6. 把这份代码扩展成“教学级”的进阶方向

写完基本版,不要急着关掉编辑器。我建议你顺手做三个小扩展,每一个都会击中一个盲区,总共花不了半小时。

6.1 加一个 resize 接口,不让容量只管不降

int seqlist_reserve(Seqlist *list, int new_capacity) { if (new_capacity <= list->capacity) { return 0; } int *new_data = (int *)realloc(list->data, new_capacity * sizeof(int)); if (new_data == NULL) { return -1; } list->data = new_data; list->capacity = new_capacity; return 0; }

这个接口的用处是:如果我知道接下来要插入 10000 个数据,我可以先seqlist_reserve(&list, 10000),一次性把内存分配到位,后续 10000 次插入完全不会触发扩容,性能提升是可感知的。

6.2 让 Seqlist 支持泛型内存块,不只用 int

C 语言没有模板,但我们可以用void *和memcpy实现泛型顺序表。核心思路是底层不再存int*,而是存void*的裸内存块;插入时传入“元素地址”和“元素大小”,扩容时按元素大小 * capacity计算字节数。这个扩展比较费劲,但它会逼着你搞明白 C 语言里“类型不过是内存的解读方式”这句话。写过一次void*版本之后,你再回去看 STL 的vector,思路就通了。

6.3 写一组辅助断言,让测试跑得更安心

除了一堆printf,还可以加几个assert检查:

#include <assert.h> void assert_seqlist_ok(Seqlist *list) { assert(list->size >= 0); assert(list->capacity >= list->size); assert(list->data != NULL); }

然后在每个 API 的开头和结尾各调一次。这样任何一步出错,程序会第一时间停下来告诉你“某一刻不变量被破坏了”,这个定位成本比崩溃后猜要低得多。

我在实际调试过程中发现,最初那个“扩容完打印地址”的小实验帮我建立了对realloc最直观的信任——你看着地址变化和数据完整,才知道内存里发生的事和教科书上写的一样。这份代码看起来简单,但每一次扩容、每一次搬移、每一次释放,背后都有值得你反复推敲的内存语义。把它吃透,后面学链表、队列、栈都会省力不少。

相关新闻

open-code-review:一套开放、高效的代码审查工程实践方案

open-code-review:一套开放、高效的代码审查工程实践方案

搞编程这么些年&#xff0c;代码审查&#xff08;code review&#xff09;这件事我踩过的坑比写过的 bug 还多。早年在小团队里&#xff0c;review 就是提测前让老大哥帮忙扫一眼&#xff0c;五分钟结束战斗&#xff0c;代码里藏没藏雷全凭他今天心情好不好&#xff1b;后来进大…

2026/10/19 16:51:38 阅读更多 →
Python实现的B站视频下载器:带进度条与断点续传

Python实现的B站视频下载器:带进度条与断点续传

简介&#xff1a;这是一份用于批量下载哔哩哔哩小视频的Python爬虫脚本&#xff0c;面向对爬虫实战感兴趣的初学者或个人视频收集爱好者。脚本以命令行方式运行&#xff0c;能够循环遍历10页排行榜JSON数据&#xff0c;提取视频标题与下载地址&#xff0c;并通过requests模块完…

2026/10/19 16:51:38 阅读更多 →
代码评审最佳实践:用GitLab与SonarQube搭建开放式审查流程

代码评审最佳实践:用GitLab与SonarQube搭建开放式审查流程

其实“代码评审”这件事&#xff0c;我们团队踩过的坑比收获的经验还多。最早是轮流在会议室里过PPT&#xff0c;后来改成在IM上丢链接让大家自己看&#xff0c;结果经常是合并之后才发现评审意见还挂在聊天记录里。直到我们把整套流程搬进代码仓库、用工具链把“评审”变成一道…

2026/10/19 16:51:38 阅读更多 →

最新新闻

DVWA暴力破解四个级别详解:从无防护到账户锁定的攻防演进

DVWA暴力破解四个级别详解:从无防护到账户锁定的攻防演进

1. 从一次登录框测试说起&#xff1a;暴力破解到底在破什么很多人第一次接触DVWA的暴力破解模块&#xff0c;注意力都放在“怎么把密码跑出来”上&#xff0c;结果跑通一次之后&#xff0c;脑子里留下的只有Burp的Intruder界面和一堆字典文件&#xff0c;换个环境就不知道从哪下…

2026/10/19 21:40:51 阅读更多 →
如何做好开放式Code Review:一套可落地的团队评审实践

如何做好开放式Code Review:一套可落地的团队评审实践

1. 为什么我坚持把 Code Review 做成“开放式”的先聊点实在的。我做了十几年开发&#xff0c;带过团队&#xff0c;也经历过各种规模的评审流程&#xff0c;从两个人结对互相看代码&#xff0c;到几十人团队的正式评审会议&#xff0c;都试过。一个特别明显的感受是&#xff1…

2026/10/19 21:39:49 阅读更多 →
WinForms DataGridView下拉框动态绑定与联动实战

WinForms DataGridView下拉框动态绑定与联动实战

简介&#xff1a;本资源是一份面向C#初学者与Windows桌面开发者的实用代码实践包&#xff0c;聚焦解决DataGridView控件中动态集成下拉列表&#xff08;ComboBox&#xff09;这一高频交互需求&#xff0c;适用于数据录入界面、配置管理模块等实际业务场景。压缩包共22个文件&am…

2026/10/19 21:39:49 阅读更多 →
以沟通为中心的坐席工作台:DeskcommCRM的设计与实践

以沟通为中心的坐席工作台:DeskcommCRM的设计与实践

1. 坐席工作台的现实困境&#xff1a;DeskcommCRM 要解决的真正问题1.1 痛点&#xff1a;每天在五个系统之间来回切换我这几年见过太多客服团队和销售团队的真实工作状态&#xff1a;早上打开电脑&#xff0c;登录客服工单系统看排队&#xff0c;打开IM工具回消息&#xff0c;切…

2026/10/19 21:39:49 阅读更多 →
开放式代码审查:构建高效协作与质量保障的工程实践

开放式代码审查:构建高效协作与质量保障的工程实践

1. 项目概述&#xff1a;为什么我们需要"开放"的代码审查先说结论&#xff1a;open-code-review 不是什么高深莫测的框架&#xff0c;它是一套把 code review 从"走流程"变成"沉淀技术资产"的工程实践方案。我把它理解成三件事的组合&#xff1a…

2026/10/19 21:39:49 阅读更多 →
STM32H743 FPU加速FFT实战:1024点仅300us,电机电流环救星

STM32H743 FPU加速FFT实战:1024点仅300us,电机电流环救星

简介&#xff1a;这份资源面向STM32嵌入式开发者与数字信号处理学习者&#xff0c;提供一套基于STM32H743、通过STM32CubeMX搭建的纯净FFT验证例程&#xff0c;解决全网缺少简单直接FFT性能验证工程的问题。压缩包共1698个文件&#xff0c;约49.05MB&#xff0c;以C源码与头文件…

2026/10/19 21:39:49 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →