C++ 字符串处理
在 C++ 中,字符串的处理是非常重要的,而 C++ 标准库中的 std::string
类提供了强大的字符串功能。让我们从基础开始,逐步讲解如何在 C++ 中使用字符串类以及相关的操作和技巧。
1. 认识字符串
C++ 中的 std::string
其实并不是一个“真正的类型”,它是一个模板类 std::basic_string
的特化形式,底层实现是:
using string = std::basic_string<char>;
std::basic_string
是一个模板类,用来处理各种字符类型的字符串。其基本形式是存储一系列字符,可以是 char
、wchar_t
、char16_t
或 char32_t
类型。
字符串的编码和字符类型
char
:用于单字节字符。wchar_t
:宽字符类型,通常用于 Unicode 字符。char16_t
和char32_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::string
。std::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++ 中字符串的处理技巧!