Skip to content

Ch 3: 字符串与文本处理

  • 掌握 std::string 的常用操作
  • 理解字符串拼接和格式化的差异
  • 学会使用正则表达式
  • 处理编码问题

Python 的 str 是不可变对象:

# Python - 不可变对象
s = "Hello"
s[0] = 'h' # TypeError! 字符串不可变
# 常用方法
s.upper() # "HELLO"
s.lower() # "hello"
s.capitalize() # "Hello"
s.title() # "Hello"
s.strip() # 去除两端空白
s.replace("l", "L") # "HeLLo"
s.split("l") # ["He", "", "o"]
s.find("ll") # 2
s.startswith("He") # True
s.endswith("lo") # True
len(s) # 5
"Hello" + " " + "World" # "Hello World"
f"{s}!" # "Hello!"

C++ 的 std::string 也是不可变对象,但操作方式不同:

#include <string>
std::string s = "Hello";
// 不可变性
s[0] = 'h'; // OK,可以修改字符(string 是mutable的,但内容通常不可变)
// 但 string 不是 const,可以修改
// 拼接
std::string s2 = s + " World"; // "Hello World"
s += "!"; // "Hello!"
// 访问字符
char c = s[0]; // 'H'
char c2 = s.at(0); // 带边界检查,更安全
// 大小和容量
std::size_t len = s.length(); // 5
std::size_t cap = s.capacity(); // 通常 > 5
bool empty = s.empty();
// 子串
std::string sub = s.substr(1, 3); // "ell" (位置1,长度3)
// 查找
std::size_t pos = s.find("ll"); // 2
std::size_t not_found = s.find("xyz"); // std::string::npos
#include <string>
// 多种构造方式
std::string s1; // 空字符串
std::string s2("Hello"); // C 字符串
std::string s3(5, 'a'); // "aaaaa"
std::string s4(s3); // 拷贝构造
std::string s5 = {"Hello"}; // 初始化列表
std::string s6 = std::string("Hello") + " World"; // 移动构造
// 隐式构造
void process(const std::string& s);
process("Hello"); // C 字符串隐式转为 string

Python 3.6+ 的 f-string 是最方便的格式化方式:

name = "Alice"
age = 30
height = 1.75
# 基本用法
s = f"{name} is {age} years old"
# 格式化选项
s = f"PI = {3.14159:.4f}" # "PI = 3.1416"
s = f"Hex: {255:#x}" # "Hex: 0xff"
s = f"Binary: {42:b}" # "Binary: 101010"
s = f"{name:>10}" # 右对齐
s = f"{name:<10}" # 左对齐
s = f"{name:^10}" # 居中
# 表达式
s = f"{2 ** 10}" # "1024"
s = f"{len(name)}" # "5"

C++20 引入了 Python f-string 的等价物:

#include <format>
#include <string>
std::string name = "Alice";
int age = 30;
double height = 1.75;
// 基本用法
std::string s = std::format("{} is {} years old", name, age);
// 格式化选项
std::format("PI = {:.4f}", 3.14159); // "PI = 3.1416"
std::format("Hex: {:x}", 255); // "Hex: ff"
std::format("Binary: {:b}", 42); // "Binary: 101010"
std::format("{:>10}", "right"); // " right"
std::format("{:<10}", "left"); // "left "
std::format("{:^10}", "center"); // " center "
// 数字格式化
std::format("{:+d}", 42); // "+42"
std::format("{:05d}", 42); // "00042"
std::format("{:.2%}", 0.25); // "25.00%"
std::format("{:e}", 1234.5678); // "1.234568e+03"
// 输出到流
std::cout << std::format("Hello, {}!\n", name);

C++20 之前,没有标准格式化库:

#include <iostream>
#include <sstream>
#include <iomanip>
// 使用流
std::cout << "PI = " << 3.14159 << std::endl;
// stringstream
std::ostringstream oss;
oss << "PI = " << std::fixed << std::setprecision(4) << 3.14159;
std::string s = oss.str();
// printf 风格(不类型安全)
printf("PI = %.4f\n", 3.14159);
import re
text = "Date: 2024-01-15, Time: 14:30:00"
pattern = r"\d{4}-\d{2}-\d{2}"
# 搜索
match = re.search(pattern, text)
if match:
print(match.group()) # "2024-01-15"
print(match.start(), match.end()) # (6, 16)
# 查找所有
matches = re.findall(pattern, text)
print(matches) # ['2024-01-15']
# 替换
result = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", text)
print(result) # "Date: 15/01/2024, Time: 14:30:00"
# 分割
parts = re.split(r"[,;:\s]+", "a,b;c:d e")
print(parts) # ['a', 'b', 'c', 'd', 'e']
# 编译模式(性能优化)
compiled = re.compile(r"\d{4}-\d{2}-\d{2}")

C++11 引入了正则表达式库:

