字符串

ZaynPei Lv6

std::string 是 C++ 标准库中用于处理字符串的类,位于 头文件中,命名空间为 std > 在 C++ 中,强烈建议使用 string 类表示字符串,因为它是真正的字符串类型。而在 C 语言中实际上没有字符串类型,只是用字符数组和字符指针来模拟字符串,而且后者不太安全

char* (C 风格字符串):本身没有成员函数,不能使用 .size() 或 .length()。要获取其长度,必须使用 C 语言的库函数 strlen(),这个函数定义在 (或 C 的 <string.h>) 头文件中。

C++ 字符串末尾没有 \0 字符。事实上,除了 C 语言外,其他语言都是将字符串本身及其长度存在内存中,因此不用 \0 标记结尾

其迭代器是随机访问迭代器, 支持跳跃式访问

常用接口:

  • size() / length():获取字符串长度
  • empty():判断字符串是否为空
  • clear():清空字符串
  • reserve(n):预分配内存
  • append() / +=:字符串连接
  • compare():字符串比较
  • substr(pos, len):获取子字符串
  • find() / rfind():查找子字符串
  • insert(pos, str):插入子字符串
  • erase(pos, len):删除子字符串
  • push_back(char) / pop_back():在字符串末尾添加或删除字符

定义和初始化

1
2
3
4
5
6
7
8
9
10
11
#include <string>
using namespace std;

string s1; // 空字符串

string s2("Hello"); // 用C字符串初始化
string s2 = "Hello";
string s5 = {"C++11"}; // C++11列表初始化

string s3(s2); // 拷贝构造
string s4(5, 'a'); // 5个'a': "aaaaa"(重复字符串)

vector<char>和string

在实际使用中, vector可以很方便地和string互相转换:

1
2
3
string str = "Hello, World!";
vector<char> charVec(str.begin(), str.end()); // string -> vector<char>
string str2(charVec.begin(), charVec.end()); // vector<char> -> string

因此, 如果需要动态修改字符串内容, 也可以用vector来代替string以获得更灵活的接口

  • 当 std::string str 这行代码执行完毕后,变量 str 已经拥有了一个确定的、合法的、可用的值。它不是未定义的、也不是指向 null 的 ( RAII 的体现 )。std::string 的默认构造函数会将字符串初始化为一个空字符串 (Empty String), 具有以下明确的属性:
    • 值为 ““:它不包含任何字符。
    • 长度为 0:调用 str.length() 或 str.size() 会返回 0。
    • 是“空的”:调用 str.empty() 会返回 true。
    • 是有效的:可以立即对它进行各种操作,而不会导致程序错误。
  • 因为 std::string 是一个 (Class),而不是像 int 或 char[] 这样的基础数据类型。在 C++ 中,当一个类的对象被创建时,会自动调用其相应的构造函数 (Constructor) 来进行初始化。

访问和赋值

1
2
3
4
5
6
7
8
9
10
char c1 = s[0];     // 通过[]访问(不检查越界), 但注意此时s[i]返回的是char类型的单个字符
char c2 = s.at(1); // 通过at()访问(越界抛出异常)
char front = s.front(); // 首字符(C++11)
char back = s.back(); // 尾字符(C++11)

string s;
s = "Hello"; // 直接赋值
s[0] = 'J';
s.assign("World"); // assign函数赋值(string类的方法)
s.assign(s, 1, 3); // 从s2的索引1开始取3个字符:"orl"赋值给s

在赋值时和 C 字符串的差别:

1
2
3
4
5
6
7
char char1[20];
char char2[20] = "jaguar";
string str1;
string str2;

char1 = char2; // illegal
str1 = str2; // legal
  • C 风格字符串 (char[]) 本质是“数组”, 当声明 char char1[20]; 时,实际是在内存的栈上请求了一块连续的、包含 20 个 char 类型元素的空间。char1 这个名字就代表了这块内存的起始地址。

  • 尝试执行 char1 = char2; 时,实际上是在命令编译器:“请把 char1 这个地址常量,修改为 char2 这个地址常量所代表的地址”。这在逻辑上是行不通的,也是 C/C++ 语法所禁止的。编译器会报错,通常提示“表达式必须是可修改的左值”或“数组类型不可赋值”。

