行业知识
C++:String的模拟实现
��
模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程
C++:String类的使用-CSDN博客
String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义) 并且为了不和库的string冲突,我们需要自己搞一个命名空间
namespace cyx{class string{public://迭代器的实现(可读可写)typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//迭代器的实现(可读不可写)typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;//static const size_t npos=-1 vs下int类型支持给static和const同时修饰的变量用缺省值}; const size_t string::npos = -1;//静态成员遍历类外初始化} nops是一个静态变量,要类内定义类外初始化,由于nops是size_t类型,赋值-1会被强转成最大的无符号整数
一、构造+析构+赋值重载(Member functions)
1.1 全缺省构造函数
//构造函数string(const char* str = ""):_size(strlen(str)){_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];// 多开一块1.2 拷贝构造函数的传统写法
的空间strcpy(_str, str);} 1、 “ ”空字符串其实里面默认就有\0,所以缺省值直接给空字符串就行
2、_capacity 一定要给初始空间,不然后面如果涉及到2倍扩容,为0的话就扩不了了
3、要多开一块空间,连这个\0
4、可以复用strcpy函数
string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(_str, s._str);} 传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝
1.3 拷贝构造函数的现代写法和swap函数
//交换字符串void swap(string& s){std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向std::swap(_size, s._size);std::swap(_capacity, s._capacity);} 现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)
string(const string& s):_str(nullptr){string temp(s._str);swap(temp);} 1. 4 迭代器区间构造
传统写法和现代写法参数一样,不能重载,只能保留一个
//迭代器区间构造template string(InputIterator first, InputIterator last):_str(new char[last-first+1]),_size(0),_capacity(last-first){while (first != last){push_back (*first);++first;}} 1.5 赋值重载的传统写法
这里定义的模版InputIterator的意思其实是这边我们可以传不同类型对象的迭代器,我们并不知道这个迭代器里面有多少元素,所以得用指针-指针,即last-first来确定我们的容量,然后再开空间,一个个进行尾插。
//赋值重载(传统写法)string& operator=(const string& s){if (this != &s)//避免自赋值{//先开新空间再毁旧空间,避免新空间开失败导致数据丢失char* temp = new char[s._capacity + 1];strcpy(temp, s._str);delete[]_str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;} 传统的思路就是,先开一块新空间拷贝旧数据,然后再释放掉原空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。
1.6 赋值重载的现代写法
注意:要注意自赋值情况!!否则刚拷贝完自己就被释放了
//赋值重载(现代写法)string& operator=(string s)//必须用值传递,否则会导致原数据的丢失{swap(s);return *this;} 现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。
1.7 析构函数
传统写法和现代写法参数不一样,一个是const引用,一个传值传参,所以可以同时存在。
~string(){delete[]_str;_str = nullptr;_size = _capacity = 0;} 二、容量相关的接口(Capacity)
2.1 size、capacity
//获取当前sizesize_t size() const{return _size;}//获取当前capacitysize_t capacity() const{return _capacity;} 2.2 reserve
//改变capacityvoid reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}} 如果n比_capacity大,思路就是先开新空间进行拷贝,然后再释放旧空间
2.3 resize
//改变sizevoid resize(size_t n, char ch = '2.4 clear和empty
'){if (n > _size){reserve(n);memset(_str + _size, ch, n - _size);}_size = n;_str[_size] = '//清理字符串void clear(){_str[0] = '';}2.5 shrink_to_fit(用得少)
';_size = 0;}//判断字符串是否为空bool empty(){return _capacity == 0;}
如果n比_size大,我们根据n去扩容,然后因为string的底层是字符数组,所以memset就很适合,他就是可以去一个字节一个字节设置成我们想要的。缺省值给‘\0’
void shrink_to_fit(){char* temp = new char[_size + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = _size;} 三、[ ]和比较运算符重载
//[]重载(可读可写)char& operator[](size_t pos){assert(pos bool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}//==bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}//>=bool operator>=(const string& s) const{return *this > s || *this == s;}// s || *this == s;}// _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//尾插一个字符void push_back(char ch){insert(_size, ch);}//尾插一个字符串string& append(const char* str) {return insert(_size, str);}//+=重载 字符string& operator+=(char ch){push_back(ch);return *this;}//+=重载 字符串string& operator+=(const char* str){append(str);return *this;}//指定位置插入一个字符string& insert(size_t pos, char ch){assert(pos _capacity)reserve(2 * _capacity);//pos后的数据要往后挪,所以要从后往前移size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}//指定位置插入一个字符串string& insert(size_t pos, const char* str){assert(pos _capacity)reserve(_size + len);size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}//拷贝插入strncpy(_str + pos, str, len);_size += len;return *this;}//删除指定位置之后的字符string& erase(const size_t pos, size_t len = npos){assert(pos _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出{_str[pos] = '';_size = pos;}//删一部分else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}//寻找指定字符并返回下标size_t find(char ch, size_t pos = 0)const{assert(pos (std::istream& in, string& s){//读取前要先清理掉原来存在的字符s.clear();//用get获取字符char ch = in.get();//先用一个数组存起来,再一起加char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){//原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加//s += ch;buff[i++] = ch;if (i == 127){buff[127] = '';s += buff;i = 0;//重置i}ch = in.get();}//循环结束后可能还要一些字母没有存进去if (i != 0){buff[i] = '';s += buff;}return in;}//遍历方法的展示void Print(const string& s){ //下标遍历for (size_t i = 0; i 缩容到size位置,平时用的很少,我们要尽量减少扩容,思路也是一样的,开辟新空间去拷贝,再释放旧空间