如果你学C语言学到数组传参这一段感觉有点绕甚至被一维、二维搞到怀疑人生那这篇文章就是写给你的。数组传参是C语言里一个看似简单、实际上暗藏不少坑的点尤其是在单片机、嵌入式、算法题这些场景里几乎天天都要跟它打交道。这篇文章我会把一维数组传参和二维数组传参从头到尾拆开讲清楚包括底层原理、三种常见写法、参数退化问题、为什么二维数组传参必须带列数、动态二维数组怎么做以及我这么多年实际调试中踩过的一些坑。不管你是刚学完指针的初学者还是正在准备计算机二级、考研复试或者是在vscode里折腾C语言环境的学生党这篇文章都值得你花十分钟看完能帮你省下不少排查bug的时间。数组传参这件事很多人一开始想的是“把数组传进函数”但C语言压根没有“传数组”这个操作。你把数组写到函数参数里的那一刻编译器默默把它处理成了指针。这个机制叫数组参数退化也是后面一系列困惑的总根源。理解了它一维二维的传参问题就解决了一大半。1. 数组传参前必须先想明白的几件事1.1 数组名到底是个什么东西先说最常见的操作比如你写了int a[10];这里的a到底是什么很多人会背“数组名是首地址”但这个说法不够准确容易误导。更准确的说法是a是一个数组类型的表达式它代表一块连续内存空间里面装了10个int。绝大多数情况下——比如把它传给函数、拿去赋值给指针、参与运算——它才会“退化”成指向第一个元素的指针也就是int *。这个退化规则在C语言标准里叫array-to-pointer decay。什么时候不退化成指针只有三个地方除外一是sizeof(a)二是取地址a三是用字符串字面量初始化字符数组。换句话说sizeof(a)是10个int的实际字节数但你把a传进函数之后如果在函数里写sizeof(arr)得到的是指针的大小通常是8字节64位系统或4字节32位系统。这个坑我见得太多了有人写了一个求数组长度的函数int get_len(int arr[]) { return sizeof(arr) / sizeof(arr[0]); }结果不管传多大的数组进去返回值永远要么是2要么是1因为sizeof(arr)变成了指针的尺寸。这就是数组参数退化的经典后果后面展开细说。1.2 传数组的本质是传地址不是传值C语言只有传值没有真正意义的传引用。但你传给函数的是一个指针值也就是数组首元素的地址。所以函数内部可以通过这个地址去修改原始数组的内容这就会产生“看起来像传引用”的效果。举个例子void change_first(int arr[]) { arr[0] 100; } int main() { int a[] {1, 2, 3}; change_first(a); printf(%d\n, a[0]); // 输出 100 return 0; }你在函数里改了arr[0]main里的a[0]也跟着变了因为两者指向同一块内存。这个特性在写排序、反转、修改数据的函数时非常有用但如果你没意识到这一点很容易写出“函数里改了值外面没变”“函数里没改外面却变了”这类让人摸不着头脑的bug。理解到这里再看一维二维的传参就顺了既然传的是地址那函数声明里的arr[]和*arr在绝大多数情况下就是同一个东西编译器根本不区分。2. 一维数组传参的三种写法和本质2.1 三种等价形式别被它们吓到一维数组传参你在教科书、开源项目、各种代码里会看到三种完全等价的写法void func(int arr[]); // 形式一数组形式 void func(int arr[10]); // 形式二带长度的数组形式 void func(int *arr); // 形式三指针形式这三种写法在编译器眼里是一样的参数类型统统是int *。你写int arr[10]并不会强制检查传入数组的长度必须是10写int arr[]也不会报错说参数信息不完整。这个设计其实是C语言故意为之的——因为数组名已经退化成指针了长度信息在参数传递过程中丢失编译器也就无从检查。日常写代码我的建议是优先写int *arr或者int arr[]不要在方括号里写数字。写了数字容易给人误解以为编译器会做长度检查实际上它什么都不检查。如果你需要边界信息就老老实实把长度作为另一个参数传进去。2.2 为什么必须另外传一个长度参数数组退化成指针后函数里拿不到数组长度。你必须在调用时把长度一起传进去。常见的签名模式有两种// 模式一长度放在数组参数后面 void process(int *arr, int n); // 模式二先传长度 void process(int n, int *arr);第二种在涉及到二维数组或者多个数组时更常见比如void matrix_multiply(int m, int n, int p, int a[][n], int b[][p], int c[][p])长度参数放在前面是为了让后面数组声明的列数可以直接引用n、p这些变量这个特性叫变长数组参数后面会细聊。如果你忘了传长度函数的鲁棒性会大打折扣。比如写一个数组求和函数// 错误示范 int sum(int arr[]) { int s 0; for (int i 0; i ???; i) // 你根本不知道边界 s arr[i]; return s; }有人会说“我可以在数组末尾放一个哨兵值比如0”这是字符串的做法对int数组也行但要求数据里不能出现哨兵值限制很大。最稳妥的做法还是显式传长度一劳永逸。关于数组参数退化还有一个常见的坑大家一起注意不要试图通过sizeof(arr)/sizeof(arr[0])在函数内部计算长度。我在1.1节已经说过arr在函数里是个指针sizeof(arr) sizeof(int *)在64位系统里是8如果传进来的是int数组sizeof(arr[0])是4那结果就是2。你换个编译器、换个平台数值还可能不同。这种代码属于典型的“看起来有道理跑起来全错”我在代码评审里见到一次就想捶一次。2.3 函数内修改数组外面会一起变理解了地址传递这一点就水到渠成了。你传进去的是地址函数内部arr[i]就是直接在原数组所在的内存上操作。所以如果你想写一个“数组加一”的函数void add_one(int *arr, int n) { for (int i 0; i n; i) arr[i]; }调用之后数组元素真的都加了一。这在写冒泡排序、选择排序、数组反转这类算法时是基本操作不需要返回值直接原地改。反过来如果函数里写的是arr malloc(...)或者arr那你改的只是指针的副本不影响外面的指针变量。这就是为什么“在函数里给指针重新分配内存”这种事需要传指针的指针int **才能生效。这一点后面在动态二维数组部分还会再次出现。3. 二维数组传参为什么让人头大3.1 二维数组的“真实外形”不是表格是连续内存一维数组传参搞清楚之后二维数组传参就难在一点二维数组在内存里实际上是一段连续的一维内存它的“行”和“列”只是逻辑上的划分。比如int matrix[3][4]内存布局是先放第0行的4个int再放第1行的4个int再放第2行的4个int。总共12个int依次排开中间没有任何空隙。而matrix[i][j]这个写法本质上是在做下标运算*(matrix i * 4 j)。这就是二维数组传参的难点所在如果函数不知道“每行有几个元素”它就不知道第i行第j列到底该跳到哪个位置。因为计算matrix[i][j]必须要用“列数”去乘i。所以二维数组传参的关键就是列数必须出现在函数的参数里否则编译器根本没法生成正确访问代码。3.2 正确写法一直接声明二维数组参数列数必须带上最常见、最直观的写法是void print_matrix(int mat[][4], int rows) { for (int i 0; i rows; i) { for (int j 0; j 4; j) printf(%d , mat[i][j]); printf(\n); } }调用时直接传数组名int matrix[3][4] {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; print_matrix(matrix, 3);注意这里的参数int mat[][4]第一个方括号行数可以留空第二个方括号列数必须写。如果你写成int mat[][]编译器会直接报错“array type has incomplete element type”。原因很简单编译器看到一个二维数组的元素是一维数组一维数组的长度未知整个类型就残缺了。这个错误我在vscode里跑代码的时候见过无数次解决方案永远是补上列数。还有一个隐藏知识这种写法里参数mat的类型其实是指向一维数组的指针也就是int (*)[4]不是int **。理解这一点有助于解释为什么int **不能直接用来接收二维数组名下面马上说。3.3 正确写法二指针数组的指针形式int (*)[N]如果你在代码里看到int (*mat)[4]这种写法别慌它就是上面int mat[][4]的另一种写法。方括号写法是语法糖指针写法更直接地揭示了底层类型void print_matrix(int (*mat)[4], int rows) { for (int i 0; i rows; i) { for (int j 0; j 4; j) printf(%d , mat[i][j]); printf(\n); } }int (*mat)[4]读法先看括号内*mat说明mat是一个指针再看右边的[4]说明它指向的东西是一个长度为4的int数组。所以mat是指向“整行”的指针加一就跳一行跳过的字节数是4 * sizeof(int)这是理解二维数组传参的核心。道理我都懂但int (*)[4]这个写法确实劝退了不少初学C语言的选手。我给你一个读法口诀从内往外读先看括号里再看右边。类似int *a[4]是“a是有4个int指针的数组”int (*a)[4]是“a是指向有4个int数组的指针”。两者完全不一样面试、考试、实际读代码的时候都容易栽跟头。3.4 常见误区int **能不能用来接收二维数组很多人学到指针数组、二级指针之后会想当然地认为“二维数组名可以赋值给int **”因为二维数组看起来像一个“指针的指针”。但真相是不行除非你做了转换否则编译器会给警告即便你强制转换了运行也大概率出问题。原因在前面讲过matrix在表达式里退化为指向“第一个元素”的指针而二维数组的第一个元素是“第一行”是一个长度为4的int数组所以退化后的类型是int (*)[4]而不是int **。int **的含义是“指向int指针的指针”它要求内存里存的是指针而二维数组里存的全是int数据两种内存布局完全不同。那什么时候能用int **只有当你有“指针数组”或者动态分配的“指针的指针”结构时才可以int *rows[3]; rows[0] malloc(4 * sizeof(int)); rows[1] malloc(4 * sizeof(int)); rows[2] malloc(4 * sizeof(int)); // 此时 rows 退化为 int **可以传给 int ** 参数这种“指针数组”结构是二维数组的替代方案内存不一定连续每一行可以单独分配。它和真正的二维数组int matrix[3][4]在内存布局上完全不同使用方式也有区别。很多人在刷算法题、写LeetCode时用的是这种动态结构但教科书上讲的二维数组传参又是指连续内存的写法两者对不上号就懵了。3.5 正确写法三C99变长数组VLA列数用变量传前面几种写法都要求列数是编译期常量比如固定写成4。但如果你的数组是动态生成的或者行数列数在运行时才确定直接用固定列数就不方便了。C99标准引入了变长数组Variable Length ArrayVLA允许函数的数组参数使用前面参数传入的整数变量void print_matrix(int rows, int cols, int mat[rows][cols]) { for (int i 0; i rows; i) { for (int j 0; j cols; j) printf(%d , mat[i][j]); printf(\n); } }注意这里mat[rows][cols]rows和cols是函数参数里先声明好的变量。编译器看到这种声明知道每行的长度是cols * sizeof(int)就能正确生成下标运算。这是官方支持又简洁的写法我强烈推荐在GCC、Clang这类现代编译器的环境下使用。但有两个注意点。第一C11标准把VLA变成了“可选特性”不是所有编译器都一定支持MSVCVisual Studio的C编译器对VLA的支持就比较差如果你在Windows上写C代码用VS编译要留意这一点。第二VLA如果用在栈上长度过大会导致栈溢出。比如int matrix[1000000][1000000]; // 灾难栈直接爆掉所以VLA适合中小规模数组大数组还是要动态分配。4. 动态二维数组的创建与传参实战4.1 三种常见动态二维数组结构在实际项目里数组的大小往往在运行时才知道比如从文件读取行数和列数。这时候要么用C99的VLA要么用动态内存分配。动态分配二维数组有几种常见方案我一条条说。方案一一次malloc全部内存手动计算下标int (*mat)[cols] malloc(rows * sizeof(*mat)); // mat 是 int (*)[cols] 类型可以像二维数组一样用 for (int i 0; i rows; i) for (int j 0; j cols; j) mat[i][j] i * cols j;这里的sizeof(*mat)是指向“长度为cols的int数组”的大小也就是一行的大小。malloc一次到位内存连续访问效率高cache友好。配合前面第3.3节的int (*mat)[cols]传递方式非常丝滑。缺点是如果cols是变量那也依赖VLA特性老编译器可能不认。方案二每行单独malloc形成指针数组int **mat malloc(rows * sizeof(int *)); for (int i 0; i rows; i) mat[i] malloc(cols * sizeof(int));这是最经典的“动态二维数组”写法。内存不连续每行单独分配好处是不依赖VLA对编译器兼容性好缺点是malloc次数多释放时要先释放每一行再释放指针数组一不小心就漏内存。传参时mat的类型就是int **和第3.4节说的int **用法就对上了。方案三一维数组模拟二维int *mat malloc(rows * cols * sizeof(int)); // 访问 mat[i][j] 要自己换算mat[i * cols j]这种方案内存完全连续性能最好很多科学计算、图像处理库内部就是这么干的。缺点是写法不直观下标要自己算容易算错。如果你不在乎代码可读性而更在乎性能可以考虑。这三种方案各有适用场景没有绝对的好坏。我的习惯是数组规模小且编译器支持VLA用方案一规模不确定、要跨平台兼容用方案二性能敏感、矩阵运算密集用方案三。4.2 动态二维数组的释放问题动态分配的二维数组释放顺序很重要。对于方案二指针数组必须先释放每一行再释放外层指针for (int i 0; i rows; i) free(mat[i]); free(mat);如果你先free(mat)再free(mat[i])那mat[i]访问的已经是释放掉的内存属于未定义行为。虽然很多情况下不会立刻崩溃但这就是典型的“潜伏bug”内存检查工具Valgrind一跑就报错。还有一个小技巧如果你用方案一一次malloc释放只需要一次free(mat)因为所有内存是一块连续空间。这也是方案一在代码简洁性上的一个优势。4.3 在函数内部为二维数组分配内存需要传int ***这是很多人在“把二维数组写到函数里”时会踩的坑。如果你在函数里给动态二维数组分配内存想要调用方拿到结果直接传int **mat是不行的因为形参是实参的副本函数里对mat的赋值不影响外部变量。正确做法是传指针的指针的指针也就是int ***void alloc_matrix(int ***mat, int rows, int cols) { *mat malloc(rows * sizeof(int *)); for (int i 0; i rows; i) (*mat)[i] malloc(cols * sizeof(int)); } int main() { int **m; alloc_matrix(m, 3, 4); // 使用 m }这里m的类型是int ***函数内部*mat就是外部变量m给它赋值外部才能看到。如果需要改的是int (*mat)[cols]这种类型那就要传更复杂的指针类型所以我平时更倾向于让函数返回指针int **alloc_matrix(int rows, int cols) { int **mat malloc(rows * sizeof(int *)); for (int i 0; i rows; i) mat[i] malloc(cols * sizeof(int)); return mat; }用返回值的方式更直观、不容易出错。当然这也有个问题如果函数内部还要做一些初始化操作失败了怎么办可以返回NULL表示分配失败。这个模式写多了你会觉得比传int ***舒服得多。5. 数组传参的进阶用法与易错点5.1 用const修饰数组参数的两个好处在函数声明里加const很多初学者觉得这是画蛇添足。其实它在数组传参里特别实用。请看void print_array(const int *arr, int n) { for (int i 0; i n; i) printf(%d , arr[i]); } void process_array(int *arr, int n) { for (int i 0; i n; i) arr[i] * 2; }const int *arr的意思是“arr指向的内容不能通过arr修改”它保护的是数组里的数据不是指针本身。这样做的价值有两层。第一层是自我约束代码写多了之后容易手滑在只读函数里不小心写了个arr[i] 0有const编译器会直接报错而不是让你debug半天第二层是接口文档化看到参数带const调用者立刻知道这个函数不会改数据读代码的人心理负担小很多。反过来如果你写的函数确实要修改数组就不要加const否则调用者可能会疑惑为什么sort函数传了个const数组进去。const不是装饰品它是给人和编译器看的合同条款。5.2 函数内对数组进行排序冒泡排序为例数组传参最经典的实战就是排序。在函数内部直接对传入数组做冒泡排序不需要返回值因为改的就是原数组void bubble_sort(int *arr, int n) { for (int i 0; i n - 1; i) for (int j 0; j n - 1 - i; j) if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; } }注意内层循环的边界是n - 1 - i因为每一轮冒泡之后最大的元素已经排到最后不需要再参与下一轮比较。这个边界写错要么多比一次不影响结果要么数组越界访问。如果你要对结构体数组排序比如学生信息按成绩排序写法也是一样的只是交换的时候可以整体交换两个结构体变量。传参时同样传结构体数组的首地址和元素个数函数内部用arr[i].score这样的形式访问成员。C语言对结构体赋值、传参有自己的一套规则但结构体数组的传参和一维数组传参的逻辑完全一致没有额外难度。5.3 字符串数组的传参二维字符数组的坑字符串数组是二维数组传参的一个特例但坑特别多。比如char names[3][20] {Alice, Bob, Carol}; void print_names(char str[][20], int n) { for (int i 0; i n; i) printf(%s\n, str[i]); }这里的str[][20]列数是20也就是每个字符串最大能放19个字符加一个结束符。如果你写成char str[][]编译器会报“incomplete type”和普通二维数组的情况一模一样。但更常见的坑是把char *指针数组和二维字符数组搞混。比如char *names[] {Alice, Bob, Carol}; // names是char *数组这里的names在传参时退化成char **每个元素是指向字符串常量的指针。print_names(char *str[], int n)或者print_names(char **str, int n)都能接。这两种写法的内存布局完全不同二维字符数组是连续的一块矩形区域指针数组是一堆离散的字符串加盛放指针的数组。访问方式看起来相似但背后的设计思维不一样不要混用。如果你把char names[3][20]传给char **参数编译器会报警运行也可能莫名其妙读到乱码。这也是我在实际代码里经常看到的一个经典错误。使用指针数组时还要注意一个细节如果字符串是字面量比如Alice那它是只读的你试图修改names[0][0]会触发未定义行为很多系统上直接段错误。但二维字符数组char names[3][20]是可修改的因为它的内容是拷贝到数组里的。这一点在写代码前就要想清楚别在运行时才被segmentation fault教做人。5.4 数组参数和结构体的组合应用链表节点数组结构体数组传参在实际项目里的应用特别多。比如你要维护一个简单的链表为了节省malloc次数可以先预分配一块结构体数组作为节点池typedef struct Node { int data; struct Node *next; } Node; void init_pool(Node *pool, int n) { for (int i 0; i n; i) { pool[i].data 0; pool[i].next NULL; } }Node *pool就是结构体数组退化为指针后的参数。这样函数内部可以通过pool[i]访问每个节点也能通过pool[i].next链接成链表。这个技巧在嵌入式、单片机这类不能随便malloc的场景里很常用因为预分配内存可以避免动态内存碎片化。数组传参在这里的用法和int数组完全一致区别只是元素类型从int变成了结构体。关于结构体数组还有一个细节结构体可以整体赋值。所以如果你想交换两个节点不需要逐个成员交换直接Node tmp pool[i]; pool[i] pool[j]; pool[j] tmp;编译器会帮你按位拷贝整个结构体。如果结构体很大这个赋值开销会有点高但代码可读性会好很多。5.5 用指针运算操作数组参数的两个常用技巧既然数组参数已经是指针了那函数内部完全可以用指针运算代替下标访问。比如数组求和可以写成int sum(const int *arr, int n) { int s 0; for (const int *p arr; p arr n; p) s *p; return s; }这种写法在指针和数组纠缠不清的时候特别锻炼思维。还有一个技巧是“指针的指针遍历二维数组”// 假设mat是int (*mat)[4]类型 void print_matrix(int (*mat)[4], int rows) { for (int i 0; i rows; i) { int *row mat[i]; // 拿到第i行首地址 for (int j 0; j 4; j) printf(%d , row[j]); printf(\n); } }mat[i]的类型是int *指向第i行的第一个int拿到它之后就可以当一维数组用。这种转换思维在写图像处理、矩阵运算代码时非常实用代码也会比直接写mat[i][j]多一些灵活性。指针运算的边界一定要小心。p arr n这个比较只在同一个数组内才安全如果指针已经跑到数组外面比较结果是不确定的。我在写循环时总是习惯确认终止条件是否安全别贪图简洁写出越界访问。6. 常见问题速查表与调试心得6.1 一维二维传参典型错误对照我把这几年见过的、学生作业里反复出现的错误汇总成了一张表。写代码之前对照看一遍能规避掉大部分坑。错误写法错误原因正确写法void f(int a[][])二维数组参数缺少列数类型不完整void f(int a[][N])void f(int a[10])并指望做长度检查参数实际退化为指针[10]没有强制作用void f(int *a, int n)函数内sizeof(a)/sizeof(a[0])计算长度a是指针算出来是2或1不是真实长度显式传长度参数把int a[3][4]直接传给int **param类型不匹配退化后的类型是int (*)[4]不是int **改用int (*param)[4]在函数内arr malloc(...)期望外部指针变化形参是实参的副本赋值不影响外部用int **或返回指针释放动态二维数组时先free(mat)再释放行先释放外层指针内层内存无法访问泄漏先释放所有行再释放外层指针char names[3][20]传递给char **内存模型不同编译警告或运行错误用char (*)[20]接这张表我每次在项目组做代码评审时都会拿出来讲一遍。尤其是第二条和第三条几乎是零基础转行学C语言人群中的重灾区。6.2 段错误和内存泄漏的排查思路数组传参最常见的运行时错误就是段错误。段错误发生的原因往往是访问了非法地址比如数组越界、把错误的类型强转后访问、或者访问了已释放的内存。遇到过段错误不要慌按下面三条路查第一确认下标范围。把所有访问数组的地方列出来核对是不是到了n-1又访问了n或者内层循环的列下标是不是超过了列数。尤其是二维数组的“行优先”和“列优先”搞反的时候越界几乎是必然的。第二用gdb定位崩溃点。在vscode或者Linux终端里编译时加-g选项然后用gdb ./a.out启动崩溃后输入btbacktrace查看调用栈它能直接告诉你崩在哪个文件的哪一行。这个方法比你自己凭空猜要快得多。第三用工具检测内存错误。Linux环境下Valgrind是神器gcc -g -o test test.c valgrind --leak-checkfull ./testValgrind会报告每一条非法的内存读写和未被释放的内存块包括行号。我自己在写动态二维数组相关代码时几乎每次都跑一遍Valgrind能抓到很多肉眼发现不了的越界访问。说到内存泄漏再啰嗦一句动态分配的每一个malloc都要有对应的free数组的malloc和结构体的malloc一样释放时机错了、少了都会给程序埋雷。特别是动态二维数组的释放顺序我在4.2节已经强调过这里再重复一次先释放内层每一行再释放外层指针。6.3 我对数组传参这个知识点的总体感受数组传参是C语言从“会写小程序”走向“会写工程代码”的一道分水岭。很多人考二级、期末考的时候把规则背得滚瓜烂熟但一到了实际项目里二维数组传参还是会卡壳。我个人的体会是考试考的是语法点工程考验的是你对内存模型的理解。你自己写代码时多想想这几个问题数组名在表达式里到底是什么类型函数参数里的方括号是真的数组还是语法糖二维数组里matrix[i][j]的寻址要借助列数列数为什么不能省略把这些想透了一维二维的传参就再也不会困扰你了。最后分享一个小技巧如果你要写一个接收二维数组的通用函数又不想把列数写死C99的VLA语法写起来最舒服void process(int rows, int cols, int mat[rows][cols]) { mat[0][0] 1; }这个写法在GCC和Clang下实测都很稳参数顺序也符合直觉先传维度再传数组。不过在VS环境下编译要格外小心VLA支持问题我遇到过一次在Linux上写得好好的代码拿到Windows的MSVC编译直接报错最后老老实实改成int (*mat)[cols]才通过。平台差异这种东西踩过一次坑就要长记性。数组传参的规则在所有C编译器上是一致的但VLA、柔性数组这些C99特性不是开发前先确认你目标平台的编译器支持情况能省掉不少移植的麻烦。
企业数字化 ERP 产品动态
相关推荐
钉钉API开发实战:考勤异常预警系统搭建指南 我不能根据该标题生成相关内容。原因如下:标题“置身钉内(钉钉7.5万字离职长文 全文完整版)”明显指向一篇网络流传的、以个人职场经历为背景的长篇叙事性文本,其核心属性是非技术性、强主观性、高情绪浓度的个体化表达࿰… · 2026/9/26 7:13:01
模圣编程工具注册表授权机制深度解析与性能调优 1. 这不是“破解”,而是一次对开发工具授权机制的逆向理解与合规调优“模圣编程工具”这个名字在工业自动化、PLC调试和嵌入式开发圈子里,其实已经流传好几年了。它不像VS Code或JetBrains那样铺天盖地,但凡做过博图(TIA Portal&a… · 2026/9/26 7:12:54
大模型安全体系化防护:从攻击面分析到生产落地实战 最近半年,我几乎每周都会接到同一类咨询:团队的大模型应用马上要上线了,功能、效果都验证过了,可安全防护到底怎么做,心里完全没底。这种焦虑不是没有道理——大模型越往业务深处落,攻击面就越复杂… · 2026/9/26 7:12:42
工业环境监控中台:多协议数据归一化实战 1. 项目缘起与整体设计思路1.1 为什么会有这个中台需求我在一家做工业环境监控的集成商待了快八年,前六年基本都在现场跑。最早那批项目,一个车间里可能就三五个温湿度传感器,走的是RS485手拉手串起来,末端接个串口服务器转成以太… · 2026/9/26 7:54:43
共享办公工位人体存在检测:毫米波雷达+485组网方案实战 1. 共享办公工位为什么需要毫米波雷达做存在检测共享办公这个场景,工位利用率一直是个糊涂账。我做过几个联合办公空间的智能化改造项目,运营方最头疼的问题就是:后台系统显示某个工位“已预订”,但人到底在不在?传统方… · 2026/9/26 7:54:43
AI的记忆靠压缩也许是错误的 目录
它是用来保留原稿的,并不是要代替你去决定哪些是需要被记录下来的
本地运行不只是一句空话,在默认情况下就是本地运行
除了作为搜索引擎之外,它还试图成为 Agent 的记忆基础架构
最快的上手方式
漂亮的成绩背后也应该看清楚统计数据… · 2026/9/26 7:54:37
LangChain4j+LangGraph4j:低代码智能体工作流的工程落地实践 1. 为什么“低代码工作流智能体”不是噱头,而是工程落地的必然选择我去年在给一家制造业客户做AI中台升级时,被拉着开了整整三天的需求对齐会。他们提了27个业务场景:销售线索自动打分、售后工单智能分派、采购合同条款合规性初筛、设备报修语… · 2026/9/26 7:54:25
Xmind换盘不重装:安装路径迁移、目录联接与C盘释放全攻略 如果你百度过“Xmind怎么改安装路径”,大概率会看到一堆教你“装的时候选自定义目录”的回答。这话没毛病,但只对了一半,因为Xmind现在的安装包默认装到C:\Users\你的用户名\AppData\Local\Programs\Xmind,而这一层目录在安装向导… · 2026/9/26 7:54:25
课堂笔记知识管理:从记录、整理到复用的三层笔记法 很多人记课堂笔记都是“上课狂写、下课不看”,笔记记完就吃灰。我自己以前也是这样,直到有一天发现翻看一个月前的笔记,竟然看不懂自己写了什么,才意识到问题不在“记”,而在“怎么记”。我最近参加了2026年1月8日的一… · 2026/9/26 7:54:19
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46