C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
详细可查看string类的文档
在使用string类时,必须包含#include头文件以及using namespace std;

文档中可以看到这些构造的接口,但是常见的接口主要有以下几个:
(constructor) 函数名称 | 功能说明 |
|---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
这里眼神尖锐的朋友可以发现,为什么他们的容量(capacity)都是15呢?
关于这个问题我们下面一点会讲到。
回到构造函数的最后一个常用接口。
string(
size_t n,
char c
);很显然能看出,用n个字符c来初始化string类对象。
string s(5, 'x');//用5个字符x初始化对象s
函数名称 | 功能说明 |
|---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty(重点) | 检测字符串是否为空串,是返回true,否则返回false |
clear(重点) | 清除有效字符 |
reserve(重点) | 为字符串预留空间 |
resize(重点) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
size()和length()这两个函数接口在功能上没有区别,都是返回字符串的有效长度(有效长度不包含'\0'),size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
这是由于string比STL出现的还要早一些,在STL之后统一给出了size()
早期的 C++字符串处理库可能没有一个统一的标准,不同的实现中可能存在不同的函数来获取字符串长度。当引入标准库中的 std::string 时,为了保持与一些已有代码的兼容性,可能同时提供了这两个接口
我们可以看一下源代码
size_type __CLR_OR_THIS_CALL length() const
{
// return length of sequence
return (_Mysize);
}
size_type __CLR_OR_THIS_CALL size() const
{
// return length of sequence
return (_Mysize);
} 容量,代表能存多少个有效字符。
这个时候我们就来讲解一下为什么上面提到的对象的容量是15
为了优化内存,对string类做出了短字符串优化(Short String Optimization)
1. 原理: - std::string 对象内部通常有一个指向动态分配内存的指针和一些管理内存的成员变量。为了避免短字符串频繁地进行动态内存分配,实现了短字符串优化。 - 当字符串长度较短时,直接将字符串内容存储在对象内部的固定大小的缓冲区中,而不是动态分配内存。 2. 好处: - 减少内存分配和释放的开销,提高性能。对于短字符串的操作可以直接在对象内部进行,无需通过指针间接访问动态分配的内存。 - 节省内存空间,特别是在程序中存在大量短字符串的情况下,可以显著减少内存使用量。
这个固定大小的缓冲区可能会因不同编译器的差异而有所不同,不过通常是在15~20,由于我们这使用的是vs编译器,所以固定大小是15

可以看到vs的底层实现是用一个固定大小的数组_Buf,数组大小是16,由于要存放'\0',所以容量是15。
clear()用于清空有效数据。
string s1("abcdefg");
cout << "清空前:" << s1 << endl;
s1.clear();
cout << "清空后:" << s1 << endl;

