一、概述
std::string
是 C++ 标准模板库(STL)中的字符串类,用于表示和操作字符序列。与传统的 C 风格字符串(即字符数组)相比,std::string
提供了更高层次的抽象和更丰富的功能接口,使得字符串的操作更加安全和便捷。它内部管理动态内存,避免了手动管理内存的复杂性。
std::string
的一些显著特点包括:
- 动态内存管理:自动处理内存分配和释放,避免手动管理内存。
- 丰富的接口:支持各种字符串操作,如查找、拼接、删除、替换等。
- 迭代器支持:可以像容器一样通过迭代器来遍历和操作字符串。
二、使用
要在 C++ 程序中使用 <string>
库,首先需要包含这个头文件:
#include <string>
1、初始化
// string constructor
#include <iostream>
#include <string>
int main()
{
// 1. 默认构造函数 string();
// 功能:创建一个空字符串对象
std::string s1;
// 2. 拷贝构造函数 string(const string& str);
// 功能:使用已有字符串 s0 拷贝构造 s2
std::string s0("Initial string"); // 初始化字符串 "Initial string"
std::string s2(s0);
// 3. 子串构造函数 string(const string& str, size_t pos, size_t len = npos);
// 功能:从 s0 的第 8 个字符开始,取 3 个字符
std::string s3(s0, 8, 3); // 子串 -> "str"
// 4.用字符串初始化对象 string(const char* s);
std::string s4("A character sequence"); // C 风格字符串 -> "A character sequence"
// 5. 取字符串前n个字符初始化对象 string(const char* s, size_t n);
std::string s5("Another character sequence", 12); // 前 12 个字符 -> "Another char"
// 6. 以n个字符初始化对象 string(size_t n, char c);
// 功能:构造一个由 10 个 'x' 字符组成的字符串
std::string s6(10, 'x'); // -> "xxxxxxxxxx"
// 7. 使用字符 ASCII 码构造 string(size_t n, char c);
// 功能:构造一个由 10 个 ASCII 码为 42(字符 '*')的字符组成的字符串
std::string s7(10, 42); // 42 是 '*' 的 ASCII 码 -> "**********"
// 8. 迭代器范围构造 template <class InputIterator> string(InputIterator first, InputIterator last);
// 功能:从 s0 的开始到第 7 个字符构造字符串
std::string s8(s0.begin(), s0.begin() + 7); // -> "Initial"
// 打印输出所有字符串内容
std::cout << "s1: " << s1 << std::endl; // 输出为空字符串
std::cout << "s2: " << s2 << std::endl; // 输出:Initial string
std::cout << "s3: " << s3 << std::endl; // 输出:str
std::cout << "s4: " << s4 << std::endl; // 输出:A character sequence
std::cout << "s5: " << s5 << std::endl; // 输出:Another char
std::cout << "s6: " << s6 << std::endl; // 输出:xxxxxxxxxx
std::cout << "s7: " << s7 << std::endl; // 输出:**********
std::cout << "s8: " << s8 << std::endl; // 输出:Initial
return 0;
}
2、迭代器
C++ 标准库中的 std::string
实际上是一个动态字符数组,可以使用 STL(标准模板库)的迭代器来遍历、操作和访问字符串的内容。迭代器是一种抽象的指针,用于遍历容器中的元素。
常见的 std::string
迭代器类型
str.begin()
:返回指向字符串第一个字符的迭代器。str.end()
:返回指向字符串最后一个字符的下一个位置的迭代器,即不包含任何内容的位置。str.rbegin()
:返回指向字符串末尾的反向迭代器,反向遍历字符串。str.rend()
:返回指向字符串第一个字符前一个位置的反向迭代器。str.cbegin()
:返回指向字符串第一个字符的常量迭代器,常量迭代器不能修改内容。str.cend()
:返回指向字符串末尾的常量迭代器。str.crbegin()
:返回指向字符串末尾的常量反向迭代器。str.crend()
:返回指向字符串第一个字符前一个位置的常量反向迭代器。
- 正向迭代器:遍历字符串从头到尾。使用
begin()
和end()
。 - 反向迭代器:遍历字符串从尾到头。使用
rbegin()
和rend()
。 - 常量迭代器:遍历字符串只能读取,不能修改。使用
cbegin()
和cend()/
crbegin()
和crend()
。
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
// 1. 正向迭代器
std::string::iterator it = str.begin(); // 定义正向迭代器
std::cout << "Using forward iterator: ";
while (it != str.end()) {
std::cout << *it << ' ';
++it; // 移动到下一个字符
}
std::cout << std::endl;
// 2. 反向迭代器
std::string::reverse_iterator rit = str.rbegin(); // 定义反向迭代器
std::cout << "Using reverse iterator: ";
while (rit != str.rend()) {
std::cout << *rit; // 输出字符
++rit; // 移动到下一个字符
}
std::cout << std::endl;
// 3. 常量迭代器
std::string::const_iterator cit = str.cbegin(); // 定义常量迭代器
std::cout << "Using constant iterator: ";
while (cit != str.cend()) {
std::cout << *cit << ' ';
++cit; // 移动到下一个字符
}
std::cout << std::endl;
return 0;
}
3、元素访问
string::operator[]:下标运算符,可以用来访问字符串中指定位置的字符。
string::at(pos):和 operator[]
类似,通过 pos
访问字符串中对应位置的字符。
std::string::front():返回字符串的第一个字符的引用,支持读写。
std::string::back():返回字符串的最后一个字符的引用,支持读写。
使用方法:
- 读取字符:
char ch = str[i];
- 修改字符:
str[i] = 'x';
#include <iostream>
#include <string>
int main() {
std::string str = "Programming";
// 1. 使用 operator[] 访问
std::cout << str[2] << std::endl; // 输出 'o'
// 2. 使用 at() 访问并修改
try {
std::cout << str.at(2) << std::endl; // 输出 'o'
str.at(2) = 'X'; // 修改字符
std::cout << str << std::endl; // 输出 "PrXgramming"
}
catch (const std::out_of_range& e) {
std::cout << e.what() << std::endl;
}
// 3. 使用 front() 访问并修改
std::cout << str.front() << std::endl; // 输出 'P'
str.front() = 'M'; // 修改第一个字符
std::cout << str << std::endl; // 输出 "MrXgramming"
// 4. 使用 back() 访问并修改
std::cout << str.back() << std::endl; // 输出 'g'
str.back() = '!';
std::cout << str << std::endl; // 输出 "MrXgrammin!"
return 0;
}
-
operator[]
:通过下标访问字符串中的字符,不进行边界检查,适合需要高效访问和修改的场景。要小心边界检查,因为越界访问会导致未定义行为。 -
at()
:与operator[]
类似,但会进行边界检查,如果pos
超出范围则抛出std::out_of_range
异常。适合需要安全访问的场景。 -
back()
:通过 return _data[size() - 1]
返回最后一个字符的引用,如果字符串为空会产生未定义行为。 -
front()
:通过 return _data[0]
返回第一个字符的引用,同样,如果字符串为空会产生未定义行为。
4、长度及容量
size()
和length()
:返回字符串的当前字符个数。
std::string str = "Hello, World!";
std::cout << "Size: " << str.size() << std::endl; // 输出: 13
std::cout << "Length: " << str.length() << std::endl; // 输出: 13
max_size()
:返回字符串可以容纳的最大字符数,具体数值与平台相关。capacity()
:返回分配给字符串的内存容量,可以比字符串的实际长度大,以减少内存的频繁分配。
std::string str = "Hello";
std::cout << "Capacity: " << str.capacity() << std::endl; // 15
std::cout << "Size: " << str.size() << std::endl; // 5
resize()
:改变字符串的长度,自动扩展或截断字符串。
resize(size_t n)
:调整字符串的长度为 n
,如果 n
小于当前长度,则截断字符串;如果 n
大于当前长度,则在末尾添加空字符。
resize(size_t n, char c)
:调整字符串长度为 n
,并用字符 c
填充不足的部分。
std::string str = "Hello";
str.resize(10); // 将长度扩展到10,用默认字符填充
std::cout << "Resized (default): " << str << std::endl; // 输出 "Hello"
std::cout << "Size: " << str.size() << std::endl; // 输出 10
str.resize(15, 'X'); // 将长度扩展到15,用字符 'X' 填充
std::cout << "Resized with 'X': " << str << std::endl; // 输出 "HelloXXXXX"
std::cout << "Size: " << str.size() << std::endl; // 输出 15
-
当
resize()
扩展字符串时:- 如果新的长度大于当前容量,内存会重新分配。
- 所有迭代器都会失效,因为内存地址发生了变化。
-
当
resize()
截断字符串时:- 容量不会改变,不会发生内存重新分配。
- 指向被截断部分的迭代器会失效。例如,指向截断后被删除的字符的迭代器失效,因为那些字符已经不存在了。
- 指向未被截断部分的迭代器仍然有效,它们指向的内容没有变化。
reserve()
:为字符串预留至少n
个字符的内存容量,以减少频繁的内存分配。
reserve()
函数只会调整字符串的容量,而不会改变实际的长度。如果新请求的容量大于当前容量,则会重新分配内存(迭代器失效),否则不做任何操作(迭代器不会失效)。
#include <iostream>
#include <string>
int main() {
std::string str = "Hello";
std::cout << str.capacity() << std::endl; // 15
std::string::iterator it = str.begin(); // 获取迭代器
str.reserve(12); // 预留内存
std::cout << str.capacity() << std::endl; //15
std::cout << *it << std::endl; //迭代器不失效
str.reserve(100); // 预留内存,会重新分配
std::cout << str.capacity() << std::endl;
std::cout << *it << std::endl; //迭代器失效
return 0;
}
clear()
:清空字符串内容,长度变为 0,容量保持不变。empty()
:检查字符串是否为空。如果字符串长度为 0,则返回true
,否则返回false
。
任何对容器进行可能导致重新分配内存的操作(如 resize()
、push_back()
等),都有可能导致迭代器失效.
5、修饰
(1)append()在结尾添加字符串
string & append(const string & str)//在结尾添加一个string字符串
string & append(const string & str, size_type subpos, size_type sublen)//追加str中从subpos开始的sublen个字符(子串)
string & append(const charT * s)//C语言字符串
string & append(const charT * s, size_type n)//C语言字符串(长度为n的子串)
string & append(size_type n, charT c)//n个字符c
string & append(InputIterator first, InputIterator last)//使用迭代器append
// appending to string
#include <iostream>
#include <string>
int main()
{
std::string str;
std::string str2 = "Writing ";
std::string str3 = "print 10 and then 5 more";
// 1. append(const string &str)
// 在字符串末尾追加 string 对象的内容
str.append(str2);
std::cout << str << '\n'; // 输出: Writing
// 2. append(const string &str, size_type subpos, size_type sublen)
// 追加 str3 中从下标 6 开始的 3 个字符
str.append(str3, 6, 3);
std::cout << str << '\n'; // 输出: Writing 10
// 3. append(const char *s, size_type n)
// 追加字符串的前 5 个字符
str.append("dots are cool", 5);
std::cout << str << '\n'; // 输出: Writing 10 dots
// 4. append(const char *s)
// 追加字符串 "here: "
str.append("here: ");
std::cout << str << '\n'; // 输出: Writing 10 dots here:
// 5. append(size_type n, char c)
// 追加 10 个字符 '.'
str.append(10u, '.');
std::cout << str << '\n'; // 输出: Writing 10 dots here: ..........
// 6. append(InputIterator first, InputIterator last)
// 追加迭代器范围 [str3.begin() + 8, str3.end()]
str.append(str3.begin() + 8, str3.end());
std::cout << str << '\n'; // 输出: Writing 10 dots here: .......... and then 5 more
return 0;
}
如果 append()
操作导致重新分配内存(即字符串的容量不足以容纳新的数据),则指向原字符串的迭代器都会失效。
(2)push_back(char c) 将单个字符字符添加到串尾
std::string str = "Hello";
std::cout << str << std::endl; // 输出: "Hello"
// 使用 push_back() 向末尾添加字符
str.push_back('!');
std::cout << str << std::endl; // 输出: "Hello!"
如果 push_back() 导致了内存重新分配,则指向该字符串的迭代器会失效。如果没有发生重新分配,现有的迭代器仍然有效。
(3)assign赋值
string &assign(const char *s);///char*类型的字符串赋给当前字符串
string &assign(const char *s,int n);//用c字符串s开始的n个字符赋值
string &assign(const string &s);//把字符串s赋给当前字符串
string &assign(int n,char c);//用n个字符c赋值给当前字符串
string &assign(const string &s,int start,int n);//把字符串s中从start开始的n个字符赋给当前字符串 string &assign(const_iterator first,const_itertor last);//把first和last迭代器之间的部分赋给字符串
#include <iostream>
#include <string>
int main() {
std::string str; // 定义一个空字符串
std::string s2 = "Hello World!"; // 用于赋值操作的字符串
// 1. assign(const char *s) 赋值字符串
str.assign("Hello");
std::cout << str << std::endl; // 输出: Hello
// 2. assign(const char *s, int n) 从字符串取前n个字符赋值
str.assign("Hello World", 7);
std::cout << str << std::endl; // 输出: Hello W
// 3. assign(const string &s) 用string对象赋值
str.assign(s2);
std::cout << str << std::endl; // 输出: Hello World!
// 4. assign(int n, char c) 用n个字符c赋值
str.assign(5, 'A');
std::cout << str << std::endl; // 输出: AAAAA
// 5. assign(const string &s, int start, int n) 取s2中从start 开始的n个字符赋值
str.assign(s2, 6, 5);
std::cout << str << std::endl; // 输出: World
// 6. assign(const_iterator first, const_iterator last) 用迭代器赋值
str.assign(s2.begin(), s2.begin() + 7); // 取s2的前7个字符
std::cout << str << std::endl; // 输出: Hello W
return 0;
}
(4)insert 在字符串中间插入
string & insert(size_type pos, const string & str)//在pos位置插入字符串str
string & insert(size_type pos, const string & str,size_type subpos, size_type sublen)// 在pos位置插入从subpos开始的sublen的子串
string & insert(size_type pos, const charT * s)//在pos位置插入字符串
string & insert(size_type pos, const charT * s, size_type n) // 在pos 插入字符串的前 n个字符
string & insert(size_type pos, size_type n, charT c) //在pos位置插入 n 个字符 c
iterator insert(const_iterator p, size_type n, charT c)//使用迭代器索引插入n个字符c
iterator insert(const_iterator p, charT c) //使用迭代器索引插入单个字符c
iterator insert(iterator p, InputIterator first, InputIterator last)//使用迭代器insert
#include <iostream>
#include <string>
int main() {
std::string str = "abcdef";
std::string s2 = "Hello World!";
// 1. insert(size_type pos, const string & str) 在pos位置插入字符串str
str.insert(3, s2);
std::cout << str << std::endl; // 输出: abcHello World!def
// 2. insert(size_type pos, const string & str, size_type subpos, size_type sublen)
// 从字符串 s2 的位置 6 开始取 5 个字符插入到位置 3
str = "abcdef"; // 重置 str
str.insert(3, s2, 6, 5); // 插入 "World"
std::cout << str << std::endl; // 输出: abcWorlddef
// 3. insert(size_type pos, const charT * s) 在pos 插入字符串
str = "abcdef"; // 重置 str
str.insert(3, "12345");
std::cout << str << std::endl; // 输出: abc12345def
// 4. insert(size_type pos, const charT * s, size_type n) 在pos 插入字符串的前 n个字符
str = "abcdef"; // 重置 str
str.insert(3, "12345", 5);
std::cout << str << std::endl; // 输出: abc12345def
// 5. insert(size_type pos, size_type n, charT c) 在pos位置插入 n 个字符 c
str = "abcdef"; // 重置 str
str.insert(3, 5, '*');
std::cout << str << std::endl; // 输出: abc*****def
// 6. insert(const_iterator p, size_type n, charT c) 在迭代器索引位置插入 n 个字符 c
str = "abcdef"; // 重置 str
str.insert(str.begin() + 4, 3, '#');
std::cout << str << std::endl; // 输出: abcd###ef
// 7. insert(const_iterator p, charT c) 在迭代器索引位置插入单个字符 c
str = "abcdef"; // 重置 str
str.insert(str.begin() + 2, 'B');
std::cout<< str << std::endl; // 输出: abBcdef
// 8. insert(iterator p, InputIterator first, InputIterator last) 使用迭代器insert
str = "abcdef"; // 重置 str
str.insert(str.begin() + 3, s2.begin(), s2.begin() + 5); // 插入 "Hello"
std::cout << str << std::endl; // 输出: abcHellodef
return 0;
}
(5)erase 删除字符串中的特定字符
string & erase(size_type pos=0, size_type len=npos)//从pos处删除len长度的字符
iterator erase(const_iterator p)//删除迭代器所指的单一字符
iterator erase(const_iterator first, const_iterator last)//删除2迭代器中间的字符
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
// 1. erase(size_type pos = 0, size_type len = npos) 从pos位置开始删除 n 个字符
str.erase(7, 5); // 删除 ", Wor"
std::cout << str << std::endl; // 输出: Hello, !
// 2. erase(const_iterator p) 删除字符串中迭代器索引所在位置
str = "Hello, World!"; // 重置 str
str.erase(str.begin() + 2); // 删除 'l'
std::cout << str << std::endl; // 输出: Helo, World!
// 3. erase(const_iterator first, const_iterator last) 删除first 和last 迭代器之间的内容
str = "Hello, World!"; // 重置 str
str.erase(str.begin() + 2, str.begin() + 5); // 删除 "llo"
std::cout << str << std::endl; // 输出: He, World!
return 0;
}
pop_back 删除最后一个字符
std::string str = "Hello, World!";
// 使用 pop_back 删除最后一个字符
str.pop_back(); // 删除 '!'
std::cout << str << std::endl; // 输出: Hello, World
如果对空字符串调用 pop_back()
会产生未定义行为(例如:崩溃)。因此在调用之前,应该通过 empty()
函数检查字符串是否为空。
指向末尾的迭代器失效:pop_back()
会使指向最后一个字符(例如 end()
、rbegin()
)的迭代器失效,因为最后一个字符被移除。
(7) swap 交换两个字符串
- void swap (& str);//交换self和str
// swap strings
#include <iostream>
#include <string>
int main()
{
std::string buyer("money");
std::string seller("goods");
std::cout << "Before the swap, buyer has " << buyer;
std::cout << " and seller has " << seller << '\n';
seller.swap(buyer);
std::cout << " After the swap, buyer has " << buyer;
std::cout << " and seller has " << seller << '\n';
//输出
//Before the swap, buyer has money and seller has goods
//After the swap, buyer has goodsand seller has money
return 0;
}
swap()
操作只交换以下元数据:
- 指向字符串数据的指针(这个指针指向堆上的内存)。
- 字符串的长度(size)。
- 字符串的容量(capacity)。
只交换了两个字符串对象内部的指针和其他元数据。这意味着,虽然字符串对象的内容被交换了,但指向原始字符串数据的指针没有改变,因此迭代器仍然指向有效的内存位置。
短字符串优化(SSO, Short String Optimization):在许多标准库实现中,短字符串不使用堆内存,而是存储在字符串对象的内部固定缓冲区里。当调用 swap()
时,可能涉及内部指针和内存块的重新分配。
所以字符串小于某个范围(15~23)时,会导致迭代器失效,如果都在堆内存上,迭代器不会失效。