在C++编程中,处理文本数据是最基础也最频繁的操作之一。早期C语言使用字符数组(char[])来存储和操作字符串,这种方式存在诸多限制:
C++的string类正是为了解决这些问题而设计的。它封装了字符串的存储和操作,提供了更安全、更便捷的字符串处理方式。string本质上是一个模板类basic_string的特化版本:
cpp复制typedef basic_string<char> string;
string类最显著的优势是自动管理内存。它内部维护一个动态数组,会根据字符串长度自动调整存储空间:
cpp复制string s = "hello"; // 初始分配
s += " world!"; // 自动扩容
这种动态特性意味着:
string类提供了数十种成员函数,覆盖了字符串处理的常见需求:
构造与赋值
cpp复制string s1; // 空字符串
string s2("hello"); // 从C字符串构造
string s3(s2); // 拷贝构造
string s4(5, 'a'); // 重复字符构造
s1 = "new value"; // 赋值操作
容量操作
cpp复制s.size(); // 当前字符数
s.capacity(); // 当前分配的内存大小
s.empty(); // 是否为空
s.reserve(100); // 预分配内存
元素访问
cpp复制s[0]; // 不检查越界
s.at(0); // 会检查越界,抛出异常
s.front(); // 首字符
s.back(); // 末字符
修改操作
cpp复制s.append("abc"); // 追加
s.insert(3, "xyz"); // 插入
s.erase(2, 3); // 删除
s.replace(1, 2, "12"); // 替换
s.clear(); // 清空
字符串查找
cpp复制s.find("ll"); // 首次出现位置
s.rfind("ll"); // 最后一次出现位置
s.find_first_of("abc"); // 查找任意指定字符
s.find_last_not_of("123"); // 查找不在集合中的字符
子字符串处理
cpp复制string sub = s.substr(2, 5); // 获取子串
虽然string类更安全方便,但有时需要与C函数交互。string提供了转换方法:
cpp复制const char* cstr = s.c_str(); // 获取C风格字符串
const char* data = s.data(); // 获取底层数组
size_t len = s.copy(buf, 5); // 复制到缓冲区
注意:c_str()返回的指针在string对象修改后可能失效,如需长期使用应先复制
结合sstream可以实现字符串与其他类型的转换:
cpp复制#include <sstream>
stringstream ss;
ss << "Value: " << 42;
string result = ss.str(); // "Value: 42"
// 反向解析
string input = "3.14 100";
stringstream ss2(input);
double pi; int num;
ss2 >> pi >> num;
预分配内存:对于已知大小的字符串,使用reserve()避免多次重分配
cpp复制string s;
s.reserve(1000); // 预分配足够空间
移动语义:C++11后支持移动构造,减少拷贝开销
cpp复制string createString() {
string tmp("large data");
return tmp; // 触发移动构造
}
短字符串优化:大多数实现对小字符串(通常15-22字符)有特殊优化,直接存储在对象内部,避免堆分配
cpp复制void parseConfig(const string& filename) {
ifstream file(filename);
string line;
while(getline(file, line)) {
// 跳过注释和空行
if(line.empty() || line[0] == '#') continue;
size_t pos = line.find('=');
if(pos != string::npos) {
string key = line.substr(0, pos);
string value = line.substr(pos+1);
// 处理键值对...
}
}
}
cpp复制vector<string> split(const string& s, char delimiter) {
vector<string> tokens;
size_t start = 0;
size_t end = s.find(delimiter);
while(end != string::npos) {
tokens.push_back(s.substr(start, end-start));
start = end + 1;
end = s.find(delimiter, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
C++11引入了regex库,与string配合使用:
cpp复制#include <regex>
string text = "Phone: 123-456-7890";
regex pattern(R"((\d{3})-(\d{3})-(\d{4}))");
smatch matches;
if(regex_search(text, matches, pattern)) {
cout << "Full match: " << matches[0] << endl;
cout << "Area code: " << matches[1] << endl;
}
string基于char类型(通常1字节),处理多字节字符(如UTF-8)时需要特别注意:
cpp复制string chinese = "你好";
cout << chinese.length(); // 输出6(UTF-8下),而非字符数2
解决方案:
string操作可能的性能问题:
频繁拼接:+=操作可能导致多次重分配
cpp复制// 低效写法
string result;
for(int i=0; i<1000; ++i) {
result += to_string(i); // 可能多次重分配
}
// 优化写法
string result;
result.reserve(4000); // 预分配足够空间
for(int i=0; i<1000; ++i) {
result += to_string(i);
}
不必要的拷贝:函数参数传递优先使用const引用
cpp复制void processString(const string& s); // 推荐
void processString(string s); // 可能产生拷贝
对于特殊场景,可以自定义内存分配策略:
cpp复制template<typename T>
class MyAllocator {
// 实现分配器接口...
};
using CustomString = basic_string<char, char_traits<char>, MyAllocator<char>>;
C++17引入了string_view,作为string的非拥有视图,适用于只读场景:
cpp复制void display(string_view sv) {
cout << sv.substr(0, 10) << endl; // 无需拷贝
}
string longStr("very long string...");
display(longStr); // 隐式转换
display("C-string"); // 直接使用
string_view优势:
实际项目中,我通常会根据场景选择: