C语言指针辨析:数组指针、指针数组与二级指针一次讲透

把int *p[3]、int (*p)[3]和int **pp并排放在一起,绝大多数人会在头两秒内卡壳。我当年在某项目里排查一个段错误,查了整整一下午,最后发现就是把数组指针和指针数组混着用了——声明差一个括号,函数传参时整个类型都变了,编译器报了一堆看不懂的警告,我硬是没往这儿想。这事之后我花了好几天把这三个概念彻底收拾明白,发现它们其实没有那么玄,只是被“指针”“数组”这两个词反复组合,再加上C语言那套声明语法优先级的坑,才显得傻傻分不清楚。

这篇就是专门给还在“分不清”的人写的。我会从声明语法、内存布局、函数传参和动态内存分配几个角度,把数组指针、指针数组、二级指针一次讲透,争取让你看完之后不用再背口诀,而是真正能“一眼辨认”。我尽量用大白话,但涉及的代码和内存模型都是实打实的,看完可以直接在编译器里跑。

1. 分不清的根源:C声明语法里那点优先级的事

先回答一个最根本的问题:为什么这三个东西这么容易混淆?因为它们长得太像了。int *p[3]和int (*p)[3]的差别只有一个括号,而int **pp看起来只是“多了一个星号”。但它们的含义南辕北辙——一个是数组,一个是指针,另一个还是指针,只是指向对象不同。

1.1 中括号的优先级比星号高,这是总开关

C语言里声明变量时,[](下标运算符)的优先级比*(指针运算符)高。这意味着什么呢?看int *p[3],解析器先看到p[3],知道 p 首先是一个有 3 个元素的数组,然后数组的元素类型是int *——所以 p 是一个“装着3个指针的数组”。

而int (*p)[3]用括号把*p先圈起来,括号的存在强制改变了结合顺序——p 首先是一个指针,这个指针指向的东西是“有3个整数的数组”。所以它叫“指向数组的指针”,简称数组指针。

打个比方:中括号像是一个“粘性很高”的容器,谁先靠上去,谁就先被装进去。不括起来,int * 和数字 3 就会像排队一样先组成数组;括起来,星号才有机会抢在前面。

1.2 三个声明对应的“步长”完全不同

真正决定你后续代码怎么写、能不能编译过的,是这些声明背后“一步跨多远”的问题。指针加减运算不是按字节走的,而是按它指向对象的体积跨步:

  • int *p:p + 1 跨越 1 个 int。
  • int (*p)[5]:p + 1 跨越 5 个 int,因为 p 指向的整个对象是“5个int组成的一整块”。
  • int **pp:pp + 1 跨越 1 个int *,也即一个地址值的宽度。

这就好比走路步长。普通人一步几十厘米,但你要是踩在一辆板车上,你推一下它,整个板车就移动了几米。数组指针就是这样一辆“板车”,它指向的是整个数组货柜,而不是货柜里的某一个格子。这个步长概念如果不建立起来,后面看arr、&arr、&arr[0]的类型对比时会继续懵。

2. 指针数组:一个装着地址的普通数组

先讲最容易理解的——指针数组。名字拆开就是“指针”和“数组”的偏正短语,核心词是“数组”。它本质上就是一个数组,只不过数组的每个元素是地址。

2.1 内存布局看起来是什么样

假设我这样声明:

int a = 10, b = 20, c = 30; int *parr[3] = {&a, &b, &c};

parr 里有三个格子,每个格子存的是一个int*地址值。内存示意图大致是:

  • parr[0] -> 存 &a 的值
  • parr[1] -> 存 &b 的值
  • parr[2] -> 存 &c 的值

sizeof(parr)在 64 位机器上是 24 字节(3 个指针 × 8 字节),如果换成int arr[3],那就是 12 字节(3 个 int × 4 字节)。所以指针数组里面装的不是整数,而是地址。你遍历它,拿到的是一个一个地址,再用*解引用,才能拿到真正的整数。

