C++ string 的使用与模拟实现
一、标准库中的 string 类
string 是 C++ 中最常用的类之一,但很多人对其底层实现并不熟悉。本文将带你掌握核心用法,并深入模拟实现其关键功能,理解深浅拷贝等概念。
1.1 auto 和范围 for
在深入学习之前,先回顾两个 C++11 的小语法,这对后续遍历容器很有帮助。
auto 关键字 C++11 中,auto 不再仅仅是存储类型指示符,而是作为新的类型指示符,让编译器在编译期推导变量类型。
int main() {
int a = 10;
auto b = a;
auto c = 'a';
return 0;
}
用 auto 声明指针时,auto 和 auto* 没有区别;但声明引用时必须加 &:
int main() {
int x = 10;
auto y = &x; // 指针
auto* z = &x; // 指针
auto& m = x; // 引用
return 0;
}
注意:同一行声明多个变量时,它们必须是相同类型,否则编译器报错。auto 不能直接用来声明数组,也不能作为函数参数(返回值需谨慎)。
范围 for 循环 C++11 引入了基于范围的 for 循环,简化了集合遍历。其底层本质是迭代器的封装。
迭代器简单解释: 迭代器在行为上类似指针,用于统一遍历各种数据结构,是 STL 的基石。
#include <iostream>
#include <string>
using namespace std;
int main() {
int array[] = {1, 2, 3, 4, 5};
// C++98 遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) {
array[i] *= 2;
}
// C++11 遍历
for (auto& e : array) e *= 2;
for (auto e : array) cout << e << " ";
cout << endl;
string str("hello world");
for (char ch : str) {
cout << ch << " ";
}
cout << endl;
return 0;
}
1.2 string 类的常用接口说明
string 类接口众多,这里只讲解最常用的部分。
1. 构造方式
| 函数名称 | 功能说明 |
|---|---|
string() | 构造空字符串 |
string(const char* s) | 用 C-string 构造 |
string(size_t n, char c) | 包含 n 个字符 c |
string(const string& s) | 拷贝构造 |
void test() {
string s1; // 空串
string s2("hello world"); // C 格式字符串
string s3(s2); // 拷贝构造
}
2. 容量操作
| 函数名称 | 功能说明 |
|---|---|
size / length | 返回有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测是否为空 |
clear | 清空有效字符(不释放内存) |
reserve | 预留空间 |
resize | 改变有效字符个数 |
注意:
size()与length()原理相同,建议统一用size()。clear()仅重置_size,不改变底层容量。resize(n)若增加长度可能扩容,减少则不变。reserve(n)只是建议保留空间,小于当前容量时是否缩容不确定。
3. 访问及遍历
| 函数名称 | 功能说明 |
|---|---|
operator[] | 访问指定位置字符 |
begin + end | 获取迭代器(左闭右开) |
rbegin + rend | 反向迭代器 |
| 范围 for | C++11 简洁遍历 |
#include <string>
#include <iostream>
using namespace std;
int main() {
string str = "hello world";
// 1. operator[]
for (size_t i = 0; i < str.size(); ++i) {
cout << str[i] << " ";
}
cout << endl;
// 2. 迭代器
for (auto it = str.begin(); it != str.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 3. 反向迭代器
for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {
cout << *rit << " ";
}
cout << endl;
// 4. 范围 for
for (char ch : str) {
cout << ch << " ";
}
cout << endl;
return 0;
}
4. 修改操作
| 函数名称 | 功能说明 |
|---|---|
push_back | 尾插字符 |
append | 追加字符串 |
operator+= | 追加字符串(常用) |
c_str | 返回 C 格式字符串 |
find / rfind | 查找字符/子串 |
substr | 截取子串 |
关于 npos:
npos表示无效或未找到的位置。由于size_t是无符号类型,-1 会变成最大值,因此用它作为查找失败的标志。
std::string str = "hello";
str.push_back('!'); // hello!
str.append(" world"); // hello! world
str += " C++"; // hello! world C++
const char* cstr = str.c_str();
size_t pos = str.find('o'); // 查找位置
if (pos != std::string::npos) {
std::cout << "Found at: " << pos << std::endl;
}
std::string sub = str.substr(0, 5);
5. 非成员函数
| 函数名称 | 功能说明 |
|---|---|
operator+ | 拼接(慎用,效率低) |
operator>> | 输入流提取 |
operator<< | 输出流插入 |
getline | 读取整行 |
| 关系运算符 | 比较大小 |
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "Hello";
string s2 = "World";
// 拼接(产生临时对象)
string s3 = s1 + " " + s2;
cout << s3 << endl;
// 输入(遇到空格停止)
string input;
cin >> input;
// 输出
cout << s1 << " " << s2 << endl;
// getline(推荐,可含空格)
cin.ignore();
getline(cin, input);
cout << "You entered: " << input << endl;
// 比较
if (s1 == s2) cout << "Equal" << endl;
else if (s1 < s2) cout << "Less" << endl;
return 0;
}
二、string 类的模拟实现
面试中常要求手写 string 类,重点在于构造、析构、拷贝构造及赋值重载。
2.1 头文件设计
我们可以将 string 的底层视为一个动态字符数组,配合 _size 和 _capacity 管理。
class string {
friend std::ostream& operator<<(std::ostream& _out, const sxn::string& s);
friend std::istream& operator>>(std::istream& _in, sxn::string& s);
public:
typedef char* iterator;
string(const char* str = "");
string(const string& s);
string& operator=(string s);
~string();
iterator begin();
iterator end();
void swap(string& s);
void push_back(char c);
string& operator+=(char c);
void append(const char* str);
string& operator+=(const char* str);
void clear();
const char* c_str() const;
size_t size() const;
size_t capacity() const;
bool empty() const;
void resize(size_t n, char c = '\0');
void reserve(size_t n);
char& operator[](size_t index);
const char& operator[](size_t index) const;
bool operator<(const string& s);
bool operator==(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator!=(const string& s);
size_t find(char c, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
string& erase(size_t pos, size_t len);
private:
char* _str = nullptr;
size_t _capacity = 0;
size_t _size = 0;
};
2.2 浅拷贝与深拷贝
编译器生成的默认拷贝构造和赋值是浅拷贝,会导致两个对象指向同一块内存。析构时重复释放会引发崩溃,因此必须实现深拷贝。
2.3 核心代码实现
#include "string.h"
#include <cstring>
#include <cassert>
const size_t string::npos = static_cast<size_t>(-1);
// 构造函数
string::string(const char* str) : _str(new char[strlen(str) + 1]) {
_size = strlen(str);
strcpy(_str, str);
_capacity = _size;
}
// 拷贝构造(现代写法:利用交换法)
string::string(const string& s) {
string tmp(s._str);
swap(tmp);
}
// 析构函数
string::~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 迭代器
string::iterator string::begin() { return _str; }
string::iterator string::end() { return _str + _size; }
// 赋值运算符重载(现代写法:取传值 + 交换)
string& string::operator=(string s) {
swap(s);
return *this;
}
// 尾插
void string::push_back(char c) {
reserve(_size + 1);
_str[_size++] = c;
}
string& string::operator+=(char c) {
push_back(c);
return *this;
}
// 追加
void string::append(const char* str) {
int len = strlen(str);
reserve(_size + len);
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(const char* str) {
append(str);
return *this;
}
void string::clear() {
_size = 0;
_str[0] = '\0';
}
void string::swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* string::c_str() const {
return _str;
}
// 容量相关
size_t string::size() const { return _size; }
size_t string::capacity() const { return _capacity; }
bool string::empty() const { return _size == 0; }
void string::resize(size_t n, char c) {
if (n <= _size) {
_str[n] = '\0';
_size = n;
} else {
reserve(n);
while (_size < n) {
_str[_size++] = c;
}
_str[_size] = '\0';
}
}
void string::reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// 访问
char& string::operator[](size_t index) {
assert(index < _size);
return _str[index];
}
const char& string::operator[](size_t index) const {
assert(index < _size);
return _str[index];
}
// 关系运算
bool string::operator<(const string& s) {
int i = 0, j = 0;
while (i < _size && j < s._size) {
if (_str[i] >= s._str[j]) return false;
++i; ++j;
}
if (i < _size && j == s._size) return false;
return true;
}
bool string::operator==(const string& s) {
int i = 0, j = 0;
while (i < _size && j < s._size) {
if (_str[i] != s._str[j]) return false;
++i; ++j;
}
return i == _size && j == s._size;
}
bool string::operator<=(const string& s) { return *this == s || *this < s; }
bool string::operator>(const string& s) { return !(*this <= s); }
bool string::operator>=(const string& s) { return *this == s || *this > s; }
bool string::operator!=(const string& s) { return !(*this == s); }
// 查找
size_t string::find(char c, size_t pos) const {
if (pos >= _size) return npos;
while (pos < _size) {
if (_str[pos] == c) return pos;
++pos;
}
return npos;
}
size_t string::find(const char* s, size_t pos) const {
char* str = strstr(_str + pos, s);
if (str == nullptr) return npos;
return str - _str;
}
// 插入
string& string::insert(size_t pos, char c) {
assert(pos <= _size);
reserve(_size + 1);
size_t i = _size + 1;
while (i > pos) {
_str[i] = _str[i - 1];
--i;
}
_str[pos] = c;
++_size;
return *this;
}
string& string::insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (len == 0) return *this;
reserve(_size + len);
size_t i = _size + len;
while (i >= pos + len) {
_str[i] = _str[i - len];
--i;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
// 删除
string& string::erase(size_t pos, size_t len) {
assert(pos < _size);
if (pos + len >= _size) {
_str[pos] = '\0';
_size = pos;
} else {
size_t i = 0;
while (pos + i < _size) {
_str[pos + i] = _str[pos + len + i];
++i;
}
_size -= len;
}
return *this;
}
// 输入输出
std::ostream& operator<<(std::ostream& _out, const sxn::string& s) {
int n = 0;
while (n < s._size) {
_out << s._str[n++];
}
return _out;
}
std::istream& operator>>(std::istream& _in, sxn::string& s) {
s.clear();
const int N = 5;
char buffer[N];
int ch = _in.get();
int i = 0;
while (ch != '\n') {
if (i < N) {
buffer[i++] = ch;
} else {
s.reserve(s._size + N);
strncpy(s._str + s._size, buffer, N);
s._size += N;
s._str[s._size] = '\0';
i = 0;
}
ch = _in.get();
}
if (i > 0) {
s.reserve(s._size + i + 1);
strncpy(s._str + s._size, buffer, i + 1);
s._size += i + 1;
s._str[s._size] = '\0';
}
return _in;
}
通过上述实现,我们不仅掌握了 string 的基本用法,更理解了 C++ 中资源管理的核心机制。真正掌握一个类,不仅要会用,更要知其所以然。

