string
的模拟实现系列文章:
文章目录
5. 调整操作5. 1 `push_back()`5. 2 `append()`5. 3 `operator+=()`5. 4 `insert()`5. 5 `erase()`5. 5. 1 `npos`5. 6 `swap()`5. 6. 1 为什么要实现成员函数 `swap` 6. 访问操作6. 1 `operator[]`6. 2 `front()`和`back()`
5. 调整操作
5. 1 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';}
5. 2 append()
append()
有很多重载,但是这里我们只实现插入字符串的和插入多个相同字符的,其他的可以自行尝试。
void string::append(const char* str){ // 要断言 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;}
追加多个相同字符 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 (int i = 0; i < n; i++) { //push_back(c);//这里也可以对push_back()进行复用 _str[_size++] = c; } // 如果是对push_back进行复用,这里就不用再手动加'\0'了 _str[_size] = '\0';}
5. 3 operator+=()
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;}
5. 4 insert()
string& insert(size_t pos, char c);string& insert(size_t pos, const char* str);
在pos
位置(下标)插入字符或者字符串,需要把pos
位置之后的所有元素都向后挪动,因此效率上可能比较差。
在挪动的时候,要特别注意不要发生越界!
挪动时也要注意不要让先挪动的数据盖住还没挪动的数据,应该从后往前依次挪动来避免这一情况。
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; }
5. 5 erase()
string& erase(size_t pos, size_t len = npos);
5. 5. 1 npos
npos
是一个静态成员常量值,对于 size_t
类型的元素具有最大可能的值。
当此值用作字符串成员函数中长度时,表示“直到字符串的末尾”。
作为返回值,它通常用于指示没有匹配项。
此常量定义为值 -1
,因为 size_t 是无符号整型,所以它是此类型的最大可能可表示值。\
其定义为:
const static size_t npos = -1;
那么回到erase
的模拟实现:
删除一段数据,就是把后面的直接拉到前面进行覆盖就行了。
但是要注意,要从pos
位开始被覆盖,如果从最后面开始的话,可能会覆盖还没挪动的数据。
string& string::erase(size_t pos, size_t len){ // 如果会把pos位置之后的所有元素全部删除,就不需要挪动数据,直接在pos位置加'\0'就行了 if (len >= _size - pos - 1) { _str[pos] = '\0'; _size = 0; } 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;}
5. 6 swap()
尽管在算法库中已经有了一个swap()
函数,但是string
类中依然实现了swap
,并且有两个,一个是正常的成员函数,还有一个是std
中swap
函数的重载。
我们先看成员函数这一个:
void swap(string& s)
只需要交换this
和s
的三个成员变量就可以了:
void string::swap(string& s){ // 交换的时候还可以直接使用 std 中的 swap 进行交换 std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity);}
再看对std
中的swap
的重载:
注意为了防止重定义,要把声明和定义分离在.h
和.cpp
文件中,不能在头文件中直接实现定义。
这个函数放在全局,不放入命名空间,这样当全局有匹配的函数时,就不会在命名空间中搜索函数了,更何况库中的还是函数模板。
// string.hvoid swap(test::string& s1, test::string& s2);// string.cppvoid swap(test::string& s1, test::string& s2){ s1.swap(s2); // 调用类的成员函数 swap}
5. 6. 1 为什么要实现成员函数 swap
既然算法库中已经实现了一个swap
,而且如果你尝试的话,会发现这个swap
也是可以成功交换两个string
类型的,但是为什么我们不使用呢?
因为算法库中的swap
是通过模板实现的,一般实现为:
template <class T> void swap (T& a, T& b){ T c(a); a=b; b=c;}
可以看到,算法库中的swap
是通过创建临时变量来进行交换的,对于一个自定义类型,发生拷贝是一件很可能严重影响效率的事,如果这个string
类中存储了非常多的数据,就会大大拖慢程序的运行。
并且实际上string
类型之间的交换完全不需要创建临时变量,只需要交换所有的成员变量就可以了,这样一比较,算法库提供的swap
对string
类的交换会产生无法接受的损耗,所以在库中要想方设法避免程序员使用到原本的模板生成的函数。
6. 访问操作
6. 1 operator[]
下标访问操作符,就是直接返回_str
中对应位置的元素的引用。
但是这里要提供两个重载,因为string
类如果被const
修饰的话,直接返回引用会发生权限放大,导致报错,所以还要提供返回值也被const
修饰的重载。
因为下标访问操作符本身
char& string::operator[](size_t index){ return *(_str + index);}// 注意这里构成重载的原因是最后的那个const,它修饰的是this指针,返回值类型不同不能构成重载const char& string::operator[](size_t index)const{ return *(_str + index);}
6. 2 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];}
下接: 模拟实现下
谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章