
#include stdio.h#include string.hint main() {char names[5][20]; int count 0; int choice; do { printf(\n 学生姓名管理系统 \n); printf(1. 添加姓名\n); printf(2. 显示所有姓名\n); printf(3. 按姓名查找\n); printf(0. 退出\n); printf(请选择: ); scanf(%d, choice); while (getchar() ! \n); // 清空输入缓冲区 switch (choice) { case 1: if (count 5) { printf(存储空间已满无法添加。\n); } else { printf(请输入姓名: ); fgets(names[count], 20, stdin); names[count][strcspn(names[count], \n)] \0; count; printf(添加成功。\n); } break; case 2: if (count 0) { printf(暂无姓名。\n); } else { printf(当前共有 %d 个姓名:\n, count); for (int i 0; i count; i) { printf(%d. %s\n, i 1, names[i]); } } break; case 3: { char target[20]; printf(请输入要查找的姓名: ); fgets(target, 20, stdin); target[strcspn(target, \n)] \0; int found 0; for (int i 0; i count; i) { if (strcmp(names[i], target) 0) { printf(找到: %s下标 %d\n, names[i], i); found 1; } } if (!found) { printf(未找到该姓名。\n); } break; } case 0: printf(程序退出。\n); break; default: printf(无效选项请重新输入。\n); } } while (choice ! 0); return 0;}这里的几个细节值得说明 - fgets(names[count], 20, stdin) 用来读取一行最多读取 19 个字符避免 scanf(%s) 越界。 - strcspn(names[count], \n) 的作用是找到字符串中换行符的位置然后把它替换成 \0这样姓名末尾就不会残留换行。 - strcmp 用于比较两个字符串是否相等相等时返回 0。 - 每次输入菜单选项后while (getchar() ! \n); 会把缓冲区里剩余的回车清掉避免影响下一次 fgets。 ### 6.2 运行示例 编译并运行 bash gcc student_names.c -o student_names ./student_names一次完整操作如下 学生姓名管理系统 1. 添加姓名 2. 显示所有姓名 3. 按姓名查找 0. 退出 请选择: 1 请输入姓名: 张三 添加成功。 学生姓名管理系统 1. 添加姓名 2. 显示所有姓名 3. 按姓名查找 0. 退出 请选择: 1 请输入姓名: 李四 添加成功。 学生姓名管理系统 1. 添加姓名 2. 显示所有姓名 3. 按姓名查找 0. 退出 请选择: 2 当前共有 2 个姓名: 1. 张三 2. 李四 学生姓名管理系统 1. 添加姓名 2. 显示所有姓名 3. 按姓名查找 0. 退出 请选择: 3 请输入要查找的姓名: 李四 找到: 李四下标 1这个小系统虽然简单但已经把字符串数组的声明、输入、遍历、查找都串起来了。遇到实际问题时可以在这个基础上扩展排序、删除、统计等功能。7. 字符串数组常见错误与排查思路7.1 忘记预留字符串结束符char strs[3][5] {hello, world, csdn};第二维长度是 5但hello实际需要 6 个字节包含结束符\0。编译器可能会报“数组越界”或“字符串太长”的警告运行时也可能破坏相邻数据。正确做法计算长度时一定要加 1。如果存储 5 个字符第二维至少是 6。7.2 使用未初始化的字符串数组char strs[3][20]; strcpy(strs[0], hello); // 只拷贝第一个 printf(%s\n, strs[1]); // 未初始化输出乱码如果先声明后使用建议手动初始化char strs[3][20] {0};或者只对使用到的部分赋值并且在逻辑上保证不访问未初始化元素。7.3 直接给数组名赋值char strs[3][20]; strs[0] hello; // 错误数组名不是可修改的左值应该使用strcpy或初始化。7.4 指针数组指向了局部变量char *getNames() { char local[3][20] {a, b, c}; return local[0]; // 警告返回了局部变量的地址 }函数结束后局部数组内存失效调用方拿到的指针是悬空指针。建议用静态数组、动态内存分配或者把数组作为参数传入。7.5 输入字符串时缓冲区溢出char name[5]; scanf(%s, name); // 如果输入超过4个字符就会越界推荐使用带宽度限制的输入方式scanf(%4s, name);或者使用fgetsfgets(name, 5, stdin);7.6 字符串数组常见问题速查表问题现象常见原因解决思路赋值时报错对数组名直接赋值用strcpy或初始化输出乱码缺少\0或访问未初始化元素初始化数组预留结束符空间输入超长导致崩溃scanf(%s)无边界控制使用fgets或限制宽度比较字符串用不生效错误比较了地址使用strcmp函数返回字符串后值丢失返回了局部数组指针使用静态数组、参数返回或动态内存二维数组传参后无法遍历函数参数缺少列数用char (*arr)[N]或指针数组8. 字符串数组的最佳实践与工程建议8.1 根据场景选择二维数组还是指针数组如果字符串长度固定且数量已知二维数组更合适内存连续访问效率高。例如存储学号、IP 地址、固定格式编码。如果字符串长度差异大或者需要动态调整指针数组更灵活。例如从文件读取多行文本、解析命令行参数。8.2 统一使用宏定义长度避免在代码中到处写魔法数字#define MAX_NAME_LEN 32 #define MAX_NAMES 100 char names[MAX_NAMES][MAX_NAME_LEN];这样修改长度时只需要改宏不容易遗漏。8.3 初始化时尽量显式给出第二维长度char names[][10] {Tom, Jerry};这种写法可以自动计算第一维但第二维必须显式指定。如果字符串长度可能变化一定要给足空间。8.4 字符串操作优先使用安全函数在 MSVC 环境下strcpy、strcat会提示使用strcpy_s、strcat_s等安全版本。在 GCC/Clang 环境下常用strncpy、snprintf限制长度。使用snprintf拼接字符串更安全snprintf(buffer, sizeof(buffer), %s-%d, name, id);8.5 比较字符串一律使用strcmp很多初学者用if (strs[i] abc)判断这是比较指针地址不是比较内容。正确写法if (strcmp(strs[i], abc) 0) { // 相等 }8.6 注意指针数组中的字符串字面量不可修改char *strs[] {hello, world}; strs[0][0] H; // 可能崩溃字符串字面量一般是只读的如果需要对字符串内容修改用二维数组或动态分配内存。8.7 在工程中配合文件读写实际项目中字符串数组经常与文件读写配合。例如从文件读取配置项char lines[64][128]; int count 0; FILE *fp fopen(config.txt, r); if (fp ! NULL) { while (count 64 fgets(lines[count], 128, fp) ! NULL) { lines[count][strcspn(lines[count], \n)] \0; count; } fclose(fp); }这种模式在日志分析、配置解析、文本处理中很常见。9. 总结与下一步学习建议本文围绕 C 语言字符串数组展开了完整讲解核心要点可以概括为字符串数组本质上是二维字符数组或者是指向字符的指针数组。二维数组适合存放长度相近、数量固定的字符串内存连续、管理简单。指针数组适合存放长度差异大、需要动态调整的字符串集合。字符串数组的遍历、查找、排序都基于字符串函数比如strcpy、strcmp、strlen、strcat。声明数组时要为结束符\0预留空间初始化时优先使用{0}清零。函数传参时需要把列数信息一并传进去或者使用指针数组方式。实战中注意输入安全、越界保护、字符串是否只读等问题。接下来你可以尝试练习以下几个方向用冒泡排序对字符串数组按字典序排序。实现字符串数组的删除和插入操作。将字符串数组内容写入文件并重新读取。模拟main(int argc, char *argv[])打印命令行参数数量与内容。使用动态内存分配实现一个可以无限扩容的字符串列表。字符串是 C 语言中容易出错又非常重要的内容。只要你把字符数组、指针、字符串函数这三块理解透再回头看字符串数组就会轻松很多。建议多写几个小项目遇到问题可以一步步调试不要只看不练。如果这篇文章对你有帮助可以收藏备用也欢迎在评论区分享你的使用场景和踩坑经验。