前言
本文是C++核心语法三部曲第二部分・STL 篇的开篇,将为大家深入讲解string类的各类用法与底层实现机制。内容由浅入深、干货十足,敬请期待!
目录
[1.1 C语言中的字符串](#1.1 C语言中的字符串)
[1.2 string是什么](#1.2 string是什么)
[1.3 string有什么用](#1.3 string有什么用)
[1.4 string头文件结构](#1.4 string头文件结构)
[2.1 构造函数编辑](#2.1 构造函数编辑)
[2.1.1 函数举例](#2.1.1 函数举例)
[2.1.2 补充知识---npos](#2.1.2 补充知识—npos)
[2.2 析构函数编辑](#2.2 析构函数编辑)
[2.3 赋值运算符重载](#2.3 赋值运算符重载)
[2.3.1 函数举例](#2.3.1 函数举例)
[3.1 size()接口](#3.1 size()接口)
[3.2 c_str()接口](#3.2 c_str()接口)
[3.3 operator[] 接口](#3.3 operator[] 接口)
[3.4 iterator(迭代器)](#3.4 iterator(迭代器))
[3.4.1 迭代器的定义与存在原因](#3.4.1 迭代器的定义与存在原因)
[3.4.2 iterator接口](#3.4.2 iterator接口)
[3.4.3 使用示例](#3.4.3 使用示例)
[3.4.4 迭代器的注意事项](#3.4.4 迭代器的注意事项)
[3.5 获取类型名](#3.5 获取类型名)
[3.6 auto与范围for](#3.6 auto与范围for)
[3.6.1 auto(C++11新增)](#3.6.1 auto(C++11新增))
[3.6.2 auto特性](#3.6.2 auto特性)
[3.6.3 范围for](#3.6.3 范围for)
[3.6.4 范围for的问题与解决](#3.6.4 范围for的问题与解决)
[4.1 获取具体容量数据](#4.1 获取具体容量数据)
[4.2 resize()接口](#4.2 resize()接口)
[4.3 reserve()接口编辑](#4.3 reserve()接口编辑)
[4.3.1 VS在这里的优化](#4.3.1 VS在这里的优化)
[4.4 clear()接口](#4.4 clear()接口)
[4.5 empty()接口](#4.5 empty()接口)
[4.6 strink_to_fit](#4.6 strink_to_fit)
[5.1 at ()、back () 与 front ()](#5.1 at ()、back () 与 front ())
[5.1.1 at () 成员函数](#5.1.1 at () 成员函数)
[5.1.2 front () 成员函数](#5.1.2 front () 成员函数)
[5.1.3 back () 成员函数](#5.1.3 back () 成员函数)
[7.1.4 总结](#7.1.4 总结)
一、为什么要学习string类
1.1 C语言中的字符串
在C语言中,字符串是以 '\0' 作为结束标志的字符序列。为便于操作,C标准库提供了一系列以 str 开头的库函数,但这些函数与字符串本身相互分离,不符合面向对象的编程思想。而且字符串的底层空间需由用户自行管理,稍有不慎就可能出现越界访问的风险。
1.2 string是什么
string可理解为一个管理字符序列 的 "字符串类",它是C++标准库中用于存储和处理字符序列的容器。尽管string的诞生时间早于STL,但从功能特性来看,我们通常将其归类到 STL 的范畴中。也正因如此,string在设计过程中积累了不少冗余接口,且由于语言发展的 "向前兼容原则",这些接口一直保留至今。不过依据"二八法则",我们实际只需掌握其中20%的常用接口即可,剩余冷门接口在需要时查阅文档即可。但在本文中,我会尽可能全面且深入地讲解string的各类接口。
注:在使用string类时,必须包含#include<string>头文件以及using namespace std;
1.3 string有什么用
在实际开发与日常场景中,我们常会处理身份证号码这类超长字符序列。这类数据的数值范围远超 C++内置整型 的存储上限,且其核心属性是"字符序列"而非可运算的数值,往往还包含前导零、校验位字母等特殊内容,用C语言原生字符数组处理不仅繁琐,还极易出现内存越界、结束符遗漏等问题。也正是为了解决这类字符处理的核心痛点,string应运而生,成为了C++中处理字符序列的核心工具。
1.4 string头文件结构

这个图是C++<string>头文件的整体架构 、**string类的家族体系与功能模块划分,**我们来解释一下:
- 字符串体系的核心入口:<string>头文件<string>是C++标准库中专门用于字符串处理的核心头文件,囊括了字符串相关的所有类型定义、工具函数与操作接口,是整个C++字符串体系的统一入口。
- 字符串的底层核心模板:basic_string是<string>头文件中所有具体字符串类型的底层基础模板,它定义了字符串的通用行为与核心能力,我们常用的各类字符串类型,都是基于basic_string针对不同字符类型实例化而来。
- 四大标准字符串特化类我们日常开发中接触的string、wstring、u16string、u32string,均是 basic_string针对不同字符类型实例化生成的具体字符串类,覆盖了不同编码场景下的字符处理需求。
- wstring:针对宽字符类型wchar_t的特化,用于处理宽字符 /Unicode场景
- string:针对char类型的特化,也就是我们日常开发最常用的普通字符串类
- u16string:C++11新增,针对char16_t 的特化,用于UTF-16字符序列
- u32string:C++11新增,针对char32_t 的特化,用于UTF-32字符序列
- 核心成员接口体系图中Member functions(成员函数)、Iterators(迭代器)等模块,是所有字符串类对外提供的核心成员接口的分类汇总,完整覆盖了字符串操作的全量核心能力。
- 全局配套工具函数集图中Convert from/to strings(字符串双向转换)、Range access(范围访问)等模块,是<string>头文件提供的全局配套工具函数,为字符串的类型转换、遍历访问等场景提供了丰富的辅助能力。
二、默认成员函数
2.1 构造函数
由于历史发展与设计演进的原因,C++为我们提供了数量繁多的接口,其中不少接口的实用性有限。至于C++11标准中新增的两个接口,我们将在后续章节详细讲解。
|-----|----------|-----------------------------------------------------------------------------------|-----------------------------------------------------------------------|
| 编号 | 名称 | 函数签名 | 说明 |
| (1) | 默认构造 | string(); | 创建一个空的 string 对象,不含任何字符 |
| (2) | 拷贝构造 | string(const string& str); | 以另一个已存在的 string 对象 str 为蓝本,拷贝生成新的字符串 |
| (3) | 子串构造 | string(const string& str, size_t pos, size_t len = npos); | 从string对象str的指定位置pos开始,提取长度为len的子串构造新对象;若 en超出范围,则提取至str末尾。 |
| (4) | C风格字符串构造 | string(const char* s); | 以null结尾的C风格字符数组(字符串)s为基础,构造string对象。 |
| (5) | 字符序列构造 | string(const char* s, size_t n); | 从字符数组s中选取前n个字符构造string对象,不依赖null结尾符。 |
| (6) | 填充构造 | string(size_t n, char c); | 创建一个包含n个重复字符c的string对象。 |
| (7) | 迭代器范围构造 | template <class InputIterator> string(InputIterator first, InputIterator last); | 使用迭代器范围 [first, last) 内的字符序列构造字符串,支持任意符合InputIterator要求的迭代器类型。 |
2.1.1 函数举例
cpp
string str1("abcdef");//从C风格字符串构造
string str11;//默认构造
string str111(str1);//拷贝构造
执行结果:
abcdef
abcdef
2.1.2 补充知识---npos

npos是 std::string类中定义的一个静态成员常量,其核心特性与用法如下:
npos 的值等于 size_t 类型所能表示的最大值。它在定义时被初始化为 -1,但由于 size_t 是无符号整型类型,-1 会被隐式转换为该类型的最大值(例如,在 32 位系统上为 0xFFFFFFFF,64 位系统上为 0xFFFFFFFFFFFFFFFF)。
npos 是一个静态常量,在类中仅可读取使用,无法修改其值。
当npos用作string成员函数的参数时(例如子串构造函数中的 len 参数),它表示 "直到字符串末尾 ",即截取范围会自动延伸至字符串的最后一个字符 。
当npos作为string成员函数的返回值时(例如 find() 系列函数),它通常表示 "未找到匹配项" ,用于标识搜索操作未命中目标。
2.2 析构函数
本节不做展开。string的析构函数会在对象生命周期结束时 (如离开作用域、被 delete 释放等)由编译器自动调用,完成内存资源的自动清理,无需开发者手动干预。
2.3 赋值运算符重载

|-----|-------------|---------------------------------------------|-------------------------------------------|
| 编号 | 名称 | 函数签名 | 说明 |
| (1) | 字符串拷贝赋值 | string& operator=(const string& str); | 将另一个string对象str的内容复制到当前对象,替换原有内容。 |
| (2) | C风格字符串赋值 | string& operator=(const char* s); | 用以null结尾的 C 风格字符数组(字符串)s替换当前对象的内容。 |
| (3) | 单个字符赋值 | string& operator=(char c); | 将当前字符串的内容替换为仅包含一个指定字符c。 |
2.3.1 函数举例
cpp
string str1("asdfgh");
str2 = str1;
str2 = "qwerty";
str2 = 'k';
执行结果:
asdfgh
qwerty
k
三、遍历方法
在介绍前补充两个知识点:
3.1 size()接口

调用size()接口可以获得当前字符串的大小(字符个数,不包括\0)。
函数示例:
cpp
string str1("abcdef");
cout << str1.size() << endl;
执行结果:
6
3.2 c_str()接口

调用c_str()接口可以获取一个指向以'\0'结尾的C风格字符串的const char*指针 ,即该指针指向string对象内部存储的、以'\0'结尾的字符序列的首地址。
3.3 operator[] 接口

string类提供了两种operator[]运算符重载版本,分别适配非const对象 与const对象:
- 非const对象调用时返回字符的可写引用,允许通过下标修改字符;
- const 对象调用时返回字符的只读引用,仅支持访问。
从使用逻辑上,string可类比为一个 "封装好的字符数组",因此我们完全可以像操作普通数组那样,通过下标运算符[]来访问或修改string对象中的单个字符。
代码示例:
cpp
string str("123465789");
for (int i = 0; i < str.size(); i++)
{
cout << str[i] << " ";
}
执行结果:
1 2 3 4 5 6 7 8 9
注意:str2.size () 可替换为 strlen (str2.c_str ())。operator [] 带有越界检查,普通数组无此机制,越界访问会触发报错。

3.4 iterator(迭代器)
3.4.1 迭代器的定义与存在原因
迭代器是一种抽象的访问工具 ,用于以统一的方式顺序访问容器中的元素,而不暴露容器的底层存储结构。
迭代器是C++封装特性 的体现,它的存在有利于程序员关注业务逻辑,而非为不同容器适配不同的遍历方式,提高了代码的通用性与可维护性。
迭代器的使用方式与指针非常相似,但其底层实现并不一定是指针。例如,string的底层结构是简单的数组,因此其迭代器底层是指针;而我们后续学习的map、list等容器,其迭代器的底层遍历机制显然不是指针。
3.4.2 iterator接口

| 函数名 | 说明 | 返回值类型 | 是否为常量(const) | 是否支持反向遍历 | 引入版本 |
|---|---|---|---|---|---|
begin() |
返回指向容器第一个元素的正向迭代器 | iterator |
否(可修改) | 否 | C++98 |
end() |
返回指向容器最后一个元素之后的正向迭代器(表示结束) | iterator |
否(可修改) | 否 | C++98 |
rbegin() |
返回指向容器最后一个元素的反向迭代器(从后往前) | reverse_iterator |
否(可修改) | 是 | C++98 |
rend() |
返回指向容器第一个元素之前的反向迭代器(表示反向结束) | reverse_iterator |
否(可修改) | 是 | C++98 |
cbegin() |
返回指向容器第一个元素的常量正向迭代器(不可修改) | const_iterator |
是(只读) | 否 | C++11 |
cend() |
返回指向容器最后一个元素之后的常量正向迭代器 | const_iterator |
是(只读) | 否 | C++11 |
crbegin() |
返回指向容器最后一个元素的常量反向迭代器 | const_reverse_iterator |
是(只读) | 是 | C++11 |
crend() |
返回指向容器第一个元素之前的常量反向迭代器 | const_reverse_iterator |
是(只读) | 是 | C++11 |
3.4.3 使用示例
cpp
string str("123465789");
string::iterator sit = str.begin();
while (sit != str.end())
{
cout << *sit << " ";
sit++;
}
-
定义一个string::iterator类型的迭代器sit,并将其初始化为指向字符串str的首个有效字符。
-
在循环体中,先通过解引用迭代器 (注:此处 "解引用" 表述虽非完全严谨,但便于直观理解)访问并打印当前字符,随后将迭代器自增以指向下一位置。
-
循环的终止条件为迭代器到达end(),需特别注意,end()返回的是指向字符串最后一个有效字符之后的 "尾后位置" 的迭代器 ,而非指向最后一个有效字符本身。
执行结果:
1 2 3 4 5 6 7 8 9
反向迭代器:
cpp
string str2("123465789");
string::const_reverse_iterator crsit = str2.crbegin();
while (crsit != str2.crend())
{
cout << * crsit << " ";
crsit++;
}
执行结果:
9 8 7 6 5 4 3 2 1
- const_reverse_iterator是string类提供的只读反向迭代器类型。
- 其中,crbegin()成员函数返回指向字符串最后一个有效字符的迭代器,用于初始化只读反向迭代器crsit;
- 需特别注意的是,crend()成员函数返回的是指向字符串首字符之前位置的迭代器(即反向迭代的 "尾后位置"),并不指向任何有效字符。
3.4.4 迭代器的注意事项
正向迭代器的有效访问区间为 [ begin(), end() )------begin()指向容器的首个有效元素 ,end()则指向最后一个有效元素之后的 "尾后位置"(不指向任何有效元素)。
反向迭代器的有效访问区间为 [ rbegin(), rend() )------rbegin()指向容器的最后一个有效元素 ,rend()则指向首个有效元素之前的位置(反向迭代的 "尾后位置")。
const迭代器(如 string::const_iterator):核心限制在于 "指向的内容不可修改" ,即无法通过该迭代器变更容器内的元素值 ,但迭代器本身可自由移动(如自增、自减或重新赋值)。
const修饰的迭代器(如 string::iterator const):核心限制在于 "迭代器本身不可修改 ",即无法改变其指向的位置 (不能自增、自减或指向其他元素),但只要迭代器底层允许,仍可通过解引用修改其指向的元素值。
3.5 获取类型名
运算符typeid() 用于获取表达式或类型的类型信息 ,它返回一个const std::type_info&类型的对象。
调用该对象的name()成员函数 ,可获取此类型在当前编译器下的 "类型名字符串"。
cpp
string::const_reverse_iterator crsit = str2.crbegin();
cout << typeid(crsit).name() << endl;
cpp
class std::reverse_iterator<class std::_String_const_iterator<class std::_String_val<struct std::_Simple_types<char> > > >
3.6 auto与范围for
3.6.1 auto(C++11新增)
auto是一种类型占位符 ,编译器会在编译期通过"类型推导规则"自动推导出变量的确切类型。像上文提到的crsit,其完整类型名称往往冗长难写,此时便可使用auto来替代完整的类型名,大幅简化代码书写。
C++ 中auto的类型推导规则 是指编译器根据变量的初始化表达式,自动推断出变量确切类型的一套标准逻辑。核心规则可简化为以下几点:
- 若初始化表达式是引用类型,auto 会推导出引用指向的原始类型,而非引用本身。
- "顶层const"指变量本身是常量(如 int* const p),推导时会被忽略;但"底层 const"(如 const int* p,指向的内容是常量)会被保留。
- 若初始化表达式是数组或函数,auto会推导出对应的指针类型(除非显式声明为引用)。
cpp
string::const_reverse_iterator crsit = str2.crbegin();
auto crsit = str2.crbegin();
上面两行代码是等价的,
编译器会通过 str2.crbegin() 的返回值类型,自动推导出 auto 对应的具体类型为string::const_reverse_iterator。
这也说明 auto不能随意使用,其最基本的要求是:必须提供明确的初始化表达式,让编译器能够据此推导出变量的确切类型(无初始化的 auto 变量无法通过编译)。
3.6.2 auto特性
在早期C语言与C++98标准中,auto是用于声明自动存储周期局部变量的存储类型说明符,与标记静态存储周期的static相对。由于局部变量默认即为自动存储周期,该关键字长期无实际使用价值,处于近乎废弃的状态。
直至C++11标准,标准委员会为其赋予了全新的语义:auto不再作为存储类型指示符,而是成为一个编译期类型占位符 。编译器会根据变量的初始化表达式,在编译阶段自动推导出变量的完整类型,无需开发者手动显式指定。
使用auto与auto*声明指针变量,最终的类型推导结果完全一致。二者的细微差异仅在于,auto*对初始化表达式有强类型约束 ------ 必须传入指针类型,否则会直接触发编译报错。
若要声明引用类型,必须在auto后显式添加&;若未显式标注,编译器只会推导出基础值类型,不会自动增加引用限定。
在同一条声明语句中定义多个auto修饰的变量 时,所有变量的初始化表达式必须推导出完全一致的基础类型 ,否则编译器会直接报错。编译器仅以语句中第一个变量的初始化表达式完成类型推导,并用该推导结果定义同语句内的其他所有变量。
cpp
// 编译报错: error C3538: 在声明符号列表中,"auto"必须始终推导为同一类型
auto i = 3, j = 4.0;
注意:auto不能作为函数的参数,可以做返回值 。(非常不建议使用)
cpp
void func(auto a){}// 不能做参数
auto funC(){return 3;}// 可以做返回值,建议谨慎使用

如上举例,auto在编译上是可以通过的,但是降低了代码可读性,隐藏类型细节,影响代码的维护成本反而得不偿失。
注意:auto不能直接用来声明数组
cpp
// 编译报错: error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
auto a[] = { 4, 5, 6 };
3.6.3 范围for
范围for循环是C++11标准基于迭代器衍生出的语法糖,编译器在编译阶段会将其完整展开为迭代器遍历的原生代码,二者的底层执行逻辑完全等价。
如果说手动编写的迭代器遍历是"半自动遍历方案"------ 需要开发者手动完成迭代器初始化、边界校验、步进位移的全流程控制;那么范围 for 循环就是迭代器的"全自动遍历方案",它彻底屏蔽了迭代器的底层操作细节,开发者只需关注遍历到的元素本身,大幅简化了容器遍历的代码写法。
cpp
string str3 = "abcdef";
for (auto ch : str3)
{
cout << ch << " ";
}
3.6.4 范围for的问题与解决
先看一下代码

打印一下试试,出问题了:为什么在第二次我们使用范围for修改了字符串本身并打印但是根据第三次打印结果看字符串并没有变化?

原理很简单:这里将范围for编译时转换成迭代器,将*it给到了ch,ch实际上是迭代器的一份拷贝,而不是实际指向string的指针。
解决办法是把auto改成auto&,这样ch就是*it迭代器的别名,也就可以修改字符串了。再打印试试,问题解决。(值得注意的是:如果涉及对象比较大,也尽量把引用给加上)

四、容量相关接口
4.1 获取具体容量数据

| 函数名 | 说明 | 返回值类型 | 是否可修改 | 适用场景 |
|---|---|---|---|---|
size() |
返回当前字符串中有效字符的数量(即实际长度) | size_type(通常是 size_t) |
否(只读) | 获取当前内容长度 |
length() |
功能与 size() 完全相同,返回字符串的有效长度 |
size_type |
否(只读) | 与 size() 等价,历 |
max_size() |
返回该容器理论上能容纳的最大元素数量(受系统限制) | size_type |
否(只读) | 判断最大可能容量,避免溢出 |
capacity() |
返回当前已分配内存空间的大小(可存储元素的总容量) | size_type |
否(只读) | 查看预留空间,了解是否需要扩容 |
4.2 resize()接口

string::resize()的直接作用是将字符串的长度调整为指定的n个字符,具体行为根据n与当前长度的关系分为两种情况:
- 当 n < 当前字符串长度:字符串会被缩短,仅保留前n个字符,第n个字符之后的所有内容将被移除。
- 当 n > 当前字符串长度:字符串会被扩展,在末尾插入足够数量的字符,使总长度达到 n。
新增字符的初始化规则 由第二个可选参数c决定:
- 若指定了 c:新增的每个字符都会被初始化为c的拷贝;
- 若未指定 c:新增字符为值初始化的字符(即空字符 '\0')
代码
cpp
string str("123456");
cout << "resize测试1前str.size():" << str.size() << endl;
str.resize(2);
cout << "resize测试1后str.size():" << str.size() << endl;
cout << "resize测试1:" << " ";
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl;
cout << "resize测试2前str.size():" << str.size() << endl;
str.resize(6, 'x');
cout << "resize测试2后str.size():" << str.size() << endl;
cout << "resize测试2:" << " ";
for (auto ch1 : str)
{
cout << ch1 << " ";
}
运行结果:

4.3 reserve()接口
std::string::reserve()的核心作用是向字符串容器发起容量预调整请求 ,用于调整容器的内存容量(capacity) ------ 即容器无需重新分配内存即可容纳的最大字符数 ,使其可容纳至少n个字符。该函数仅修改容器的内存预留空间 ,不会改变字符串的有效长度(size)与存储内容,与直接修改有效字符长度的resize()有本质区别。
其具体行为分为两种场景,同时受 C++ 标准约束与编译器 STL 实现的影响:
- n 大于当前字符串容量此时该请求为强制性扩容指令。容器会触发内存重分配,确保扩容后的容量大于或等于 n。C++ 标准并未强制要求扩容后的容量必须精确等于 n,不同编译器的 STL 实现会基于内存对齐、降低后续扩容开销的目标做优化。例如Visual Studio环境下,会按照固定的内存对齐规则分配空间,最终开辟的内存容量通常会大于等于n。
- n 小于等于当前字符串容量此时该缩容请求为非强制性的优化建议,C++ 标准未强制要求容器必须将容量缩减至 n,具体行为完全由 STL 的编译器实现决定。例如 Visual Studio的 STL 实现会直接忽略此类缩容请求,不会主动释放已分配的内存、缩减容量,以避免后续字符串增长时再次触发扩容的性能开销;而 Linux 平台主流的 g++ 实现,会在符合条件时执行缩容操作,释放多余内存以降低内存占用,二者的差异源于不同的设计取舍。
4.3.1 VS在这里的优化
reserve扩容这里VS下扩容与我们所想的不一样,它的底层实际上是经过编译器优化的。
我们想string的底层,它的成员变量是这样的:
cpp
class string{
priveate:
char* _str;
size_t _capacity;
size_t _size;
}
但是实际上编译器在其中还加上了buffer[] 这个数组(这个名字的设计和这个功能有点缓冲区的意思)。这个数组的作用是,当开辟的数据个数在16个以内时(不包括16),将数据存入buffer[]中 ,否则才会开辟空间并让指针str指向开辟的空间,同时废弃buffer[]。
示例:
cpp
int main(){
string str0("abcdefg");
str0.reserve(100);
return 0;
}
在VS编译器上,我们开始调试 ;首先,调试到达string str0("abcdefg")语句时,如下:

提示 :STL在监视窗口为了让程序员看起来更加舒服,进行了优化,但同时也有一个[原始视图]选项,以便观察底层原理。
**_Bx下表示的是buf数组,_Mysize下表示的是_size,Myres下表示的是_capacity。**这里看到buf数组中存放者"abcdef"字符串。

继续调试,运行到reserve语句时,**开辟100个空间我们发现,buf数组被废弃了,_Myres数据被扩大,数据被存放在了堆中。**因此,证明了前面的观点。
此外我还想讲的是VS的扩容逻辑。以下是一个测试代码:
cpp
#include<iostream>
using namespace std;
int main(){
string str0("a");
int n = 999;
int old_capacity = 0;
for (int i = 0; i < n; i++){
str0.push_back('b');
if (old_capacity != str0.capacity()){cout << "capacity: " << str0.capacity() << endl;}
old_capacity=str0.capacity();
}
return 0;}
执行结果:
capacity: 15
capacity: 31
capacity: 47
capacity: 70
capacity: 105
capacity: 157
capacity: 235
capacity: 352
capacity: 528
capacity: 792
capacity: 1188
非常显然capacity在15->31的过程中进行了2倍扩容,但是在此之后实行的是1.5倍扩容。
这是因为最初空间并不是在堆上,*2倍开辟是因为内存从buffer到堆的内存迁移需要 。后续按照VS的规定来,进行1.5倍开辟。不过Linux下又是另外一套规则,没有buffer也没有1.5倍开辟,而是标准的2倍开辟:

4.4 clear()接口

功能是擦除字符串的内容 ,使其成为一个空字符串(长度为 0 个字符)。
cpp
string str("123456");
str.clear();
cout << "测试str.clear(): " << str.size();
打印结果是0。清空了字符串str的所有内容。
4.5 empty()接口

检测字符串是否为空
以布尔形式返回字符串是否为空(即长度是否为 0)。该函数不会以任何方式修改字符串的值。
4.6 strink_to_fit

收缩容量以适应大小
请求将字符串的容量缩减到刚好适合其当前大小。**该请求是非强制性的,容器实现可以自由优化,也可能保留比字符串实际大小更大的容量。**该函数不会影响字符串的长度,也不会修改其内容。
cpp
cout << "测试strink_to_fit: ";
string str("asdfghjkl");
cout << str.capacity() << " ";
str5.reserve(100);
cout << str.capacity() << " ";
str5.shrink_to_fit();
cout << str.capacity() << " ";
打印结果:9,我们为str字符串保留了100个字节的空间,也就是说我们现在有105个字节的容量属于字符串str,然后调用strink_to_fit()接口,使得容量缩小到9字节。
五、字符串字符获取相关接口
前文已对下标访问运算符operator[]的用法、特性与底层逻辑做了详细讲解,本节不再赘述,将重点介绍std::string提供的三组核心字符访问接口:at()、back()与front(),分别覆盖带安全校验的随机访问、首尾字符便捷访问的核心场景。
5.1 at ()、back () 与 front ()
5.1.1 at () 成员函数
at()是std::string提供的带强制越界安全校验的下标访问接口 ,用于访问字符串中指定下标位置的字符,是operator[]的标准化安全替代方案。
标准定义与重载
C++ 标准规定at()提供两个重载版本,完美适配const与非const字符串对象的访问权限控制:

C++标准强制要求,当传入的pos不满足pos < size() 时(即发生越界访问),at()会主动抛出std::out_of_range类型的标准异常;
而operator[]的越界访问属于未定义行为,无标准强制的报错机制,可能直接导致程序崩溃、数据污染等不可预期的结果。
非const对象调用返回可写引用,可直接通过返回值修改对应字符;
const对象调用仅返回只读引用,禁止修改字符内容。
cpp
string str = "hello world";
// 合法访问:读取下标为4的字符,输出 'o'
cout << str.at(4) << std::endl;
// 合法修改:将下标0的字符修改为'H'
str.at(0) = 'H';
// 越界访问:触发std::out_of_range异常
// str.at(100) = '!';
5.1.2 front () 成员函数
front()是C++11标准新增的首字符便捷访问接口 ,用于快速获取字符串的第一个有效字符,无需手动指定下标,大幅提升代码的可读性与简洁性。
标准定义与重载

- 在字符串非空的前提下,str.front() 与 str[0]、*str.begin() 完全等价,底层访问逻辑一致,无额外性能开销。
- 对空字符串(str.empty() == true)调用front(),属于 C++ 标准规定的未定义行为,使用前必须确保字符串非空。
- 非const对象可通过返回值直接修改字符串首字符,const 对象仅支持读取操作。
cpp
string str = "test";
// 读取首字符,输出 't'
cout << str.front() << std::endl;
// 修改首字符为'T',字符串变为 "Test"
str.front() = 'T';
5.1.3 back () 成员函数
back() 是 C++11 标准新增的尾字符便捷访问接口 ,用于快速获取字符串的最后一个有效字符,无需手动计算size()-1下标,彻底规避下标计算错误的潜在风险。
标准定义与重载

- 在字符串非空的前提下,str.back() 与 str[str.size()-1]、*str.rbegin() 完全等价,底层访问逻辑一致,无额外性能开销。
- 对空字符串调用back(),同样属于未定义行为,使用前必须校验字符串非空。
- 非 const 对象可通过返回值直接修改字符串尾字符,const 对象仅支持读取操作。
cpp
std::string str = "demo";
// 读取尾字符,输出 'o'
std::cout << str.back() << std::endl;
// 修改尾字符为'O',字符串变为 "demO"
str.back() = 'O';
7.1.4 总结
| 函数名 | 说明 | 返回值类型 | 是否支持 const 对象 | 是否越界检查 | 引入版本 |
|---|---|---|---|---|---|
at(size_t pos) |
访问指定位置的字符(带边界检查) | char& / const char& |
是(有 const 版本) | 是(越界抛出异常) | C++98 |
back() |
获取字符串最后一个字符 | char& / const char& |
是(有 const 版本) | 是(空字符串会抛异常) | C++11 |
front() |
获取字符串第一个字符 | char& / const char& |
是(有 const 版本) | 是(空字符串会抛异常) | C++11 |
感谢你的观看!