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长度的字符

这里我们的删除我们是需要分情况讨论的

  1. 当我们的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;}

这里我给大家提供了一个版本,不过这个版本是一个错误版本,且就算成功该也会带来极大的资源损耗原因在于

  1. _cin在输入数据到缓冲区的时候,我们的空格和\n,并不能被存储在我们的缓冲区,因为这里被认为是我们多个值的间隔,会造成死循环
  2. 这里就算不会造成死循环,每次读取一个值就写入我们的对象中,就会导致我们空间的扩容过于频繁,导致资源损耗。

那么对于以上问题我们各自采用的解决方案是什么呢?

  1. 首先是解决我们空格和我们\n的读取问题,这里就需要我们使用我们的get函数,这个函数就会对其进行读取
  2. 然后扩容过于频繁,我们这里的解决方案就是使用一个数组进行写入,当这个数组被写满之后,直接一次性写入到我们的对象中,接下来请看代码
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;}};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/148806.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[学习笔记]ARXML - Data Format

参考AUTOSAR文档&#xff1a; https://www.autosar.org/fileadmin/standards/R22-11/FO/AUTOSAR_TPS_ARXMLSerializationRules.pdfhttps://www.autosar.org/fileadmin/standards/R22-11/FO/AUTOSAR_TPS_ARXMLSerializationRules.pdf 编码 arxml只允许使用UTF-8编码&#xff…

小谈设计模式(19)—备忘录模式

小谈设计模式&#xff08;19&#xff09;—备忘录模式 专栏介绍专栏地址专栏介绍 备忘录模式主要角色发起人&#xff08;Originator&#xff09;备忘录&#xff08;Memento&#xff09;管理者&#xff08;Caretaker&#xff09; 应用场景结构实现步骤Java程序实现首先&#xff…

VC++创建windows服务程序

目录 1.关于windows标准可执行程序和服务程序 2.服务相关整理 2.1 VC编写服务 2.2 服务注册 2.3 服务卸载 2.4 启动服务 2.5 关闭服务 2.6 sc命令 2.7 查看服务 3.标准程序 3.1 后台方式运行标准程序 3.2 查找进程 3.3 终止进程 以前经常在Linux下编写服务器程序…

小程序中如何开启分销

小程序共享微信生态&#xff0c;商家可以通过小程序来快速扩大自己的销售渠道&#xff0c;其中一个非常受重要的功能就是分销。通过开启分销功能&#xff0c;商家可以让更多的人参与到销售中来&#xff0c;从而提高销售额。那么&#xff0c;在小程序中如何开启设置分销呢&#…

Qt model/view 理解01

在 Qt 中对数据处理主要有两种方式&#xff1a;1&#xff09;直接对包含数据的的数据项 item 进行操作&#xff0c;这种方法简单、易操作&#xff0c;现实方式单一的缺点&#xff0c;特别是对于大数据或在不同位置重复出现的数据必须依次对其进行操作&#xff0c;如果现实方式改…

1802_在Linux系统上开发ARM单机片机嵌入式软件

全部学习汇总&#xff1a; GreyZhang/little_bits_of_linux: My notes on the trip of learning linux. (github.com) 1. 在Linux上也有嵌入式的开发环境&#xff0c;或许还有很多。不过&#xff0c;我现在接触到的大部分还是Windows居多。这一份文件介绍的是一个mbed platform…

OK3568 forlinx系统编译过程及问题汇总

1. 共享文件夹无法加载&#xff1b;通过网上把文件夹加载后&#xff0c;拷贝文件很慢&#xff0c;任务管理器查看发现硬盘读写速率很低。解决办法&#xff1a;重新安装vmware tools。 2. 拷贝Linux源码到虚拟机&#xff0c;解压。 3. 虚拟机基本库安装 forlinxubuntu:~$ sudo…

《C和指针》笔记33:指针数组

除了创建整型数组一样&#xff0c;也可以声明指针数组。 int *api[10];为了弄清这个复杂的声明&#xff0c;我们假定它是一个表达式&#xff0c;并对它进行求值。下标引用的优先级高于间接访问&#xff0c;所以在这个表达式中&#xff0c;首先执行下标引用。因此&#xff0c;a…

连续爆轰发动机

