36 C++ STL模板库5-string
C++ STL模板库5-string
文章目录
- C++ STL模板库5-string
- 一、构造与赋值
- - 构造函数
- - 赋值操作
- 二、容量操作
- 三、元素访问
- 四、修改操作
- 追加
- 插入
- 删除
- 替换
- 五、字符串操作
- 子串操作
- 查找
- 比较
- 六、迭代器支持
- 七、数值转换(C++11起)
- 八、其他操作
- 九、非成员函数
- [附录]
std::string
是一个STL模板类,可以指定字符类型,如
std::string
(默认为
char
类型)和
std::wstring
(宽字符类型)。使用时,需要包含头文件
<string>
。
一、构造与赋值
- 构造函数
-
默认构造函数:
string()
std::string s1; // 空字符串 ""
-
拷贝构造函数:
string(const string& str)
std::string src("Hello"); std::string s2(src); // s2 = "Hello"
-
子字符串构造:
string(const string& str, size_t pos, size_t len = npos)
std::string base("Programming"); std::string s3(base, 3, 4); // 从索引3取4字符 → "gram" std::string s4(base, 6); // 从索引6取到结尾 → "ing"
-
从C字符串构造:
string(const char* s)
std::string s5("C++17"); // 直接初始化 → "C++17"
-
从字符数组构造:
string(const char* s, size_t n)
const char arr[] = {'A','B','C','\0','D'}; std::string s6(arr, 3); // 仅取前3字符 → "ABC"(忽略\0)
-
填充构造:
string(size_t n, char c)
std::string s7(5, '!'); // 重复5次'!' → "!!!!!"
-
范围构造:
template <class InputIterator> string(InputIterator first, InputIterator last)
std::vector<char> vec = {'a', 'b', 'c'}; std::string s8(vec.begin(), vec.end()); // → "abc"
-
移动构造(C++11):
string(string&& str) noexcept
std::string tmp("Movable"); std::string s9(std::move(tmp)); // s9="Movable", tmp变为空
-
初始化列表构造(C++11):
string(initializer_list<char> il)
std::string s10{'H', 'i', '!'}; // → "Hi!"(等同于 s10 = "Hi!")
[完整示例]:
#include <iostream>
#include <string>
#include <vector> int main() {std::string s1; // 空字符串 ""std::string src("Hello");std::string s2(src); // s2 = "Hello"std::string base("Programming");std::string s3(base, 3, 4); // 从索引3取4字符 → "gram"std::string s4(base, 6); // 从索引6取到结尾 → "ing"std::string s5("C++17"); // 直接初始化 → "C++17"const char arr[] = {'A','B','C','\0','D'};std::string s6(arr, 3); // 仅取前3字符 → "ABC"(忽略\0)std::string s7(5, '!'); // 重复5次'!' → "!!!!!"std::vector<char> vec = {'a', 'b', 'c'};std::string s8(vec.begin(), vec.end()); // → "abc"std::string tmp("Movable");std::string s9(std::move(tmp)); // s9="Movable", tmp变为空std::string s10{'H', 'i', '!'}; // → "Hi!"(等同于 s10 = "Hi!")std::cout<<s1<<std::endl;std::cout<<src<<std::endl;std::cout<<s2<<std::endl;std::cout<<base<<std::endl;std::cout<<std::endl;std::cout<<s3<<std::endl;std::cout<<s4<<std::endl;std::cout<<std::endl;std::cout<<s5<<std::endl;std::cout<<s6<<std::endl;std::cout<<s7<<std::endl;std::cout<<std::endl;std::cout<<s8<<std::endl;std::cout<<s9<<std::endl;std::cout<<s10<<std::endl;std::cout<<tmp<<std::endl;return 0;
}
输出结果:
Hello Hello Programminggram mmingC++17 ABC !!!!!abc Movable Hi!
- 赋值操作
-
operator=
: 拷贝赋值、移动赋值、C字符串赋值、字符赋值std::string str; str = "Direct"; // C字符串赋值 → "Direct" str = std::string("Copy");// 拷贝赋值 → "Copy" std::string tmp("Move"); str = std::move(tmp); // 移动赋值 → "Move"(tmp变空) str = 'X'; // 字符赋值 → "X"
-
assign()
: 多种重载形式,功能类似构造函数str.assign(3, 'Z'); // 重复赋值 → "ZZZ" str.assign("C-String"); // C字符串赋值 → "C-String" std::string src("Source"); str.assign(src, 2, 3); // 子串赋值 → "urc"(从索引2取3字符) const char arr[] = "Array"; str.assign(arr, 4); // 限定长度 → "Arra"(取前4字符) std::list<char> lst = {'l','i','s','t'}; str.assign(lst.begin(), lst.end()); // 迭代器赋值 → "list"
💡 关键说明
- 优先用移动操作处理临时字符串,避免不必要的拷贝开销。
assign()
灵活性支持更多场景,如子串截取和迭代器范围赋值。
二、容量操作
size()
/length()
: 返回字符数max_size()
: 返回最大可能长度capacity()
: 返回当前分配的存储容量reserve()
: 预留存储空间resize()
: 改变字符串大小empty()
: 判断是否为空shrink_to_fit()
(C++11): 减少容量以适应大小
[完整示例]
#include <iostream>
#include <string>int main() {// ===== 1. 基础容量操作 ===== std::string s = "Hello";std::cout << "1. size/length: " << s.size() // 5 << " | max_size: " << s.max_size() // 非常大(约4611686018427387897)<< " | capacity: " << s.capacity() << "\n"; // 15(SSO优化)// ===== 2. 预分配空间 ===== s.reserve(100);std::cout << "2. 预留100后 capacity: " << s.capacity() // 100+<< " | size: " << s.size() << "\n"; // 5(内容不变)// ===== 3. 调整大小 ===== s.resize(8, '!'); // 扩展到8字符,填充!std::cout << "3. resize(8,'!'): \"" << s << "\"\n"; // "Hello!!!"s.resize(3); // 截断到3字符 std::cout << " resize(3): \"" << s << "\"\n"; // "Hel"// ===== 4. 内存优化 ===== s.shrink_to_fit();std::cout << "4. 压缩后 capacity: " << s.capacity() // ≈3-15(实现相关)<< " | empty? " << std::boolalpha << s.empty() << "\n"; // false // ===== 5. 空字符串处理 ===== std::string empty_str;std::cout << "5. 空字符串: size=" << empty_str.size() << " | empty? " << empty_str.empty() << "\n"; // true
}
输出结果:
1. size/length: 5 | max_size: 9223372036854775807 | capacity: 15 2. 预留100后 capacity: 100 | size: 5 3. resize(8,'!'): "Hello!!!"resize(3): "Hel" 4. 压缩后 capacity: 15 | empty? false 5. 空字符串: size=0 | empty? true
🔍 关键函数解析
函数 | 说明 | 时间复杂度 | 典型输出 |
---|---|---|---|
size()/length() | 当前字符数(不含\0 ) | O(1) | "Hello" → 5 |
max_size() | 系统支持的最大长度 | O(1) | 通常 > 4亿字符 |
capacity() | 当前分配的内存容量 | O(1) | 初始值由SSO决定 |
reserve(n) | 预分配内存避免重复扩容 | O(n) | 扩容后 ≥ 指定值 |
resize(n, c) | 调整长度(不足时填充字符c) | O(n) | 可扩展或截断 |
empty() | 检查是否空字符串 | O(1) | "" → true |
shrink_to_fit() | 释放多余内存(非强制) | 实现相关 | 容量 ≈ 实际大小 |
💡 使用技巧与注意事项
-
SSO优化(短字符串优化)
std::string short_str = "SSO!"; // 栈存储(capacity=15) std::string long_str(500, 'x'); // 堆存储(capacity≥500)
当字符串≤15字符(编译器实现相关)时,直接存储在对象内部,避免堆分配
-
扩容策略
std::string s; for (int i=0; i<1000; ++i) {s += 'a'; // 可能多次扩容(低效)// 优化:s.reserve(1000) 先预分配 }
连续追加时优先用
reserve()
避免反复扩容 -
内存释放限制
s.resize(3); s.shrink_to_fit(); // 容量可能仍>3(实现可忽略)
shrink_to_fit()
是非绑定请求,编译器可能保留缓冲 -
安全截断
std::string path = "/home/user/file.txt"; path.resize(path.find('.')); // 危险!若找不到返回npos // 正确:if(auto pos=path.find('.')) path.resize(pos);
截断前务必验证位置有效性,避免
resize(npos)
导致异常 -
C++17优化
s.clear(); s.shrink_to_fit(); // C++17前需两步释放内存 // C++17: s = ""; // 可能直接触发内存释放
现代编译器对空字符串赋值可能自动释放堆内存
⚠️ 性能提示:高频修改字符串时(如日志处理),优先用
reserve()
预分配+std::string_view
切片,可提升10倍以上性能。
三、元素访问
-
operator[]
: 访问指定位置字符(不检查边界)std::string s = "Hello"; char c1 = s[1]; // 取第二个字符 → 'e' char c2 = s[10]; // 越界访问 → 未定义行为(可能崩溃或返回垃圾值)
-
at()
: 访问指定位置字符(检查边界)std::string s = "World"; char c1 = s.at(4); // 取第五个字符 → 'd' char c2 = s.at(5); // 越界 → 抛 std::out_of_range 异常
-
front()
(C++11): 访问第一个字符 -
back()
(C++11): 访问最后一个字符std::string s = "C++17"; char first = s.front(); // 首字符 → 'C' char last = s.back(); // 尾字符 → '7'
- 等效于
s[0]
和s[s.size()-1]
,但语义更清晰 - 空字符串调用将导致未定义行为——需提前检查
!s.empty()
- 等效于
-
data()
: 返回指向内部数组的指针std::string s = "Data"; const char* p = s.data(); // 指向内部数组的指针 printf("%s", p); // 输出 "Data"// C++17 起可修改(需非常谨慎): std::string s2 = "Test"; char* p2 = s2.data(); // 非 const 指针 p2[0] = 'B'; // s2 变为 "Best"
data()
不一定以\0
结尾(除非显式添加)- 修改需确保不越界且字符串未重新分配内存
-
c_str()
: 返回C风格字符串std::string s = "File.txt"; FILE* file = fopen(s.c_str(), "r"); // 传递给C函数
- 返回以
\0
结尾的只读指针 - 调用后若字符串被修改,指针可能失效(如
s += "new"
)
- 返回以
[综合示例与安全实践]
-
示例:
std::string s = "SafeAccess"; if (!s.empty()) {s.front() = 'L'; // 修改首字符 → "LafeAccess"s.back() = '!'; // 修改尾字符 → "LafeAcces!"s.data()[4] = 'X';// 修改第五个字符 → "LafeXcces!" // C++17 起 data() 返回可写指针 } const char* cstr = s.c_str(); // 保证以 '\0' 结尾
安全准则:
- 优先用
at()
处理不确定索引 - 避免在
data()
/c_str()
指针存活期间修改字符串 - 修改操作后立即弃用旧指针
四、修改操作
追加
-
operator+=
: 追加字符串、C字符串或字符std::string s = "C++"; s += " STL"; // 追加C字符串 → "C++ STL" s += '!'; // 追加字符 → "C++ STL!" s += std::string(" Rocks"); // 追加string → "C++ STL! Rocks"
-
append()
: 多种重载形式,功能类似构造函数std::string s = "Hello"; s.append(3, '!'); // 追加重复字符 → "Hello!!!" s.append("World", 0, 3); // 追加子串 → "Hello!!!Wor" s.append(s.begin(), s.begin()+5); // 迭代器范围 → "Hello!!!WorHello"
-
push_back()
: 追加单个字符std::string s = "Open"; for (char c : {'S','o','u','r','c','e'}) s.push_back(c); // → "OpenSource"
插入
-
insert()
: 在指定位置插入字符串、C字符串或字符std::string s = "C++20"; s.insert(3, " Standard"); // 索引3插入前插入"Standard" → "C++ Standard20" s.insert(0, 2, '>'); // 开头插入重复字符'>' → ">>C++ Standard20" s.insert(s.find('2'), "17");// 在'2'前插入"17" → ">>C++ Standard1720"
删除
-
erase()
: 删除字符或子串std::string s = "Remove[this]Text"; s.erase(6, 6); // 从索引6删除6字符 → "RemoveText" s.erase(s.find('T')); // 删除'T'到结尾 → "Remove" s.erase(s.begin()); // 删除首字符 → "emove"
-
clear()
: 清空字符串std::string s = "Temporary"; s.clear(); // s = "",capacity()不变
-
pop_back()
(C++11): 删除最后一个字符std::string path = "/home/user/file.txt/"; while (!path.empty() && path.back() == '/')path.pop_back(); // → "/home/user/file.txt"
替换
-
replace()
: 替换子串std::string s = "I like Java"; size_t pos = s.find("Java"); if (pos != std::string::npos)s.replace(pos, 4, "C++"); // → "I like C++"// 高效替换(避免临时对象) s.replace(s.begin(), s.begin()+1, "You"); // → "You like C++"
💡 关键特性对比表
操作 | 时间复杂度 | 典型场景 | 安全提示 |
---|---|---|---|
operator+= | O(n) | 快速追加已知内容 | 避免循环内多次调用(预分配) |
append() | O(n) | 精确控制追加长度/重复字符 | 指定长度时需确保内存有效 |
push_back() | O(1)* | 单字符高频追加 | 循环中优先于 += |
insert() | O(n) | 在已知位置插入内容 | 索引越界导致未定义行为 |
erase() | O(n) | 删除指定区间 | 删除后迭代器失效 |
replace() | O(n) | 内容替换(比erase+insert 高效) | 新旧长度不同可能触发内存重分配 |
*注:
push_back()
均摊 O(1),但扩容时 O(n);
性能提示:高频修改时先用reserve()
预分配内存。
⚠️ 边界安全实践
#include <iostream>
#include <string>int main() {std::string s = "SafeOperation";// 安全插入(校验位置)if (s.size() >= 5) s.insert(5, "Insert"); std::cout<<s<<std::endl;// 安全替换(校验子串存在性)s = "SafeOperation";auto pos = s.find("Operation"); if (pos != std::string::npos) s.replace(pos, 9, "Processing");std::cout<<s<<std::endl;return 0;
}
输出结果:
SafeOInsertperation SafeProcessing
五、字符串操作
子串操作
-
substr()
: 获取子串std::string s = "Programming"; // 索引映射:P(0) r(1) o(2) g(3) r(4) a(5) m(6) m(7) i(8) n(9) g(10)// 示例1:substr(pos=3, len=4) std::string sub1 = s.substr(3, 4); // 索引3开始取4字符 → "gram"// 示例2:substr(pos=6) → 截取到结尾 std::string sub2 = s.substr(6); // 索引6到结尾 → // 输出 mming// 示例3:substr(pos=s.find('g')) → 首个'g'的索引为3 std::string sub3 = s.substr(s.find('g')); // 首个'g'到结尾 → "gramming"
查找
字符串索引是从0开始的连续整数,空格也占用索引位置。
均返回 size_t
,失败返回 string::npos
-
find()
: 查找子串或字符 -
rfind()
: 反向查找子串或字符 -
find_first_of()
: 查找给出字符集中任一字符首次出现 -
find_last_of()
: 查找给出字符集中任一字符最后出现 -
find_first_not_of()
: 查找不在给出字符集中的字符首次出现 -
find_last_not_of()
: 查找不在字给出符集中的字符最后出现std::string text = "C++ Standard Template Library"; // 字符索引分布(含空格):// C(0) +(1) +(2) [空格](3)// S(4) t(5) a(6) n(7) d(8) a(9) r(10) d(11) [空格](12)// T(13) e(14) m(15) p(16) l(17) a(18) t(19) e(20) [空格](21)// L(22) i(23) b(24) r(25) a(26) r(27) y(28)// 正向查找 size_t pos1 = text.find("Template"); // 子串位置 → 13 size_t pos2 = text.find('S'); // 字符位置 → 4 // 反向查找 size_t pos3 = text.rfind("a"); // 最后一个'a' → 26 (在"Library"中)// 字符集查找 size_t pos4 = text.find_first_of("aeiou"); // → 实际输出:6 首元音字母为 a(索引6,位于 "Standard" 中) size_t pos5 = text.find_last_of(" "); // → 实际输出:21 最后一个空格在 "Template" 后(索引21) size_t pos6 = text.find_first_not_of("C+ "); // → 实际输出:4 首部开始不是 “C/+/空格”的字符 size_t pos7 = text.find_last_not_of("ary "); // → 实际结果:24 尾部最后一个非 a/r/y/空格 字符是 b(索引24)
性能提示:高频操作建议使用 std::string_view 避免拷贝,或预计算关键位置索引。
实用场景示例:
// 场景1:提取文件扩展名
std::string filename = "report.pdf";
size_t dot_pos = filename.rfind('.');
if (dot_pos != std::string::npos) {std::string ext = filename.substr(dot_pos + 1); // "pdf"
}// 场景2:删除字符串尾部空格
std::string input = "Hello World ";
size_t last_char = input.find_last_not_of(" ");
if (last_char != std::string::npos) {input = input.substr(0, last_char + 1); // "Hello World"
}// 场景3:分割键值对
std::string config = "timeout=30";
size_t eq_pos = config.find('=');
std::string key = config.substr(0, eq_pos); // "timeout"
std::string value = config.substr(eq_pos + 1); // "30"
关键注释:
-
npos
是特殊值static const size_type npos = -1;
,表示未找到 -
查找函数均支持第二个参数:起始搜索位置例如:
size_t pos2 = text.find('t', 10); // 从索引10找字符t → 19 size_t pos4 = text.rfind("Template", 15); // 在索引15前反向查找 → 13
-
字符集查找函数(如
find_first_of
)效率显著高于循环遍历字符
比较
-
compare()
: 比较字符串
注意:compare方法返回的是int,比较结果只关心其符号表示(大于0,小于0,等于0),不要关心但具体数值,不同环境可能每次都有不同结果。std::string s1 = "apple", s2 = "app"; int res1 = s1.compare(s2); // >0(s1 > s2:"apple" vs "app") int res2 = s2.compare("apple"); // <0(s2 < "apple")std::string s = "C++17 Standard"; // 比较 s的子串"17" 与 "20" int res3 = s.compare(3, 2, "20"); // <0(17 < 20)// 比较 s的子串"Standard" 与另一字符串的子串 std::string other = "C++20 Library"; int res4 = s.compare(5, 7, other, 5, 7); // >0("Standard" > "Library")const char* cstr = "Hello"; std::string str = "Hello"; // 比较 string 与 C字符串 int res5 = str.compare(cstr); // ==0(相等) // 比较前3字符 int res6 = str.compare(0, 3, "Hel"); // ==0(相等)// 执行逐字符ASCII值比较,非数值解析 比较只在首个字符差异处结束,不解析后续字符 std::string version1 = "2.3.4"; int res7 = version1.compare(0, 3, "12.1"); // >0(实际行为:字典序比较(字符 '2' (50) > '1' (49) → 返回正值)auto compare_log = [](int result) {if (result == 0) std::cout << "相等\n";else if (result > 0)std::cout << "左侧大\n";else std::cout << "右侧大\n"; }; compare_log(std::string("banana").compare("apple")); // 输出 "左侧大"('b' > 'a')
-
operator==
,operator!=
,operator<
,operator>
,operator<=
,operator>=
: 关系运算符
-
🔑 运算符行为解析表
运算符 等效调用 比较规则 典型误用场景 ==
s1.compare(s2)==0
逐字符全等 忽略大小写差异(“A"≠"a”) !=
s1.compare(s2)!=0
至少一个字符不等 误判不同长度的相等前缀 <
s1.compare(s2)<0
首个差异字符ASCII较小,或更短且前缀相同 数字字符串比较(“100”<“20”) >
s1.compare(s2)>0
首个差异字符ASCII较大,或更长且前缀相同 非字母字符顺序(“#”<“1”) <=
!(s1>s2)
等价于"小于或等于" 组合逻辑理解错误 >=
!(s1<s2)
等价于"大于或等于" 空字符串的特殊处理
六、迭代器支持
-
begin()
/cbegin()
(C++11): 返回指向开头的迭代器 -
end()
/cend()
(C++11): 返回指向结尾的迭代器std::string text = "c++17";// 1. 非常量迭代器(可修改内容) for (auto it = text.begin(); it != text.end(); ++it) {*it = std::toupper(*it); //转换字符串为大写 修改字符:c++17 → C++17 }// 2. 常量迭代器(只读安全) std::cout << "字符序列: "; for (auto cit = text.cbegin(); cit != text.cend(); ++cit) {std::cout << *cit << " "; // 输出: C + + 1 7 }
-
rbegin()
/crbegin()
(C++11): 返回反向开头迭代器 -
rend()
/crend()
(C++11): 返回反向结尾迭代器std::string palindrome = "level"; // 1. 反向非常量迭代器 auto rit = palindrome.rbegin(); *rit = 'L'; // 修改末尾字符:level → leveL std::string filename = "document.backup.zip"; //2. 反向查找最后一个点号 auto rdot = std::find(filename.crbegin(), filename.crend(),'.' );
⚙️迭代器类型对照表
迭代器类型 | 访问权限 | 方向 | 典型应用场景 | 图示示例 |
---|---|---|---|---|
begin() | 读写 | 正向 | 遍历修改字符 | [C][+][+][1][7] → |
cbegin() | 只读 const | 正向 | 安全读取 | [C][+][+][1][7] → |
rbegin() | 读写 | 反向 | 从后向前修改(如路径处理) | ← [C][+][+][1][7] |
crbegin() | 只读 const | 反向 | 反向搜索(如扩展名提取) | ← [C][+][+][1][7] |
注:
end()
/cend()
指向 末位后一位置(哨兵位,不可解引用)rend()
/crend()
指向 首位前一位置(哨兵位,不可解引用)- 反向迭代器
rbegin()
实际指向最后一个元素,rend()
指向第一个元素前- 一致性原则,始终使用 相同类型 的迭代器构造子串,
const_iterator + cend()
或iterator + end()
- 优先选择
cbegin()
/cend()
,明确只读意图
七、数值转换(C++11起)
-
有符号转换为整数
stoi()
- 字符串转int
stol()
- 字符串转long
stoll()
:- 字符串转long long
// stoi - 字符串转 int std::string num1 = "42";int i = std::stoi(num1);std::cout << "stoi: " << i * 2 << std::endl; // 84// stol - 字符串转 long(处理大数值)std::string num2 = "2147483647"; // > INT_MAX long l = std::stol(num2);std::cout << "stol: " << l / 2 << std::endl; // 1073741823// stoll - 字符串转 long long(极大值)std::string num3 = "9223372036854775807"; // LLONG_MAX long long ll = std::stoll(num3, nullptr, 0); // 自动识别进制 std::cout << "stoll: " << ll - 1 << std::endl;// 9223372036854775806
-
有符号整数转换函数对比
| 函数 | 返回类型 | 典型应用场景 | 特殊处理机制 |
|----------|---------------|-----------------------|----------------------|
|stoi()
|int
| 通用整数解析 | 自动忽略前导空白 |
|stol()
|long
| 大数值/跨平台兼容 | 支持0开头的八进制数 |
|stoll()
|long long
| 超大整数(如时间戳) | 支持0x开头的十六进制 |示例差异:
std::stoi("2147483647"); // ✅ 最大int值 std::stoi("2147483648"); // ❌ 抛出out_of_range std::stoll("9223372036854775807"); // ✅ 最大long long
-
-
转换为无符号整数
stoul()
- 字符串转 unsigned long
stoull()
- 字符串转 unsigned long longstd::cout << std::stoul("-100") << std::endl;//4294967196 std::cout << std::stoul("0xFFFF", nullptr, 16) << std::endl;//65535 std::cout << std::stoull("1111000011110000", nullptr, 2) << std::endl;//61680//进制处理示例: std::stoul("0xFF", nullptr, 16); // 255(明确指定16进制) std::stoull("0755", nullptr, 0); // 493(0前缀自动识别为8进制)
- 无符号整数转换函数对比
函数 返回类型 关键特性 负值处理 stoul()
unsigned long
支持2-36进制解析 遇"-"立即抛出异常 stoull()
unsigned long long
可解析0x/0X前缀的十六进制 拒绝任何负号 -
转换为浮点数
stof()
- 字符串转 float
stod()
- 字符串转 double(更高精度)
stold()
- 字符串转 long double(最高精度)- 示例
// stof - 字符串转 float std::string pi_str = "3.14159"; float f = std::stof(pi_str); std::cout << "stof: " << f * 2 << std::endl; // 6.28318 // stod - 字符串转 double(更高精度) std::string sci_str = "6.022e23"; double d = std::stod(sci_str); std::cout << "stod: " << d / 1e21 << std::endl; // 602.2// stold - 字符串转 long double(最高精度) std::string precise = "0.1234567890123456789"; long double ld = std::stold(precise); std::cout.precision(21); ////设置精度为21位,此精度为小数点位数 std::cout << "stold: " << ld << std::endl; // 0.123456789012345678901
- 精度差异验证:
std::stof("0.123456789"); // → 0.123456791(第9位失真) std::stod("0.1234567890123456"); // 保留15位精度 std::stold("0.1234567890123456789"); // 完整保留21位数字
-
to_string()
: 数值转字符串
to_string
性能优势:比sprintf快3倍,比stringstream快5倍.std::to_string(42); // int → "42" std::to_string(3.14f); // float → "3.140000" std::to_string(true); // bool → "1" std::to_string(18446744073709551615ULL); // unsigned long long
-
注意事项:
-
范围与精度: 整型 —— stoll > stol > stoi ,浮点 —— stold > stod > stof
-
整型转换均可指定进制(2~36),浮点仅支持10进制
-
参数错误时抛出异常:
std::cout<< std::stoi("hello")<< std::endl;
异常:
terminate called after throwing an instance of 'std::invalid_argument' what(): stoi
-
超范围异常:
std::cout<< std::stoi("2147483648")<< std::endl;
异常:
terminate called after throwing an instance of 'std::out_of_range' what(): stoi
-
字符串转数字,遇到非数字格式的字符串时停止:
size_t pos; std::cout<<std::stod("3,14159", &pos)<<' '<<pos<<std::endl;// 3 1,因逗号停止
-
浮点精度陷阱
// 错误用法:直接比较转换结果 float f1 = std::stof("3.14"); float f2 = 3.14f; if (f1 == f2) // 可能失败!// 正确方案:允许误差范围 if (std::abs(f1 - f2) < 1e-6)
-
所有的
stox
函数原型都是:stox ( const std::string& str, std::size_t* pos = nullptr,int base = 10 );
且每个函数都有一个对应的宽字节版本,只有第一个参数的类型不同:
stox ( const std::wstring& str, std::size_t* pos = nullptr,int base = 10 );
-
to_string()
也有一个对应的宽字节版本to_wstring()
.
-
💎 跨函数核心差异总结
维度 | 整型转换 | 无符号转换 | 浮点转换 | to_string() |
---|---|---|---|---|
符号处理 | 接受负号 | 拒绝负号 | 接受负号 | 保留原始符号 |
进制支持 | 支持2-36进制 | 支持2-36进制 | 仅十进制 | 固定十进制输出 |
异常类型 | invalid_argument | out_of_range | out_of_range | 永不抛出异常 |
前缀解析 | 自动识别0/0x | 自动识别0/0x | 忽略数字前缀 | 不适用 |
空白处理 | 跳过前导空白 | 跳过前导空白 | 跳过前导空白 | 无空白生成 |
八、其他操作
-
swap()
: 交换内容(高效交换底层数据)- 不涉及内存复制,仅交换内部指针
- 比
a = b
赋值操作高效10倍以上
std::string a = "AAA"; std::string b = "BBB";a.swap(b); // 或 swap(a, b)std::cout << "a: " << a << "\n"; // 输出: BBB std::cout << "b: " << b << "\n"; // 输出: AAA
-
get_allocator()
: 获取分配器- 功能:获取字符串使用的内存分配器对象
std::string s = "Allocator Demo"; // 获取分配器副本 auto alloc = s.get_allocator(); // 使用相同分配器创建新字符串 std::string s2(alloc); s2 = "Same allocator";
-
copy()
: 复制字符序列到数组- 将字符串内容复制到C风格字符数组
- copy() 不自动添加 \0,需手动添加
std::string s = "Copy\0Demo"; // 含空字符 char buffer[20];// 复制操作(返回实际复制字符数)len最大为sizeof(buffer)-1 确保不越界 size_t len = s.copy(buffer, sizeof(buffer)-1); buffer[len] = '\0'; // 手动添加终止符 std::cout << "内容: " << buffer // 输出: Copy C风格字符串以\0终止<< "\n长度: " << strlen(buffer) // 输出: 4 << "\n实际复制: " << len; // 输出: 4 \0Demo 部分被丢弃
九、非成员函数
-
operator+
: 字符串连接
功能:创建新字符串(不修改原对象)- 效率较低(涉及内存分配和拷贝),循环连接优先用
std::ostringstream
- C++14起支持字面量后缀
s
简化操作
auto s1 = "Hello, "s; auto s2 = "world!"s;// 三种连接方式 auto r1 = s1 + s2; // 字符串+字符串 → "Hello, world!" auto r2 = s1 + "C++"; // 字符串+字面量 → "Hello, C++" auto r3 = "Code: "s + s2; // 字面量+字符串 → "Code: world!"
- 效率较低(涉及内存分配和拷贝),循环连接优先用
-
operator>>
/operator<<
: 流输入输出
功能:类型安全的数据读写- 自动类型转换(数值↔字符串)
- 线程安全(每个流独立缓冲区)
- 支持自定义类型的重载(如
<< vector
)
#include <iostream> #include <string> #include <sstream>int main() {// 输出流(格式化写入)std::ostringstream oss;oss << "PI=" << 3.14159 << " Score:" << 95;std::cout<<oss.str()<<std::endl;//PI=3.14159 Score:95// 输入流(空格分隔解析)std::istringstream iss("John 25 175.5");std::string name;int age; double height;// 把 iss.str()的内容以空格分隔给了 name,age,heightiss >> name >> age >> height; // name="John", age=25, height=175.5std::cout<<name<<age<<height<<std::endl;//John25175.5}
-
getline()
: 从流中读取一行
功能:读取整行文本(含空格)- 返回值是输入流对象本身的引用(std::istream&)
- 默认移除换行符
\n
(可通过std::noskipws
保留) - 比
>>
更安全(无缓冲区溢出风险)
#include <iostream> #include <sstream> #include <string>int main() {std::string text = "风急天高猿啸哀,渚清沙白鸟飞回。\n""无边落木萧萧下,不尽长江滚滚来。\n""\r\n""万里悲秋常作客,百年多病独登台。\n"" 艰难苦恨繁霜鬓,潦倒新停浊酒杯。\n" // 此行有行首空格 "End of text";std::istringstream stream(text);std::string line;int line_count = 0;//// ✅ 利用返回值状态控制循环while (std::getline(stream, line)) {++line_count;}std::cout << "文本总行数: " << line_count << "\n";//文本总行数: 6return 0; }
-
关系运算符的非成员重载版本
功能:支持混合类型比较
比较规则:- 字典序比较(区分大小写)
- 短字符串<长字符串(若前缀相同)
- 空指针
nullptr
会抛出异常(需提前检查)
#include <string> #include <cstring> #include <iostream>int main() {std::string s = "Apple";const char* p = "Apple";// 等效比较(非成员函数)bool b1 = (s == p); // true → operator==(const string&, const char*)bool b2 = (p < s); // false → operator<(const char*, const string&)bool b3 = (std::string("Banana") > s); // true → operator>(const char*, const string&)std::cout<<b1<<std::endl;//1std::cout<<b2<<std::endl;//0std::cout<<b3<<std::endl;//1// C风格字符串直接比较if (std::strcmp(p, "Apple") == 0) { std::cout<<true<<std::endl; }//1 }
-
swap()
: 特化的swap算法
功能:高效交换内容(O(1)时间复杂度)- 需
<algorithm>
算法头文件
#include <string> #include <cstring> #include <iostream> #include <string> #include <algorithm> // std::swapint main() {std::string a = "苹果";std::string b = "香蕉";// 两种交换方式//成员函数swap()a.swap(b); // 成员函数 → a="香蕉", b="苹果"std::cout<<a<<std::endl;//香蕉std::cout<<b<<std::endl;//苹果//算法std::swap()std::swap(a, b); // 算法特化 → 恢复原值std::cout<<a<<std::endl;//苹果std::cout<<b<<std::endl;//香蕉// 大型数据交换优势 std::string big(1000000, 'A'); // 1MB数据 std::string small("Hi");big.swap(small); // 瞬间完成(仅交换内部指针)std::cout<<big<<std::endl;//Histd::cout<<small<<std::endl;// 输出了1000000个'A'}
- 需
注意事项💡
- 高频查找大文本时优先用
std::string_view
,避免substr()
的拷贝开销。 - 优先用
+=
替代频繁+
,循环连接用reserve()
预分配内存.
// 每次比较都会构造临时string对象
if ("temporary" == s) { ... }// 优化方案:提前构造变量
const std::string temp = "temporary";
if (temp == s) { ... }
[附录]
string 一些类型定义
类型 定义
string char
wstring wchar_t
u8string char8_t
u16string char16_t
u32string char32_t
详见
[参考]:
[1]https://cppreference.com/w/cpp/string/basic_string.html
[2]https://legacy.cplusplus.com/reference/string/string