可以看到clear()只是把有效数据给清除了,容量并没有改变。
reserve要注意和reverse区分
reserve:保留 reverse:逆置
一、 reserve 函数 1. 用法: - string str; str.reserve(n); ,其中 n 是一个整数,表示要为字符串预留的字符容量。 2. 作用: - 预先分配足够的内存空间,以容纳至少 n 个字符。这样在后续进行字符串操作时,如果字符串的长度不超过 n ,就可以避免频繁的内存重新分配。 二、 resize 函数 1. 用法: - string str; str.resize(n); ,其中 n 是一个整数。如果 n 大于当前字符串的长度,会在字符串末尾添加足够数量的默认字符(通常是'\0')来达到新的长度;如果 n 小于当前字符串的长度,会截断字符串使其长度为 n 。 - 也可以提供第二个参数指定用于填充的字符,例如 str.resize(n, 'x'); 。 2. 作用: - 改变字符串的长度为指定的 n 。如果是增加长度,新添加的部分可以通过提供的填充字符进行初始化,或者使用默认初始化。如果是减小长度,会截断字符串。 三、两者差异 1. 内存分配影响: - reserve 只是预留内存空间,不改变字符串的实际长度和内容。 - resize 可能会改变字符串的实际长度和内容,并且根据情况可能会触发内存重新分配(如果新长度超过了当前的容量)。 2. 内容变化: - reserve 不修改字符串的内容。 - resize 会根据新的长度添加或删除字符,从而改变字符串的内容。
void Test_string()
{
string s1;
s1.reserve(100);
string s2;
s2.resize(100);
string s3;
s3.resize(100, 'x');
}
resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。 reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参 数小于string的底层空间总大小时,reserver不会改变容量大小。
最后empty()比较简单就不过多介绍了,就是用来检测字符串是否为空串
函数名称 | 功能说明 |
|---|---|
operator[] | 返回pos位置的字符,const string类对象调用 |
begin + end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位 置的迭代器 |
rbegin + rend | rbegin获取最后一个字符的反向迭代器 + rend获取第一个字符之前位 置的反向迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
如果想访问遍历一个字符串,我们可以这样:
int main()
{
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;
return 0;
}
可以达到和数组一样的效果,直接访问某一个元素,同时还可以这样写:
s1.operator[](i);这两种写法是等价的。
这里我们不仅能读,还能写,就比如:
int main()
{
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++) {
s1[i] += 1;
}
cout << s1 << endl;
return 0;
}
这里再提一嘴,在 C++中,除了 operator[] , at() 成员函数也可以访问 string 对象中的字符。 一、两者的相同点 都可以用于访问 string 对象中的特定位置的字符。 二、两者的不同点 1. 安全性: - operator[] 不进行越界检查。如果使用 operator[] 访问超出字符串范围的位置,可能会导致未定义行为。 - at() 会进行范围检查。如果索引超出范围, at() 会抛出 std::out_of_range 异常。 2. 返回值: - operator[] 返回对字符的引用,可以通过该引用修改字符。 - at() 返回字符的引用(但不能用于修改 const string 对象中的字符),如果尝试通过 at() 返回的引用修改 const string 对象中的字符,会引发编译错误。
迭代器是STL六大组件之一,你可以把它理解成一种类似指针的对象。
我们来看看它的用法:
int main()
{
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
return 0;
}
迭代器提供了对不同容器中元素的访问方式,对于那些不能使用下标访问的容器,迭代器的优势就能体现出来,今天就简单认识一下迭代器,后面会详细介绍。
范围for
在这之前我们先来认识一下auto关键字
auto 关键字
在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。 C++11 中,标准委员会变废为宝赋予了 auto 全新的含义即: auto 不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器, auto 声明的变量必须由编译器在编译时期 推导而得 。 用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto 声明引用类型时则必须加 & 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量 。 auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用 auto 不能直接用来声明数组
简单来说就是auto可以自动推导类型,不过有以上一些提到的限制
虽然用起来方便,但是也是有缺点的,就比如可读性不高。
范围 for
对于一个 有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的 for 循环。 for 循环后的括号由冒号 “ : ” 分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围 ,自动迭代,自动取数据,自动判断结束。 范围 for 可以作用到数组和容器对象上进行遍历 范围 for 的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
代码演示:
int main()
{
string s("hello world");
for (auto& ch : s)
{
cout << ch << ' ';
}
cout << endl;
return 0;
}
auto后面不接&的话,就是将指向的字符拷贝给ch,就不是引用了。
范围for也可以和下标一样,实现可读可写。
函数名称 | 功能说明 |
|---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的 位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的 位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
push_back : 尾插一个字符,但不能插入一个字符串
int main()
{
string s("hello world");
s.push_back('c');
cout << s << endl;
return 0;
}
append : 可以尾插入一个字符串,但不能只插入一个字符

其中最常用的是(1)和(3),插入一个字符串和插入一个string类的对象
int main()
{
string s1("hello world");
s1.append("hello");//插入一个字符串
cout << s1 << endl;
string s2("world");
s1.append(s2);//插入一个string类对象
cout << s1 << endl;
return 0;
}
而operator+=就可以尾插一个字符或者字符串,还可以尾插一个string类对象

int main()
{
string s1("hello world");
s1 += 'c';
cout << s1 << endl;
s1 += " cpp ";
cout << s1 << endl;
string s2("world");
s1 += s2;
cout << s1 << endl;
return 0;
}
返回字符串的指针
int main()
{
string s("hello world");
cout << s << endl;
cout << s.c_str() << endl;
return 0;
}
结果可以看到是一样的,但是为什么要有一个这个函数接口呢?
一、与 C 语言接口交互 很多 C 语言的函数和库只接受以空字符结尾的字符数组(C 风格字符串)作为参数。例如,标准 C 库中的 printf 函数、文件操作函数等。通过 c_str() 可以将 std::string 对象转换为 C 风格字符串,以便在需要与 C 语言代码进行交互的场景中使用。 二、提供底层字符串表示 它返回一个指向以空字符结尾的字符数组的指针,这个指针指向的内容与 std::string 对象中的内容是一致的。这个指针可以用于一些需要直接访问底层字符串表示的操作,但需要注意的是,返回的指针所指向的内存区域的有效性与 std::string 对象的生命周期相关。如果 std::string 对象被修改或者销毁,这个指针可能会变得无效。
如果我们想获取一个文件的后缀,应该怎么做?
这里就可以使用find() 和 substr() 两个函数接口来实现
我们先来了解一下这两个函数接口


