Bootstrap

C++ 字符串处理

C++ 字符串处理

在 C++ 中,字符串的处理是非常重要的,而 C++ 标准库中的 std::string 类提供了强大的字符串功能。让我们从基础开始,逐步讲解如何在 C++ 中使用字符串类以及相关的操作和技巧。

1. 认识字符串

C++ 中的 std::string 其实并不是一个“真正的类型”,它是一个模板类 std::basic_string 的特化形式,底层实现是:

using string = std::basic_string<char>;

std::basic_string 是一个模板类,用来处理各种字符类型的字符串。其基本形式是存储一系列字符,可以是 charwchar_tchar16_tchar32_t 类型。

字符串的编码和字符类型

  • char:用于单字节字符。
  • wchar_t:宽字符类型,通常用于 Unicode 字符。
  • char16_tchar32_t:分别用于 UTF-16 和 UTF-32 编码。

为了支持 Unicode 编码,C++ 设计了多个字符类型,且提供了多种字符串类型:

  • std::string:基于 char 类型的字符串。
  • std::wstring:基于 wchar_t 类型的字符串。
  • std::u16string:基于 char16_t 类型的字符串。
  • std::u32string:基于 char32_t 类型的字符串。

虽然 C++ 在编码支持上进行了努力,但由于字符编码的问题相当复杂,因此很多程序员为了避免麻烦,选择使用 std::string,并在需要时处理编码转换。

C++ 中的字符编码问题

C++ 标准库并没有专门提供对字符编码(如 UTF-8、UTF-16)的专门支持,因此,很多 C++ 程序员选择使用 std::string 来处理 UTF-8 编码,因为它与 char 类型兼容,可以轻松处理大部分字符串操作。


2. 用好字符串

std::string 是 C++ 标准库中唯一的字符串类,因此它有很多强大的功能,但也有一些设计上的缺陷。我们需要了解它的优缺点,并尽量发挥它的优势。

基本用法

std::string 提供了常用的字符串操作,如提取子串、比较、长度检查、字符查找等。下面是一些常见的操作:

std::string str = "abc";

// 获取字符串长度
assert(str.length() == 3);

// 字符串比较
assert(str < "xyz");

// 提取子串
assert(str.substr(0, 1) == "a");

// 访问单个字符
assert(str[1] == 'b');

// 查找字符
assert(str.find("1") == std::string::npos);

// 字符串拼接
assert(str + "d" == "abcd");
重要函数:
  • size()length():返回字符串的长度。
  • substr(pos, len):返回从位置 pos 开始的长度为 len 的子串。
  • find():查找某个子串,返回其位置,若未找到则返回 std::string::npos
  • operator[]:通过索引访问字符串中的字符。

字符串与容器的区别

虽然 std::string 类提供了一些类似容器的操作,如 size()begin()end() 等,但字符串和容器是两个不同的概念。

  • 字符串 是“文本”,字符之间有顺序和语义,不能随意修改或拆分。
  • 容器 是“集合”,元素之间没有语义关系,可以任意增删。

因此,字符串应该作为一个整体来处理,而不是当作一个容器来操作。

存储字符容器

有时我们需要存储字符的容器,例如字节序列或数据缓冲区,这时我们应该使用 std::vector<char>,而不是 std::stringstd::vector<char> 更适合存储和操作单独的字符数据。


3. 字符串处理技巧

3.1 字面量后缀

在 C++14 中,新增了一个字面量后缀 s,它将普通的 C 字符串字面量 "xxx" 转换为 std::string 类型,方便进行类型推导。

using namespace std::literals::string_literals; // 必须打开此命名空间

auto str = "std string"s;  // 后缀s,表示它是std::string类型
assert(str.size() == 11);   // 直接调用成员函数

这使得字符串的类型推导变得简单且易于阅读,不需要显式声明 std::string 类型。

3.2 原始字符串字面量

C++11 引入了原始字符串字面量(Raw String Literal),允许我们在字符串中直接使用特殊字符(如换行、反斜杠等),而不需要进行转义。

auto str = R"(nier:automata)";  // 原始字符串,不会对其中的字符进行转义

使用原始字符串字面量,可以避免字符串中的特殊字符被转义,适用于正则表达式、文件路径等场景。

  • 注意:如果字符串中需要包含圆括号或引号等特殊字符,可以使用自定义的分隔符,如:
auto str = R"==(R"(xxx)")==";  // 原样输出:R"(xxx)"

3.3 字符串与数字转换

C++11 增加了一些方便的字符串与数字之间转换的函数,如 stoi()stol()stof() 等:

assert(stoi("42") == 42);      // 字符串转整数
assert(stol("253") == 253L);   // 字符串转长整数
assert(stod("3.14") == 3.14);  // 字符串转浮点数
assert(to_string(1984) == "1984");  // 整数转字符串

这些函数简化了字符串与数字之间的转换,尤其在处理用户输入时非常有用。

3.4 字符串视图类 std::string_view

std::string_view 是 C++17 引入的一个轻量级字符串视图类。它不像 std::string 那样拥有拷贝和内存分配的开销,而是直接指向一个字符串的部分或全部内容。

std::string_view 内部仅存储指向字符串的指针和长度,因此它是非常高效的,并且可以避免不必要的内存分配。

std::string_view sv = "Hello, World!";
std::cout << sv.substr(7, 5) << std::endl;  // 输出 "World"

使用 std::string_view 可以减少字符串复制的开销,特别是当你需要频繁操作字符串时。


4. 字符串与正则表达式

在 C++11 中,C++ 标准库引入了正则表达式库 regex,用于处理复杂的字符串匹配和替换。C++ 的正则表达式功能强大,但也有一定的学习曲线。

正则表达式的基本类

  • std::regex:表示一个正则表达式对象。
  • std::smatch:表示匹配结果,存储匹配的字符串和子匹配项。

常用函数

  • regex_match():用于检查整个字符串是否完全匹配正则表达式。
  • regex_search():用于在字符串中查找匹配正则表达式的部分。
  • regex_replace():用于将字符串中匹配正则表达式的部分替换成新的字符串。
示例代码:
std::string str = "neir:automata";
std::regex reg(R"(^(\w+):(\w+)$)");  // 使用原始字符串定义正则表达式
std::smatch match;
if (std::regex_match(str, match, reg)) {
    std::cout << "First word: " << match[1] << std::endl;  // neir
    std::cout << "Second word: " << match[2] << std::endl;  // automata
}

std::string replaced_str = std::regex_replace(str, std::regex(R"(\w+)$"), "world");
std::cout << replaced_str << std::endl;  // 输出 "neir:world"

正则表达式的性能

正则表达式的编译和执行开销较大,建议避免频繁地在循环中创建新的 std::regex 对象。应尽量重用已编译的正则表达式。


总结

C++ 的字符串处理提供了强大的功能,但也有其复杂性。在大多数应用中,std::string 足以应对大多数需求。为了避免性能问题,可以

使用 std::string_view 作为优化手段,在处理不需要修改的字符串时,减少不必要的复制。此外,正则表达式和字符串转换函数也极大地扩展了 C++ 字符串处理的能力。

希望这篇文章能帮助你更好地掌握 C++ 中字符串的处理技巧!

;