news 2026/9/1 19:58:18

C语言字符串数组详解:内存模型与两种主流写法对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言字符串数组详解:内存模型与两种主流写法对比

C语言字符串数组,这一篇把内存模型和常见写法讲透

字符串数组是 C 语言里绕不开的一个知识点。很多初学者卡在“字符数组”和“字符串数组”的区别上,又分不清char a[10][20]char *a[10]到底谁适合哪种场景。这次就把字符串数组拆开讲清楚:怎么定义、怎么初始化、怎么遍历、怎么排序,以及最常见的几个坑在哪。

这篇文章适合正在学 C 语言指针和数组的读者,也适合准备期末考试或 GESP、CSP 之类竞赛的同学。看完之后,你应该能区分两种字符串数组的内存布局,能写代码完成字符串排序、查找,也知道为什么有些写法会直接段错误。

1. 核心概念:C 语言里的“字符串数组”到底指什么

在 C 语言中,字符串本身就是一个以\0结尾的字符数组。所以“字符串数组”本质上是一个二维结构,只是有两种常见实现方式:

定义方式类型特点典型场景
char str[5][20]二维字符数组每行固定长度,内存连续,字符串可原地修改数量固定、长度可控的字符串集合
char *str[5]字符指针数组数组里存放指向字符串的指针,内存开销小,定义灵活字符串数量固定,但长度变化较大
const char *str[5]指向字符串字面量的指针数组只能读不能写,适合存储固定提示语、菜单项只读字符串表
char **str指针的指针二级指针,通常用于动态分配和函数传参动态字符串列表、命令行参数

这里先记住一个最关键的结论:到底选二维数组还是指针数组,取决于“字符串要不要修改”和“长度是否固定”。

2. 两类主流写法的内存模型

2.1 二维字符数组:连续内存,每行固定

char names[3][16] = { "Tom", "Jerry", "Spike" };

这段代码在内存中申请了一块连续空间:3 行 × 16 字节,共 48 字节。每行都固定占用 16 字节,即使 "Tom" 只需要 4 字节,剩下的 12 字节也会被占用,其中\0之后的内容是未初始化的零值。

这种方式的优点是:

  • 编译器在编译时就能确定总大小。
  • 可以原地修改names[0][0]这种元素。
  • 传给函数时,参数类型是char (*)[16],边界清晰。

缺点是:

  • 如果字符串长度参差不齐,浪费空间。
  • 如果某一行超过 15 个有效字符,就会放不下\0,超过部分被截断或溢出。

2.2 字符指针数组:只存指针,字符串另放

char *names[3] = { "Tom", "Jerry", "Spike" };

这里names数组只存放 3 个char *指针,每个指针 8 字节(64 位系统),总共 24 字节。字符串本身存在只读数据区,数组里的指针指向对应字符串首地址。

这种写法的关键点在于:

  • 字符串字面量不一定能修改names[0][0] = 't'在多数平台上会触发段错误,因为字面量可能被放在只读常量区。不同编译器、不同平台行为不同,但标准 C 明确规定修改字符串字面量是未定义行为。
  • 适合存放长度差异很大的字符串,因为内存按需分配,不浪费固定空间。
  • 适合把字符串作为整体传递、排序。排序时可以只交换指针,不需要复制整段字符数据。

2.3 一个典型对比表格

操作char a[3][16]char *a[3]
存储模型连续二维数组指针数组 + 独立字符串常量
单行最大长度固定 15 字符可用取决于字面量长度
排序交换成本需要逐字节拷贝只交换指针,速度快
修改字符串内容可以指向字面量时不可
动态读入长字符串容易越界需要先分配空间
函数形参char a[][16]char (*a)[16]char **achar *a[]

初学者最容易犯的错是把第二种写法当作第一种来修改字符串。记住原则:如果字符串是在运行时要被修改的,优先用二维数组或动态分配的内存;如果只是只读集合,用指针数组更省空间、更高效。

3. 字符串数组的定义与多种初始化方式

3.1 二维数组初始化

#include <stdio.h> int main() { // 方式一:直接初始化 char week[7][10] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; // 方式二:省略第一维大小,由编译器数出来 char week2[][10] = { "Monday", "Tuesday", "Wednesday" }; // 方式三:不写大小,全部由初始化决定 char week3[][10] = {"Mon", "Tue"}; printf("%zu\n", sizeof(week) / sizeof(week[0])); // 7 printf("%zu\n", sizeof(week2) / sizeof(week2[0])); // 3 printf("%s\n", week[0]); return 0; }

