调整操作
push_back()
这个函数负责在字符串末尾追加单个字符。核心逻辑在于先判断容量是否充足,如果不够就扩容,最后记得补上结束符 \0。
void string::push_back(char c) {
// 检查是否需要扩容
if (_size == _capacity) {
// 这里采用 2 倍扩容,或者你也可以 1.5 倍扩容
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
_capacity = newcapacity;
}
// 插入字符和 \0
_str[_size++] = c;
_str[_size] = '\0';
}
append()
append() 有很多重载,我们主要关注追加字符串和追加多个相同字符这两种场景。
追加字符串
需要断言指针不为空,计算长度,判断容量并扩容,最后利用 strcpy 拷贝数据。
void string::append(const char* str) {
assert(str);
size_t len = strlen(str);
// 扩容
if (_capacity < len + _size) {
size_t newcapacity = len + _size > 2 * _capacity ? len + _size : 2 * _capacity;
reserve(newcapacity);
_capacity = newcapacity;
}
// 追加新的数据,strcpy 会拷贝\0,所以不需要手动加
strcpy(_str + _size, str);
_size += len;
}
追加多个相同字符
逻辑类似,只是循环写入字符。如果复用了 push_back,就不需要手动加 \0 了。
void string::append(size_t n, char c) {
// 扩容
if (_capacity < n + _size) {
size_t newcapacity = n + _size > 2 * _capacity ? n + _size : 2 * _capacity;
reserve(newcapacity);
_capacity = newcapacity;
}
// 插入数据
for (size_t i = 0; i < n; i++) {
_str[_size++] = c;
}
// 如果是对 push_back 进行复用,这里就不用再手动加'\0'了
_str[_size] = '\0';
}
operator+=()
这个运算符重载有两个功能:插入字符(相当于 push_back)和插入字符串(相当于 append)。为了保证能连续运算,记得返回 *this。
// 追加字符串,复用 append()
string& string::operator+=(const char* str) {
append(str);
return *this;
}
// 追加字符,复用 push_back()
string& string::operator+=(char c) {
push_back(c);
return *this;
}
insert()
在指定位置插入字符或字符串,需要将后续元素向后移动。这里有个关键点:必须从后往前挪动,否则先挪动的数据会覆盖掉还没处理的数据。
string& string::insert(size_t pos, char c) {
assert(pos <= _size);
// 扩容
if (_capacity < _size + 1) {
size_t newcapacity = _size + 1 > 2 * _capacity ? 1 + _size : 2 * _capacity;
reserve(newcapacity);
_capacity = newcapacity;
}
for (size_t i = _size; i > pos; i--) {
// 不能改成_str[i + 1] = _str[i] 的形式
_str[i] = _str[i - 1];
}
// 记得加'\0'
_str[++_size] = '\0';
_str[pos] = c;
return *this;
}
string& string::insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
// 扩容
if (_capacity < len + _size) {
size_t newcapacity = len + _size > 2 * _capacity ? len + _size : 2 * _capacity;
reserve(newcapacity);
_capacity = newcapacity;
}
// 挪动数据
for (size_t i = _size + len; i >= len; i--) {
_str[i] = _str[i - len];
}
// 插入字符串
for (size_t i = 0; i < len; i++) {
_str[i + pos] = str[i];
}
_size += len;
// 加'\0'
_str[_size] = '\0';
return *this;
}
erase()
删除一段数据,本质上就是把后面的数据拉到前面覆盖。同样要注意移动方向,避免覆盖未处理数据。
npos
npos 是一个静态成员常量,对于 size_t 类型具有最大可能的值。当用作长度参数时,表示'直到字符串末尾'。它定义为 -1,因为 size_t 是无符号整型,所以 -1 就是该类型的最大值。
const static size_t npos = -1;
回到 erase 的实现:
string& string::erase(size_t pos, size_t len = npos) {
// 如果会把 pos 位置之后的所有元素全部删除,就不需要挪动数据,直接在 pos 位置加'\0'就行了
if (len >= _size - pos) {
_str[pos] = '\0';
_size = pos;
} else {
// 挪动数据
// 从 pos + len 位开始向前挪动
for (size_t i = pos + len; i <= _size; i++) {
_str[i - len] = _str[i];
}
_size -= len;
// 加'\0'
_str[_size] = '\0';
}
return *this;
}
swap()
尽管算法库中有通用的 swap,但 string 类依然实现了自己的版本,包括成员函数和全局重载。
成员函数 swap
直接交换三个成员变量即可,效率极高。
void string::swap(string& s) {
// 交换的时候还可以直接使用 std 中的 swap 进行交换
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
全局 swap 重载
为了防止重定义冲突,声明和定义通常分离在 .h 和 .cpp 文件中。放在全局命名空间是为了让 ADL(参数依赖查找)能优先找到它,而不是去搜索模板实例。
// string.h
void swap(test::string& s1, test::string& s2);
// string.cpp
void swap(test::string& s1, test::string& s2) {
s1.swap(s2); // 调用类的成员函数 swap
}
为什么要实现成员函数 swap
既然算法库有 swap,为什么还要自己写?因为通用模板通常是借助临时变量交换的:
template<class T>
void swap(T& a, T& b) {
T c(a);
a = b;
b = c;
}
对于存储大量数据的 string,这种拷贝开销是巨大的。而自定义 swap 只需交换指针和计数,瞬间完成。所以在标准库中,我们会尽量通过重载来避免使用默认的模板版本。
访问操作
operator[]
下标访问操作符直接返回对应位置元素的引用。需要提供两个重载:非 const 版本返回普通引用,const 版本返回 const 引用,防止权限放大。
char& string::operator[](size_t index) {
return *(_str + index);
}
// 注意这里构成重载的原因是最后的那个 const,它修饰的是 this 指针
const char& string::operator[](size_t index) const {
return *(_str + index);
}
front() 和 back()
分别返回首尾元素的引用。出于同样的原因,也要提供 const 版本。
char& string::front() {
return _str[0];
}
const char& string::front() const {
return _str[0];
}
char& string::back() {
return _str[_size - 1];
}
const char& string::back() const {
return _str[_size - 1];
}


