C语言中输入字符串末尾加空格后循环终止问题求助
嘿,咱们来拆解下你的代码问题,顺便解决那个“末尾加空格后循环就卡壳”的事儿。你的代码里有几个关键错误,不仅导致了循环问题,还会引发内存bug和逻辑混乱:
1. 栈内存复用导致的无效引用
你直接把局部数组sentenceInput的地址赋值给words->pWord,然后又用strtok修改这个数组的内容。但sentenceInput是栈上的变量,每次fgets循环都会覆盖它,这会导致之前链表节点里的pWord指针全部指向同一块栈内存——最终所有节点的内容都会变成最后一次输入的句子,甚至程序运行到后面会出现野指针错误。
2. 句子节点的创建逻辑完全跑偏
你的需求是每行句子对应一个链表节点,但现在的代码是每个单词创建一个句子节点——每次调用strtok拿到一个单词,就遍历整个链表去添加新的sentence节点,这完全违背了你的初衷,也会把链表结构彻底搞乱。
3. strtok与循环终止条件的冲突
当你在句子末尾加空格时,strtok会自动忽略末尾的分隔符,但你的内层循环是处理单词,而外层循环的终止条件是sentenceInput[0] != '\n'——如果用户输入“hello world ”(末尾带空格)然后回车,fgets会读取到包含空格和换行的字符串,sentenceInput[0]不是换行,外层循环本应继续,但前面的逻辑错误已经把程序状态搞乱,导致循环异常终止。
4. 未初始化的结构体字段与内存泄漏
每次malloc出来的word结构体,你只设置了pWord字段,numOfChar、posOfWord、nextWord都没初始化,这会导致后续使用时出现未定义行为。同时,你每次处理单词都新建word和sentence,但没有正确管理链表,会造成严重的内存泄漏。
修复后的代码
下面是修正后的代码,完美实现每行句子对应一个链表节点,每个句子节点里存储该句子的所有单词链表:
// 辅助函数:计算字符串长度 int getStrLength(const char* str) { int len = 0; while(str[len] != '\0') len++; return len; } // 辅助函数:复制字符串到堆内存 char* copyString(const char* str) { int len = getStrLength(str); char* newStr = (char*)malloc(len + 1); if(newStr == NULL) return NULL; for(int i=0; i<=len; i++) { newStr[i] = str[i]; } return newStr; } // 创建单词节点 word* createWord(const char* wordStr, int pos) { word* newWord = (word*)malloc(sizeof(word)); if(newWord == NULL) return NULL; newWord->pWord = copyString(wordStr); newWord->numOfChar = getStrLength(wordStr); newWord->posOfWord = pos; newWord->nextWord = NULL; return newWord; } // 创建句子节点(包含该句子的单词链表) sentence* createSentence(word* firstWord, int wordCount, int linePos) { sentence* newSentence = (sentence*)malloc(sizeof(sentence)); if(newSentence == NULL) return NULL; newSentence->wordList = firstWord; newSentence->totalWords = wordCount; newSentence->posOfLine = linePos; newSentence->nextSentence = NULL; return newSentence; } void addSentence(control* list) { printf("Enter a Sentence (press Enter alone to stop):\n"); char sentenceInput[1000]; int linePos = list->sentenceCounter + 1; // 记录句子的位置序号 while(fgets(sentenceInput, 1000, stdin) != NULL) { // 去除fgets读取到的换行符 size_t len = strlen(sentenceInput); if(len > 0 && sentenceInput[len-1] == '\n') { sentenceInput[len-1] = '\0'; len--; } // 输入为空(只按了回车),终止循环 if(len == 0) { break; } // 构建当前句子的单词链表 word* firstWord = NULL; word* currentWord = NULL; int wordCount = 0; int wordPos = 1; // 分割句子为单词 char* token = strtok(sentenceInput, " "); while(token != NULL) { word* newWord = createWord(token, wordPos); if(newWord == NULL) { printf("Memory allocation failed for word!\n"); // 这里可以添加内存清理逻辑,避免泄漏 return; } // 将单词节点加入链表 if(firstWord == NULL) { firstWord = newWord; currentWord = newWord; } else { currentWord->nextWord = newWord; currentWord = newWord; } wordCount++; wordPos++; token = strtok(NULL, " "); } // 创建当前句子的节点 sentence* newSentence = createSentence(firstWord, wordCount, linePos); if(newSentence == NULL) { printf("Memory allocation failed for sentence!\n"); // 内存清理逻辑 return; } // 将句子节点加入主链表 if(list->firstSentence == NULL) { list->firstSentence = newSentence; } else { // 找到链表末尾 sentence* currentSentence = list->firstSentence; while(currentSentence->nextSentence != NULL) { currentSentence = currentSentence->nextSentence; } currentSentence->nextSentence = newSentence; } // 更新计数器和位置序号 list->sentenceCounter++; linePos++; printf("Enter a Sentence (press Enter alone to stop):\n"); } }
关键修复点说明
- 字符串复制:不再直接使用栈内存指针,而是通过
copyString为每个单词分配独立的堆内存,彻底避免内容覆盖和无效引用问题。 - 逻辑修正:先把当前句子的所有单词整理成
word链表,再创建对应的sentence节点添加到主链表,完全符合“每行句子对应一个节点”的需求。 - 换行符处理:手动去除
fgets读取到的换行符,避免句子末尾带换行,同时正确识别空输入(只按回车)来终止循环。 - 结构体初始化:所有结构体字段都做了初始化,杜绝未定义行为。
- 内存管理:每个节点都正确分配内存,后续你可以添加对应的内存释放函数(比如
freeSentenceList)来彻底避免内存泄漏。
现在你再测试末尾加空格的情况,程序会正常处理,循环也能继续执行,直到你输入空行(只按回车)才会终止。
内容的提问来源于stack exchange,提问作者hitthechooch