需要特别注意的是:sizeof(week) / sizeof(week[0])这种方法只适用于数组本身,不能用于函数形参。因为形参传进去后已经退化成指针,sizeof结果是指针大小。

3.2 指针数组初始化

#include <stdio.h> int main() { // 指向字符串字面量 const char *colors[] = {"Red", "Green", "Blue", "Yellow"}; // 指向可修改的字符数组 char c1[] = "Red"; char c2[] = "Green"; char *colors2[] = {c1, c2}; // 动态分配 char *list[3]; list[0] = (char *)malloc(20); list[1] = (char *)malloc(20); list[2] = (char *)malloc(20); for (int i = 0; i < 3; i++) { sprintf(list[i], "item-%d", i); } for (int i = 0; i < 3; i++) { puts(list[i]); free(list[i]); } return 0; }

这里可以清楚看到指针数组的灵活性:每个元素可以来自常量区,也可以来自栈上的字符数组,还可以来自堆上的动态内存。前提是你必须知道自己管理的是哪一类内存,否则free的时候会出错。

3.3 运行时逐行输入字符串

竞赛或者实际开发中,经常需要从标准输入读入多行字符串,存在数组里。推荐两种写法。

写法一:二维数组,适合行数和行长都已知。

#include <stdio.h> int main() { char lines[5][128]; int n = 0; printf("请输入行数:"); scanf("%d", &n); // 读取缓冲区中残留的换行符 getchar(); for (int i = 0; i < n; i++) { // fgets 会保留换行符,这里手动去掉 fgets(lines[i], sizeof(lines[i]), stdin); size_t len = strlen(lines[i]); if (len > 0 && lines[i][len - 1] == '\n') { lines[i][len - 1] = '\0'; } } for (int i = 0; i < n; i++) { printf("第%d行:%s\n", i + 1, lines[i]); } return 0; }

写法二:指针数组 + 动态分配,适合行长不确定、需要节省内存。

#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int n = 3; char *lines[3]; char buffer[256]; for (int i = 0; i < n; i++) { fgets(buffer, sizeof(buffer), stdin); buffer[strcspn(buffer, "\n")] = '\0'; lines[i] = (char *)malloc(strlen(buffer) + 1); strcpy(lines[i], buffer); } for (int i = 0; i < n; i++) { puts(lines[i]); free(lines[i]); } return 0; }

strcspn(buffer, "\n")是一个比较精简的写法,作用是找到第一个换行符的位置,然后替换成\0。这个函数在字符串处理中很常用,比手动遍历更简洁。

4. 字符串数组的遍历与访问

4.1 二维数组的遍历

#include <stdio.h> int main() { char names[3][16] = {"Tom", "Jerry", "Spike"}; // 按行遍历,注意每行是字符串,用 %s 输出 for (int i = 0; i < 3; i++) { printf("%s\n", names[i]); } // 按字符逐个遍历,输出每个字符的 ASCII for (int i = 0; i < 3; i++) { for (int j = 0; names[i][j] != '\0'; j++) { printf("%c ", names[i][j]); } printf("\n"); } return 0; }

二维数组的遍历比较直观,names[i]得到第 i 行字符串的首地址,names[i][j]得到具体字符。

4.2 指针数组的遍历

#include <stdio.h> int main() { const char *fruits[] = {"apple", "banana", "cherry", "date"}; // 方式一:下标遍历 for (int i = 0; i < 4; i++) { printf("%s\n", fruits[i]); } // 方式二:指针自增遍历 const char **p = fruits; for (int i = 0; i < 4; i++) { printf("%s\n", *p); p++; } // 方式三:如果数组以 NULL 结尾,可以用 NULL 判断 const char *args[] = {"-h", "--version", NULL}; for (int i = 0; args[i] != NULL; i++) { printf("%s\n", args[i]); } return 0; }

第三种写法在命令行参数处理里非常常见。以NULL作为结束标记,可以避免维护额外的长度变量。

4.3 用 sizeof 计算数组长度的注意事项