这里有一个很直觉的应用场景:如果你有多个字符串,且长度长短不齐,用指针数组比用二维字符数组要省空间。字符串常量本身就是“指向 const char 的指针”,所以声明一个const char *strs[],就相当于把一个一个字符串指针装进了同一个数组。

const char *names[] = {"alice", "bob", "charlie", "dora"};

names[0] 是 "alice",但准确说 names[0] 存的是"alice"首字母的地址。这种写法在做菜单映射、错误码转字符串、配置项解析时非常常见,我在不少小工具里都是用这个结构写的,遍历直接for (i = 0; i < 4; i++) printf("%s\n", names[i]);,方便得很。

2.2 典型应用:命令行参数的 argv

如果你写过 C 语言的 main,那么你已经见过最经典的指针数组:

int main(int argc, char *argv[])

argv 就是一个char *argv[],即“指针数组”。argv[0] 指向程序名,argv[1] 指向第一个参数,依次类推。string 类型的命令行参数天然就是一个字符串指针列表,所以接口设计成char *argv[]是顺理成章的。

这同时也说明了一个规律:当一组数据本身就是“地址/字符串”集合时,指针数组就是最自然的数据容器。说白了,它跟一个普通数组没有任何区别,只是元素的单位从 int、float 变成了地址。

2.3 和二维字符数组的取舍

不少初学者会拿char str[3][16]和char *str[3]对比,这个对比特别有用:

特性char str[3][16]char *str[3]
存储方式连续的一块48字节内存数组本身24字节,字符串另行存放
能否改字符串内容可以原地修改视字符串是否常量而定
空间利用率固定行长,短字符串浪费空间长度灵活,几乎不浪费
初始化方式逐字符拷贝只是把指针指向字符串常量
适用场景需要原地修改且行长均匀只读字符串集合、参数表

所以不需要硬背,只需要记住一点:你声明的是一个数组,数组元素是地址,那它就是指针数组。你声明的是一个指针,指针指向数组,那就是数组指针——这就是接下来要拆开的部分。

3. 数组指针:一个指向整块数组的指针

数组指针这个词的语法结构和“山谷里的风”类似,核心词是“指针”。它不是数组,是一个指针。这个指针的特殊之处在于,它指向的对象是数组,而不是数组里某一个元素。

3.1 为什么说步长是它的灵魂

来看这行:

int (*pa)[5] = NULL;

pa 是一个指针,它指向的类型是“有5个int的数组”。一旦 pa 指向了这样的数组,pa + 1 就会整体跳过 5 个 int 的距离。这一点和指针数组有着天壤之别——指针数组的parr + 1只是跳过 8 字节(即数组的第二个元素),而pa + 1跳过的是 20 字节的整块数组。

这个“大步长”特性正是它能当好二维数组“行指针”的原因。我们看一个完整例子:

int matrix[2][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10} }; int (*row)[5] = matrix; // matrix 是 int (*)[5] 类型

这时 row 指向第一行;row + 1指向第二行,因为它一次性跨越了一行的 5 个整数。访问元素时(*row)[i]是“解引用得到那一行的数组,再去下标 i”。更惯用的写法是row[0][i]、row[1][i],编译器会自动按行跨越。

3.2 数组名和数组指针的微妙关系

很多初学者以为“数组名就是指针”,这个说法必须修正:数组名在绝大多数表达式中会退化为首元素地址,但数组名本身不是一个指针变量,它的类型里保存着数组维度信息。matrix的类型是int (*)[5](指向长度为5的int数组的指针),而不是int *,更不是int **。

这也是很多人犯错的根源。你写:

int *p = matrix;

编译器大概率报警告,因为在matrix[0][0]是 int 的上下文里,matrix是一个行指针,行指针和单元素指针的类型不一致。强制转换也能编过,但访问方式就变成了一维视角,步长完全错了。

我最早在这个细节上栽过跟头:拿int *p = matrix之后想p[1][2],直接编译报错,因为 p 已经被降级成普通 int 指针,p[1]拿到的都不是第二行。后来我才明白,要让 p 能按行访问二维数组,必须声明成int (*p)[5],让 p 的步长跟行宽匹配。

