1. C++ string类深度解析与实战指南
作为C++程序员,string类是我们日常开发中使用频率最高的工具之一。但你是否真正理解它的底层实现?是否能在面试中完美实现一个自定义String类?本文将带你从使用到底层实现,全面掌握string类的精髓。
2. 为什么需要string类
2.1 C风格字符串的痛点
在C语言中,字符串本质是字符数组,以'\0'结尾。这种设计导致了一系列问题:
c复制char str[20] = "hello";
strcat(str, " world"); // 需要确保数组足够大
printf("%d", strlen(str)); // 需要遍历整个数组计算长度
主要问题包括:
- 需要手动管理内存,容易发生缓冲区溢出
- 操作函数与数据分离,不符合面向对象思想
- 每次操作都需要遍历字符串,效率较低
- 无法直接进行字符串拼接、比较等常见操作
2.2 string类的优势
C++的string类解决了上述所有问题:
cpp复制std::string s = "hello";
s += " world"; // 自动处理内存
cout << s.size(); // 直接获取长度
string类的核心优势:
- 自动内存管理
- 丰富的成员函数
- 操作符重载提供直观接口
- 更高的安全性和效率
实际开发中,除非特殊需求,否则都应使用string而非C风格字符串。这也是面试中经常考察string实现的原因。
3. 标准库string类详解
3.1 string类的基本特性
string实际上是basic_string模板类的char特化版本:
cpp复制typedef basic_string<char> string;
关键特性:
- 动态分配内存,自动扩容
- 提供迭代器支持
- 支持各种编码(但按字节操作)
- 接口与STL容器保持一致
3.2 string的构造与内存管理
3.2.1 构造函数
cpp复制string s1; // 空字符串
string s2("hello"); // 从C字符串构造
string s3(5, 'a'); // "aaaaa"
string s4(s2); // 拷贝构造
3.2.2 内存分配策略
不同编译器实现不同:
- VS:小字符串优化(SSO),<=15字符使用栈空间
- g++:写时拷贝(Copy-On-Write)
验证方法:
cpp复制string s("short");
cout << sizeof(s); // VS输出28,g++输出8
3.3 string常用接口解析
3.3.1 容量操作
cpp复制s.size(); // 实际长度
s.capacity(); // 总容量
s.reserve(100); // 预分配空间
s.resize(10); // 调整大小
reserve可以避免频繁扩容,但不会缩小内存。resize会改变内容,多出的部分填充'\0'。
3.3.2 元素访问
cpp复制s[0]; // 不检查越界
s.at(0); // 越界抛出异常
s.front(); // 首字符
s.back(); // 末字符
3.3.3 修改操作
cpp复制s += " append"; // 推荐方式
s.append(" text");
s.push_back('!');
s.insert(0, "prefix ");
s.erase(5, 3); // 从位置5删除3个字符
3.3.4 查找与子串
cpp复制size_t pos = s.find("ll"); // 返回位置或npos
string sub = s.substr(2, 4); // 从2开始取4个字符
3.4 string的性能优化
- 使用reserve预分配空间
- 优先使用+=而非+(避免临时对象)
- 小字符串直接初始化
- 避免频繁的substr(可能引发拷贝)
4. string类的模拟实现
4.1 经典问题:浅拷贝陷阱
默认拷贝构造函数进行浅拷贝,会导致双重释放:
cpp复制class String {
char* _str;
public:
String(const char* str = "") {
_str = new char[strlen(str)+1];
strcpy(_str, str);
}
~String() { delete[] _str; }
};
String s1("hello");
String s2 = s1; // 灾难!
4.2 深拷贝实现
4.2.1 传统实现
cpp复制String(const String& s) {
_str = new char[strlen(s._str)+1];
strcpy(_str, s._str);
}
String& operator=(const String& s) {
if(this != &s) {
char* temp = new char[strlen(s._str)+1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
return *this;
}
4.2.2 现代实现(推荐)
cpp复制String(String&& s) noexcept : _str(s._str) {
s._str = nullptr;
}
String& operator=(String s) {
swap(_str, s._str);
return *this;
}
现代实现的优势:
- 利用拷贝交换惯用法
- 自然支持移动语义
- 代码更简洁安全
4.3 写时拷贝(Copy-On-Write)
高级优化技术,共享内存直到修改:
cpp复制class COWString {
struct Data {
char* _ptr;
int _refcount;
// 其他元数据
};
Data* _data;
void detach() {
if(_data->_refcount > 1) {
Data* newData = /* 深拷贝 */;
--_data->_refcount;
_data = newData;
}
}
public:
char& operator[](size_t pos) {
detach();
return _data->_ptr[pos];
}
};
5. string相关面试题解析
5.1 字符串转整数
cpp复制int atoi(const string& s) {
int res = 0;
for(char c : s) {
if(c < '0' || c > '9') break;
res = res * 10 + (c - '0');
}
return res;
}
5.2 字符串相加
cpp复制string addStrings(string num1, string num2) {
int i = num1.size()-1, j = num2.size()-1;
string res;
int carry = 0;
while(i >= 0 || j >= 0 || carry) {
int n1 = i >= 0 ? num1[i--]-'0' : 0;
int n2 = j >= 0 ? num2[j--]-'0' : 0;
int sum = n1 + n2 + carry;
res.push_back(sum%10 + '0');
carry = sum/10;
}
reverse(res.begin(), res.end());
return res;
}
6. 工程实践建议
- 避免返回string&,除非是成员函数
- 大字符串传参使用const string&
- 慎用c_str()获取的指针,它可能在string修改后失效
- 多线程环境下注意string的非原子操作
- 对于固定字符串,考虑使用string_view(C++17)
string类是C++中最常用的工具之一,深入理解其实现原理不仅能帮助我们在面试中脱颖而出,更能写出高效、安全的代码。现代C++中string的功能仍在不断增强,值得持续关注和学习。