最简单可靠的方法是:非空字符串用 std::count(s.begin(), s.end(), '\n') + 1,空字符串特判为 1 行;windows 换行符需先标准化为 '\n' 再计数,避免 getline 导致末尾空行漏计。

用 std::count 统计换行符最简单可靠
直接统计 '\n' 的数量,再加 1,就是总行数(前提是字符串非空且不以 '\n' 结尾)。这是 C++ 中最轻量、无额外分配、兼容所有标准库版本的做法。
常见错误是忽略空字符串或全空行场景:比如 "" 应为 1 行,"\n\n" 应为 3 行。别用 std::getline 循环——它会吃掉末尾的 '\n',导致最后一行被漏判。
- 对空字符串
"",行数是1 - 字符串以
'\n'结尾时,每个多余的'\n'都算一行(如"a\nb\n"是 3 行) - 使用
std::count(s.begin(), s.end(), '\n') + 1即可,无需额外容器或状态机
处理 Windows 换行符 "\r\n" 要先标准化
如果输入可能来自 Windows 文本(含 "\r\n"),而你又不能依赖外部库做预处理,std::count 会把 "\r\n" 算成两个字符,但只应计为一个换行。这时候不能简单替换整个字符串(开销大),而是遍历并跳过 '\r' 后紧跟的 '\n'。
更稳妥的做法是:先用 std::replace 把所有 "\r\n" 替换成 "\n",再把残留的孤立 '\r'(如 "\r" 或 "\rhello")也替换成 '\n',最后统一 count('\n')。注意 std::replace 是就地修改,若原字符串不可变,需先拷贝。
- 不要用
find("\r\n")手动循环——边界易错,且std::string::find不支持重叠匹配 - 若确定输入只有 Unix 或 Windows 风格(不会混用),可跳过标准化,直接按
'\n'计数 -
"\r"单独出现(Mac OS 9 及更早)在现代 C++ 场景极少,通常可忽略,除非协议明确要求兼容
用 std::stringstream + getline 容易少算最后一行
很多人习惯写:
std::stringstream ss(s); std::string line; int lines = 0; while (std::getline(ss, line)) ++lines;
这段代码在 s 不以 '\n' 结尾时,会正确返回行数;但如果 s == "a\nb",它返回 2 —— 正确;但若 s == "a\nb\n",它也返回 2,而实际应为 3(末尾空行)。因为 std::getline 在读到结尾前的 '\n' 时才结束当前行,但遇到文件尾(EOF)且无换行时,仍会把最后一段内容作为一行返回;唯独当 EOF 前刚读完一个 '\n',它不会触发下一次循环,于是末尾空行丢失。
- 这个行为由
std::getline的实现定义:仅当成功提取字符后才设goodbit,末尾'\n'后的 EOF 不触发新迭代 - 如果你必须用流式解析(比如还要同时处理每行内容),应在循环后检查
ss.peek() == EOF && !s.empty() && s.back() == '\n',手动补一行 - 纯计数场景下,这方法既慢又容易出错,不推荐
性能敏感时避免构造临时 std::string_view 或子串
有人为了“安全”把原始字符串转成 std::string_view 再遍历,其实没必要。C++17 起 std::string::data() 和 .size() 是 O(1),直接用指针遍历比 string_view 还快一点点(少了成员变量访问)。
真正要小心的是误用 s.substr(i, 1) 或 s[i] == '\n' 在 debug 模式下触发边界检查(虽然 release 下通常优化掉),但更隐蔽的问题是:某些旧编译器对 s.data() 在空字符串时返回 nullptr,所以务必先判空。
- 高效写法:
int lines = s.empty() ? 1 : 1 + std::count(s.begin(), s.end(), '\n'); - 如果字符串极大(百 MB 级),考虑用
memchr分块扫描,但绝大多数业务场景用std::count已足够 - 别为“看起来更现代”而引入
std::ranges::count——它在 GCC 12 前有 ABI 兼容问题,且调试器支持差
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