3.3 数组指针不只是“二维数组专用”

二维数组遍历是数组指针最经典的应用,但它用在一维数组上也会带来不同的语义。比如:

int arr[10]; int (*p10)[10] = &arr;

这里&arr的类型是int (*)[10],指向的是“整个 arr 这个 10 元素数组”。*p10解引用后得到的是一个有着 10 个 int 的数组对象,(*p10)[i]等价于arr[i]。这时你会发现sizeof(*p10)是 40 字节,而不是 4 字节,很多人在sizeof上会踩坑。

顺带一提:&arr和arr在数值上一样,但类型完全不同。前者是数组指针,指向整个数组;后者退化成int *,指向首元素。数值相同不代表类型相同,接着在函数传参里这种差异会突然从“无所谓”变成“生死存亡”。

4. 二级指针:指向指针的指针

一级指针存的是普通变量的地址,那二级指针存的是什么?它存的是“指针变量”的地址。很多场景下,一级指针搞不定的事,二级指针轻松搞定——最典型的就是“要在函数里改变外面指针变量的值”和“动态构建二维数组”。

4.1 二级指针的内存模型别想得太玄

声明一个二级指针只需要一个星号接一个星号:

int a = 10; int *p = &a; // p 指向 a,p 的值是 a 的地址 int **pp = &p; // pp 指向 p,pp 的值是 p 的地址

内存中发生的事情是:a 在某个地址存着 10;p 这个指针变量在另一个地址存着 a 的地址;pp 又在另一个地址,存着 p 的地址。要拿到最终整数,你需要**pp——第一次解引用得到 p 的值,第二次解引用得到 a。

用一句话说就是:一级指针拐一个弯到数据,二级指针拐一个弯到指针,再拐一个弯才能到数据。

4.2 动态分配二维数组的两种姿势

很多人在需要“二维数组”时习惯写int a[3][4],但在运行时才知道行数、列数时,就必须借助二级指针动态分配。最常见的写法是分两步:

int **matrix; matrix = malloc(rows * sizeof(int *)); if (matrix == NULL) return -1; for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); if (matrix[i] == NULL) { /* 要记得清理之前已分配的内存 */ } }

matrix 先被分配成一个“指针数组”——每一行是一个指针,然后再让每个指针指向 malloc 出来的一行内存。此时matrix[i][j]访问的就是第 i 行第 j 列。用完之后的释放顺序必须是反的:先释放每一行,再释放 matrix 本身,这个顺序写错就会内存泄漏或者 double free。

也可以一次性分配一整块内存:

int **matrix = malloc(rows * sizeof(int *)); int *data = malloc(rows * cols * sizeof(int)); for (int i = 0; i < rows; i++) matrix[i] = data + i * cols;

这种方式的优点是内存连续,数据访问对缓存更友好,释放时只需释放两次(matrix 和 data),缺点是代码多一步指针计算。两种方式我都用过:如果行列规模不大、建模阶段图省事,用第一种;如果数据量大、追求访问性能,用第二种。

4.3 函数里要修改指针本身,必须传二级指针

这里有一个在封装初始化函数时必踩的坑。假设你想写一个函数来分配内存并把指针带出来:

void alloc_bad(int *p) { p = malloc(100 * sizeof(int)); }

调用方:

int *p = NULL; alloc_bad(p);

这个函数执行完,p 还是 NULL。为什么?因为 C 的参数是值传递,函数里的 p 是实参的副本,你让副本指向了新内存,外面的 p 根本没变。这就是经典的“传值改不了外边的变量”。

要让函数改变外边的指针变量,你必须收到“外边那个指针变量本身”的地址,也就是传二级指针:

void alloc_good(int **pp) { *pp = malloc(100 * sizeof(int)); } int *p = NULL; alloc_good(&p);

