在C++编程中,字符串处理是最基础也最频繁的操作之一。传统C风格的字符串(char*/char[])存在诸多痛点:
C++的string类完美解决了这些问题。它采用RAII(Resource Acquisition Is Initialization)技术自动管理内存,提供丰富的成员函数支持各种字符串操作,完全兼容STL算法和迭代器。根据2022年C++开发者调查报告,string类在标准库中的使用频率高达89%,是所有容器中最常用的。
实际案例:某金融系统曾因使用strcat导致缓冲区溢出被攻击,改用string后安全性显著提升。string的自动扩容机制能有效防止这类安全问题。
string提供多种构造方式,满足不同场景需求:
cpp复制// 空字符串
string s1;
// C风格字符串初始化
string s2 = "Hello";
string s3("World");
// 重复字符构造
string s4(5, 'A'); // "AAAAA"
// 拷贝构造
string s5 = s2;
// 移动构造(C++11)
string s6 = std::move(s2);
// 子串构造
string s7("ABCDEF", 3); // "ABC"
性能提示:对于已知长度的字符串,使用reserve预分配空间可避免多次扩容:
cpp复制string s;
s.reserve(1000); // 预分配1000字节
string内部维护三个关键属性:
关键容量操作:
| 方法 | 作用 | 时间复杂度 |
|---|---|---|
| size() | 返回当前字符数 | O(1) |
| length() | 同size() | O(1) |
| capacity() | 返回当前分配的内存容量 | O(1) |
| empty() | 检查是否为空字符串 | O(1) |
| clear() | 清空内容但不释放内存 | O(1) |
| reserve(n) | 预分配至少n字节的内存 | O(n) |
| shrink_to_fit() | 释放多余内存(C++11) | O(n) |
扩容策略:大多数实现采用2倍扩容策略,即当size达到capacity时,新capacity = max(2*old_capacity, required_size)
string提供多种访问方式,各有适用场景:
cpp复制string s = "Hello";
// 下标访问(不检查边界)
char c1 = s[1]; // 'e'
// at()方法(边界检查,越界抛出异常)
char c2 = s.at(1); // 'e'
// 迭代器访问
for(auto it = s.begin(); it != s.end(); ++it) {
cout << *it;
}
// 范围for循环(C++11)
for(char c : s) {
cout << c;
}
// 数据指针访问(兼容C API)
const char* p = s.c_str();
printf("%s", p);
安全建议:在性能敏感场景使用[],需要安全性时用at()。
string支持丰富的修改操作:
cpp复制string s = "Hello";
// 追加操作
s.push_back('!'); // "Hello!"
s.append(" World"); // "Hello! World"
s += " C++"; // "Hello! World C++"
// 插入
s.insert(6, "Dear "); // "Hello Dear World C++"
// 删除
s.erase(5, 5); // "Hello World C++"
// 替换
s.replace(6, 5, "C++"); // "Hello C++ C++"
// 查找与子串
size_t pos = s.find("C++"); // 6
string sub = s.substr(6, 3); // "C++"
性能对比:+=操作通常比append和push_back更高效,因为编译器会做特殊优化。
现代string实现普遍采用SSO(Small String Optimization)技术,对小字符串(通常≤15字节)直接存储在对象内部,避免堆分配:
cpp复制string s1 = "short"; // 栈存储
string s2 = "a very long string..."; // 堆存储
验证SSO的方法:
cpp复制cout << sizeof(string); // 通常为24或32字节(包含SSO缓冲区)
部分旧实现采用COW(Copy-On-Write)技术,多个string共享同一内存,直到有修改操作时才真正复制。但在C++11后,由于多线程安全问题,主流实现已弃用COW。
string支持自定义内存分配器,适用于特殊内存管理需求:
cpp复制template<typename T>
class MyAllocator { /*...*/ };
using MyString = std::basic_string<char, std::char_traits<char>, MyAllocator<char>>;
cpp复制// 低效写法
string result;
for(int i=0; i<10000; i++) {
result += to_string(i); // 可能多次扩容
}
// 高效写法
string result;
result.reserve(10000*5); // 预分配足够空间
for(int i=0; i<10000; i++) {
result += to_string(i);
}
cpp复制// 低效
string s3 = s1 + s2; // 产生临时对象
// 高效
string s3;
s3.reserve(s1.size() + s2.size());
s3 = s1;
s3 += s2;
string本质是char的容器,不直接支持Unicode。处理多字节编码时需注意:
cpp复制string utf8 = "你好"; // UTF-8编码
cout << utf8.length(); // 返回字节数(6),而非字符数(2)
建议使用专门的Unicode库如ICU处理复杂字符。
当需要调用C库函数时:
cpp复制string s = "data";
FILE* f = fopen("file.txt", "w");
// 正确做法
fwrite(s.data(), 1, s.size(), f); // 避免依赖\0
// 危险做法
fputs(s.c_str(), f); // 如果字符串内含\0会截断
实现string类需要遵循三/五法则:
cpp复制class String {
public:
// 默认构造函数
String() : size_(0), capacity_(0), data_(nullptr) {}
// 构造函数
String(const char* str) {
size_ = strlen(str);
capacity_ = size_ + 1;
data_ = new char[capacity_];
memcpy(data_, str, capacity_);
}
// 拷贝构造函数
String(const String& other) {
copy_from(other);
}
// 移动构造函数
String(String&& other) noexcept {
move_from(std::move(other));
}
// 析构函数
~String() {
delete[] data_;
}
// 拷贝赋值
String& operator=(const String& other) {
if(this != &other) {
delete[] data_;
copy_from(other);
}
return *this;
}
// 移动赋值
String& operator=(String&& other) noexcept {
if(this != &other) {
delete[] data_;
move_from(std::move(other));
}
return *this;
}
private:
size_t size_;
size_t capacity_;
char* data_;
void copy_from(const String& other) {
size_ = other.size_;
capacity_ = other.capacity_;
data_ = new char[capacity_];
memcpy(data_, other.data_, capacity_);
}
void move_from(String&& other) {
size_ = other.size_;
capacity_ = other.capacity_;
data_ = other.data_;
other.size_ = 0;
other.capacity_ = 0;
other.data_ = nullptr;
}
};
string类是C++标准库中最常用的组件之一,深入理解其原理和使用技巧,能显著提升代码质量和开发效率。建议通过实际项目练习,逐步掌握各种高级用法。