从一个完整的字符串里提取出某一部分内容,通常称为 “字符串截取”,比如从网址 "www.lvyenet.com" 中取出域名,或者从日期 "2026-10-20" 中取出月份。
在 C++ 中,我们可以使用 std::string 提供的 substr() 成员函数来实现。其中,substr 是 “substring(子字符串)” 的缩写。
截取指定长度的子串
调用 substr() 函数时,最常用的方式是传入 2 个参数,分别指定 “从哪个下标开始截” 以及 “一共截取多少个字符”。
语法:
str.substr(pos, count)说明:
调用 substr(pos, count) 会返回一个新生成的子字符串,它的 2 个参数含义如下:
- pos(必选):是 “position(位置)” 的缩写,表示从哪个下标位置开始截取,下标从 0 开始计算。
- count(可选):表示从
pos位置开始,向后连续截取的字符个数。需要注意,这里是 “字符数量”,而不是结束下标。
我们先来看一下字符串 "lvyenet" 中每个字符对应的下标位置,如下表所示。
| 字符位置 | 下标 | 对应字符 |
|---|---|---|
| 第 1 个字符 | 0 | 'l' |
| 第 2 个字符 | 1 | 'v' |
| 第 3 个字符 | 2 | 'y' |
| 第 4 个字符 | 3 | 'e' |
| 第 5 个字符 | 4 | 'n' |
| 第 6 个字符 | 5 | 'e' |
| 第 7 个字符 | 6 | 't' |
如果我们想从中取出前面的 "lvye",只需要从下标 0 开始取 4 个字符;如果想取出后面的 "net",只需要从下标 4 开始取 3 个字符。
示例 1:截取指定长度的子串
#include <iostream>
#include <string>
int main() {
std::string text = "lvyenet";
std::string first = text.substr(0, 4);
std::string second = text.substr(4, 3);
std::cout << first << '\n';
std::cout << second << '\n';
std::cout << text << '\n';
return 0;
}运行结果如下。
lvye
net
lvyenet分析:
在上面例子中,text.substr(0, 4) 从下标 0 处的 'l' 开始连续取出 4 个字符,得到 "lvye"。text.substr(4, 3) 则从下标 4 处的 'n' 开始取出 3 个字符,得到 "net"。
很多接触过 Python 或 JavaScript 的小伙伴,容易把 substr(pos, count) 的第 2 个参数误当成 “结束下标”。比如想从日期 "2026-10-20" 中提取月份 "10",我们可以对比下面两种写法:
std::string date = "2026-10-20";
// 正确方式
std::string month1 = date.substr(5, 2);
// 错误方式
std::string month2 = date.substr(5, 7);上面代码中,月份 "10" 从下标 5 开始,一共只有 2 个字符,因此第 2 个参数应该传入 2。如果误把结束下标 7 传进去,程序就会从下标 5 开始往后截取 7 个字符,结果就变成了 "10-20"。
下面代码中,sub 的最终值是 "gram",小伙伴们可以对照下标算一算为什么。
std::string word = "program";
std::string sub = word.substr(3, 4);注意 调用 substr() 不会修改原字符串本身,而是返回一个新字符串。如果希望更新原变量的内容,记得写成 text = text.substr(0, 4); 重新接收返回值。
截取到字符串末尾
很多时候,我们只知道要从哪个下标位置开始截,并且希望把后面的内容一直截取到字符串末尾,却不想自己去算后面还剩多少个字符。
遇到这种情况,我们可以直接省略第 2 个参数 count,只传入起始位置 pos 即可。
语法:
str.substr(pos)说明:
当省略第 2 个参数时,substr(pos) 默认会从下标 pos 开始,把后面剩余的所有字符一直截取到字符串末尾。
示例 2:截取到字符串末尾
#include <iostream>
#include <string>
int main() {
std::string url = "www.lvyenet.com";
std::string domain = url.substr(4);
std::cout << domain << '\n';
return 0;
}运行结果如下。
lvyenet.com分析:
在 "www.lvyenet.com" 中,开头的 "www." 占了 4 个字符,字母 'l' 的下标正好是 4。
调用 url.substr(4) 会从下标 4 一路截取到末尾,直接得到 "lvyenet.com",省去了手动调用 size() 计算剩余长度的步骤。
C++ substr() 注意事项
在使用 substr(pos, count) 时,如果传进去的数字超过了字符串的长度,会发生什么情况呢?实际上,第 2 个参数 count 超出范围,与第 1 个参数 pos 超出范围,两者的表现完全不一样。
1. 截取个数 count 超出剩余长度
如果第 2 个参数 count 超过了从 pos 开始往后剩下的字符总数,substr() 并不会报错,而是截取到字符串末尾就自动停下来。如果把 count 写成 0,则会返回一个空字符串 ""。
示例 3:count 超过剩余长度
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
std::string sub1 = text.substr(2, 100);
std::string sub2 = text.substr(2, 0);
std::cout << "[" << sub1 << "]" << '\n';
std::cout << "[" << sub2 << "]" << '\n';
return 0;
}运行结果如下。
[llo]
[]分析:
在 "Hello" 中,从下标 2 往后只剩下 "llo" 这 3 个字符。虽然 text.substr(2, 100) 指定截取 100 个字符,但读到末尾就会自动停止,最终得到 "llo"。
而第二行的 text.substr(2, 0) 表示截取 0 个字符,因此返回了一个空字符串 ""。
2. 起始位置 pos 的有效范围
相比之下,第 1 个参数 pos 的范围限制就要严格得多了,主要分为以下 3 种情况:
- pos < size():起始位置在有效字符下标范围内,正常向后截取。
- pos == size():起始位置刚好落在末尾最后一个字符之后,此时调用依然合法,直接返回空字符串
""。 - pos > size():起始位置超出了允许范围,程序运行时会直接报出
std::out_of_range越界错误并停止运行。
示例 4:检查起始位置的范围
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
std::string emptyStr = text.substr(text.size());
std::cout << "[" << emptyStr << "]" << '\n';
std::size_t pos = 10;
if (pos <= text.size()) {
std::cout << text.substr(pos) << '\n';
} else {
std::cout << "起始位置超出有效范围" << '\n';
}
return 0;
}运行结果如下。
[]
起始位置超出有效范围分析:
"Hello" 的长度是 5,所以 text.substr(text.size()) 相当于 text.substr(5)。因为起始下标刚好等于字符串长度,这次调用是合法的,最终得到了空字符串 ""。
后面定义的无符号下标 pos 等于 10,已经大于字符串长度 5 了。直接调用 text.substr(10) 会报越界错误,因此在动态计算下标时,我们可以先配合 if 语句,确认 pos <= text.size() 后再执行截取。