这时函数内部通过*pp = ...修改的是 p 变量自己,外面自然生效。理解了这一点,你再看自己在文件操作、链表初始化和各种资源管理代码里看到的int **handle、Node **head这类参数,一下子就通了——不是设计者故意写得绕,是 C 语言的值传递机制逼出来的。

5. 混战实测:用一段代码看清三者差异

概念讲了一堆,不如直接上代码。我写一段对比程序,把三种类型的声明、sizeof 和访问方式打印出来,你在自己的机器上跑一下就能看到这种差异有多么直观。

5.1 声明对比与 sizeof 差异

#include <stdio.h> int main(void) { int a = 10, b = 20, c = 30; int *parr[3] = {&a, &b, &c}; // 指针数组:3个指针 int (*parr2)[3]; // 数组指针:指向含3个int的数组 int **pp; // 二级指针 printf("sizeof(parr) = %zu\n", sizeof(parr)); // 24 printf("sizeof(parr2) = %zu\n", sizeof(parr2)); // 8(因为parr2本身是个指针) printf("sizeof(pp) = %zu\n", sizeof(pp)); // 8 /* 把 parr2 指向一个真正的数组 */ int tmp[3] = {100, 200, 300}; parr2 = &tmp; pp = &parr[0]; printf("parr[0] 的值是: %p\n", (void*)parr[0]); printf("(*parr2)[0] = %d\n", (*parr2)[0]); printf("**pp = %d\n", **pp); return 0; }

注意sizeof(parr)是 24 字节,而sizeof(parr2)只有 8 字节。这是因为 parr 本身是一个数组,数组的大小等于元素大小乘元素个数;而 parr2 只是一个指针变量——它指向多大的数组,那是“别人家”的事情,不是它自己的体积。

这个对比很能说明问题:指针数组里“数组”是本体,指针只是元素;数组指针里“指针”是本体,数组只是它指向的猎物。

5.2 典型错误之一:把数组指针当成指针数组来用

我见过的一种错误写法是这样的:

int arr[3][3]; int **pp = arr; // 试图用二级指针接二维数组名

这个在很多编译器下会报不兼容的类型警告,但勉强强制转换后能编译,运行结果大概率错乱。原因还是一样:arr的类型是“指向第一个元素(长度为3的int数组)的指针”,也就是int (*)[3],它和int **的内存含义完全不同。二级指针期待的是“一个指针变量的地址”,但arr这个名字表示的本来就是地址值本身,你拿它的值喂给int**,后面的pp[i][j]实际上在用一个错误的步长去扫内存。

如果你非要“看效果”,可以运行这段:

int arr[2][3]; int (*row)[3] = arr; // 对,这是正确的姿势 int *bad = (int*)arr; // 强行当成一维用,步长也错

row + 1跳过一整行 3 个 int;而bad + 1只跳过一个 int。同一个地址,因为“解释它的类型”不同,加法运算后的落点也完全不同。理解了步长差异,你就明白为什么编译器会拦着你不让你拿int **去接二维数组名——它们跨步的计量单位根本不在一个频道上。

5.3 典型错误之二:释放顺序写反导致崩溃

动态分配的二维数组,释放顺序是必须背下来的经验:

/* 正确:先释放内层,再释放外层 */ for (int i = 0; i < rows; i++) free(matrix[i]); free(matrix); /* 错误:先 free(matrix) 的话,matrix[i] 已经变成野指针 */ free(matrix); for (int i = 0; i < rows; i++) free(matrix[i]); // 踩非法地址

这个错误我在帮同事排查问题时见过不止一次。症状是一会儿崩溃、一会儿不崩溃,特别难定位。实际上崩溃发生在第二轮循环访问matrix[1]时,因为整个指针数组内存已经被归还给系统,读到的地址值已经不可信。像这种问题,只要动手画一下内存模型,很快就能看出来,不需要靠猜。

6. 识别技巧、记忆套路与调试建议

到了最后这一部分,我想分享一些我实际工作中沉淀下来的辩认和调试经验,不一定在教科书上看到过,但对快速解决问题很有帮助。