一、 find 接口 1. 功能描述:用于在一个字符串中查找另一个字符串或字符第一次出现的位置。 2. 语法: - size_type find(const string& str, size_type pos = 0) const; :在当前字符串中从位置 pos 开始查找子字符串 str 第一次出现的位置。 - size_type find(const char* s, size_type pos = 0) const; :在当前字符串中从位置 pos 开始查找 C 风格字符串 s 第一次出现的位置。 - size_type find(const char* s, size_type pos, size_type n) const; :在当前字符串中从位置 pos 开始查找长度为 n 的 C 风格字符串 s 第一次出现的位置。 - size_type find(char c, size_type pos = 0) const; :在当前字符串中从位置 pos 开始查找字符 c 第一次出现的位置。 3. 返回值:如果找到,则返回匹配的起始位置;如果未找到,则返回 std::string::npos 。 二、 substr 接口 1. 功能描述:用于从一个字符串中提取子字符串。 2. 语法: string substr(size_type pos = 0, size_type n = npos) const; ,从当前字符串中从位置 pos 开始提取长度为 n 的子字符串。如果未指定 n ,则提取从 pos 到字符串末尾的子字符串。 3. 返回值:返回提取的子字符串。
关于npos这里也顺带说明一下
在 C++中, string::npos 是 std::string 类的一个静态成员常量。它具有以下特点和用途: 1. 表示最大的 size_t 值: size_t 是一个无符号整数类型,用于表示字符串的长度、索引等。 string::npos 的值通常被设置为 size_t 类型所能表示的最大值。在不同的系统和编译器环境下,具体的数值可能会有所不同,但一般来说,在 64 位系统上其值为 18446744073709551615 。 2. 指示不存在的位置:主要用于 std::string 类的一些成员函数,如 find() 、 rfind() 、 find_first_of() 、 find_last_of() 等,当这些函数在字符串中未找到指定的子字符串或字符时,就会返回 string::npos 。 3. 作为长度参数表示直到字符串结束:在一些 std::string 类的成员函数中,当 string::npos 作为一个长度参数时,表示从指定位置开始一直到字符串的结束。比如 substr() 函数, substr(pos, n) 用于提取从 pos 位置开始长度为 n 的子字符串,如果 n 的值为 string::npos ,则表示提取从 pos 位置开始到字符串末尾的所有字符。 总之, string::npos 是 std::string 类中一个用于表示特定状态(未找到或直到字符串结束)的特殊值,在字符串的操作和处理中具有重要的作用。在使用时,需要注意返回值的类型应该与 string::npos 的类型(即 std::string::size_type ,通常是 size_t )相匹配,以正确地进行比较和判断。
void Test()
{
string file("test.txt");
FILE* pf = fopen(file.c_str(), "w");//以读的形式打开file文件
size_t pos = file.find('.');
if (pos != string::npos)
{
string suffix = file.substr(pos);
cout << suffix << endl;
}
}
如果一个文件有多个后缀呢?比如 .txt.zip ,这个时候我们想取 .zip
如果还用刚才的方式的话,在这里取到的就是两个后缀一起,这个时候我们如果可以倒着找是不是就能实现,这就不得不提到rfind()了

1. 基本函数原型: - size_type rfind(const basic_string& str, size_type pos = npos) const; :在当前字符串中从 pos 位置开始从右向左查找与给定字符串 str 相等的子串最后一次出现的位置。如果没有找到,则返回 std::string::npos 。 - size_type rfind(const char* s, size_type pos = npos) const; :在当前字符串中从 pos 位置开始从右向左查找与给定的 C 风格字符串 s 相等的子串最后一次出现的位置。通过 traits::length(s) 来确定 C 风格字符串的长度。如果没有找到,则返回 std::string::npos 。 - size_type rfind(char ch, size_type pos = npos) const; :在当前字符串中从 pos 位置开始从右向左查找给定字符 ch 最后一次出现的位置。如果没有找到,则返回 std::string::npos 。 2. 返回值:
返回值是一个 size_type 类型(通常是 std::string::size_type ,它是一个无符号整数类型),表示找到的子串或字符在原字符串中的位置索引。这个索引是从字符串的开头开始计算的,而不是从结尾。如果返回 std::string::npos ,则表示没有找到相应的子串或字符。
void Test()
{
string file("test.txt.zip");
FILE* pf = fopen(file.c_str(), "w");
size_t pos = file.rfind('.');
if (pos != string::npos)
{
string suffix = file.substr(pos);
cout << suffix << endl;
}
}
函数名称 | 功能说明 |
|---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operatoe>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串 |
relational operators | 大小比较 |
string还有很多函数接口,这里就不一一列举细讲了,可以查看文档了解