1. C++ string类概述
在C++编程中,字符串处理是最基础也是最频繁的操作之一。与C语言中使用字符数组处理字符串不同,C++提供了string类来简化字符串操作。string类是标准模板库(STL)的一部分,定义在
string本质上是一个类模板basic_string的特化版本:
cpp复制typedef basic_string<char> string;
这个设计使得string类具有以下核心优势:
- 自动内存管理:无需手动分配和释放内存
- 丰富的操作接口:拼接、查找、替换等操作都有现成方法
- 安全性:提供边界检查等安全机制
- 与STL兼容:可以配合算法、迭代器等STL组件使用
2. string类的核心特性
2.1 内存管理机制
string类内部使用动态数组存储字符数据,其内存管理有几个关键特点:
-
容量(capacity)与大小(size)分离:
- size()返回实际存储的字符数
- capacity()返回当前分配的内存能存储的字符数
- 当size达到capacity时,会自动扩容(通常是2倍增长)
-
短字符串优化(SSO):
现代实现通常会对短字符串(一般15-22字符)进行特殊优化,直接存储在对象内部,避免堆分配。
cpp复制std::string s1 = "short"; // 可能使用SSO
std::string s2 = "this is a very long string..."; // 使用堆分配
2.2 常用成员函数
string类提供了丰富的成员函数,主要分为以下几类:
-
构造与赋值:
cpp复制string(); // 默认构造 string(const char* s); // C字符串构造 string(const string& str); // 拷贝构造 string& operator=(const string& str); // 赋值操作 -
元素访问:
cpp复制char& operator[](size_t pos); // 无检查访问 char& at(size_t pos); // 有边界检查的访问 const char* c_str() const; // 获取C风格字符串 const char* data() const; // 获取底层数据(C++17后保证以null结尾) -
容量操作:
cpp复制size_t size() const; // 实际长度 bool empty() const; // 是否为空 void reserve(size_t new_cap); // 预留空间 -
修改操作:
cpp复制string& append(const string& str); // 追加 string& insert(size_t pos, const string& str); // 插入 string& erase(size_t pos = 0, size_t len = npos); // 删除 void clear(); // 清空 -
字符串操作:
cpp复制size_t find(const string& str, size_t pos = 0) const; // 查找 string substr(size_t pos = 0, size_t len = npos) const; // 子串 int compare(const string& str) const; // 比较
3. string类的高效使用技巧
3.1 避免不必要的拷贝
string管理动态内存,拷贝可能带来性能开销。以下方法可以减少拷贝:
-
使用移动语义(C++11起):
cpp复制string createString() { string s(1000, 'x'); // 大字符串 return s; // 触发返回值优化(RVO)或移动语义 } string s = createString(); // 无拷贝 -
传递引用:
cpp复制void processString(const string& s); // 避免拷贝 -
使用string_view(C++17):
cpp复制void processString(std::string_view sv); // 零拷贝视图
3.2 字符串拼接优化
频繁拼接字符串时,以下方法可以提高性能:
-
预分配足够空间:
cpp复制string result; result.reserve(1000); // 预分配 for(int i=0; i<100; i++) { result += "some data"; } -
使用ostringstream处理复杂拼接:
cpp复制#include <sstream> std::ostringstream oss; oss << "Value: " << 42 << ", Time: " << 3.14; string s = oss.str(); -
使用append()替代多次+=:
cpp复制string s = "start"; s.append(100, 'x'); // 比循环+=高效
3.3 字符串查找与替换
string提供了多种查找方法,各有适用场景:
-
find()系列:
cpp复制size_t pos = s.find("substring"); if(pos != string::npos) { // 找到 } -
C++20新增的starts_with/ends_with:
cpp复制if(s.starts_with("http")) {...} if(s.ends_with(".cpp")) {...} -
高效替换:
cpp复制void replaceAll(string& s, const string& from, const string& to) { size_t pos = 0; while((pos = s.find(from, pos)) != string::npos) { s.replace(pos, from.length(), to); pos += to.length(); } }
4. string与其他类型的转换
4.1 数值转换
C++11提供了一组方便的转换函数:
-
字符串转数值:
cpp复制int i = stoi("42"); double d = stod("3.14"); long l = stol("10000000000"); -
数值转字符串:
cpp复制string s1 = to_string(42); string s2 = to_string(3.14159);
注意:这些函数会抛出invalid_argument或out_of_range异常,必要时需捕获处理。
4.2 与C字符串互操作
虽然应尽量减少使用C字符串,但有时需要互操作:
-
string转C字符串:
cpp复制string s = "hello"; const char* cstr = s.c_str(); // 自动以null结尾 -
C字符串转string:
cpp复制const char* cstr = "world"; string s(cstr); // 安全构造
重要:c_str()返回的指针在string修改后可能失效,如需长期保存应复制数据。
5. 现代C++中的string增强
5.1 C++17的string_view
string_view提供字符串的轻量级视图,避免拷贝:
cpp复制void process(std::string_view sv) {
// 可以像string一样操作,但不拥有数据
if(sv.starts_with("http")) {...}
}
string s = "http://example.com";
process(s); // 无拷贝
process("https://test.com"); // 无临时string构造
5.2 C++20的新功能
-
contains()方法:
cpp复制if(s.contains("debug")) {...} // 比find()更直观 -
starts_with/ends_with:
cpp复制if(s.starts_with("file://")) {...} -
常量表达式支持:
cpp复制constexpr string_view sv = "compile-time";
6. 性能优化与陷阱规避
6.1 常见性能陷阱
-
循环中的临时字符串:
cpp复制// 低效 - 每次循环都构造临时string for(int i=0; i<1000; i++) { string s = "Item " + to_string(i); process(s); } // 优化 - 复用字符串对象 string s; for(int i=0; i<1000; i++) { s.assign("Item ").append(to_string(i)); process(s); } -
不必要的拷贝:
cpp复制void processString(string s); // 按值传递导致拷贝 // 改为常量引用 void processString(const string& s);
6.2 内存管理建议
-
shrink_to_fit的使用:
cpp复制string s(1000, 'x'); s.erase(0, 900); // size=100, capacity可能仍为1000 s.shrink_to_fit(); // 请求减少capacity接近size -
大字符串处理:
cpp复制string readLargeFile(const string& filename) { ifstream ifs(filename, ios::ate); // 直接定位到末尾 size_t size = ifs.tellg(); string content; content.reserve(size); // 一次性预留足够空间 ifs.seekg(0); content.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>()); return content; }
7. 字符串编码与国际化
7.1 宽字符与多字节字符串
C++支持多种字符串类型处理不同编码:
-
wstring - 宽字符字符串:
cpp复制wstring ws = L"宽字符字符串"; -
u16string/u32string (C++11):
cpp复制u16string utf16 = u"UTF-16字符串"; u32string utf32 = U"UTF-32字符串"; -
u8string (C++20):
cpp复制u8string utf8 = u8"UTF-8字符串";
7.2 编码转换
标准库没有直接提供编码转换,但可以通过以下方式实现:
-
使用系统API(Windows):
cpp复制// UTF-8到UTF-16转换示例 wstring utf8ToUtf16(const string& utf8) { int size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0); wstring utf16(size, 0); MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &utf16[0], size); return utf16; } -
使用第三方库(如ICU、iconv)。
8. string与正则表达式
C++11起支持正则表达式,与string配合强大:
cpp复制#include <regex>
string s = "Email: test@example.com, Phone: 123-456-7890";
regex email_pattern(R"(\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b)");
smatch matches;
if(regex_search(s, matches, email_pattern)) {
cout << "Found email: " << matches[0] << endl;
}
9. 自定义字符串操作
9.1 实现常用工具函数
-
字符串分割:
cpp复制vector<string> split(const string& s, char delimiter) { vector<string> tokens; string token; istringstream tokenStream(s); while(getline(tokenStream, token, delimiter)) { tokens.push_back(token); } return tokens; } -
字符串trim:
cpp复制string trim(const string& s) { auto start = s.begin(); while(start != s.end() && isspace(*start)) { start++; } auto end = s.end(); do { end--; } while(distance(start, end) > 0 && isspace(*end)); return string(start, end + 1); }
9.2 实现字符串格式化
C++20之前没有标准字符串格式化,可以这样实现:
cpp复制string format(const string& format, ...) {
va_list args;
va_start(args, format);
// 确定所需缓冲区大小
va_list args_copy;
va_copy(args_copy, args);
int size = vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1;
va_end(args_copy);
vector<char> buf(size);
vsnprintf(buf.data(), size, format.c_str(), args);
va_end(args);
return string(buf.data());
}
C++20引入了std::format,提供了更现代的解决方案。
10. string在项目中的实际应用
10.1 配置文件解析
cpp复制class ConfigParser {
map<string, string> config;
public:
void load(const string& filename) {
ifstream file(filename);
string line;
while(getline(file, line)) {
line = trim(line);
if(line.empty() || line[0] == '#') continue;
size_t pos = line.find('=');
if(pos != string::npos) {
string key = trim(line.substr(0, pos));
string value = trim(line.substr(pos+1));
config[key] = value;
}
}
}
string get(const string& key) const {
auto it = config.find(key);
return it != config.end() ? it->second : "";
}
};
10.2 日志系统实现
cpp复制class Logger {
ofstream logFile;
mutex logMutex;
string getCurrentTime() {
auto now = chrono::system_clock::now();
time_t now_time = chrono::system_clock::to_time_t(now);
tm now_tm = *localtime(&now_time);
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &now_tm);
return string(buf);
}
public:
Logger(const string& filename) : logFile(filename, ios::app) {}
void log(const string& message) {
lock_guard<mutex> lock(logMutex);
string entry = format("[%s] %s\n", getCurrentTime().c_str(), message.c_str());
logFile << entry;
cout << entry;
}
};
11. string与模板元编程
string也可以用于编译期计算(C++20起增强支持):
cpp复制// 编译期字符串连接
template<size_t N>
struct FixedString {
char buf[N+1] = {};
constexpr FixedString(const char (&s)[N]) {
for(size_t i=0; i<N; i++) buf[i] = s[i];
}
constexpr operator const char*() const { return buf; }
};
template<FixedString S1, FixedString S2>
struct Concat {
static constexpr char value[] = {S1[0], S1[1], S2[0], S2[1], 0};
};
constexpr auto hello = Concat<"he", "ll">::value; // "hell"
12. string的最佳实践总结
- 优先使用string而非C风格字符串,提高安全性和便利性
- 对于函数参数,优先使用const string&传递只读字符串
- 需要修改字符串内容时,考虑传递string&
- 对于不拥有字符串所有权的场景,使用string_view(C++17)
- 频繁拼接字符串时预分配足够空间
- 注意编码问题,明确字符串的编码格式
- 利用现代C++特性(移动语义、字符串字面量等)优化性能
- 在性能关键路径上,考虑避免不必要的string构造和拷贝
- 使用标准算法(如
中的函数)配合string操作 - 对于复杂的字符串处理,考虑正则表达式或专门解析库
string类是C++中最常用的工具之一,深入理解其原理和特性,能够帮助开发者编写出更高效、更安全的字符串处理代码。随着C++标准的演进,string类也在不断获得新的功能和优化,值得持续关注和学习。