#include <stdio.h> void print_size(char arr[][20], int rows) { // 形参已退化,不能这样算行数 // printf("%zu\n", sizeof(arr) / sizeof(arr[0])); printf("函数内 rows = %d\n", rows); } int main() { char arr[3][20] = {"a", "bb", "ccc"}; printf("数组总大小:%zu\n", sizeof(arr)); printf("每行大小:%zu\n", sizeof(arr[0])); printf("行数:%zu\n", sizeof(arr) / sizeof(arr[0])); print_size(arr, 3); return 0; }

这是 C 语言数组和指针最容易混淆的地方。数组作为参数传入函数时,第一维会退化成指针,sizeof(arr)在函数内部得到的是指针大小,不是整个数组大小。因此要在函数外算好行数,或者使用NULL结尾的约定。

5. 修改字符串数组中的内容

5.1 修改二维数组中的字符串

#include <stdio.h> #include <string.h> int main() { char colors[3][16] = {"Red", "Green", "Blue"}; // 直接改写单个字符 colors[0][0] = 'r'; printf("%s\n", colors[0]); // red // 用 strcpy 整体覆盖 strcpy(colors[1], "Dark Green"); printf("%s\n", colors[1]); // Dark Green // 只要不超过行容量,都可以写入 strcpy(colors[2], "DeepSkyBlue"); printf("%s\n", colors[2]); return 0; }

二维数组“可修改”的本质在于:每一行都是存放在栈或全局区的字符数组,有独立的可写空间。只要strcpy的长度不超过行大小 - 1,就不会越界。

5.2 修改指针数组中的字符串

#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *names[2]; // 动态分配,可修改 names[0] = (char *)malloc(32); names[1] = (char *)malloc(32); strcpy(names[0], "Alice"); strcpy(names[1], "Bob"); strcpy(names[0], "Alice Smith"); printf("%s\n", names[0]); free(names[0]); free(names[1]); return 0; }

如果names[0]直接指向字符串字面量,比如char *names[2] = {"Alice", "Bob"};,那么strcpy(names[0], "Alice Smith")就是未定义行为。常见结果是段错误,因为目标地址在只读区。更稳妥的判断是:只要是运行时要修改的字符串,要么用二维数组,要么用 malloc 分配内存。

5.3 排序时交换指针与交换数组的区别

假设有一个指针数组,需要按字符串顺序排序。交换两个元素时,只需要交换指针:

#include <stdio.h> #include <string.h> int main() { const char *words[] = {"pear", "apple", "orange", "banana"}; int n = 4; // 选择排序,只交换指针 for (int i = 0; i < n - 1; i++) { int min_idx = i; for (int j = i + 1; j < n; j++) { if (strcmp(words[j], words[min_idx]) < 0) { min_idx = j; } } if (min_idx != i) { const char *tmp = words[i]; words[i] = words[min_idx]; words[min_idx] = tmp; } } for (int i = 0; i < n; i++) { printf("%s\n", words[i]); } return 0; }

如果换成二维数组,交换就需要strcpy到临时数组再互相拷贝,成本高很多。

6. 字符串数组的常见操作示例

6.1 查找指定字符串

#include <stdio.h> #include <string.h> int find_string(const char *arr[], int n, const char *target) { for (int i = 0; i < n; i++) { if (strcmp(arr[i], target) == 0) { return i; } } return -1; } int main() { const char *cities[] = {"Beijing", "Shanghai", "Guangzhou", "Shenzhen"}; int idx = find_string(cities, 4, "Guangzhou"); if (idx != -1) { printf("找到,下标为:%d\n", idx); } else { printf("未找到\n"); } return 0; }

6.2 按字母顺序排序并输出

#include <stdio.h> #include <stdlib.h> #include <string.h> int cmp_str(const void *a, const void *b) { // a 和 b 是 const char ** 类型 return strcmp(*(const char **)a, *(const char **)b); } int main() { const char *subjects[] = {"math", "english", "physics", "chemistry", "biology"}; int n = 5; qsort(subjects, n, sizeof(char *), cmp_str); for (int i = 0; i < n; i++) { printf("%s\n", subjects[i]); } return 0; }

qsort的比较函数比较的是“数组元素”,而这里数组元素是char *指针。所以ab实际上是被qsort传进来的指向指针的指针,先转成const char **,再解引用得到const char *,才能交给strcmp

6.3 统计每个字符串长度并找出最长