6.1 一眼识别的“三步法”

遇到任何复杂的指针声明,我都用三步走:

  1. 先找变量名。
  2. 看变量名最近处有没有括号——如果变量名右边先出现[],那它是数组;如果变量名先被*绑定(比如出现在圆括号里),那它是指针。
  3. 再看它剩下的修饰是“什么数组”还是“指向什么”。

举例:int *p[3],变量名 p 右边紧跟[3],所以是数组,元素是int *。int (*p)[3],变量名 p 先被括号里的*绑住,所以是指针,指向int [3]。int **pp,变量名 pp 右边没有任何[],只有两个*,所以它是指向指针的指针。这个方法几乎可以拆解 C 语言里 90% 的复杂声明,包括函数指针那类更怪的写法。

6.2 调试时先画内存图,再决定下一步

遇到指针相关 bug 时,我习惯先在纸上画三样东西:变量的地址、变量存的值、这个值指向的目标。每次画完,多半 bug 原因就清楚了。比如segment fault有时不是因为你解引用了空指针,而是你把它当成了另一个类型的指针,导致错位访问。画图能让你迅速看到“步长”和“存储类型”是否匹配。

用调试工具时也可以打印类型信息(比如使用编译器对sizeof或报错信息的提示),不要急着改代码。编译器对类型不兼容的警告非常精准,往往第一行警告就是病根——你读懂它,就不会去改无关的行。

6.3 几条从实战里攒下来的经验

最后给几条具体的操作建议,都是我踩过或者帮别人排查过的坑:

  • 函数参数里,当你需要传出指针本身时,用二级指针;当你只需要修改指针指向的内容时,用一级指针。不要混用。
  • 数组名和&数组名数值相同,但类型不同,前者常退化成元素指针,后者是数组指针。需要多维遍历时,优先考虑int (*)[N]而不是int **。
  • 声明复杂类型时加括号一定不会错:宁可多写一对括号,也不要赌优先级。
  • 释放动态分配的二维数组,务必记住“先内层、后外层”,释放完把指针置成 NULL,避免后续误用。

拿一句话总结这三个东西就是:指针数组是“一堆指针”,数组指针是“指向堆的指针”,二级指针是“指向指针的指针”。它们分别对应着数据结构的三种组织习惯——平铺一堆地址、按行锁定一块连续区域、通过一层重定向再找指针。真正常用起来,没有谁比谁更高级,只有用对场景的问题。

我在实际项目里最深的感受是:这类概念看着像考试题,但一旦你在代码里被int**和int (*)[N]搞出过一两个内存错误,就再也不会忘了。别担心记不住,多跑几个不同写法的 hello world 程序、多打印几次地址和 sizeof,比背十遍口诀都管用。下一次再有人问你“数组指针和指针数组的区别”,你可以吐槽一句:括号真的能救命。

相关新闻

LlamaIndex RAG评估实战:从原理到可落地流程

LlamaIndex RAG评估实战:从原理到可落地流程

做RAG应用的人应该都有过这种体验&#xff1a;demo阶段跑得挺欢&#xff0c;一上生产就开始翻车。检索回来的片段和问题驴唇不对马嘴&#xff0c;模型明明看到了正确答案却在回答里胡编乱造&#xff0c;更气人的是它在不知道答案的时候还一本正经地"编故事"。这种时候…

2026/10/20 12:54:46 阅读更多 →
C语言实现顺时针打印矩阵:三种解法与边界陷阱详解

C语言实现顺时针打印矩阵:三种解法与边界陷阱详解

不铺垫了&#xff0c;直接说正事。剑指 Offer 29 这道“顺时针打印矩阵”&#xff0c;放在《剑指Offer》里算是看着简单、写起来容易翻车的一道经典题。我在给同学做模拟面试时&#xff0c;亲眼见过很多人在白板上把矩阵画得清清楚楚&#xff0c;一到代码就开始纠结“下边这条边…

