C++之模拟实现string
文章目录
- 前言
- 一、包含的相关头文件
- 二、构造和析构
-
- 1.构造函数
- 2.拷贝构造
-
- 1.传统写法
- 2.现代写法
- 3.赋值运算符重载
-
- 1.传统写法
- 2.现代写法
- 4.析构函数
- 三、iterator
- 四、modify
- 五、capacity
-
- 1.size
- 2.capacity
- 3.empty
- 4.resize
- 5.reserve
- 六、access
-
- 1.普通对象的接口(可读可写)
- 2.const对象的接口(只读)
- 七、relational operators
-
- 1.运算符<重载
- 2.运算符>重载
- 3.运算符<=重载
- 4.运算符>=重载
- 5.运算符==重载
- 6.运算符!=重载
- 八、String operations
-
- 1.find
-
- 1.找字符
- 2.找子串
- 2.c_str
- 九、Non-member function overloads
-
- 1.流插入
- 2.流提取
- 十、私有属性
- 总结
前言
因为学习了string的相关知识,了解了string大部分接口的底层实现原理,所以我决定自己模拟实现一个mini版的string类,用来加深对string各方面知识的理解。
如果有错误或不足之处,还望各位读者小伙伴们指出。
一、包含的相关头文件
#include<iostream>
#include<assert.h>
#include<cstring>
using std::ostream;
using std::istream;
using std:: cout;
using std:: endl;
二、构造和析构
1.构造函数
//构造string(const char* str = ""){size_t len = strlen(str);_capacity = _size = len;_str = new char[_capacity + 1];strcpy(_str, str);}
2.拷贝构造
1.传统写法
该对象自己一点一点的进行深拷贝
//拷贝构造string(const string& s){_str = new char[s._capacity + 1];_size = s._size;_capacity = s._capacity;strcpy(_str, s._str);}
2.现代写法
找一个中间对象,让这个中间对象用参数的值进行直接构造,再将这个中间对象的内容与自己的内容进行交换。相较于传统写法,现代写法更加简洁。
//拷贝构造string(const string& s):_str(nullptr),//此处要注意将该对象的地址赋值为nullptr,否则析构中间对象时会因为发生野指针的访问而导致程序崩溃。_size(0),_capacity(0){string temp(s);swap(temp);}
此处的swap用的是string自己实现的swap,为什么不用库里的swap呢?因为库里的swap是进行深拷贝,会降低效率。我们自己实现的swap只需要进行浅拷贝,即将它们对应的资源进行交换即可。
void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
3.赋值运算符重载
1.传统写法
//赋值运算符重载string& operator=(const string &s){if (this != &s){char* temp = new char[s._capacity + 1];//用一个中间值记录新开辟的空间,如果开辟空间成功,再将该空间的地址给_str。避免因为开辟空间失败,_str原本的内容也销毁的情况发生。strcpy(temp, s._str);delete[] _str;_str = temp;_capacity = s._capacity;_size = s._size;}return *this;}
2.现代写法
与拷贝构造的现代写法思想类似,可以使程序更加简洁。并且此处不需要专门定义一个中间对象,因为传值传参传过来的形参是实参拷贝构造出来的对象,它就是一个很好的中间对象,我们直接和它进行交换即可。
//赋值运算符重载string& operator=(string s){swap(s);return *this;}
4.析构函数
//析构~string(){delete[] _str;_str = nullptr;//释放指针所指向的空间后要将该指针置为空(避免出现野指针的非法访问)_size = _capacity = 0;}
三、iterator
迭代器是一个使用起来像指针的东西,实际上string的迭代器就是char*类型的指针,因此我们先在最开始进行typedef
typedef char* iterator;
定义两个迭代器:分别指向字符串开头和结尾
iterator begin(){return _str;}iterator end(){return _str + _size;}
四、modify
1.push_back(尾插一个字符)
string只提供了push_back的接口没有提供头插的接口,因为头插需要移动数据,效率很低,所以尽量不要在string中使用头插。
void push_back(char c){if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;//要考虑到如果初始字符串的容量为0的情况reserve(newcapacity);_capacity = newcapacity;}_str[_size++] = c;_str[_size] = '\\0';}
2.append(尾插一个字符串)
void append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}strcpy(end(), str);_size += len;}
3.运算符重载+=
1.尾插字符
尾插一个字符(复用尾插)
string& operator+=(char c){push_back(c);return *this;}
2.尾插字符串
尾插一个字符串(复用append)
string& operator+=(const char* str){append(str);return *this;}
4.clear
清除string中所有元素
void clear(){_size = 0;_str[0] = '\\0';}
5.insert
1.插入一个字符
在字符串某位置插入一个字符:
// 在pos位置上插入字符c/字符串strstring& insert(size_t pos, char c){assert(pos <= _size);if (_size + 1 > _capacity){reserve(_size + 1);}for (size_t i = _size + 1; i > pos; --i)//要注意避免i减为-1变成一个极大的无符号数,导致死循环{_str[i] = _str[i - 1];}_str[pos] = c;++_size;return *this;}
2.插入一个字符串
在字符串某位置插入一个字符串:
string& insert(size_t pos, const char* str){assert(pos <= _size);size_t i = 0;size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}for (i = _size + len; i > pos + len - 1; --i){_str[i] = _str[i - len];}strncpy(_str + pos, str, len);_size += len;return *this;}
6.erase
删除从字符串某位置开始,某长度的子串(如果不说明删除子串的长度,则默认从开始位置到最后的所有内容都删除)
// 删除pos位置上的元素string& erase(size_t pos, size_t len = npos){if (len == npos || pos + len > _size){_str[pos] = '\\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}
五、capacity
1.size
类外成员不能直接访问类的私有属性/数据,因此需要提供一个接口进行访问数据,但是要对数据进行保护,因此用了const传参和传值返回。
访问string中的元素个数。
size_t size()const{return _size;}
2.capacity
访问string的容量。
size_t capacity()const{return _capacity;}
3.empty
判断字符串是否为空。
bool empty()const{if (_size == 0){return true;}return false;}
4.resize
修改字符串的元素个数,可以尾插指定的字符,未指定则默认插入’\\0’。
void resize(size_t n, char c = '\\0')//更改元素个数{if (n <= _size){_size = n;_str[_size] = '\\0';}else{reserve(n);//先扩容,再加元素for (size_t i = _size; i < n; ++i){_str[i] = c;}_str[n] = '\\0';_size = n;}}
5.reserve
修改字符串的容量。
void reserve(size_t n)//更改容量大小{if (n > _capacity){char* temp = new char[n + 1];//先开空间再拷贝值(避免因为空间开辟失败的异常使_str原地址也丢失了)strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}
六、access
访问字符串中的字符,运算符[]重载。
1.普通对象的接口(可读可写)
char& operator[](size_t index)//普通对象的接口(可读可写){assert(index < _size);return _str[index];}
2.const对象的接口(只读)
const char& operator[](size_t index)const//const对象的接口(只读){assert(index < _size);return _str[index];}
七、relational operators
1.运算符<重载
bool operator<(const string& s){size_t len = _size < s._size ? _size : s._size;for (size_t i = 0; i < len; ++i){if ((*this)[i] >= s[i]){return false;}return true;}}
2.运算符>重载
bool operator>(const string& s){size_t len = _size < s._size ? _size : s._size;for (size_t i = 0; i < len; ++i){if ((*this)[i] <= s[i]){return false;}return true;}}
3.运算符<=重载
复用>。
bool operator<=(const string& s){return !(*this > s);}
4.运算符>=重载
复用<。
bool operator>=(const string& s){return !(*this < s);}
5.运算符==重载
复用<和>。
bool operator==(const string& s){return (!((*this) < s)) && (!((*this) > s));}
6.运算符!=重载
复用==。
bool operator!=(const string& s){return !((*this) == s);}
八、String operations
1.find
1.找字符
找一个字符第一次在字符串中出现的下标
// 返回c在string中第一次出现的位置(下标)size_t find(char c, size_t pos = 0) const{assert(pos < _size);for (size_t i = 0; i < _size; ++i){if ((*this)[i] == c){return i;}}return npos;}
2.找子串
找一个子串第一次出现在字符串中的下标
复用C语言对字符串的操作——strstr函数(在一个字符串中找子串)
// 返回子串s在string中第一次出现的位置(下标)size_t find(const char* s, size_t pos = 0) const{assert(pos < _size);const char* ptr = strstr(_str + pos, s);if (ptr == nullptr){return npos;}else{return ptr - _str;}}
也可以一个字符一个字符的遍历寻找:
// 返回子串s在string中第一次出现的位置(下标)size_t find(const char* s, size_t pos = 0) const{assert(pos < _size);size_t i = 0;while (i < _size){int flag = 0;if ((*this)[i] == s[0]){size_t k = i;for (size_t j = 0; j < strlen(s); ++j, ++k){if ((*this)[k] != s[j]){flag = 1;}}if (flag == 0) return i;}i++;}return npos;}
2.c_str
返回字符串的地址
const char* c_str()const{return _str;}
九、Non-member function overloads
1.流插入
ostream& operator<<(ostream& _cout, const string& s){for (size_t i = 0; i < s.size(); i++){_cout << s[i];}return _cout;}
2.流提取
istream& operator>>(istream& _cin, string& s){s.clear();//将原来s中的值清除char buff[128] = { '\\0' };size_t i = 0;char ch = _cin.get();//这里不能使用getc或者scanf函数的原因是他们都是按照空格' '或者换行'\\n'进行分割内容的,所以无法取到空格和换行,因此只能用get()while (ch != ' ' || ch != '\\n'){if (i == 127)//缓存部分满了{s += buff;//将内容存入s,同时将缓存部分情况i = 0;}buff[i++] = ch;ch = _cin.get();}return _cin;}
十、私有属性
private:char* _str;size_t _capacity;size_t _size;const static size_t npos = -1;//只有这个特例可以这样定义,其他static数据成员都要在类内声明,在类外定义。
总结
以上就是今天要讲的内容,本文介绍了作者自己实现的string类的相关类成员函数,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!