#include <stdio.h> #include <string.h> int main() { char words[5][32] = {"cat", "elephant", "dog", "hippopotamus", "bird"}; int max_len = 0; char *longest = NULL; for (int i = 0; i < 5; i++) { int len = strlen(words[i]); if (len > max_len) { max_len = len; longest = words[i]; } } printf("最长字符串:%s,长度:%d\n", longest, max_len); return 0; }

7. 字符串数组作为函数参数

7.1 二维数组传参

#include <stdio.h> // 必须指定第二维大小 void print_names(char names[][32], int rows) { for (int i = 0; i < rows; i++) { printf("%s\n", names[i]); } } // 等价写法:指针数组形式 void print_names_2(char (*names)[32], int rows) { for (int i = 0; i < rows; i++) { printf("%s\n", names[i]); } } int main() { char names[3][32] = {"Alice", "Bob", "Charlie"}; print_names(names, 3); print_names_2(names, 3); return 0; }

二维数组传参有一个硬性要求:从第二维开始的所有维度必须写明。这是因为编译器需要知道每一行跨多少字节,才能计算names[i]的地址。

7.2 指针数组传参

#include <stdio.h> void print_list(char **list, int n) { for (int i = 0; i < n; i++) { printf("%s\n", list[i]); } } void print_list_2(char *list[], int n) { for (int i = 0; i < n; i++) { printf("%s\n", list[i]); } } int main() { char *arr[] = {"one", "two", "three"}; print_list(arr, 3); print_list_2(arr, 3); return 0; }

这里char **char *[]在函数形参中是等价的。从调用者角度看,传入的是一个指针数组,数组名退化之后就是char **

7.3 二级指针作为输出参数

#include <stdio.h> #include <stdlib.h> #include <string.h> void create_strings(char ***result, int n) { *result = (char **)malloc(n * sizeof(char *)); for (int i = 0; i < n; i++) { (*result)[i] = (char *)malloc(32); sprintf((*result)[i], "string-%d", i); } } int main() { char **list = NULL; int n = 3; create_strings(&list, n); for (int i = 0; i < n; i++) { printf("%s\n", list[i]); free(list[i]); } free(list); return 0; }

这种写法在封装字符串列表工具函数时很常见。调用方只声明一个char **,通过&list传地址,函数内部分配内存并填充。

8. 字符串数组最常踩的坑

8.1 修改字符串字面量

char *p = "hello"; p[0] = 'H'; // 未定义行为,常见段错误

正确做法:用数组保存,或在堆上分配空间。

8.2 对未初始化的指针 strcpy

char *p; strcpy(p, "hello"); // 错误:p 指向未知地址

正确做法:先分配空间,或者改用snprintf、动态内存。

8.3 fgets 保留换行符

使用fgets读入字符串时,换行符会留在缓冲区里,导致字符串后面多出一个\n。常见处理:

buffer[strcspn(buffer, "\n")] = '\0';

8.4 越界写入

char names[3][10] = {"123", "456", "789"}; strcpy(names[0], "This is a very long string"); // 溢出

names[0]最多容纳 9 个字符加一个\0。超过就会写入到相邻行的内存,导致难以排查的内存破坏。建议使用snprintfstrncpy,并且始终预留结束符位置。

8.5 混淆指针数组行长度

const char *p[] = {"abc", "def"}; // 错误:sizeof(p[0]) 是指针大小,不是"abc"的长度 printf("%zu\n", sizeof(p[0])); // 正确:用 strlen 获取字符串长度 printf("%zu\n", strlen(p[0]));

8.6 在函数内部错误计算数组长度