字符串长度

1
2
3
4
5
int len = s.size();  // 或 s.length()
int len = s.length();
bool isEmpty = s.empty(); // 是否为空
s.clear(); // 清空字符串
s.reserve(100); // 预分配内存

字符串连接

1
2
3
4
5
6
7
string s = "Hello";
s += " World"; // 追加字符串
s.append("!!"); // 追加:"Hello World!!"
s.push_back('!'); // 追加单个字符

string str3;
str3 = str1 + str2;

字符串比较

1
2
3
string a = "apple", b = "banana";
if (a == b) { /* ... */ } // 直接比较,同理>和<、<=等也一样,比较字典顺序
int cmp = a.compare(b); // 返回0(相等)、正数(a > b)、负数(a < b)

关于单引号和双引号:

符号 用途 类型 内存内容 示例
’ ’ 单个字符 char ASCII 值 ‘A’
” ” 字符串 const char* 字符序列 + \0 “Hello”

因此对于string类型的s, 下列代码不正确s[i] == “V”而应该是s[i] == ‘V’

子串操作

substr() 函数 - 获取子字符串:s.substr(pos, len), 注意参数不是迭代器 - 参数: - pos:起始索引。 - len:长度(可选,默认到字符串末尾)( 不是终点位置!!! 已知终点算长度要 pos-start)

1
2
3
string s = "Hello World";
string sub1 = s.substr(6); // "World"(从索引6开始到结尾)
string sub2 = s.substr(0, 5); // "Hello"(从0开始取5个字符)

子串查找

1
2
3
size_t pos = s.find("World");    // 查找子串,返回起始索引,找不到返回 string::npos
size_t pos = s.find('o', 5); // 从索引5开始查找字符'o'
size_t pos = s.rfind("lo"); // 反向查找子串

插入和删除

1
2
3
s.insert(5, " INSERTED "); // 在索引5插入字符串
s.erase(5, 8); // 从索引5删除8个字符, (pos, length)
s.erase(s.begin() + 2); // 删除迭代器指向的字符

字符串输入和输出

1
2
3
4
5
6
7
std::string s;
std::cin >> s; // 默认停止读取空格,分隔多单词输入

std::getline(std::cin, s); // 读取整行内容,包括空格

std::string s("Hello");
std::cout << s << std::endl; // 输出 "Hello"
  • 如果 cin 之后用到 getline,由于 cin 遇到空白字符时就停止往后读,输入流里可能还有未被读取的换行符,而 getline 将会读取一行字符串,直到遇到换行符。所以在使用 getline 前,应当先用 cin.get() 读取换行符(这个函数的功能是读取单个字符),然后再用 getline。

string和栈

string本身提供了push_back()和pop_back()方法, 可以把string当作栈来用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
#include <stack>
using namespace std;
int main() {
string s;
s.push_back('a');
s.push_back('b');
s.push_back('c');
cout << "String as stack: " << s << endl; // 输出 "abc"

s.pop_back();
cout << "After pop_back: " << s << endl; // 输出 "ab"

// 使用标准库的 stack
stack<char> charStack;
charStack.push('x');
charStack.push('y');
charStack.push('z');
cout << "Stack top: " << charStack.top() << endl; // 输出 'z'

charStack.pop();
cout << "After pop, new top: " << charStack.top() << endl; // 输出 'y'

return 0;
}

底层实现

std::string 通常在底层使用动态数组来存储字符数据。它会自动管理内存的分配和释放,以适应字符串长度的变化。常见的实现细节包括: - 动态内存分配:当字符串长度超过当前容量时,std::string 会分配更大的内存块,并将现有数据复制过去。 - 小字符串优化 (SSO):许多 std::string 实现会使用小字符串优化技术,将较短的字符串直接存储在对象内部的固定大小缓冲区中,以减少动态内存分配的开销。