2026/10/20 12:54:46 阅读更多 →
Transfuser端到端自动驾驶:注意力融合与仿真实践全解析

Transfuser端到端自动驾驶:注意力融合与仿真实践全解析

做了两年多端到端自动驾驶的仿真实验&#xff0c;各种模型架构也差不多摸了一遍。说实话&#xff0c;很多论文里的方案&#xff0c;真到自己动手复现的时候&#xff0c;总会觉得差点意思&#xff0c;要么是训练不稳定&#xff0c;要么是泛化能力不行&#xff0c;在仿真器里跑得…

2026/10/20 12:54:45 阅读更多 →

最新新闻

智能驾驶感知算法岗面试高频考点与实战指南

智能驾驶感知算法岗面试高频考点与实战指南

智能驾驶感知算法岗的面试&#xff0c;跟我平时帮人做模拟面试时看到的准备方式&#xff0c;几乎不在一个频道上。很多人拿到一份八股题集就开始背&#xff1a;Transformer结构、Focal Loss公式、NMS流程、BEV感知演进……背得滚瓜烂熟&#xff0c;结果一进面试间&#xff0c;被…

2026/10/20 13:38:46 阅读更多 →
Hyper-Laplacian先验快速去卷积:从原理到工程实现

Hyper-Laplacian先验快速去卷积:从原理到工程实现

简介&#xff1a;这份资源是图像去模糊方向经典论文《Fast Image Deconvolution using Hyper-Laplacian Priors》的配套Matlab实现&#xff0c;面向从事图像恢复、计算摄影与视觉研究的学生和开发者&#xff0c;用于解决模糊图像复原中卷积核未知、边缘细节难以保持的问题。压缩…

2026/10/20 13:38:46 阅读更多 →
TensorFlow从零手写ResNet18:80行代码详解残差连接与BasicBlock实现

TensorFlow从零手写ResNet18:80行代码详解残差连接与BasicBlock实现

1. 为什么80行代码值得认真拆解ResNet18这个网络结构&#xff0c;搞深度学习的人几乎没有不知道的。2015年那篇残差学习的论文出来之后&#xff0c;基本上把深层网络训练难的问题给解决了。但很多人第一次接触ResNet&#xff0c;要么是直接调tf.keras.applications.ResNet50&am…

2026/10/20 13:38:46 阅读更多 →
Hyper-Laplacian先验快速去卷积:查找表加速非凸优化

Hyper-Laplacian先验快速去卷积:查找表加速非凸优化

简介&#xff1a;这份资源是图像去模糊领域经典论文《Fast Image Deconvolution using Hyper-Laplacian Priors》的配套Matlab实现&#xff0c;面向从事图像恢复、计算摄影与视觉研究的学生和开发者&#xff0c;用于解决模糊图像复原及盲去卷积中卷积核与清晰图像联合估计的问题…

2026/10/20 13:38:46 阅读更多 →
10月arXiv机器学习论文盘点:强化学习、长尾分布与数据效率

10月arXiv机器学习论文盘点:强化学习、长尾分布与数据效率

1. 另类入口&#xff1a;当arXiv变成一座每周更新的森林如果说学术搜索像一张精准的地图&#xff0c;那arXiv的cs.LG板块就更像一片每周定时更新的森林——你永远知道自己能在这里找到最新的机器学习论文&#xff0c;但也正因为"最新"这两个字&#xff0c;每次走进来…

2026/10/20 13:38:46 阅读更多 →
AI编程工具进化:从代码补全到能独立交付项目的“许愿机”

AI编程工具进化:从代码补全到能独立交付项目的“许愿机”

说实话&#xff0c;我已经不太想用“Coding Agent”来称呼手上这批AI开发工具了。不是矫情&#xff0c;是最近连续几周用它们做真正的项目——不是教它写个冒泡排序或者生成一段样板代码&#xff0c;而是从需求梳理、数据结构设计到部署脚本、线上监控告警的完整链路走下来——…

2026/10/20 13:37:45 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →