引言
在 C++ 编程中,std::string 类是处理字符串的核心工具之一。作为一个动态管理字符数组的类,它不仅提供了丰富的功能,还通过高效的内存管理和操作接口,极大地方便了字符串操作。通过深入探讨 std::string 的各类成员函数,我们将更加全面地理解它背后的机制和使用场景。
在本系列博客中,我们将详细剖析 std::string 的各类成员函数,带你深入理解 C++ 中字符串的操作与管理。以下内容均是在c++11标准版本的介绍。
1.默认成员函数
1.1string类的构造函数(Construct)
1.1.1函数原型
在C++中,std::string 是标准库提供的一个字符串类,它提供了多种构造函数来创建字符串对象。构造基本语法如下:
public member function
std::string::string
default (1) string();
copy (2) string (const string& str);
from c-string (3) string (const char* s);
from sequence (4) string (const char* s, size_t n);
fill (5) string (size_t n, char c);
substring (6) string (const string& str, size_t pos, size_t len = npos);
range (7) template <class InputIterator>
string (InputIterator first, InputIterator last);
initializer list (8) string (initializer_list<char> il);
move (9) string (string&& str) noexcept;
1.1.2 noexcept
noexcept是c++11引入的关键字,用来表明不会抛出异常,它在函数声明中使用,告诉编译器和程序员该函数在运行时不会引发异常。
(1)noexcept 的作用:
优化性能:编译器可以利用 noexcept 进行优化。如果编译器知道某个函数不会抛出异常,它可以避免生成一些与异常处理相关的代码,从而提高性能。
提高代码安全性:当你明确声明某个函数不会抛出异常,调用者可以放心地使用该函数,而不需要担心异常的传播和处理。
异常安全保障:当你编写不应该抛出异常的函数时,使用 noexcept 明确表达这种意图,防止由于代码错误导致异常意外抛出。
(2)使用场景
移动构造函数和移动赋值运算符:对于某些类型的对象,移动操作(如移动构造函数、移动赋值运算符)通常是 noexcept 的。这是因为在某些容器(如 std::vector)中,出于性能考虑,当元素在容器内部移动时,要求这些操作不会抛出异常。
异常安全的保证:如果你的函数不打算抛出异常,最好明确使用 noexcept 进行标记,提升代码的可读性和可维护性。
1.1.3深入解析
(1) 默认构造函数
创建一个空字符串对象。
std::string str;
此时,`str` 是一个空字符串。
(2)拷贝构造函数
通过另一个 `std::string` 对象来构造字符串。
string (const string& str);
std::string str1 = "Hello";
std::string str2 = str1; // 使用str1来构造str2
(3)从 C 字符串构造
使用 C 风格的字符串(即字符数组)来构造 `std::string` 对象。
string (const char* s);
const char* cstr = "Hello";
std::string str(cstr);
(4)从 C 字符串的部分字符构造
可以指定从 C 字符串中提取的字符个数。
string (const char* s, size_t n);
const char* cstr = "Hello, World!";
std::string str(cstr, 5); // 只提取前5个字符,即"Hello"
(5)从字符构造
可以指定使用某个字符构造一个指定长度的字符串。
string (size_t n, char c);
std::string str(5, 'A'); // 构造一个包含5个'A'字符的字符串 "AAAAA"
(6)从子字符串开始构造
可以从现有的std::string对象中提取一个子字符串来构造新的std::string。pos是子字符串的起始位置,len表示要提取的字符数(默认为npos,表示从起始位置到字符串末尾)。
string (const string& str, size_t pos, size_t len = npos);
std::string original = "Hello, World!";
// 从位置7开始,提取5个字符构造子字符串
std::string subStr(original, 7, 5);// 输出 "World"
(7)从迭代器范围构造
可以使用迭代器范围来构造字符串,例如从数组或其他容器中提取内容。
template <class InputIterator>
string (InputIterator first, InputIterator last);
std::vector<char> vec = {'H', 'e', 'l', 'l', 'o'};
std::string str(vec.begin(), vec.end()); // 构造 "Hello"
(8)初始化构造列表(C++11及更高版本)
std::string 的 initializer list 构造函数允许你使用一个 初始化列表(std::initializer_list<char>)直接初始化一个字符串对象。
string (initializer_list<char> il);
std::string str = {'H', 'e', 'l', 'l', 'o'};
std::cout << "str: " << str << std::endl; // 输出 "Hello"
(9)移动构造函数 (C++11及更高版本)
可以利用移动语义,从一个临时的 `std::string` 对象中转移资源,而不需要复制。
string (initializer_list<char> il);
std::string str1 = "Hello";
std::string str2 = std::move(str1); // str2获得str1的资源,str1被置为空
总结
`std::string` 的构造方式灵活多样,可以从字符数组、部分字符、字符迭代器等多种来源构造。此外,在C++11及以上版本中,`std::string` 还支持移动构造,进一步优化了性能。
1.2赋值运算符重载(operator=)
1.2.1函数原型:
string (1) string& operator= (const string& str);
c-string (2) string& operator= (const char* s);
character (3) string& operator= (char c);
initializer list (4) string& operator= (initializer_list<char> il);
move (5) string& operator= (string&& str) noexcept;
1.2.2深入解析
(1)拷贝赋值运算符
拷贝赋值运算符用于将一个 `std::string` 对象赋值给另一个 `std::string` 对象。
string& operator= (const string& str);
std::string str1 = "Hello";
std::string str2;
str2 = str1; // 使用拷贝赋值运算符
std::cout << "str1: " << str1 << std::endl; // 输出 "Hello"
std::cout << "str2: " << str2 << std::endl; // 输出 "Hello"
(2)C 风格字符串赋值运算符
用于将从风格的字符串(字符数组指针)赋值给std::string对象
string& operator= (const char* s);
const char* cStr = "Hello, World!";
std::string str;
str = cStr; // 使用C风格字符串赋值运算符
std::cout << "str: " << str << std::endl; // 输出 "Hello, World!"
(3)字符赋值运算符
用于将一个字符赋值给 `std::string` 对象,将该字符作为字符串的唯一字符。
string& operator= (char c);
char c = 'A';
std::string str;
str = c; // 使用字符赋值运算符
std::cout << "str: " << str << std::endl; // 输出 "A"
(4)初始化列表赋值运算符
用于通过 C++11 提供的初始化列表,将多个字符赋值给 `std::string` 对象。
string& operator= (std::initializer_list<char> il);
std::string str;
str = {'H', 'e', 'l', 'l', 'o'}; // 使用初始化列表赋值运算符
std::cout << "str: " << str << std::endl; // 输出 "Hello"
(5)移动赋值运算符
用于将一个临时或即将被销毁的 `std::string` 对象的内容移动到当前对象,而不是复制。
string& operator= (string&& str) noexcept;
string str1 = "hello";
string str2;
str2 = std::move(str1);
cout << str1 << endl; // str1 可能为空或处于未定义状态
cout << str2 << endl;// 输出 "Hello"
1.2.3总结
- `string& operator=(const string& str)`:将一个 `std::string` 对象赋值给另一个。
- `string& operator=(const char* s)`:将 C 风格字符串赋值给 `std::string`。
- `string& operator=(char c)`:将一个字符赋值给 `std::string`,该字符成为字符串的唯一字符。
- `string& operator=(initializer_list<char> il)`:使用初始化列表赋值多个字符。
- `string& operator=(string&& str) noexcept`:使用移动赋值运算符,将一个临时对象的内容移动到当前对象。
这些赋值运算符提供了不同的赋值方式,极大地增强了 `std::string` 的灵活性。
2.返回迭代器的成员函数
在 C++中,迭代器是一种对象,它能够用来遍历容器中的元素。迭代器提供了一种统一的方式来访问不同类型容器中的元素,而无需了解容器的内部实现细节。这里如果对迭代器不太了解,可以去看这篇博客C++标准库类——string类-CSDN博客。
std::string 提供了一系列返回迭代器的成员函数,用于遍历和操作字符串中的字符。通过这些迭代器,您可以轻松地访问、修改和遍历字符串中的每个字符。常见的迭代器包括正向迭代器、常量迭代器、反向迭代器以及它们的常量版本。
2.1正向迭代器(iterator)
(1)begin()
- begin():返回指向字符串首个字符的迭代器。
iterator begin() noexcept;
const_iterator begin() const noexcept;
- iterator begin() noexcept 返回一个可修改的迭代器,指向字符串的第一个字符。
- const_iterator begin() const noexcept 返回一个常量迭代器,指向字符串的第一个字符,且不允许通过该迭代器修改字符串内容。
(2)end()
- end():返回指向字符串末尾(即最后一个字符的下一个位置)的迭代器。
iterator end() noexcept;
const_iterator end() const noexcept;
- iterator end()返回一个可修改的迭代器,指向字符串末尾的下一个位置。
- const_iterator end() const noexcept返回一个常量迭代器,指向末尾的下一个位置,且不允许通过该迭代器修改字符串内容。
(3)示例
#include <iostream>
using namespace std;
int main()
{
//使用非const迭代器遍历并修改字符
string st1 = "Hello, henu";
string::iterator it1 = st1.begin();
while (it1 != st1.end())
{
if (*it1 == ',')
*it1 = ';';
it1++;
}
cout << st1 << endl;//Hello; henu
//使用const迭代器遍历,但不能修改字符
const string st2 = "Hello, world";
string::const_iterator it2 = st2.begin();
while (it2 != st2.end())
{
cout << *it2;//Hello, world
it2++;
}
return 0;
}
(4)常量版本
- cbegin():返回指向字符串首个字符的常量迭代器。
const_iterator cbegin() const noexcept;
- cend():返回指向字符串末尾的常量迭代器。
const_iterator cend() const noexcept;
常量版本其实就是与上面返回常量迭代器的声明类型的作用是相同的,在此不再赘述。
2. 2反向迭代器(reverse_iterator)
(1)rbegin()
- rbegin():返回指向字符串末尾(即最后一个字符)的反向迭代器。
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
- reverse_iterator rbegin() 返回一个可修改的反向迭代器,指向字符串的最后一个字符。
- const_reverse_iterator rbegin() const noexcept 返回一个常量反向迭代器,指向字符串的最后一个字符,且不允许通过该迭代器修改字符串内容。
(2)rend()
- rend():返回指向字符串首个字符的前一个位置的反向迭代器。
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
- reverse_iterator rend() 返回一个可修改的反向迭代器,指向第一个元素之前的位置。
- const_reverse_iterator rend() const noexcept 返回一个常量反向迭代器,指向第一个元素之前的位置,且不允许通过该迭代器修改字符串内容。
(3)示例
#include <iostream>
using namespace std;
int main()
{
//使用 reverse_iterator遍历并修改字符
string st1 = "Hello, world";
//auto it1 = st1.rbegin();
string::reverse_iterator it1 = st1.rbegin();
while (it1 != st1.rend())
{
if (*it1 == ',')
*it1 = ';';
cout << *it1;
it1++;//注意,这里用++,而不是--
}
cout << endl;
//使用 const_reverse_iterator遍历并修改字符
const string st2 = "Hello, henu";
//auto it2 = st2.rbegin();
string::const_reverse_iterator it2 = st2.rbegin();
//string::const_reverse_iterator it2 = st2.crbegin();
while (it2 != st2.rend())
//while (it2 != st2.crend())
{
cout << *it2;
it2++;
}
return 0;
}
(4)常量版本:
- crbegin():返回指向字符串末尾的常量反向迭代器。
const_reverse_iterator crbegin() const noexcept
- crend():返回指向字符串首个字符的前一个位置的常量反向迭代器。
const_reverse_iterator crend() const noexcept;
这里的常量版本同样与上面返回常量迭代器的声明的类型的作用相同,不再赘述。
2.3总结
- begin():正向迭代器,指向首个字符。
- end():正向迭代器,指向末尾后的位置。
- rbegin():反向迭代器,指向最后一个字符。
- rend():反向迭代器,指向第一个字符之前的位置。
- cbegin():常量正向迭代器,指向首个字符。
- cend():常量正向迭代器,指向末尾后的位置。
- crbegin():常量反向迭代器,指向最后一个字符。
- crend():常量反向迭代器,指向第一个字符之前的位置。
更多string类的成员函数:string - C++ Reference (cplusplus.com)