void func(char *arr[]) { // sizeof(arr) = 8,不是数组总大小 }

解决办法是让调用方传入元素个数,或者在数组末尾放NULL哨兵元素。

9. 字符串数组的应用场景:命令行参数与配置表

9.1 main 函数的 argv

C 语言程序的main(int argc, char *argv[])本质上就是一个字符串数组。argv[0]是程序名,argv[1]argv[argc-1]是命令行参数。

#include <stdio.h> int main(int argc, char *argv[]) { for (int i = 0; i < argc; i++) { printf("参数 %d:%s\n", i, argv[i]); } return 0; }

编译运行:

gcc test.c -o test ./test hello world 123

输出:

参数 0:./test 参数 1:hello 参数 2:world 参数 3:123

理解字符串数组之后,再看argv的传参方式就非常清晰:它是一个指针数组,每个元素是char *,指向一个以\0结尾的字符串。

9.2 只读配置表

#include <stdio.h> typedef struct { const char *key; const char *value; } ConfigItem; ConfigItem config[] = { {"host", "127.0.0.1"}, {"port", "8080"}, {"debug", "true"}, {NULL, NULL} }; const char *get_config(const char *key) { for (int i = 0; config[i].key != NULL; i++) { if (strcmp(config[i].key, key) == 0) { return config[i].value; } } return NULL; } int main() { const char *port = get_config("port"); if (port) { printf("port = %s\n", port); } return 0; }

用字符串数组 + 哨兵元素组织配置表,代码非常干净,适合入门项目练习。

10. 两道经典练习题的拆解思路

很多刷题平台上有字符串数组的练习题,比如输入若干单词、按字典序输出。这类题目核心考察点就是二维数组和指针数组的选择。下面给一个通用练习题模板,理解后可以套到多数题目上。

题目描述:第一行输入一个整数 n,接下来 n 行每行一个字符串,按字典序升序输出。

#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_LEN 101 #define MAX_N 1000 int main() { int n; char words[MAX_N][MAX_LEN]; scanf("%d", &n); getchar(); for (int i = 0; i < n; i++) { fgets(words[i], MAX_LEN, stdin); words[i][strcspn(words[i], "\n")] = '\0'; } // 简单选择排序 for (int i = 0; i < n - 1; i++) { int min_idx = i; for (int j = i + 1; j < n; j++) { if (strcmp(words[j], words[min_idx]) < 0) { min_idx = j; } } if (min_idx != i) { char tmp[MAX_LEN]; strcpy(tmp, words[i]); strcpy(words[i], words[min_idx]); strcpy(words[min_idx], tmp); } } for (int i = 0; i < n; i++) { puts(words[i]); } return 0; }

如果改成指针数组版本,交换时只需要交换指针:

#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_LEN 101 #define MAX_N 1000 int main() { int n; char *words[MAX_N]; char buffer[MAX_LEN]; scanf("%d", &n); getchar(); for (int i = 0; i < n; i++) { fgets(buffer, MAX_LEN, stdin); buffer[strcspn(buffer, "\n")] = '\0'; words[i] = (char *)malloc(strlen(buffer) + 1); strcpy(words[i], buffer); } // 只交换指针 for (int i = 0; i < n - 1; i++) { int min_idx = i; for (int j = i + 1; j < n; j++) { if (strcmp(words[j], words[min_idx]) < 0) { min_idx = j; } } if (min_idx != i) { char *tmp = words[i]; words[i] = words[min_idx]; words[min_idx] = tmp; } } for (int i = 0; i < n; i++) { puts(words[i]); free(words[i]); } return 0; }

两种方案都能通过。二维数组版更简单,但是行数上限和行长受限;指针数组版内存利用率高,但需要手动管理释放。刷题时优先选择二维数组,够用且不容易内存泄漏。实际工程中,如果数据量很大,再考虑指针数组或链表。

11. 常见错误排查清单

问题现象可能原因排查方法解决方案
段错误修改了字符串字面量检查是否有p[0] = ...改为字符数组或动态内存
段错误指针未初始化就strcpy检查目标指针是否为 NULL 或野指针先 malloc 再复制
输出乱码fgets保留了换行符打印字符串长度或逐字符输出strcspn去掉换行符
死循环scanf读完数字后未处理换行符检查输入缓冲加一句getchar()
程序崩溃字符串越界写入AddressSanitizer或检查strcpy的长度换成strncpysnprintf
排序结果不对qsort比较函数写错类型打印比较函数的参数内容转成const char **再解引用
无法编过形参和实参数组维度不一致检查函数声明第二维大小必须匹配
内存泄漏malloc 后没有 free用 valgrind 检测每个 malloc 对应一个 free

12. 最佳实践建议

第一,默认优先使用char arr[][MAX_LEN]。大多数入门和竞赛场景,字符串数量和单行长度都是有限的,二维数组简单、直观、不容易出错。

第二,如果字符串长度差异大、数量多,再考虑char *arr[]和动态分配。换排序算法、插入删除时,指针数组更高效,但必须时刻记得释放内存。

第三,写代码时习惯性留下边界余量。比如最大字符串长度是 100,数组长度就写成 128 或者 101 + 1,防止忘记\0导致越界。

第四,能用fgets就不要用scanf("%s")scanf("%s")无法限制长度,容易缓冲区溢出;fgets有上限,配合strcspn去掉换行符。这是最稳定的标准输入读取方式。

第五,涉及字符串复制时优先用snprintfstrncpy,并手动保证结束符。

char buf[32]; snprintf(buf, sizeof(buf), "%s", source);

第六,函数传参时如果参数是字符串数组,最好把数组长度也传进去,不要在函数内部用sizeof计算。

第七,修改完代码记得用编译选项排查问题:

gcc -Wall -Wextra -g test.c -o test

如果怀疑内存越界,可以用-fsanitize=address

gcc -Wall -Wextra -fsanitize=address -g test.c -o test

第八,不要忘记释放动态分配的内存。可以按模块组织代码,一个模块负责创建字符串列表,另一个模块负责销毁,尽量保证每个 malloc 都有配对的 free。

13. 总结

字符串数组不是一个孤立的知识点,它把数组、指针、字符串处理函数和内存管理串在了一起。理解二维字符数组和指针数组的区别,就等于理解了 C 语言中“静态存储”和“动态引用”两种思路的差别。

建议动手做三件事:

  • 用两种方式定义字符串数组,分别完成一次按字典序排序。
  • 写一个函数,接收char **,遍历并统计字符串总长度。
  • 模拟main(int argc, char *argv[])的传参方式,自己封装一个可复用的参数解析小函数。

上面这些代码可以放到同一个工程里反复编译、修改、测试。字符串数组这块通了,后面的链表、树、哈希表都会轻松不少,因为核心还是那套“数据在内存里怎么放、指针怎么指”的底子。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 19:58:02

灾害事件可视化仪表盘:用 Python 提取公开事件 API,并导出给 Tableau / Power BI

㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~ ㊙️本期爬虫难度指数:⭐⭐⭐⭐☆(高级) 🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。 全文目录: 🌟 开…

作者头像 李华
网站建设 2026/9/1 19:57:48

跑团Replay制作实战:从OBS多音轨录制到ffmpeg后期全流程

看跑团 Replay 已经成了不少人的固定娱乐方式&#xff0c;尤其是《寄生者之间》这类 PVP 秘密团&#xff0c;每一集都充满了互相试探、身份反转和“节目效果拉满”的名场面。很多观众看的时候会好奇&#xff1a;这种一群人挂在语音频道里聊天、GM 私下传信息、最后还要剪成完整…

作者头像 李华
网站建设 2026/9/1 19:57:14

JavaEE多线程(2)

1.死锁1.1产生死锁四个必要条件只要破坏其中一条就能打破死锁1&#xff09;互斥条件&#xff1a;锁是互斥的&#xff0c;同一时间只能一个线程拿到锁。2&#xff09;请求与保持&#xff1a;线程拿到一把锁&#xff0c;又申请另一把锁&#xff0c;并且不释放锁。3&#xff09;不…

作者头像 李华
网站建设 2026/9/1 19:49:29

DeepSeek Harness 上手实操:从装好到跑起来,再到插件管理,一篇全给你

如果你是个 AI Agent / 模型开发的人&#xff0c;最近应该没少听说 DeepSeek Harness。简单说&#xff0c;它是一个围绕 DeepSeek 模型打造的、可以自托管、能插件的智能体运行框架——所有能力都是插件&#xff0c;你想让它干什么&#xff0c;装个插件就行。 但网上的资料大多…

作者头像 李华
网站建设 2026/9/1 19:48:21

路面垃圾检测系统:Django+MySQL+YOLO深度学习实战解析

简介&#xff1a;这是一套面向计算机专业本科生的毕业设计级路面垃圾智能检测系统完整实现&#xff0c;基于Python深度学习与Django Web框架构建&#xff0c;解决城市环卫管理中垃圾识别效率低、任务闭环难的问题。资源包含1707个文件&#xff0c;涵盖63个核心Python模块&#…

作者头像 李华
网站建设 2026/9/1 19:47:12

Besiege中造三进制机械计算机:从三态编码到全加器实现

在 Besiege 里做机械三进制计算机&#xff0c;最容易产生“先试试”冲动的模块往往是全加器&#xff0c;因为它逻辑清晰、规模可控&#xff0c;又直接决定后续所有算术单元能否工作。真正动手之后会发现&#xff0c;三进制全加器不是简单换一张真值表&#xff0c;它要求整个信号…

作者头像 李华