0.什么是爆轰 其反应区前沿为一激波。反应区连同前驱激波称为爆轰波。爆轰波扫过后&#xff0c;反应区介质成为高温高压的爆轰产物。能够发生爆轰的系统可以是气相、液相、固相或气-液、气-固和液-固等混合相组成的系统。通常把液、固相的爆轰系统称为炸药。 19世纪80年代初&a…

Spring Boot中的@Controller使用教程

一 Controller使用方法&#xff0c;如下所示&#xff1a; Controller是SpringBoot里最基本的组件&#xff0c;他的作用是把用户提交来的请求通过对URL的匹配&#xff0c;分配个不同的接收器&#xff0c;再进行处理&#xff0c;然后向用户返回结果。下面通过本文给大家介绍Spr…

Vue中如何进行网页截图与截屏

在Vue中实现网页截图与截屏功能 网页截图与截屏功能在许多Web应用程序中都非常有用。Vue.js作为一个流行的JavaScript框架&#xff0c;提供了许多工具和库来简化网页截图和截屏的实现。本文将介绍如何使用Vue来实现一个网页截图和截屏功能的示例&#xff0c;包括使用html2canv…

联想M7216NWA一体机连接WiFi及手机添加打印机方法

联想M7216NWA一体机连接WiFi方法&#xff1a; 1、首先按打印机操作面板上的“功能键”&#xff1b;【用“”&#xff08;上翻页&#xff09;“-”&#xff08;下翻页&#xff09;来选择菜单的内容】 2、下翻页键找到并选择“网络”&#xff0c;然后“确认键”&#xff1b; 3…

YOLOV7改进实操-添加Wise IoU,实现有效提点

1、打开utils->general.py&#xff0c;找到bbox_iou&#xff08;&#xff09;&#xff0c;345行左右&#xff0c;将下面的与源码进行替换 wiou有三个版本&#xff0c;可以替换&#xff0c;看看哪一个提点多 class WIoU_Scale: monotonous: {None: origin v1True: monotoni…

【Unet系列】

https://tianfeng.space/1947.html 前言概念 图像分割 分割任务就是在原始图像中逐像素的找到你需要的家伙! 语义分割 就是把每个像素都打上标签&#xff08;这个像素点是人&#xff0c;树&#xff0c;背景等&#xff09; &#xff08;语义分割只区分类别&#xff0c;不区…

MySQL:温备份和恢复-mysqldump (4)

介绍 温备&#xff1a;同样是在数据库运行的时候进行备份的&#xff0c;但对当前数据库的操作会产生影响。&#xff08;只可以读操作&#xff0c;不可以写操作&#xff09; 温备份的优点&#xff1a; 1.可在表空间或数据文件级备份&#xff0c;备份时间短。 2.备份时数据库依然…

十四天学会C++之第一天(入门和基本语法)

C的起源和历史 C诞生于20世纪80年代初&#xff0c;它的创造者是计算机科学家Bjarne Stroustrup。当时&#xff0c;Stroustrup在贝尔实验室工作&#xff0c;他希望为C语言添加一些功能&#xff0c;以便更好地支持系统开发。这个愿望促使他创建了C。 C的名字来源于它的基因&…

Mongodb学习

一、初步了解 1.1 Mongodb 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 1.2 数据库是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的 应用程序 1.3 数据库的作用 数据库的主要…

微服务技术栈-初识Docker

文章目录 前言一、Docker概念二、安装Docker三、Docker服务命令四、Docker镜像和容器Docker镜像相关命令Docker容器相关命令 总结 前言 docker技术风靡全球&#xff0c;它的功能就是将linux容器中的应用代码打包,可以轻松的在服务器之间进行迁移。docker运行程序的过程就是去仓…

深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康检测(一)

目录 前言 环境配置 Quartz 什么是Quartz&#xff1f; 应用场景 核心组件 Job JobDetail Trigger CronTrigger SimpleTrigger Scheduler 任务存储 RAM JDBC 导入依赖 定时任务 销量统计 Redis检测 使用 注意事项 前言 在悦享校园1.0中引入了Quartz框架实现…

ARTS 第一期

Algorithm 本周刷的算法是&#xff1a;57. 插入区间 这道算法对我来说很有意义&#xff0c;为什么&#xff1f; 因为这一道算法让我意识到我之前写的算法都是「混」过来的&#xff0c;理解根本不到位&#xff0c;怎么解决&#xff1f;重复的刷之前写过的重要的算法&#xff0…