#include <regex>
#include <string>
std::string text = "Date: 2024-01-15, Time: 14:30:00";
std::regex pattern(R"(\d{4})-(\d{2})-(\d{2})");
// 搜索
std::smatch match;
if (std::regex_search(text, match, pattern)) {
std::cout << match.str() << std::endl; // "2024-01-15"
std::cout << match[0] << std::endl; // "2024-01-15"
std::cout << match[1] << std::endl; // "2024"
std::cout << match[2] << std::endl; // "01"
}
// 查找所有(C++ 需要迭代)
std::sregex_iterator it(text.begin(), text.end(), pattern);
std::sregex_iterator end;
while (it != end) {
std::cout << it->str() << std::endl;
++it;
}
// 替换
std::string result = std::regex_replace(text,
std::regex(R"((\d{4})-(\d{2})-(\d{2}))"),
"$3/$2/$1"); // "Date: 15/01/2024, Time: 14:30:00"
// 分割
std::string input = "a,b;c:d e";
std::regex delim(R"([,;:\s]+)");
std::sregex_token_iterator it(input.begin(), input.end(), delim, -1);
// -1 表示分割符
PythonC++说明
\d\d数字
\w\w单词字符
\s\s空白
..任意字符
**0或更多
++1或更多
??0或1
{n}{n}精确 n
{n,m}{n,m}n 到 m
[abc][abc]字符类
[^abc][^abc]取反
(...)(...)捕获组
^^行首
$$行尾

Python 3 字符串是 Unicode:

# 编码
text = "你好"
bytes_utf8 = text.encode("utf-8") # b'\xe4\xb8\xad\xe6\x96\x87'
bytes_gbk = text.encode("gbk") # b'\xc4\xe3\xba\xc3'
# 解码
decoded_utf8 = bytes_utf8.decode("utf-8") # "你好"
decoded_gbk = bytes_gbk.decode("gbk") # "你好"
# 编码检测
import chardet
detected = chardet.detect(raw_bytes)
print(detected) # {'encoding': 'utf-8', 'confidence': 0.99}
# 处理文件编码
with open("file.txt", "r", encoding="utf-8") as f:
content = f.read()

C++ 的编码处理更底层:

#include <string>
#include <codecvt>
#include <locale>
// UTF-8 字符串
std::string utf8_str = u8"你好";
// UTF-16 / UTF-32
std::u16string u16_str = u"你好";
std::u32string u32_str = U"你好";
// C++20 char8_t
std::u8string u8_str = u8"你好";
// 转换(C++11)
std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> conv16;
std::u16string u16 = conv16.from_bytes(utf8_str);
std::string back = conv16.to_bytes(u16);
// Windows 下的宽字符
#ifdef _WIN32
std::wstring wstr = L"你好";
SetConsoleOutputCP(CP_UTF8);
#endif
#include <string>
#include <string_view>
// 简单的 UTF-8 长度(不等于字符数)
std::size_t utf8_length(std::string_view s) {
std::size_t len = 0;
for (unsigned char c : s) {
if ((c & 0xC0) != 0x80) ++len; // 非延续字节
}
return len;
}
// 判断是否为 ASCII
bool is_ascii(std::string_view s) {
for (unsigned char c : s) {
if (c > 127) return false;
}
return true;
}
PythonC++说明
s.upper()手动或 std::transform转大写
s.lower()手动或 std::transform转小写
s.strip()std::isspace 手动处理去除空白
s.split(",")std::stringstream + std::getline分割
",".join(list)std::accumulate 或循环连接
s.find("abc")s.find("abc")查找子串
s.replace("a","b")std::regex_replace 或循环替换
f"{x}"std::format("{}", x)格式化
s[0]s[0] 或 s.at(0)访问字符
len(s)s.length() 或 s.size()长度
s.startswith("a")s.rfind("a", 0) == 0前缀检查
s.endswith("a")s.size() >= 1 && s.back() == 'a'后缀检查
s.isdigit()std::all_of(s.begin(), s.end(), ::isdigit)数字检查

C++17 引入了 string_view,避免不必要的拷贝:

#include <string_view>
// string_view - 不拥有数据的视图
void process(std::string_view sv) {
std::cout << sv << std::endl;
std::cout << "Length: " << sv.length() << std::endl;
}
int main() {
std::string s = "Hello, World!";
std::string_view sv = s; // 不拷贝
sv = sv.substr(7, 5); // "World"
process("Hello"); // 直接用字面量
process(s); // 不拷贝,自动转 string_view
return 0;
}

优势:

// ❌ 低效:创建临时 string
void bad(const std::string& s);
bad(s.substr(0, 5)); // 拷贝子串
// ✅ 高效:用 string_view
void good(std::string_view s);
good(s.substr(0, 5)); // 只复制指针和长度
  • std::string 操作类似 Python str,但需要包含 <string>
  • std::format 是 C++20 的格式化方案,类似 Python f-string
  • std::regex 提供正则表达式支持
  • 编码处理需要显式转换,C++ 没有内置的编码检测
  • std::string_view 是 C++17 的重要改进,避免不必要的拷贝

下章预告:ch04 容器与数据结构。