C语言二维字符数组单词匹配计数及strcmp使用问题求解
问题背景
我创建了两个用于存储单词的二维字符数组,示例初始化代码如下:
loadKey[25][30] = { {'J','a','v','a','\0'}, {'P','y','t','h','o','n','\0'}, {'C','+','+','\0'}, {'H','T','M','L','\0'}, {'S','Q','L','\0'} // ... 剩余20个单词 }; resume[189][30] = { {'L','a','l','a','\0'}, {'H','i','h','i','h','i','\0'}, {'C','+','+','\0'}, {'Y','o','Y','o','\0'}, {'S','Q','L','\0'} // ... 剩余184个单词 };
开发需求:将loadKey数组存储的25个关键词逐一与resume数组中的所有单词进行比对,统计loadKey内的关键词在resume中匹配成功的总次数。
尝试调用strcmp(loadKey[i], resume[j])实现比对逻辑时,遇到数组指针相关问题导致运行结果不符合预期,附完整程序代码如下,求排查问题、提供解决方案。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #define PAUSE myPause() #define KEYWORD 25 #define WORDS 250 #define MAX_LETTER 30 //********************************************* // 函数声明 void compAndCount(char loadKey[][MAX_LETTER], char resume[][MAX_LETTER]); void myPause(); void readAndLoadKeyword(char loadKey[][MAX_LETTER]); // 主函数 int main() { char loadKey[KEYWORD][MAX_LETTER]; char resume[WORDS][MAX_LETTER]; // 从keywords文件加载关键词到loadKey数组 readAndLoadKeyword(loadKey); for (int j = 0; j < KEYWORD; j++) { puts(loadKey[j]); } puts("\n"); // 比对并统计关键词在简历文件中的出现次数 compAndCount(loadKey, resume); } // 函数定义 void compAndCount(char loadKey[][MAX_LETTER], char resume[][MAX_LETTER]) { FILE* fpr; fpr = fopen("resumes.txt", "r"); int r = 0, count = 0, num = 0, res = 0; char temp; while ((temp = fgetc(fpr)) != EOF) { if (temp != ' ' && temp != '\n') { resume[res][r] = temp; r++; } else { resume[res][r] = '\0'; r = 0; res++; } } printf("words in resume file %i\n", res); for (int j = 0; j < res; j++) { puts(resume[j]); } puts("\n"); /* // 比对统计方式1(认为有问题?) for (int i = 0; i < res; i++) { if (i < KEYWORD) { scanf(" %[^\n]", loadKey[i]); } scanf(" %[^\n]", resume[i]); } for (int k = 0; k < KEYWORD; k++) { for (int l = 0; l < res; l++) { if (strcmp(loadKey[k], resume[l]) == 0) count++; } }*/ /* // 比对统计方式2(认为有问题?) char key[MAX_LETTER] = {'\0'}, r[MAX_LETTER] = {'\0'}; for (int i = 0; i < KEYWORD; i++) { strcpy(key, loadKey[i]); for (int l = 0; l < res; l++) { strcpy(r, resume[l]); if (strcmp(key, r) == 0) count++; } } */ printf("Resume Rating: %i\n", count); fclose(fpr); } // compAndCount函数结束 void myPause() { puts("\nPress ENTER to continue\n"); exit(0); } void readAndLoadKeyword(char loadKey[][MAX_LETTER]) { FILE* fp; fp = fopen("keywords.txt", "r"); char ch; int row = 0, col = 0; if (fp == NULL) { puts("Not able to open keyword file!"); PAUSE; } // 加载25个关键词和分隔逗号到line数组 char line[181]; // 180个字符 + '\0'终止符 fgets(line, 181, fp); puts(line); puts("\n"); // 将line数组中的25个单词存入loadKey数组 for (int i = 0; i < 180; i++) { ch = line[i]; if (ch != ',') { loadKey[row][col] = ch; col++; } else { loadKey[row][col] = '\0'; col = 0; row++; } } fclose(fp); } // readAndLoadKeyword函数结束
问题排查结果
问题不是strcmp参数传错或者二维数组指针用法不对——二维数组作为函数形参传递时,loadKey[k]、resume[l]本身就是合法的char*类型指针,完全符合strcmp的入参要求。运行结果异常是以下5个逻辑bug导致的:
- 缺少简历文件打开校验:
compAndCount函数里打开resumes.txt后没有判断文件是否打开成功,如果文件路径错误、权限不足导致fopen返回NULL,后续fgetc操作空指针会直接触发内存访问错误,就是遇到的指针类报错。 - 简历文件读取漏了最后一个单词的终止符:现有读取逻辑只有遇到空格、换行时才会给当前单词加
\0终止符,循环读到文件末尾EOF时,最后一个单词不会触发终止符赋值逻辑,strcmp比对时会顺着数组地址一直读到栈上的随机垃圾值,必然返回错误的比对结果。 - 关键词读取逻辑越界:
readAndLoadKeyword里固定循环180次读取line数组内容,如果关键词行实际长度不足180,会读到line数组未初始化的垃圾值写入loadKey。 - 最后一个关键词漏加终止符:关键词是用逗号分隔的,最后一个关键词末尾没有逗号,循环结束后不会给最后一个关键词补
\0,同样会导致strcmp读到垃圾值比对失败。 fgetc返回值类型错误:fgetc返回值是int类型,用char类型变量存储会导致读取到值为0xFF的字符时和EOF(值为-1)判断冲突,提前结束文件读取。
修复方案
针对以上问题做对应修改即可,核心修改点如下:
- 所有文件打开操作后立刻加空指针校验,打开失败直接报错退出
- 读完简历文件的EOF后,判断如果当前有未收尾的单词(
r>0),手动补\0终止符,单词计数加1,同时过滤连续空格、换行生成的空字符串 - 关键词读取时不要固定循环180次,循环到
line数组的\0终止符就停止 - 关键词读取循环结束后,给最后一个关键词手动补
\0终止符 - 取消比对逻辑的注释,两种写法都可以正常运行,不需要额外拷贝字符串到临时数组,直接传
loadKey[k]和resume[l]给strcmp即可 - 增加单词长度防护,避免单个单词长度超过数组上限导致内存越界
修复后的核心代码片段参考:
// 修复后的compAndCount函数 void compAndCount(char loadKey[][MAX_LETTER], char resume[][MAX_LETTER]) { FILE* fpr; fpr = fopen("resumes.txt", "r"); // 新增文件打开校验 if (fpr == NULL) { puts("无法打开简历文件!"); PAUSE; } int r = 0, count = 0, res = 0; int temp; // 修正fgetc返回值存储类型 while ((temp = fgetc(fpr)) != EOF) { if (temp != ' ' && temp != '\n') { resume[res][r] = (char)temp; r++; // 长度防护,避免单词超过数组上限越界 if (r >= MAX_LETTER - 1) { resume[res][r] = '\0'; r = 0; res++; } } else { // 过滤连续空格/换行生成的空字符串 if (r > 0) { resume[res][r] = '\0'; r = 0; res++; } } } // 处理文件末尾最后一个未收尾的单词 if (r > 0) { resume[res][r] = '\0'; res++; } printf("简历文件中读取到单词数:%i\n", res); // 直接调用strcmp比对即可 for (int k = 0; k < KEYWORD; k++) { for (int l = 0; l < res; l++) { if (strcmp(loadKey[k], resume[l]) == 0) count++; } } printf("简历匹配度:%i\n", count); fclose(fpr); } // 修复后的readAndLoadKeyword函数 void readAndLoadKeyword(char loadKey[][MAX_LETTER]) { FILE* fp; fp = fopen("keywords.txt", "r"); char ch; int row = 0, col = 0; if (fp == NULL) { puts("无法打开关键词文件!"); PAUSE; } char line[181] = {0}; // 初始化全0,避免垃圾值干扰 fgets(line, 181, fp); puts(line); puts("\n"); int i = 0; // 循环到line的字符串结束符为止,不固定循环次数 while (line[i] != '\0' && line[i] != '\n' && row < KEYWORD) { ch = line[i]; if (ch != ',') { loadKey[row][col] = ch; col++; // 长度防护 if (col >= MAX_LETTER -1) { loadKey[row][col] = '\0'; col = 0; row++; } } else { loadKey[row][col] = '\0'; col = 0; row++; } i++; } // 给最后一个关键词补终止符 if (col > 0 && row < KEYWORD) { loadKey[row][col] = '\0'; } fclose(fp); }
内容的提问来源于stack exchange,提问作者C beginner
相关产品推荐
相关产品推荐

