# string - 字符串

## string 容器基本概念

C风格字符串（以`\0`结尾的字符数组）太过复杂，难于掌握，不太适合大程序的开发，所以C++STL中定义了一种string类，在头文件`<string>`中。

![](https://images.unsplash.com/photo-1615230892109-ae38ab7e8a64?crop=entropy\&cs=srgb\&fm=jpg\&ixid=MnwxOTcwMjR8MHwxfHNlYXJjaHw3fHxzdHJpbmd8ZW58MHx8fHwxNjQ0NDk2MzM4\&ixlib=rb-1.2.1\&q=85)

### string 和 C风格字符 串对比

* `char*`是一个指针，`string`是一个类

  `string`封装了`char*`，管理这个字符串，是一个`char*`型的容器。
* `string` 封装了很多实用的成员方法

  查找`find`，拷贝`copy`，删除`erase`，替换`replace`，插入`insert`......
* 不用考虑内存释放和越界

  `string`管理`char*`所分配的内存，每一次`string`的复制/赋值，取值都由`string`类负责维护，不用担心复制越界和取值越界等。

> `string` 本质上是一个动态的char数组。

## string 容器常用操作

### string 构造函数

```cpp
string();
// 默认构造函数，创建一个空的字符串
string(const string& str);
// 拷贝构造函数，使用一个string对象初始化另一个string对象
string(const char* s);
// 含参构造函数，使用C风格字符串初始化
string(int n, char c);
// p含参构造函数，使用n个字符c初始化
```

### string 基本赋值操作

#### &#x20;**`=` 赋值操作符**

```cpp
string& operator=(const char* s);
// C风格字符串赋值给当前的字符串
string& operator=(const string& s);
// 把字符串s赋给当前的字符串
string& operator=(const char c);
//字符赋值给当前的字符串
```

#### &#x20;**`assign`成员函数**

```cpp
string& assign(const char* s); 
// C风格字符串赋值给当前的字符串
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 存取字符操作

#### **`[]`下标获取操作符**

```cpp
char& operator[](int n); 
// 通过[]下标方式获取字符
```

使用下标操作符获取字符时，如果下标越界，程序将会强制终止。

#### `at`成员函数

```cpp
char& at(int n); 
// 通过at方法获取字符
```

使用at方法获取字符时，如果下标越界，at方法内部会抛出异常（`exception`），可以使用`try-catch`捕获并处理该异常。示例如下：

```cpp
#include <stdexception> 
//标准异常头文件
#incldue <iostream>
using namespace std;

int main()
{
    string s = "hello world";
    try
    {
        //s[100]不会抛出异常，程序会直接挂掉
        s.at(100);
    }
    catch (out_of_range& e) 
        //如果不熟悉异常类型，可以使用多态特性， catch(exception& e)。
    {
        cout << e.what() << endl; 
        //打印异常信息
    }
    return 0;
}
```

{% hint style="warning" %}
关于C++的异常处理用法可以查看相关教程了解，参考教程：

<https://www.runoob.com/cplusplus/cpp-exceptions-handling.html>

后续教程将默认读者了解C++异常处理的相关知识点。
{% endhint %}

### string 拼接操作

#### **`+=`复合操作符**

```cpp
string& operator+=(const string& str); 
// 将字符串str追加到当前字符串末尾
string& operator+=(const char* str); 
// 将C风格字符数组追加到当前字符串末尾
string& operator+=(const char c); 
// 将字符c追加到当前字符串末尾
/* 上述操作重载了复合操作符+= */
```

#### **`append`成员函数**

```cpp
string& append(const char* s); 
// 把C风格字符数组s连接到当前字符串结尾
string& append(const char* s, int n); 
// 把C风格字符数组s的前n个字符连接到当前字符串结尾
string& append(const string &s); 
// 将字符串s追加到当前字符串末尾
string& append(const string&s, int pos, int n); 
// 把字符串s中从pos开始的n个字符连接到当前字符串结尾
string& append(int n, char c); 
// 在当前字符串结尾添加n个字符c
```

### string 查找和替换

#### **`find`成员函数**

```cpp
int find(const string& str, int pos = 0) const; 
// 查找str在当前字符串中第一次出现的位置，从pos开始查找，pos默认为0
int find(const char* s, int n = 0) const; 
// 查找C风格字符串s在当前字符串中第一次出现的位置，从pos开始查找，pos默认为0
int find(const char* s, int pos, int n) const; 
// 从pos位置查找s的前n个字符在当前字符串中第一次出现的位置
int find(const char c, int pos = 0) const; 
// 查找字符c第一次出现的位置，从pos开始查找，pos默认为0
```

> 当查找失败时，`find`方法会返回`-1`，`-1`已经被封装为string的静态成员常量`string::npos`。
>
> ```cpp
> static const size_t nops = -1;
> ```

#### **`rfind`成员函数**

```cpp
int rfind(const string& str, int pos = npos) const; 
// 从pos开始向左查找最后一次出现的位置，pos默认为npos
int rfind(const char* s, int pos = npos) const; 
// 查找s最后一次出现的位置，从pos开始向左查找，pos默认为npos
int rfind(const char* s, int pos, int n) const; 
// 从pos开始向左查找s的前n个字符最后一次出现的位置
int rfind(const char c, int pos = npos) const; 
// 查找字符c最后一次出现的位置
```

{% hint style="info" %}
`find`方法通常查找字串第一次出现的位置，而`rfind`方法通常查找字串最后一次出现的位置。

`rfind(str, pos)`的实际的开始位置是`pos + str.size()`，即从该位置开始（不包括该位置字符）向前寻找匹配项，如果有则返回字符串位置，如果没有返回`string::npos`。

`-1`其实是`size_t`类的最大值（学过补码的同学应该不难理解），所以`string::npos`还可以表示“直到字符串结束”，这样的话rfind中pos的默认参数是不是就不难理解啦？
{% endhint %}

#### **`replace`成员函数**

```cpp
string& replace(int pos, int n, const string& str); 
// 替换从pos开始n个字符为字符串s
string& replace(int pos, int n, const char* s);
// 替换从pos开始的n个字符为字符串s
```

### string 比较操作

#### **`compare`成员函数**

```cpp
int compare(const string& s) const; // 与字符串s比较
int compare(const char* s) const; // 与C风格字符数组比较
```

`compare`函数依据字典序比较，在当前字符串比给定字符串小时返回`-1`，在当前字符串比给定字符串大时返回`1`，相等时返回`0`。

#### **比较操作符**

```cpp
bool operator<(const string& str) const;
bool operator<(const char* str) const;
bool operator<=(const string& str) const;
bool operator<=(const char* str) const;
bool operator==(const string& str) const;
bool operator==(const char* str) const;
bool operator>(const string& str) const;
bool operator>(const char* str) const;
bool operator>=(const string& str) const;
bool operator>=(const char* str) const;
bool operator!=(const string& str) const;
bool operator!=(const char* str) const;
```

`string`类重载了所有的比较操作符，其含义与比较操作符本身的含义相同。

### string 子串

#### **`substr`成员函数**

```cpp
string substr(int pos = 0, int n = npos) const;
// 返回由pos开始的n个字符组成的字符串
```

### string 插入和删除操作

#### **`insert` 成员函数**

```cpp
string& insert(int pos, const char* s); // 在pos位置插入C风格字符数组
string& insert(int pos, const string& str); // 在pos位置插入字符串str
string& insert(int pos, int n, char c); // 在pos位置插入n个字符c
```

> 返回值是插入后的字符串结果，`erase`同理。其实就是指向自身的一个引用。

#### **`erase` 成员函数**

```cpp
string& erase(int pos, int n = npos); // 删除从pos位置开始的n个字符
```

> 默认一直删除到末尾。

### `string` 和 `C-Style` 字符串的转换

#### **`string` 转 `const char*`**

```cpp
string str = "demo";
const char* cstr = str.c_str();
```

#### **`const char*` 转 `string`**

```cpp
const char* cstr = "demo";
string str(cstr); // 本质上其实是一个有参构造
```

{% hint style="info" %}
在c++中存在一个从`const char*`到`string`类的隐式类型转换，但却不存在从一个`string`对象到`const char*`的自动类型转换。对于`string`类型的字符串，可以通过`c_str()`方法返回`string`对象对应的`const char*` 字符数组。

比如说，当一个函数的参数是`string`时，我们可以传入`const char*`作为参数，编译器会自动将其转化为`string`，但这个过程不可逆。

为了修改string字符串的内容，下标操作符`[]`和`at`都会返回字符串的引用，但当字符串的内存被重新分配之后，可能发生错误。（结合字符串的本质是动态字符数组的封装便不难理解了）
{% endhint %}

## 和 string 相关的全局函数

> 注：有的可能需要C++11标准。

### 大小写转换

```cpp
#include <cctype>
// 在iostream中已经包含了这个头文件，如果没有包含iostream头文件，则需手动包含cctype

int tolower(int c); // 如果字符c是大写字母，则返回其小写形式，否则返回本身
int toupper(int c); // 如果字符c是小写字母，则返回其大写形式，否则返回本身

/**
  * C语言中字符就是整数，这两个函数是从C库沿袭过来的，保留了C的风格
*/
```

如果想要对整个字符串进行大小写转化，则需要使用一个`for`循环，或者配合和`algorithm`库来实现。例如：

```cpp
#include <string>
#include <cctype>
#include <algorithm>

string str = "Hello, World!";
transform(str.begin(), str.end(), str.begin(), toupper); //字符串转大写
transform(str.begin(), str.end(), str.begin(), tolower); //字符串转小写
```

### 字符串和数字的转换

#### **`int`/`double` 转 `string`**

> c++11标准新增了全局函数`std::to_string`，十分强大，可以将很多类型变成`string`类型。

```cpp
#include <string>
using namespace std;

/** 带符号整数转换成字符串 */
string to_string(int val);
string to_string(long val);
string to_string(long long val);

/** 无符号整数转换成字符串 */
string to_string(unsigned val);
string to_string(unsigned long val);
string to_string(unsigned long long val);

/** 实数转换成字符串 */
string to_string(float val);
string to_string(double val);
string to_string(long double val);
```

#### **`string` 转 `double` / `int`**

```cpp
#include <cstdlib>
#include <string>
using namespace std;

/** 字符串转带符号整数 */
int stoi(const string& str, size_t* idx = 0, int base = 10);
long stol(const string& str, size_t* idx = 0, int base = 10);
long long stoll(const string& str, size_t* idx = 0, int base = 10);

/**
  * 1. idx返回字符串中第一个非数字的位置，即数值部分的结束位置
  * 2. base为进制
  * 3. 该组函数会自动保留负号和自动去掉前导0
 */

/** 字符串转无符号整数 */
unsigned long stoul(const string& str, size_t* idx = 0, int base = 10);
unsigned long long stoull(const string& str, size_t* idx = 0, int base = 10);

/** 字符串转实数 */
float stof(const string& str, size_t* idx = 0);
double stod(const string& str, size_t* idx = 0);
long double stold(const string& str, size_t* idx = 0);
```

与之类似的在同一个库里的还有一组基于字符数组的函数如下。

```cpp
// 'a' means array, since it is array-based. 

int atoi(const char* str); // 'i' means  int
long atol(const char* str); // 'l' means long
long long atoll(const char* str); // 'll' means long long

double atof(const char* str); // 'f' means double
```

{% hint style="success" %}
至此，读者应当详细了解了C++STL中string容器的各种用法以及其他一些字符串处理的常用函数。可能量有些大，无法一下子记住，可以暂时留个印象，待到使用时多翻一翻，慢慢就记住了。
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://cui-jiacai.gitbook.io/c++-stl-tutorial/rong-qi-container/string-zi-fu-chuan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
