最直接方法是用 std::count_if 统计 . ? ! 数量,适用于规范文本;更可靠需用正则 [.?!](?=\s|$) 或手写循环检查句末边界,中文需额外支持。

用 std::count 统计句号、问号、感叹号最直接
字符串中“句子”的定义通常依赖标点:以 .、?、! 结尾,且后面紧跟空格、换行或字符串末尾。最轻量的做法是统计这些终止符数量,但要注意避免误判(如小数点 3.14 或缩写 Mr.)。若文本规范(如用户输入的简单段落),可先用 std::count 快速估算:
size_t count = std::count_if(s.begin(), s.end(), [](char c) {
return c == '.' || c == '?' || c == '!';
});
- 适用于纯人工录入、无嵌入标点的场景(如聊天消息、日志条目)
- 不区分是否真为句末——
"Price: $2.99!"会被算作 2 句 - 性能好,O(n),无额外内存分配
用 std::regex 匹配真正句末位置更可靠
要排除小数点和缩写干扰,需检查标点后是否为边界(空格、制表、换行、字符串尾)。C++11 起支持 std::regex,正则 [.?!](?=\s|$) 可匹配“后跟空白或结尾”的终止符:
std::regex re(R"([.?!](?=\s|$))"); auto begin = std::sregex_iterator(s.begin(), s.end(), re); auto end = std::sregex_iterator(); size_t count = std::distance(begin, end);
-
(?=\s|$)是零宽正向先行断言,不消耗字符,避免重复匹配 - 注意 Windows 换行
\r\n中的\r不属于\s,建议先用std::replace替换\r\n为\n - 正则编译有开销,短字符串反复调用时不如手写循环
手写循环判断句末边界,可控性最强
当需要精确控制逻辑(比如跳过引号内标点、处理省略号 ...、忽略 URL 中的点),手写循环最稳妥。核心是遍历每个字符,检查当前是否为终止符,且下一个字符满足边界条件:
size_t count = 0;
for (size_t i = 0; i < s.length(); ++i) {
if (s[i] == '.' || s[i] == '?' || s[i] == '!') {
size_t next = i + 1;
bool is_end = (next >= s.length()) ||
std::isspace(static_cast<unsigned char>(s[next]));
if (is_end && !(i > 0 && s[i-1] == '.')) { // 排除 "..."
count++;
}
}
}
- 显式处理
...:连续多个点只算一次句末(或跳过) - 可扩展判断前文是否为缩写(维护一个
std::unordered_set<std::string>白名单) - 注意
std::isspace要强转unsigned char,否则对负值字符(如 UTF-8 非 ASCII 字节)行为未定义
中文句号 。 和混合文本需额外处理
纯 ASCII 正则或循环默认不识别中文标点。若文本含中文,必须显式加入匹配:
立即学习“C++免费学习笔记(深入)”;
- 正则改用
R"([.?!。?!](?=\s|$))",但需确保编译器/库支持 UTF-8 字符字面量(GCC/Clang OK,MSVC 需 /utf-8) - 手写循环中,直接比较
s[i] == '。' || s[i] == '?' || s[i] == '!'即可(UTF-8 下这些是单字节字符) - 混合中英文时,常见错误是把英文缩写
e.g.或中文括号后标点(如“例句。”)当成独立句子——此时仅靠标点计数必然不准,需结合分词或 NLP 库
真正难的不是数标点,而是定义“什么是句子”。没有上下文语义,所有方法都只是启发式近似;一旦遇到技术文档、代码注释或带表情符号的聊天记录,就得退回到业务规则本身去补丁。


















