1. 字符串操作基础与核心概念
C++中的string类是标准模板库(STL)提供的一个强大工具,它封装了字符数组的复杂操作,让文本处理变得简单高效。与C风格的字符数组相比,string自动管理内存、提供丰富的成员方法,并且完全兼容STL算法。在实际项目中,合理运用string方法可以显著提升开发效率和代码质量。
string本质上是一个动态字符序列容器,它根据内容长度自动调整存储空间,其底层通常采用写时复制(Copy-On-Write)或短字符串优化(SSO)等策略来提升性能。理解这些底层机制有助于我们更好地使用string——比如知道短字符串(通常≤15字符)会直接存储在栈上而非堆内存,这解释了为何小字符串操作特别高效。
重要提示:虽然string用起来简单,但不当使用仍会导致性能问题。比如在循环中反复拼接字符串时,+=操作可能引发多次内存重分配。
2. 字符串创建与初始化方法
2.1 基础构造方式
string提供了多种构造函数,适应不同初始化场景:
cpp复制string s1; // 空字符串
string s2("hello"); // 从C风格字符串构造
string s3(5, 'a'); // 填充构造:"aaaaa"
string s4(s2); // 拷贝构造
string s5(s2, 1, 3);// 子串构造:"ell"
C++11后还支持初始化列表:
cpp复制string s6{'h', 'e', 'l', 'l', 'o'};
2.2 现代C++的改进
移动语义的引入让string操作更高效:
cpp复制string getString() { return "temp"; }
string s7 = getString(); // 触发移动构造而非拷贝
原始字符串字面量(Raw string literal)简化了特殊字符处理:
cpp复制string path = R"(C:\Program Files\data)"; // 无需转义反斜杠
string xml = R"(<tag attr="value">)</tag>)"; // 保留所有格式
3. 字符串内容访问与遍历
3.1 元素访问方法
安全访问推荐使用at(),它在越界时抛出异常:
cpp复制try {
char c = s.at(100); // 可能抛出std::out_of_range
} catch(...) { /* 处理异常 */ }
非安全但高效的operator[]:
cpp复制char c = s[0]; // 不检查边界,性能更高
C++11引入的front()和back():
cpp复制s.front() = 'H'; // 首字符
s.back() = '!'; // 末字符
3.2 迭代器遍历
标准迭代器模式:
cpp复制for(auto it = s.begin(); it != s.end(); ++it) {
cout << *it;
}
C++11范围for循环:
cpp复制for(char c : s) {
cout << c;
}
反向迭代器:
cpp复制for(auto rit = s.rbegin(); rit != s.rend(); ++rit) {
cout << *rit; // 逆序输出
}
4. 字符串修改与操作
4.1 内容修改方法
append()的多种重载:
cpp复制s.append(" world"); // 追加字符串
s.append(3, '!'); // 追加字符
s.append(s2.begin()+1, s2.end()); // 追加迭代器范围
insert()的灵活插入:
cpp复制s.insert(5, " dear"); // 在位置5插入
s.insert(s.begin(), '#'); // 在开头插入字符
replace()的替换操作:
cpp复制s.replace(7, 5, "there"); // 从7开始替换5个字符
4.2 高效拼接技巧
小字符串拼接直接用+或+=:
cpp复制string msg = "Hello" + string(" ") + "world";
大量拼接时使用reserve()+append():
cpp复制string result;
result.reserve(1000); // 预分配避免多次重分配
for(int i=0; i<100; ++i) {
result.append(data[i]);
}
C++17引入的string_view拼接:
cpp复制string_view sv1 = "hello";
string_view sv2 = "world";
string s = string(sv1) + string(sv2); // 零拷贝拼接
5. 字符串查找与子串操作
5.1 查找方法详解
find()系列方法:
cpp复制size_t pos = s.find("ll"); // 查找子串
pos = s.find('o'); // 查找字符
pos = s.find("ll", 3); // 从位置3开始查找
pos = s.rfind('l'); // 反向查找
查找扩展方法:
cpp复制pos = s.find_first_of("aeiou"); // 找任意元音首次出现
pos = s.find_last_not_of(" \t\n"); // 找最后一个非空白符
5.2 子串处理
substr()的典型用法:
cpp复制string sub = s.substr(6); // 从6到结尾
sub = s.substr(0, 5); // 前5个字符
配合查找的实用模式:
cpp复制size_t start = s.find("[");
size_t end = s.find("]");
if(start != string::npos && end != string::npos) {
string content = s.substr(start+1, end-start-1);
}
6. 字符串容量与大小操作
6.1 容量管理
reserve()的预分配策略:
cpp复制s.reserve(100); // 预分配100字节容量
cout << s.capacity(); // 查看实际容量
shrink_to_fit()的优化:
cpp复制s.shrink_to_fit(); // 请求缩减容量以匹配大小
6.2 大小调整
resize()的两种模式:
cpp复制s.resize(10); // 扩展时填充空字符
s.resize(3); // 截断多余字符
s.resize(10, 'x'); // 扩展时填充'x'
clear()与empty():
cpp复制s.clear(); // 清空内容
if(s.empty()) { // 判空检查
cout << "字符串为空";
}
7. 字符串比较与数值转换
7.1 比较操作
compare()的多种形式:
cpp复制int res = s.compare("hello"); // 全串比较
res = s.compare(0, 3, "hel"); // 部分比较
运算符重载的直观比较:
cpp复制if(s == "hello") { /*...*/ }
if(s < "world") { /*...*/ }
7.2 数值转换
C++11引入的便捷方法:
cpp复制string num = to_string(3.1415); // 数字转字符串
double val = stod("3.14"); // 字符串转double
int i = stoi("42", nullptr, 16); // 16进制字符串转int
错误处理示例:
cpp复制try {
size_t pos;
int x = stoi("123abc", &pos); // pos将指向第一个非数字字符
cout << "转换值:" << x << ",停止位置:" << pos;
} catch(const invalid_argument& e) {
cerr << "无效参数:" << e.what();
}
8. 字符串与C风格接口互操作
8.1 安全转换方法
c_str()和data()的区别:
cpp复制const char* p1 = s.c_str(); // 保证以null结尾
const char* p2 = s.data(); // C++17前不保证null结尾
copy()的安全复制:
cpp复制char buf[10];
size_t copied = s.copy(buf, sizeof(buf)-1); // 确保不越界
buf[copied] = '\0'; // 手动添加终止符
8.2 现代替代方案
优先使用string_view:
cpp复制void process(string_view sv) { /*...*/ }
process("C风格字符串"); // 零拷贝传递
process(s); // 从string转换
9. 高级字符串处理技巧
9.1 正则表达式支持
C++11的regex集成:
cpp复制regex pattern(R"(\d{3}-\d{4})");
if(regex_search(s, pattern)) {
cout << "找到匹配";
}
smatch matches;
if(regex_search(s, matches, pattern)) {
cout << "匹配内容:" << matches[0];
}
9.2 字符串分割实现
使用find和substr:
cpp复制vector<string> split(const string& s, char delim) {
vector<string> tokens;
size_t start = 0, end = s.find(delim);
while(end != string::npos) {
tokens.push_back(s.substr(start, end-start));
start = end + 1;
end = s.find(delim, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
C++20的split_view:
cpp复制// C++20示例(编译器支持时可用)
for(string_view word : s | views::split(' ')) {
cout << word << endl;
}
10. 性能优化与最佳实践
10.1 避免常见陷阱
警惕临时字符串:
cpp复制// 低效写法
for(int i=0; i<10000; ++i) {
s += string("a") + string("b"); // 创建多个临时对象
}
// 高效写法
for(int i=0; i<10000; ++i) {
s += "ab"; // 减少临时对象
}
移动语义的正确使用:
cpp复制string process(string&& input) {
// 处理input...
return move(input); // 明确所有权转移
}
10.2 自定义分配器
针对特定场景优化内存分配:
cpp复制template<class T>
class MyAllocator { /* 自定义分配器实现 */ };
using CustomString = basic_string<char, char_traits<char>, MyAllocator<char>>;
CustomString s("使用自定义分配器");
11. 跨平台注意事项
11.1 编码处理
UTF-8字符串处理:
cpp复制u8string utf8 = u8"中文"; // C++20引入
wstring wide = L"宽字符";
编码转换工具:
cpp复制wstring_convert<codecvt_utf8<wchar_t>> converter;
wstring wide = converter.from_bytes("你好");
string utf8 = converter.to_bytes(wide);
11.2 平台差异处理
Windows路径处理:
cpp复制string path = "C:\\temp\\file.txt"; // 注意转义
path.replace(path.begin(), path.end(), '\\', '/'); // 统一分隔符
行结束符标准化:
cpp复制size_t pos = 0;
while((pos = s.find("\r\n", pos)) != string::npos) {
s.replace(pos, 2, "\n");
}
12. 实战案例解析
12.1 日志处理系统
高效日志拼接技术:
cpp复制void log(const string& message) {
string entry;
entry.reserve(message.size() + 30); // 预留时间戳空间
// 添加时间戳
auto now = chrono::system_clock::now();
time_t t = chrono::system_clock::to_time_t(now);
entry += ctime(&t);
entry.pop_back(); // 移除换行符
entry += " - " + message + "\n";
// 写入日志文件
log_file.write(entry.data(), entry.size());
}
12.2 网络协议处理
报文解析示例:
cpp复制bool parseHttpHeader(const string& header, string& method, string& path) {
size_t space1 = header.find(' ');
if(space1 == string::npos) return false;
size_t space2 = header.find(' ', space1+1);
if(space2 == string::npos) return false;
method = header.substr(0, space1);
path = header.substr(space1+1, space2-space1-1);
return true;
}
13. C++17/20新特性应用
13.1 string_view的妙用
零拷贝处理示例:
cpp复制void processSubstring(string_view sv) {
// 无需拷贝即可处理字符串片段
if(sv.starts_with("http")) {
cout << "检测到URL";
}
}
// 调用方式
processSubstring(string("http://example.com").substr(0,4));
processSubstring("http://example.com");
13.2 starts_with/ends_with
C++20新增方法:
cpp复制if(s.starts_with("Hello")) {
cout << "字符串以Hello开头";
}
if(s.ends_with(".txt")) {
cout << "文本文件";
}
14. 调试与性能分析
14.1 内存使用分析
查看字符串内存布局:
cpp复制cout << "内容:" << s << "\n";
cout << "大小:" << s.size() << "\n";
cout << "容量:" << s.capacity() << "\n";
cout << "栈优化:" << (s.capacity() <= 15 ? "是" : "否") << "\n";
14.2 性能测试技巧
拼接操作基准测试:
cpp复制void testConcat() {
const int count = 10000;
// 测试+=操作
auto start = chrono::high_resolution_clock::now();
string s1;
for(int i=0; i<count; ++i) {
s1 += "a";
}
auto end = chrono::high_resolution_clock::now();
cout << "+=用时:" << chrono::duration_cast<chrono::microseconds>(end-start).count() << "μs\n";
// 测试append操作
start = chrono::high_resolution_clock::now();
string s2;
s2.reserve(count);
for(int i=0; i<count; ++i) {
s2.append("a");
}
end = chrono::high_resolution_clock::now();
cout << "append用时:" << chrono::duration_cast<chrono::microseconds>(end-start).count() << "μs\n";
}
15. 扩展与自定义
15.1 自定义字符串类
继承basic_string的注意事项:
cpp复制class MyString : public std::basic_string<char> {
public:
using basic_string::basic_string;
bool contains(string_view substr) const {
return find(substr) != npos;
}
MyString toUpper() const {
MyString result(*this);
transform(result.begin(), result.end(), result.begin(), ::toupper);
return result;
}
};
15.2 第三方库集成
使用folly::fbstring替代方案:
cpp复制// Facebook的优化字符串实现
folly::fbstring fbs("高性能字符串");
fbs += " 附加内容";
16. 异常安全与错误处理
16.1 内存异常防护
安全的大字符串处理:
cpp复制try {
string huge(1'000'000'000, 'x'); // 可能抛出bad_alloc
} catch(const bad_alloc& e) {
cerr << "内存分配失败:" << e.what();
// 优雅降级处理
}
16.2 数据验证模式
输入安全检查示例:
cpp复制string sanitizeInput(string_view input) {
string result;
result.reserve(input.size());
for(char c : input) {
if(isalnum(c) || c == '_' || c == '-') {
result += c;
}
}
return result;
}
17. 多线程环境下的字符串
17.1 线程安全注意事项
只读共享的安全模式:
cpp复制const string sharedConfig = loadConfig();
void workerThread() {
// 安全读取共享字符串
cout << sharedConfig;
}
17.2 写时复制的影响
COW实现的潜在问题:
cpp复制string s1 = "共享数据";
string s2 = s1; // 可能共享底层数据
// 某个线程修改s1
thread t1([&](){
s1[0] = 'X'; // 可能触发复制,但不保证线程安全
});
// 另一个线程读取s2
thread t2([&](){
cout << s2; // 潜在的数据竞争
});
t1.join();
t2.join();
18. 字符串算法应用
18.1 模式匹配优化
KMP算法实现:
cpp复制vector<int> computeLPS(string pattern) {
vector<int> lps(pattern.length());
int len = 0;
for(int i=1; i<pattern.length(); ) {
if(pattern[i] == pattern[len]) {
lps[i++] = ++len;
} else {
if(len != 0) len = lps[len-1];
else lps[i++] = 0;
}
}
return lps;
}
int KMPSearch(string text, string pattern) {
vector<int> lps = computeLPS(pattern);
int i=0, j=0;
while(i < text.length()) {
if(text[i] == pattern[j]) {
i++; j++;
if(j == pattern.length()) return i-j;
} else {
if(j != 0) j = lps[j-1];
else i++;
}
}
return -1;
}
18.2 字符串压缩
简单游程编码:
cpp复制string runLengthEncode(const string& input) {
string result;
if(input.empty()) return result;
char current = input[0];
int count = 1;
for(size_t i=1; i<input.size(); ++i) {
if(input[i] == current) {
++count;
} else {
result += to_string(count) + current;
current = input[i];
count = 1;
}
}
result += to_string(count) + current;
return result;
}
19. 二进制数据处理
19.1 字节字符串处理
十六进制表示转换:
cpp复制string bytesToHex(const string& data) {
static const char hexdigits[] = "0123456789ABCDEF";
string result;
result.reserve(data.size() * 2);
for(unsigned char c : data) {
result += hexdigits[c >> 4];
result += hexdigits[c & 0xF];
}
return result;
}
19.2 Base64编码解码
基础实现示例:
cpp复制string base64Encode(const string& input) {
static const char* codes =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string result;
int bits = 0, bitCount = 0;
const unsigned char* p = reinterpret_cast<const unsigned char*>(input.data());
for(size_t i=0; i<input.size(); ++i) {
bits = (bits << 8) | p[i];
bitCount += 8;
while(bitCount >= 6) {
bitCount -= 6;
result += codes[(bits >> bitCount) & 0x3F];
}
}
if(bitCount > 0) {
result += codes[(bits << (6 - bitCount)) & 0x3F];
}
while(result.size() % 4) {
result += '=';
}
return result;
}
20. 现代C++工程实践
20.1 字符串作为API接口
良好的API设计原则:
cpp复制// 推荐:使用string_view接收只读参数
void processText(string_view text);
// 需要修改内容时使用非const引用
void transformText(string& text);
// 返回新字符串而非修改参数
string filterText(string_view source);
20.2 字符串资源管理
RAII包装器示例:
cpp复制class StringResource {
string data;
FILE* file;
public:
explicit StringResource(const string& path)
: file(fopen(path.c_str(), "r")) {
if(file) {
char buf[1024];
while(fgets(buf, sizeof(buf), file)) {
data += buf;
}
}
}
~StringResource() {
if(file) fclose(file);
}
operator string() const { return data; }
// 禁用拷贝
StringResource(const StringResource&) = delete;
StringResource& operator=(const StringResource&) = delete;
// 允许移动
StringResource(StringResource&&) = default;
StringResource& operator=(StringResource&&) = default;
};