一、字符串处理——C++里最常用的基本功

说实话,字符串处理是每个C++程序员都绕不开的坎。我刚开始写C++那会儿,还在用C风格字符串,踩过的坑能写满一页A4纸。后来有了std::string,我才算真正解放了双手。

这一章,我会带你彻底搞懂C++里的字符串处理。从老派的C风格字符串,到现代的std::string,再到字符串流和正则表达式,咱们一步步来。

1.1 C风格字符串——老祖宗留下的遗产

C风格字符串,说白了就是一个以'\0'结尾的字符数组。嗯,就是这么简单粗暴。

// C风格字符串的定义
char str1[] = "Hello";          // 自动添加'\0'
char str2[] = {'H', 'e', 'l', 'l', 'o', '\0'};  // 手动加'\0'
const char* str3 = "World";     // 字符串常量

// 常用操作——得用C标准库函数
#include <cstring>

char dest[20];
strcpy(dest, "Hello");          // 复制
strcat(dest, " World");         // 拼接
int len = strlen(dest);         // 长度(不含'\0')
int cmp = strcmp("abc", "abd"); // 比较,返回负值/0/正值
⚠️ 我曾经踩过的坑:用strcpy时目标数组太小,直接导致缓冲区溢出。程序跑着跑着就崩了,查了半天才发现是字符串拷贝越界。后来我改用strncpy,至少能指定最大长度。

C风格字符串的问题很明显:你得自己管理内存,自己保证缓冲区够大,自己记得加'\0'。说实话,这太容易出错了。

1.2 std::string——现代C++的救星

std::string是C++标准库提供的字符串类。它帮你管理内存,自动调整大小,再也不用担心缓冲区溢出了。

#include <string>

std::string s1 = "Hello";
std::string s2("World");
std::string s3 = s1 + " " + s2;  // 拼接,s3 = "Hello World"

// 常用操作
s1.length();        // 长度
s1.size();          // 同length()
s1.empty();         // 是否为空
s1[0];              // 访问字符,像数组一样
s1.at(0);           // 带边界检查的访问
s1.append("!!!");   // 追加
s1.insert(5, " ");  // 在位置5插入
s1.erase(5, 3);     // 从位置5删除3个字符
s1.replace(0, 5, "Hi"); // 替换子串
s1.find("World");   // 查找,返回位置或npos
s1.substr(0, 5);    // 取子串
💡 我个人习惯:能用std::string就别用C风格字符串。除非你要跟C语言接口打交道,或者做极高性能要求的场景。std::string的便利性远大于那一点点性能开销。

1.3 string的常用操作——实战中高频使用

下面这些操作,我在项目中几乎天天用。你想想看,字符串处理无非就是拼接、查找、替换、分割这几件事。

#include <string>
#include <iostream>

std::string text = "  Hello, C++ World!  ";

// 1. 去除空白
text.erase(0, text.find_first_not_of(" \t\n"));  // 去除前导空白
text.erase(text.find_last_not_of(" \t\n") + 1);  // 去除尾部空白

// 2. 大小写转换
#include <algorithm>
#include <cctype>

std::transform(text.begin(), text.end(), text.begin(), ::toupper);
std::transform(text.begin(), text.end(), text.begin(), ::tolower);

// 3. 分割字符串
std::vector<std::string> split(const std::string& s, char delim) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream(s);
    while (std::getline(tokenStream, token, delim)) {
        tokens.push_back(token);
    }
    return tokens;
}

// 4. 数字与字符串互转
int num = std::stoi("42");          // 字符串转int
double d = std::stod("3.14");       // 字符串转double
std::string s = std::to_string(42); // 数字转字符串
🔑 关键点:std::string的find()方法返回的是size_t类型,如果没找到会返回std::string::npos。判断时一定要用npos,不要用-1。

1.4 字符串流(stringstream)——灵活的数据转换器

字符串流是个好东西。它把字符串当成流来处理,可以像操作cin/cout一样读写字符串。我在解析配置文件时经常用它。

#include <sstream>

// 基本用法:拼接和格式化
std::ostringstream oss;
oss << "Name: " << "Alice" << ", Age: " << 25;
std::string result = oss.str();  // "Name: Alice, Age: 25"

// 解析:从字符串中提取数据
std::istringstream iss("42 3.14 hello");
int i;
double d;
std::string s;
iss >> i >> d >> s;  // i=42, d=3.14, s="hello"

// 实战:解析CSV行
std::string line = "1001,Alice,85.5";
std::istringstream lineStream(line);
std::string id, name, score;
std::getline(lineStream, id, ',');
std::getline(lineStream, name, ',');
std::getline(lineStream, score, ',');
// id="1001", name="Alice", score="85.5"
⚠️ 注意:stringstream在反复使用时,记得调用clear()和str("")重置状态。我遇到过因为流状态错误导致后续解析全部失败的情况,排查了好久才发现是忘了重置。

1.5 正则表达式入门(regex)——模式匹配的利器

正则表达式,说白了就是一套描述字符串模式的规则。C++11开始标准库直接支持regex,不用再依赖第三方库了。

#include <regex>

std::string text = "我的邮箱是 alice@example.com,电话是 138-0000-0000";

// 1. 匹配邮箱
std::regex emailPattern(R"(\w+@\w+\.\w+)");
std::smatch match;
if (std::regex_search(text, match, emailPattern)) {
    std::cout << "找到邮箱: " << match.str() << std::endl;
}

// 2. 替换
std::regex phonePattern(R"(\d{3}-\d{4}-\d{4})");
std::string result = std::regex_replace(text, phonePattern, "***-****-****");
// 结果: "我的邮箱是 alice@example.com,电话是 ***-****-****"

// 3. 迭代匹配所有
std::regex wordPattern(R"(\w+)");
auto words_begin = std::sregex_iterator(text.begin(), text.end(), wordPattern);
auto words_end = std::sregex_iterator();
for (auto it = words_begin; it != words_end; ++it) {
    std::cout << it->str() << std::endl;
}
💡 我建议:正则表达式虽然强大,但别滥用。简单的字符串查找用find()就够了。正则表达式编译有开销,如果同一个模式要匹配多次,记得把regex对象缓存起来。

1.6 本章知识体系

下面这张图帮你理清本章的核心脉络:

字符串处理知识体系 字符串处理 C风格字符串 char数组,以'\0'结尾 需手动管理内存 易缓冲区溢出 std::string 自动管理内存 拼接、查找、替换 数字与字符串互转 字符串流 ostringstream输出 istringstream解析 CSV/配置文件处理 正则表达式 模式匹配(regex_search) 替换(regex_replace) 迭代匹配(sregex_iterator)

从图中你能看到,字符串处理有四个核心方向。C风格字符串是基础,但日常开发我更推荐std::string。字符串流擅长数据转换,正则表达式则搞定复杂模式匹配。把这四块吃透,大部分字符串处理场景你都能应对自如。


好了,这一章的内容就到这里。字符串处理是C++编程的基石,多写多练自然就熟练了。下一章我们会深入探讨C++的输入输出流,到时候见。

公众号:蓝海资料掘金营,微信 deep3321