string类的模拟实现(万字讲解超详细)
目录
前言
1.命名空间的使用
2.string的成员变量
3.构造函数
4.析构函数
5.拷贝构造
5.1 swap交换函数的实现
6.赋值运算符重载
7.迭代器部分
8.数据容量控制
8.1 size和capacity
8.2 empty
9.数据修改部分
9.1 push_back
9.2 append添加字符串
9.3 +=运算符重载
9.4 clear函数
9.5 insert
9.6 erase
9.7 substr
9.8 []运算符的重载
10.c_str
11.关系运算符
12. find函数
13.<<流插入操作符重载
14.>>流提取
总代码:
前言
这里我们就开始介绍我们string的模拟实现了,我相信在经过之前给大家介绍的标准库string类的使用后,大家对我们的string类都已经有一定的认识,心里对该底层实现也有了一定的猜想,那么现在我们就为大家打消疑虑,给大家揭开我们string神秘的面纱(注意:小编这里只给大家是实现了一部分我们经常使用的函数)。
1.命名空间的使用
首先我们需要使用我们命名空间来避免和我们的库中的string导致冲突
namespace xhj{class string{};};
2.string的成员变量
要知道我们string的成员变量,我们要从两个方面入手,首先是我们的string的存储结构,其次是根据我们string的成员函数。
1.很明显我们的string存储的是一串字符串,那么该底层的存储结构用的就是我们的char类型的变长数组数组,因此我们确定了第一个变量就是我们的char类型的指针
2.第二根据我们的size(),返回我们的有效字符个数,因此我们要使用一个int类型的变量记录我们的有效字符个数。
3.第三就是我们的capacity()了,这里我们就需要使用一个int类型变量,记录我们的容量大小
4.第四点比较难想到,但是小编在之前给大家提到了一个静态变量npos,这个在string常用来表示我们无限大,因此该是确定的一个size_t类型的常变量。
因此我们的成员变量如下:
namespace xhj{class string{private:char* _str;size_t _capacity;size_t _size;const static size_t npos;};const size_t string::npos = -1;
};
3.构造函数
这里我们只需要实现我们比较重要的两个即可,也就是我们的无参构造,和我们的C-string进行的构造,但是这里我们可以使用我们的缺省参数将两个合并为一个,代码如下:
string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//由于多一位需要存储我们的‘/0’,因此要进行+1strcpy(_str, str);}
4.析构函数
对于析构函数我们是需要自己实现的,因为这里都是内置类型,且我们这些内置类型中我们还开辟了新的空间如果我们不自己实现,很大程度会造成内存的泄露。
~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;}
5.拷贝构造
在实现拷贝构造前我们需要确认我们是否需要写我们的拷贝构造,很明显我们这里是非常有必要的,因为这里会涉及到浅拷贝的问题:
因此以下我们需要实现我们的深拷贝。
5.1 swap交换函数的实现
为什么我们在介绍拷贝构造函数之前,要先给大家介绍我们的交换函数呢?这里就涉及了我们拷贝构造函数的两种写法。
实现我们的swap函数是非常简单的,也就是:
void swap(string& s){//这里调用的是我们算法库中的函数std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
那么我们的拷贝构造的两类写法是:
传统写法:
string(const string& s):_str(nullptr),_size(0), _capacity(0){//传统写法_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];memcpy(_str, s._str,s._size+1);//C语言的字符数组,是以\0为终止算长度//string不看\0,而是以size算终止长度}
现代写法:
注意:对于传统写法,我们的现代写法依赖于编译器对数据的初始化,如果编译没有对数据进行初始化操作那么在交换的过程中很可能会出现随机值的情况,然后在最后对tmp进行析构的时候就会出现程序崩溃的情况,所以这里我们需要先走初始化列表
string(const string& s):_str(nullptr),_size(0), _capacity(0){//现代写法string tmp(s._str);swap(tmp);}
对于传统写法我相信大家都能理解,对于现代写法这里小编就需要给大家解释一下了,我们这里先调用构造函数,使用我们s这个对象的_str部分去构造我们的tmp对象,这里我们只需要将我们的tmp和我们的当前对象进行交换,这也就达成了我们的当前对象的所有成员对象都赋予了我们tmp的值,而我们的当前地址空间,只需要交给我们的tmp出局部作用域进行销毁,也就是:
6.赋值运算符重载
对于赋值运算符我们也是需要进行重载的,这里也牵涉到我们的浅拷贝带来的问题,因此我们这里也是需要重新开辟一段空间进行我们数据的存储的,那么这里我们也有我们的两种写法:
注意:1.我们的原本空间可能小于我们的形参的数据空间,因此我们要重新开辟新空间
2.不要使用原空间指针开辟新空间,以免开空间失败破坏原空间
传统版本:
string& operator=(const string& s){if (this != &s){//传统写法char* temp = new char[s._capacity + 1];//避免我们原本指针开空间失败导致旧空间被破坏memcpy(temp, s._str, s._size + 1);delete[]_str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;}
现代版本:
string& operator=(string s)//传值传参调用拷贝构造{swap(s);return *this;}
对于传统版本相信凭借大家的基础一定是随便掌握,这里小编仅给大家介绍一下我们的现代版本,这里我们先让此处直接进行传值传参,调用我们的拷贝构造,那么我们此时的s对象就是我们外部参数的一个拷贝,那么我们直接使用老方法,将我们s产生的新空间给我们的当前对象,我们的旧空间就给我们的s出作用域的时候销毁即可。
7.迭代器部分
对于迭代器部分,首先我们要想到该使用方式:
string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}
通过该定义的方式我们可以看出,我们的迭代器也是一个类型,一个被定义在string类中的类型,而对于该使用方式来看我们的迭代器很类似于我们的指针,而实际上我们的迭代器就是我们的指针,或者是对我们指针进行封装的类,那么该打印结果是:
那么很明显,我们的是从头到尾的一个遍历的过程,而我们的begin()函数返回的就是我们的数组首元素的地址,我们的end()函数返回的就是我们数组末尾的下一个位置的地址。此外我们的迭代器(这里仅仅给大家介绍我们的正向迭代器,对于反向迭代器,小编会在之后的内容给大家介绍)在库中一共分为两个版本:
一个是我们的普通版本,一个是我们的const版本,那么这两者又有着什么不同呢?首先对于我们普通迭代器,我们即允许了读,也允许了写,而我们的const版本只允许读而不允许写,其次就是我们调用对象的不同,我们的iterator是给普通对象调用的,我们的const_iterator就需要我们用我们的const修饰我们的this指针,虽然按照语法来说该既可以被我们的普通对象调用(权限缩小),也可以被我们的const对象调用(权限平移),但是在iterator版本出现时我们的编译器在每次调用中会给我们最匹配的那个,因此也就达到了我们的普通对象调用我们的普通版本,const对象调用我们const版本。
那么该具体实现如下:
typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}const_iterator begin() const{return _str;}iterator end(){return _str + _size;}const_iterator end() const{return _str + _size;}
那么之前给大家介绍了范围for,实际上我们的范围for就是我们以上正向迭代器的使用放式,只不过上层做了一层封装,因此只有有迭代器才可以使用我们的范围for
8.数据容量控制
数据的容量控制,我们就需要实现以下几个函数:
8.1 size和capacity
首先是我们获取我们有效数据和容量大小的函数
size_t size()const{return _size;}size_t capacity()const{return _capacity;}
8.2 empty
其次是我们判断我们的有效数据是否为空
bool empty()const//这里不仅仅要被我们的普通对象调用,也要被我们的const对象调用{return _size == 0;}
最后比较关键的两个就是我们有效数据控制和我们容量控制的函数:
容量控制函数:
void reserve(size_t n){if (n > _capacity)//只有空间大小大于当前才需要进行扩容{char* temp;temp = new char[n + 1];//多的一个用于存储'/0'memcpy(temp, _str,_size+1);delete[]_str;_str = temp;_capacity = n;}}
对于扩容逻辑我想大家并不陌生,这里就是我们开辟一个新的扩容后的空间,再将我们当前的内容拷贝到我们扩容后的空间,最后将我们的当前指针指向新开辟好的空间即可。
有效数据个数控制函数:
void resize(size_t n, char c = '\0'){if (n < _size){_str[n] = '\0';_size = n;}else{reserve(n);for (int i = _size; i < n; i++){_str[i] = c;}_size = n;_str[_size] = '\0';}}
以上我们一共存在三种情况:
n<_size 直接删除数据:只需要我们将有效位置的位置的下一个置为‘/0’,然后改变我们的——_size即可
_size<n<capacity 只需要将剩余的空间初始化:这里只需要从原先的_size开始依次往后填写直到达到我们有效数据个数即可,最后需要改变我们的_size大小,然后最后一位存上我们的'/0'。
n>capacity 扩容+初始化:我们这里的操作只是比我们的情况二多了一个扩容操作,这里小编将情况三和情况二的判断放在了我们的reserve函数中,大家可以体会一下。
9.数据修改部分
9.1 push_back
我们的push_back通常只是在后面添加字符,但是在添加字符的过程中我们需要注意到的就是我们在添加过后是否需要进行增容,代码如下:
void push_back(char c){if (_capacity = _size){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = c;_size++;_str[_size] = '\0';}
这里我们扩容逻辑是,当我们的_capacity和我们的_size相等时就需要进行扩容,也就是说当我们的有效数据和我们的容量大小相等时就需要进行扩容,这里我们的扩容逻辑就是,当我们的容量为0的时候就只开四个空间,其余情况按照旧容量的两倍进行扩容,最后就是将我们的添加的字符放在我们的数组末,然后将我们的_size++,最后记得将有效数据的下一位赋值上我们的'/0'即可。
9.2 append添加字符串
这里小编仅仅给大家实现了我们append添加字符串的那个版本,在实现的过程中我们任然需要注意的是我们的扩容操作,代码如下:
void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity)//判断是否需要进行扩容{reserve(_size + len);//扩容到能够存储我们新增字符串大小}strcpy(_str + _size, str);//从_size位置开始将我们新增字符串复制到该后面_size = _size + len;//更新我们的_size}
9.3 +=运算符重载
我们的+=运算符,是我们string类,常用于添加我们的字符串或者字符的一个操作符,那么该如何同时能添加字符串和我们的操作符的呢?很简单,那就是我们的函数重载。
字符版本:
string& operator+=(char c)//这里我们的*this并没有被销毁,所以可以使用引用返回{push_back(c);return *this;//注意我们的+=需要返回+=后的值}
这里我们发现实际上这里只是对我们push_back的一个复用,那么字符串版本呢?相信聪明的小伙伴已经猜到了,没错,这里就是对我们append的一个复用。
字符串版本:
string& operator+=(const char* str){append(str);return* this;}
9.4 clear函数
clear函数的作用就是清除我们string对象中所有有效元素,这实际上是非常简单的一种操作,只需要更改我们的_size为,以及将我们数组的起始位置填上'/0'即可。
void clear(){_str[0] = '\0';_size = 0;}
9.5 insert
我们的insert,小编这里也给大家实现两个版本,一个是在pos位置前插入我们n个字符c,一个是在我们pos位置前插入字符串。
版本一:
void insert(size_t pos,size_t n, char c){assert(pos < _size);//判断pos位置的合理性if (_size + n > _capacity)//判断是否需要进行扩容操作{reserve(_size + n);}size_t end = _size;//end指向我们的数组末尾while (end >= pos && end != npos)//当我们的end大于我们的pos,且我们end值合理时{_str[end + n] = _str[end];//往后移动数据--end;//pos位置以及该后的得全部要往后移n位}for (size_t i = 0; i < n; i++)//从pos位置写入我们n个c{_str[pos + i] = c;}_size += n;//修改我们的_size的值}
版本二:
void insert(size_t pos, const char* str){assert(pos < _size);//判断pos位置的合法性size_t len = strlen(str);//获取字符串长度,方便后续操作if (_size + len> _capacity)//判断是否需要扩容{reserve(_size + len);}size_t end = _size;while (end >= pos && end != npos)//移动元素{_str[end + len] = _str[end];--end;}for (size_t i = 0; i < len; i++)//写入元素{_str[pos + i] = str[i];}_size += len;}
这里我们的版本二实际上和我们版本一的思路是一样的,只不过该插入字符串时需要判断字符串长度,才能进行元素的移动和元素的写入。
9.6 erase
删除pos位置开始的len长度的字符
这里我们的删除我们是需要分情况讨论的
- 当我们的pos+len>=size或者我们的len=npos,那么说明我们pos位置开始的值是要全部删除的,也就是。
2.诺pos+len<size,那么我们只需要删除我们pos位置的len长度的字符即可,这里我的 思路是将pos+len位置后的值按次序移到我们pos位置以及该后面位置进行覆盖直到我们的/0(_size的位置就是我们/0存储位置)也被移过来之后就完成了我们的删除。
代码如下:
void erase(size_t pos, size_t len=npos){assert(pos < _size);//判断pos位置的合法性if (len == npos || pos + len > _size){//全部删除_str[pos] = '\0';_size = pos;}else{//部分删除size_t end = pos + len;while (end <= _size)//注意我们是<=,因为此处需要将/0也移过来{_str[pos++] = _str[end++];}_size = _size - len;}}
9.7 substr
该函数的作用是截取从pos位置开始的len长度的字符串,注意该函数的返回值是一个我们的string对象,该函数也有两类情况:
情况一:len==npos或者len+pos>size,这里就需要将我们pos后面的值全部截取,但是对于截取我们部分截取和全部截取的逻辑都是一致的,这里我们需要注意的是我们需要修正我们的len值,否则就会造成我们的越界截取。
情况二:pos+len<size,这里我们只需要做到部分截取即可,这里的逻辑是首先构造一个string对象,将其空间开辟好,然后将pos位置极其以后的值全部写入到该对象即可
string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){//修正lenn = _size - pos;}//截取逻辑string tmp;tmp.reserve(n);for (size_t i = pos; i < pos + n; i++){tmp += _str[i];//复用}return tmp;}
9.8 []运算符的重载
为什么要重载我们的[]呢?因为我们的string类是将我们的底层数组封装了,外界并不能直接访问,因此我们要提供我们的[]接口,给大家使用从而间接访问到我们的底层数组,但是需要注意的是我们[]涉及到数据的写入和读取,因此该要提供给我们的普通对象读取和写入的权力,给我们的const对象只提供读取的权力,因此这里也就要实现两个版本:
char& operator[](size_t index){assert(index < _size&&index>=0);return _str[index];}const char& operator[](size_t index)const{assert(index < _size&& index >= 0);return _str[index];}
10.c_str
之前给大家说过,我们这个接口是为了和我们C语言进行配合,因此我们返回的就是我们C语言字符串类型,也就是我们的字符指针
const char* c_str()const{return _str;}
11.关系运算符
bool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size > s._size ? s._size : _size);return ret == 0 ? _size < s._size : ret < 0;}bool operator<=(const string& s) const{return(*this < s || *this == s);}bool operator>(const string& s) const{return !(*this <= s);}bool operator>=(const string& s) const{return !(*this < s);}bool operator==(const string& s) const{return _size == s._size && memcmp(_str, s._str, _size)==0;}bool operator!=(const string& s)const{return !(*this == s);}
对于我们的关系运算符,我们这里使用的是C语言的memcmp函数去比较我们的大小,由于我们的memcmp是按一个字节,一个字节去进行比较的,因此我们是按string中有效数据个数最短的那个对象去进行我们的比较操作,但是最短的字符比较肯会出现以下两类情况:
这里我就给大家简单的介绍一下我们的<运算符以及==运算符的重载逻辑,其他的都是对两者的复用
<预算符重载:
==运算符重载:
12. find函数
对于find函数我们这里给大家实现了两个版本,一个是查找单个字符,一个是查找字符串,对于查找字符串,我们可以使用我们C语言中学习过的strstr函数进行字串的查找
单个字符版本:
size_t find(char c, size_t pos = 0) const{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}
字符串版本:
size_t find(const char* s, size_t pos = 0) const{assert(pos < _size);const char* ptr = strstr(_str + pos, s);if (ptr){return ptr - _str;}else{return npos;}}
对于这里的实现都比较简单,大家只需要注意找不到返回我们的npos即可。
13.<<流插入操作符重载
在给大家介绍友元函数的时候,就给大家介绍过一次我们Date类的流插入运算符的重载,由于我们的流插入的调用参数的原因,我们不得不把我们的该函数写在类外,然后又由于我们要直接去访问我们的私有成员变量,又不得不去构造我们的友元关系。那么实际上我们也可以通过间接的函数去获得我们的内部成员,但是我们C++语言是不常使用的,但是对于我们string的<<操作符我们是否可以调用我们的C-str接口去实现我们这个接口呢?
答案是不行的,原因是我们的C-str返会的是我们C语言的字符串,因此遇到\0,会自动停止打印,但是我们的string类是以我们的size作为结束标志,因此这里是不可取的。
因此我们这里是通过构造友元关系实现的,具体实现代码如下:
ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}
14.>>流提取
在实现流提取我们需要注意一点就是,我们这里的流提取在遇到空格和我们的\n就会停止读取,因此我们要在此处加以我们的判断。
istream& operator>>(istream& _cin, xhj::string& s){char ch;_cin >> ch;while (ch != ' ' && ch != '\n'){s += buff;_cin >> ch;}return _cin;}
这里我给大家提供了一个版本,不过这个版本是一个错误版本,且就算成功该也会带来极大的资源损耗原因在于
- _cin在输入数据到缓冲区的时候,我们的空格和\n,并不能被存储在我们的缓冲区,因为这里被认为是我们多个值的间隔,会造成死循环
- 这里就算不会造成死循环,每次读取一个值就写入我们的对象中,就会导致我们空间的扩容过于频繁,导致资源损耗。
那么对于以上问题我们各自采用的解决方案是什么呢?
- 首先是解决我们空格和我们\n的读取问题,这里就需要我们使用我们的get函数,这个函数就会对其进行读取
- 然后扩容过于频繁,我们这里的解决方案就是使用一个数组进行写入,当这个数组被写满之后,直接一次性写入到我们的对象中,接下来请看代码
istream& operator>>(istream& _cin, xhj::string& s){char ch = _cin.get();s.clear();//为了每次达到输入后覆盖的效果char buff[128];//这里使用数组首先是避免了每次+=造成的多次开辟空间而导致效率上的降低//其次避免了我们预先在堆上开辟大量空间而可能造成空间浪费的情况//清理缓冲区while (ch == ' ' || ch == '\n'){ch = _cin.get();}int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){ buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i != 0){buff[i] = '\0';s += buff;}return _cin;}
此外我们的clear是对该对象中原先的值进行清理,以达到我们后输入的值对其进行覆盖的效果。
总代码:
#include<iostream>
#include<assert.h>
using namespace std;
namespace xhj{class string{friend ostream& operator<<(ostream& _cout, const xhj::string& s);friend istream& operator>>(istream& _cin, xhj::string& s);public:typedef char* iterator;typedef const char* const_iterator;public:string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//由于多一位需要存储我们的‘/0’,因此要进行+1strcpy(_str, str);}//对于传统写法,我们的现代写法依赖于编译器对数据的初始化,如果编译没有对数据进行初始化操作那么在交换的过程中很可能会出现随机值的情况//然后在最后对tmp进行析构的时候就会出现程序崩溃的情况,所以这里我们需要先走初始化列表string(const string& s):_str(nullptr),_size(0), _capacity(0){//传统写法/*_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];memcpy(_str, s._str,s._size+1);*///C语言的字符数组,是以\0为终止算长度//string不看\0,而是以size算终止长度//现代写法string tmp(s._str);swap(tmp);}/*string& operator=(const string& s){if (this != &s){//传统写法char* temp = new char[s._capacity + 1];memcpy(_str, s._str, s._size + 1);delete[]_str;_str = temp;_size = s._size;_capacity = s._capacity//现代写法:拷贝构造一个新的对象,让两者进行交换,可以将新的值搞到我们对应的对象,然后就空间可以让我们的局部对象出了作用域出了析构对象进行销毁string temp(s);std::swap(_str, temp._str);std::swap(_size, temp._size);std::swap(_capacity, temp._capacity);}return *this;}*/string& operator=(string s)//传值传参调用拷贝构造{swap(s);return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}// iteratoriterator begin(){return _str;}const_iterator begin() const{return _str;}iterator end(){return _str + _size;}const_iterator end() const{return _str + _size;}// modifyvoid push_back(char c){if (_capacity = _size){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = c;_size++;_str[_size] = '\0';}string& operator+=(char c){push_back(c);return *this;}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size = _size + len;}string& operator+=(const char* str){append(str);return* this;}void clear(){_str[0] = '\0';_size = 0;}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}const char* c_str()const{return _str;}/// capacitysize_t size()const{return _size;}size_t capacity()const{return _capacity;}bool empty()const{return _size == 0;}void resize(size_t n, char c = '\0'){if (n < _size){_str[n] = '\0';_size = n;}else{reserve(n);for (int i = _size; i < n; i++){_str[i] = c;}_size = n;_str[_size] = '\0';}}void reserve(size_t n){if (n > _capacity){char* temp;temp = new char[n + 1];memcpy(temp, _str,_size+1);delete[]_str;_str = temp;_capacity = n;}}char& operator[](size_t index){assert(index < _size&&index>=0);return _str[index];}const char& operator[](size_t index)const{assert(index < _size&& index >= 0);return _str[index];}///relational operatorsbool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size > s._size ? s._size : _size);return ret == 0 ? _size < s._size : ret < 0;}bool operator<=(const string& s) const{return(*this < s || *this == s);}bool operator>(const string& s) const{return !(*this <= s);}bool operator>=(const string& s) const{return !(*this < s);}bool operator==(const string& s) const{return _size == s._size && memcmp(_str, s._str, _size)==0;}bool operator!=(const string& s)const{return !(*this == s);}// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}// 返回子串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){return ptr - _str;}else{return npos;}}// 在pos位置上插入字符c/字符串str,并返回该字符的位置void insert(size_t pos,size_t n, char c){assert(pos < _size);if (_size + n > _capacity){reserve(_size + n);}size_t end = _size;while (end >= pos && end != npos){_str[end + n] = _str[end];--end;}for (size_t i = 0; i < n; i++){_str[pos + i] = c;}_size += n;}void insert(size_t pos, const char* str){assert(pos < _size);//判断pos位置的合法性size_t len = strlen(str);//获取字符串长度,方便后续操作if (_size + len> _capacity)//判断是否需要扩容{reserve(_size + len);}size_t end = _size;while (end >= pos && end != npos)//移动元素{_str[end + len] = _str[end];--end;}for (size_t i = 0; i < len; i++)//写入元素{_str[pos + i] = str[i];}_size += len;}// 删除pos位置上的元素,并返回该元素的下一个位置void erase(size_t pos, size_t len=npos){assert(pos < _size);//判断pos位置的合法性if (len == npos || pos + len > _size){_str[pos] = '\0';_size = pos;}else{size_t end = pos + len;while (end <= _size){_str[pos++] = _str[end++];}_size = _size - len;}}string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);for (size_t i = pos; i < pos + n; i++){tmp += _str[i];}return tmp;}private:char* _str;size_t _capacity;size_t _size;const static size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& _cin, xhj::string& s){char ch = _cin.get();s.clear();//为了每次达到输入后覆盖的效果char buff[128];//这里使用数组首先是避免了每次+=造成的多次开辟空间而导致效率上的降低//其次避免了我们预先在堆上开辟大量空间而可能造成空间浪费的情况//清理缓冲区while (ch == ' ' || ch == '\n'){ch = _cin.get();}int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){ buff[i] = '\0';s += buff;i = 0;}ch = _cin.get();}if (i != 0){buff[i] = '\0';s += buff;}return _cin;}};
相关文章:
string类的模拟实现(万字讲解超详细)
目录 前言 1.命名空间的使用 2.string的成员变量 3.构造函数 4.析构函数 5.拷贝构造 5.1 swap交换函数的实现 6.赋值运算符重载 7.迭代器部分 8.数据容量控制 8.1 size和capacity 8.2 empty 9.数据修改部分 9.1 push_back 9.2 append添加字符串 9.3 运算符重载…...
C 函数指针
就像指针可以指向一般变量、数组、结构体那样,指针也可以指向函数。 函数指针的主要用途是向其他函数传递“回调”,或者模拟类和对象。 形式如下: int (*POINTER_NAME)(int a, int b) 这类似于指向数组的指针可以表示所指向的数组。指向函数…...
zkVM设计性能分析
1. 引言 本文主要参考: 2023年9月ZKSummit10 Wei Dai 1k(x) & Terry Chung 1k(x)分享视频 ZK10: Analysis of zkVM Designs - Wei Dai & Terry Chung 当前有各种zkVM,其设计思想各有不同,且各有取舍,本文重点对现有各z…...
调用gethostbyname实现域名解析(附源码)
VC常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...&a…...
面向无线传感器网络WSN的增强型MODLEACH设计与仿真(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
前端页面初步开发
<template><div><el-card class"box-card" style"height: 620px"><el-input v-model"query.name" style"width:200px" placeholder"请输入用户姓名"></el-input>   …...
【赠书活动第3期】《构建新型网络形态下的网络空间安全体系》——用“价值”的视角来看安全
目录 一、内容简介二、读者受众三、图书目录四、编辑推荐五、获奖名单 一、内容简介 经过30多年的发展,安全已经深入到信息化的方方面面,形成了一个庞大的产业和复杂的理论、技术和产品体系。 因此,需要站在网络空间的高度看待安全与网络的…...
基于SpringBoot的智能推荐的卫生健康系统
目录 前言 一、技术栈 二、系统功能介绍 用户管理 科室类型管理 医生信息管理 健康论坛管理 我的发布 我的收藏 在线咨询 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在…...
几种开源协议的区别(Apache、MIT、BSD、MPL、GPL、LGPL)
作为一名软件开发人员,你一定也是经常接触到开源软件,但你真的就了解这些开源软件使用的开源许可协议吗? 你不会真的认为,开源就是完全免费吧?那么让我们通过本文来寻找答案。 一、开源许可协议简述 开源许可协议是指开…...
通过usb串口发送接收数据
USB通信使用系统api,USB转串口通信使用第三方库usb-serial-for-android, 串口通信使用Google官方库android-serialport-api。x 引入包后在本地下载的位置:C:\Users\Administrator\.gradle\caches\modules-2\files-2.1 在 Android 中&#x…...
Python3数据科学包系列(三):数据分析实战
Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 Python3数据科学包系列(一):数据分析实战 Python3数据科学包系列(二):数据分析实战 Python3数据科学包系列(三):数据分析实战 国庆中秋宅家自省: Pyth…...
UE4.27.2 自定义 PrimitiveComponent 出现的问题
目录 CreatePrimitiveUniformBufferImmediateFLocalVertexFactory 默认构造函数GetTypeHashENQUEUE_RENDER_COMMANDnull resource entry in uniform buffer parameters FLocalVertexFactory 在看大象无形,其中关于静态物体网络绘制的代码出错的 bug 我也搞了一会………...
【docker】数据卷和数据卷容器
一、如何管理docker容器中的数据? 二、数据卷 1、数据卷原理 将容器内部的配置文件目录,挂载到宿主机指定目录下 数据卷默认会一直存在,即使容器被删除 宿主机和容器是两个不同的名称空间,如果想进行连接需要用ssh,…...
HTML——列表,表格,表单内容的讲解
文章目录 一、列表1.1无序(unorder)列表1.2 有序(order)列表1.3 定义列表 二、表格**2.1 基本的表格标签2.2 演示 三、表单3.1 form元素3.2 input元素3.2.1 单选按钮 3.3 selcet元素 基础部分点击: web基础 一、列表 …...
Mongodb学习
一、初步了解 1.1 Mongodb 是什么 MongoDB 是一个基于分布式文件存储的数据库,官方地址 https://www.mongodb.com/ 1.2 数据库是什么 数据库(DataBase)是按照数据结构来组织、存储和管理数据的 应用程序 1.3 数据库的作用 数据库的主要…...
2024届计算机毕业生福利来啦!Python毕业设计选题分享Django毕设选题大全Flask毕设选题最易过题目
💕💕作者:计算机源码社 💕💕个人简介:本人七年开发经验,擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等,大家有这一块的问题可以一起交流! 💕&…...
网络爬虫指南
一、定义 网络爬虫,是按照一定规则,自动抓取网页信息。爬虫的本质是模拟浏览器打开网页,从网页中获取我们想要的那部分数据。 二、Python为什么适合爬虫 Python相比与其他编程语言,如java,c#,Cÿ…...
9、媒体元素标签
9、媒体元素标签 一、视频元素 video标签 二、音频元素 audio标签 <!--音频和视频 video:视频标签 audio:音频标签 controls:控制选项,可以显示进度条 autoplay:自动播放 -->示例 <!DOCTYPE html> &…...
php单独使用think-rom数据库 | thinkphp手动关闭数据库连接
背景(think-orm2.0.61) 由于需要长时间运行一个php脚本,而运行过程并不是需要一直与数据库交互,但thinkphp主要是为web站点开发的框架,而站点一般都是数据获取完则进程结束,所以thinkphp没提供手动关闭数据…...
337. 打家劫舍 III
题目描述 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。 除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两…...
tio-websocket-spring-boot-starter的最简单实例,看完你一定有所收获
前言 我最近一个月一直在寻找能够快速开发实时通讯的简单好用的模块,所以我就去寻找了一下相关的内容.在此之前我使用的是Spring原生的webSocket,她有个弊端就是设置组不容易设置,而且配置上也稍微复杂一点,需要配置拦截器和处理器,还需要把它放入到Springboot的启动容器里面,也…...
列出连通集
输入样例: 8 6 0 7 0 1 2 0 4 1 2 4 3 5 输出样例: { 0 1 4 2 7 } { 3 5 } { 6 } { 0 1 2 7 4 } { 3 5 } { 6 } solution #include <stdio.h> #include <string.h> int arcs[10][10]; int visited[10] {0}; void DFS(int n, int v); void BFS(int n , int i)…...
前端 富文本编辑器原理——从javascript、html、css开始入门
文章目录 ⭐前言⭐html的contenteditable属性💖 输入的光标位置(浏览器获取selection)⭐使用Selection.toString () 返回指定的文本⭐getRangeAt 获取指定索引范围 💖 修改光标位置💖 设置选取range ⭐总结⭐结束 ⭐前…...
堆--数据流中第K大元素
如果对于堆不是太认识,请点击:堆的初步认识-CSDN博客 数据流与上述堆--数组中第K大元素-CSDN博客的数组区别: 数据流的数据是动态变化的,数组是写死的 堆--数组中第K大元素-CSDN博客题的小顶堆加一个方法: class MinH…...
【算法|动态规划No.12】leetcode152. 乘积最大子数组
个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 🍔本专栏旨在提高自己算法能力的同时,记录一下自己的学习过程,希望…...
Covert Communication 与选择波束(毫米波,大规模MIMO,可重构全息表面)
Covert Communication for Spatially Sparse mmWave Massive MIMO Channels 2023 TOC abstract 隐蔽通信,也称为低检测概率通信,旨在为合法用户提供可靠的通信,并防止任何其他用户检测到合法通信的发生。出于下一代通信系统安全链路的强烈…...
计算机毕业设计 基于协调过滤算法的绿色食品推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...
华为云云耀云服务器L实例评测|部署在线影音媒体系统 Jellyfin
华为云云耀云服务器L实例评测|部署在线影音媒体系统 Jellyfin 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 产品规格1.3 应用场景1.4 支持镜像 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 Jellyfin3.1 Jellyfin 介绍3.2 Docke…...
GhostNet原理解析及pytorch实现
论文:https://arxiv.org/abs/1911.11907 源码:https://github.com/huawei-noah/ghostnet 简要论述GhostNet的核心内容。 Ghost Net 1、Introduction 在训练良好的深度神经网络的特征图中,丰富甚至冗余的信息通常保证了对输入数据的全面理…...
视频二维码的制作方法,支持内容修改编辑
现在学生经常会需要使用音视频二维码,比如外出打开、才艺展示、课文背诵等等。那么如何制作一个可以长期使用的二维码呢?下面来给大家分享一个二维码制作(免费在线二维码生成器-二维码在线制作-音视频二维码在线生成工具-机智熊二维码&#x…...
网站建设外包还是自己做/sem竞价推广托管代运营公司
apply结合concat拉平数组 [].concat.apply([],arr) let arr[[1,2,3],[4,5],[6]];console.log([].concat.apply([],arr));//输出 [1, 2, 3, 4, 5, 6]...
wordpress 主题 失败/深圳网络运营推广公司
学习目标: Mysql学习三、 学习内容: 1、管理Mysql 2、实用的SQL语句 1、管理Mysql 列出所有数据库 SHOW DATABASES; 创建新的数据库 CREATE DATABASE 数据库名; 删除一个数据库 DROP DATABASE 数据库名; 切换到使用的数据库(注…...
个人网站模板html代码/如何免费做网站网页
事件对象 每个事件处理函数都会获得一个事件对象,该对象中包含和此事件相关的方法及属性。 事件对象在事件触发时自动传入。 事件对象的属性有: type:事件类型,如click、mouseover等 which:被按下的按钮或键 data&…...
谷歌关键词排名优化/优化师
一、使用nestx-log4js/core增加日志打印(日志打印更容易分辨,会生成对应的日志文件,默认目录logs) 1.安装nestx-log4js/core $ yarn add nestx-log4js/core2.修改app.module.ts // imports 加入 Log4jsModule import { Log4jsModule } from "nes…...
单页面销售信网站赚钱系统/查询收录
目录 一、平台适用性: 二、使用灵活性: 三、引用单元: 四、使用方式总结: 五、下载:演示源代码 一、平台适用性: RTL(TMessageManager)所有平台都可以,包括&#x…...
邓州微网站开发/seo百家论坛
背景在做单车项目的时候,有一个功能点是展示周围的车辆。其实这个功能在之前的项目中也用到过,只不过一直都是用的mongoDB来实现的,后来在看文档的时候发现了,mysql也支持就想尝试使用下,在使用的过程中,出…...