C++如何按句号分割字符串但忽略Mr.、Mrs.等头衔中的句号?
句子分割问题:忽略头衔中的句号
作为学校项目的一部分,我需要编写代码读取文件并将内容分割为句子。最初用strtok方法,把句号、问号、感叹号当成分隔符,基本能运行,但碰到Mr.、Mrs.、Ms.、Dr.这类头衔里的句号时,代码会误将其识别为句子分隔符。现在需要一种能按句号分割字符串,但自动忽略上述头衔中句号的解决方案。
现有代码
std::string buffer; while(fileRead) { std::getline(fileRead, buffer); char *temp = new char[buffer.length() + 1]; strcpy(temp, buffer.c_str()); char *q = std::strtok(temp, ".?!"); while(q != NULL) { std::cout << q << std::endl; q = strtok(NULL, ".?!"); } }
问题示例
当输入文件内容为:
I am Mr. Smith.
现有代码输出:
I am Mr Smith
期望输出:
I am Mr. Smith
解决方案
strtok是基于简单字符匹配的分割工具,无法区分普通句号和头衔后的句号,所以换两种更灵活的实现思路:
方法一:手动遍历字符串(适合理解底层逻辑)
通过遍历字符串,逐个检查标点符号,判断当前句号是否属于头衔后缀,再决定是否分割:
#include <iostream> #include <fstream> #include <string> #include <vector> #include <cctype> using namespace std; // 判断当前位置的句号是否是头衔后的句号 bool isTitlePeriod(const string& s, size_t pos) { // 检查Mr. if (pos >= 2 && s.substr(pos-2, 2) == "Mr" && (pos == 2 || isspace(s[pos-3]))) { return true; } // 检查Mrs. if (pos >= 3 && s.substr(pos-3, 3) == "Mrs" && (pos == 3 || isspace(s[pos-4]))) { return true; } // 检查Ms. if (pos >= 2 && s.substr(pos-2, 2) == "Ms" && (pos == 2 || isspace(s[pos-3]))) { return true; } // 检查Dr. if (pos >= 2 && s.substr(pos-2, 2) == "Dr" && (pos == 2 || isspace(s[pos-3]))) { return true; } return false; } vector<string> splitSentences(const string& line) { vector<string> sentences; size_t start = 0; size_t lineLen = line.size(); for (size_t i = 0; i < lineLen; ++i) { char c = line[i]; // 遇到非头衔后缀的标点时分割句子 if ((c == '.' || c == '?' || c == '!') && !isTitlePeriod(line, i)) { string sentence = line.substr(start, i - start); // 去除句子前后的空格 size_t first = sentence.find_first_not_of(" \t"); size_t last = sentence.find_last_not_of(" \t"); if (first != string::npos && last != string::npos) { sentences.push_back(sentence.substr(first, last - first + 1)); } start = i + 1; } } // 处理最后一段未分割的内容 if (start < lineLen) { string sentence = line.substr(start); size_t first = sentence.find_first_not_of(" \t"); size_t last = sentence.find_last_not_of(" \t"); if (first != string::npos && last != string::npos) { sentences.push_back(sentence.substr(first, last - first + 1)); } } return sentences; } int main() { ifstream fileRead("input.txt"); string buffer; while (getline(fileRead, buffer)) { vector<string> sentences = splitSentences(buffer); for (const auto& sen : sentences) { cout << sen << endl; } } fileRead.close(); return 0; }
方法二:正则表达式(代码更简洁)
利用C++11及以后支持的正则表达式库,通过负向断言排除头衔后的句号,直接分割句子:
#include <iostream> #include <fstream> #include <string> #include <regex> #include <vector> using namespace std; vector<string> splitSentences(const string& line) { vector<string> sentences; // 正则规则:匹配非头衔后的句号、问号、感叹号 regex re(R"((?<!Mr|Mrs|Ms|Dr)\.|\?|!)"); // 迭代器提取分隔符之间的内容 sregex_token_iterator it(line.begin(), line.end(), re, -1); sregex_token_iterator end; for (; it != end; ++it) { string sen = *it; // 去除句子前后空格 size_t first = sen.find_first_not_of(" \t"); size_t last = sen.find_last_not_of(" \t"); if (first != string::npos && last != string::npos) { sentences.push_back(sen.substr(first, last - first + 1)); } } return sentences; } int main() { ifstream fileRead("input.txt"); string buffer; while (getline(fileRead, buffer)) { vector<string> sentences = splitSentences(buffer); for (const auto& sen : sentences) { cout << sen << endl; } } fileRead.close(); return 0; }
两种方法对比
- 手动遍历:逻辑直观,适合学习调试,后续要添加更多头衔(如
Prof.)只需修改isTitlePeriod函数即可 - 正则表达式:代码更紧凑,但需要熟悉正则语法,复杂场景下调试难度稍高
内容的提问来源于stack exchange,提问作者TRD
相关产品推荐
相关产品推荐

