C++_string类

目录

一、string的模拟实现

1、初始化字符串

2、拷贝构造

3、赋值重载 

4、迭代器

5、比较字符串

6、尾插字符、字符串

7、resize

8、中间插入数据、删除数据

8.1 插入数据

8.2 删除数据

9、查找数据

10、打印对象(流插入、流提取)

结语:


前言:

        C++中的string类是专门用于处理字符串的,比如对字符串的增删查改、以及对字串进行各种操作,当然,上面说到的这些在c语言中一样可以使用字符数组实现,那为什么还要费尽心思的去专门实现一个类来解决字符串相关的问题呢,原因就是C++中的string类对边界访问更严格,并且用string类操作字符串相对于c语言中操作字符数组更加简便。

一、string的模拟实现

        string类是STL(标准模板库)中的八大容器之一,STL又是C++中标准库的一部分,简单来说就是库里面已经写好了一个string类,程序员只需要调用该string类就能操作字符串(使用库里的string类要包含头文件<string>),调用该类很简单,但是只有通过了解string类的底层实现是如何实现,并且自己模拟实现出一个string,才能更进一步的了解string类。

1、初始化字符串

        首先写一个类,可以实现字符串的创建和打印,初始化字符串代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}size_t size()const//私有域不可直接被外部访问,因此需要用函数返回_size{return _size;}char& operator[](size_t i)//为了让外部能用[]访问字符串的内容{assert(i < _size);return _str[i];}const char* c_str()//返回首元素地址(类似数组名的作用){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("hello world");ZH::string st2;for (size_t i = 0; i < st1.size(); i++){cout << st1[i] << " ";}cout << endl;for (size_t i = 0; i < st2.size(); i++){cout << st2[i] << " ";}cout << endl;cout << st1.c_str() << endl;//用c语言的打印字符串方式,传首地址打印cout << st2.c_str() << endl;return 0;
}

        运行结果:

        string类的成员变量具体含义作用如下图所示:

2、拷贝构造

        我们知道拷贝构造如果不自己实现,那么系统会自动生成一个拷贝构造并且调用,但是系统自动生成的拷贝构造只能完成浅拷贝(即值拷贝)的场景,比如string类的拷贝就不能用浅拷贝完成,具体原因如下:

        因此系统自己生成的浅拷贝不能完成这类场景的拷贝,需要我们手动写一个拷贝构造函数完成深拷贝。

        演示拷贝构造代码如下(将拷贝构造代码放到上文的代码中也同样可以实现,这里省去了与拷贝构造代码无关的代码):

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}const char* c_str()//返回首元素地址(类似数组名的作用){return _str;}string(const string& s)//深拷贝-拷贝构造:_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];//开辟一块独立的空间strcpy(_str, s._str);}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("hello world");ZH::string st2(st1);//将st1的内容拷贝给st2cout << st1.c_str() << endl;//用c语言的打印字符串方式,传首地址打印cout << st2.c_str() << endl;return 0;
}

         运行结果:

3、赋值重载 

        赋值的时候要考虑以下几个问题:

        由于以上几个问题涉及的点太多,而且过程过于复杂或造成不必要的消耗,因此库里面的string类在面对赋值时是重新开辟一块空间然后把s1的数据拷贝到该空间中,并且让s2指向该空间即可。

        模拟实现的赋值重载代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}const char* c_str()//返回首元素地址(类似数组名的作用){return _str;}string& operator=(const string& s)//赋值重载{if (&s != this){char* temp = new char[s._capacity+1];//重新开辟一块空间strcpy(temp, s._str);//把数据拷贝到该空间中delete[] _str;//释放拷贝对象的原先空间内容_str = temp;//让拷贝对象指向该空间_size = s._size;_capacity = s._capacity;}return *this;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("hello world");ZH::string st2("zdzdzzdzd");st2 = st1;cout << st1.c_str() << endl;//用c语言的打印字符串方式,传首地址打印cout << st2.c_str() << endl;return 0;
}

        运行结果:

4、迭代器

        在string类中,他的迭代器可以理解成是由一个指针实现的,不仅可以遍历打印字符串,还能够修改字符串中的内容,因此string类中迭代器的实现很简单,只需要一个指向首元素的指针,和一个指向尾部的指针(注意:尾部表示最后一个元素的下一个位置,因为迭代器区间是一个左闭右开的区间)即可。


        迭代器模拟实现代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public://用typedef来模拟迭代器的两种类型typedef char* iterator;typedef const char* const_iterator;string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}iterator begin()//返回首地址{return _str;}iterator end()//返回末尾地址{return _str + _size;}const_iterator end()const//const版本迭代器{return _str + _size;}const_iterator begin()const//const版本迭代器{return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("hello world");ZH::string st2("hello world");ZH::string::iterator it = st1.begin();while (it!=st1.end()){(*it)++;cout << *it << " ";it++;}cout << endl;ZH::string::const_iterator cit = st2.begin();while (cit != st2.end()){//(*cit)++;//被const修饰的迭代器是不能通过该迭代器修改对象里面的内容cout << *cit << " ";cit++;}cout << endl;return 0;
}

        运行结果:

        总结:迭代器的模拟实现实际上就是begin()和end()两个成员函数的返回指针构成的。 

5、比较字符串

        在字符数组中,只能通过strcmp函数对比两个字符串的大小(按照元素的ASCII码值进行比较),每次对比都需要调用strcmp,写起来也麻烦。然而在string类中可以使用比较运算符进行字符串的对比,比如:‘<','>','<=','>=','==','!=',这些平时用于内置类型的比较运算符如今也可以用在string类(自定义类型)中,原因就是类里面实现了运算符重载。

        当对象支持了运算符重载,则对象与对象之间的对比写起来就更加简单了,模拟实现string运算符重载代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public://用typedef来模拟迭代器的两种类型typedef char* iterator;typedef const char* const_iterator;string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}bool operator>(const string& s)const{return strcmp(this->_str, s._str) > 0;}bool operator<(const string& s)const{return strcmp(this->_str, s._str) < 0;}bool operator==(const string& s)const{return strcmp(this->_str, s._str) == 0;}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 !(*this == s);}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("jello world");ZH::string st2("hello world");cout << (st1 > st2) << endl;//1cout << (st1 < st2) << endl;//0cout << (st1 == st2) << endl;//0cout << (st1 != st2) << endl;//1cout << (st1 <= st2) << endl;//0cout << (st1 >= st2) << endl;//1return 0;
}

        运行结果:

        可以以上代码可以看到,实际上还是复用了strcmp这个函数,只不过对其进行了又一层的封装,这么做的目的就是为了在主函数中可以直接用‘<','>','<=','>=','==','!='进行对象之间的直接运算,总结就是:底层变得复杂,使用变得简单。

6、尾插字符、字符串

        尾插数据时,需要考虑容量是否足够,实现尾插字符串时可以使用函数strcpy进行复用。尾插字符时就如同在数组插入新元素一样。

        扩容函数为reserve,他的作用是单纯的开空间,增大容量,不会对空间内的数据进行修改,即只会改变_capacity的值,不会改变_size的值。(注意扩容的时候也要为’\0'单独开一块空间,但是不会把该空间记录到_capacity中

        尾插代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}void reserve(size_t n)//扩容函数{if (n > _capacity)//只有n大于_capacity才需要扩容{char* temp = new char[n + 1];if (_str != nullptr){strcpy(temp, _str);delete[] _str;}_str = temp;_capacity = n;}}void push_back(char ch)//尾插字符{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void append(const char* str)//尾插字符串{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}const char* c_str(){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1;st1.push_back('h');st1.push_back('e');st1.push_back('l');st1.push_back('l');st1.push_back('o');st1.append(" world");cout << st1.c_str() << endl;return 0;
}

        运行结果:


        如果每次尾插数据时还需要调用成员函数,则就体现不出string类的优势了,因此尾插数据的方式还能再进一步的优化,可以把尾插写成运算符重载,用'+='来实现尾插功能。

        优化尾插功能代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}void reserve(size_t n)//扩容函数{if (n > _capacity)//只有n大于_capacity才需要扩容{char* temp = new char[n + 1];if (_str != nullptr){strcpy(temp, _str);delete[] _str;}_str = temp;_capacity = n;}}void push_back(char ch)//尾插字符{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void append(const char* str)//尾插字符串{size_t len = strlen(str);if (_size+len > _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}strcpy(_str + _size, str);_size += len;}//两个运算符重载构成函数重载string& operator+=(char ch)//尾插字符--运算符重载{push_back(ch);//复用尾插函数return *this;}string& operator+=(const char* str)//尾插字符串--运算符重载{append(str);//复用尾插函数return *this;}const char* c_str(){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1;st1 += 'h';st1 += 'e';st1 += 'l';st1 += 'l';st1 += 'o';st1 += " world";cout << st1.c_str() << endl;return 0;
}

         运行结果:

7、resize

void resize(int n,char ch='\0')
//n表示期望字符串的长度,如果n>_capacity则会扩容
//当n大于_size时,后面n-_size个空间赋予ch值
//n小于_size则表示字符串长度从_size缩小了n,但是空间不会缩容

        resize的作用是控制字符串的长度,可能会改变_size也可能会改变_capacity,注意的是:resize会增大空间但是不会缩小空间,原因就是缩小空间的代价太大,需要重新开辟一块空间然后拷贝数据,因此reszie缩小字符串长度时不会减小该字符串所在的空间。具体代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}void reserve(size_t n)//扩容函数{if (n > _capacity)//只有n大于_capacity才需要扩容{char* temp = new char[n + 1];if (_str != nullptr){strcpy(temp, _str);delete[] _str;}_str = temp;_capacity = n;}}void resize(int n,char ch='\0'){if (n < _size)//当n小于_size则更新_size并且直接将该位置的元素赋\0{_size = n;_str[_size] = '\0';}else//n大于_size时,_size后面的_size-n个值赋ch{if (n > _capacity)//n大于当前空间的容量则需要扩容{reserve(n);}while (_size!=n){_str[_size] = ch;_size++;}_str[_size] = '\0';}}const char* c_str(){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("hello world");ZH::string st2("hello world");st1.resize(20, 'x');st2.resize(5, 'x');cout << st1.c_str() << endl;cout << st2.c_str() << endl;return 0;
}

        运行结果:

        通过调试可以发现resize缩小字符串长度时,并没有对其空间容量进行缩小:

8、中间插入数据、删除数据

8.1 插入数据

        中间插入数据时要注意扩容问题,并且从某个位置插入时,原本该位置的数据要往后挪动,具体示意图如下:

        某个位置插入数据的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}void reserve(size_t n)//扩容函数{if (n > _capacity)//只有n大于_capacity才需要扩容{char* temp = new char[n + 1];if (_str != nullptr){strcpy(temp, _str);delete[] _str;}_str = temp;_capacity = n;}}void Insert(size_t pos, char ch)//插入字符{assert(pos <= _size);//判断位置是否合规if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;//从此处刚好可以把\0拷贝过去while (end > pos){//采用的是指向位置的前一个位置的元素拷贝给指向位置_str[end] = _str[end - 1];end--;}_str[end] = ch;_size++;}void Insert(size_t pos, const char* str)//插入字符串{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end >= pos + len){//采用的是指向位置的前len个位置的元素拷贝给指向位置_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size = _size + len;}const char* c_str(){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小};
}int main()
{ZH::string st1("hello world");st1.Insert(3, 'z');st1.Insert(3, "aaa");cout << st1.c_str() << endl;return 0;
}

         运行结果:

8.2 删除数据

void erase(size_t pos, size_t n = npos)
//删除pos位置数据,n表示从pos位置起要删除n个数据
//npos表示-1,-1的正整数表示四十多亿,其实就是表示从pos位置开始后面数据全部删除

        npos可以用静态成员来定义,因为静态成员属于所有对象,属于整个类,删除的物理逻辑也和插入的物理逻辑相似,即删除一个数据后,后面的数据往前面挪动。

        删除数据的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}void reserve(size_t n)//扩容函数{if (n > _capacity)//只有n大于_capacity才需要扩容{char* temp = new char[n + 1];if (_str != nullptr){strcpy(temp, _str);delete[] _str;}_str = temp;_capacity = n;}}void erase(size_t pos, size_t n = npos)//删除pos位置数据,n表示从pos位置起要删除n个元素{assert(pos <= _size);//判断要删除的位置是否合格if (n == npos || n + pos >= _size)//如果用的是缺省值nops,则表示从该位置起后面全部删除{_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + n);//用拷贝的形式实现删除_size -= n;}}const char* c_str(){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小static size_t npos;//定义一个静态成员变量};
}size_t ZH::string::npos = -1;//-1的正整数表示一个40多亿的数int main()
{ZH::string st1("hello world");ZH::string st2("hello world");st1.erase(3, 7);//从位置3开始删除7个元素st2.erase(3);//从位置3开始后面全部删除cout << st1.c_str() << endl;cout << st2.c_str() << endl;return 0;
}

        运行结果:

9、查找数据

        逻辑就是遍历字符串,找到了就返回该元素的下标,没找到可以返回npos,示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}size_t find(char c, size_t pos = 0)//查找字符{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;//找到了返回该元素的下标}}return npos;//没找到返回npos}size_t find(const char* str, size_t pos = 0)//查找字符串{assert(pos < _size);char* poi = strstr(_str + pos, str);//复用strstr函数if (poi != nullptr)return poi - _str;//找到了通过指针-指针得到整数返回该数值elsereturn npos;//没找到返回npos}const char* c_str(){return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小static size_t npos;//定义一个静态成员变量};
}size_t ZH::string::npos = -1;//-1的正整数表示一个40多亿的数int main()
{ZH::string st1("hello world");cout << st1.find('l') << endl;cout << st1.find("wor") << endl;cout << st1.c_str() << endl;return 0;
}

        运行结果:

10、打印对象(流插入、流提取)

        以上打印字符串用的方法都是传首地址打印方式,可以对流插入、流提取符号:‘<<'和'>>'进行运算符重载,就可以直接使用‘<<'和'>>'打印对象。

        代码如下:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<assert.h>
using namespace std;namespace ZH//把string放在命名空间中,放在与库里的string重名
{class string{public:string(const char* str = "")//构造函数初始化对象:_size(strlen(str)){_capacity = _size;//实际上会给\0开一个空间,但是capacity不记录该空间_str = new char[_capacity + 1];strcpy(_str, str);}void reserve(size_t n)//扩容函数{if (n > _capacity)//只有n大于_capacity才需要扩容{char* temp = new char[n + 1];if (_str != nullptr){strcpy(temp, _str);delete[] _str;}_str = temp;_capacity = n;}}void push_back(char ch)//尾插字符{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void append(const char* str)//尾插字符串{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}//两个运算符重载构成函数重载string& operator+=(char ch)//尾插字符--运算符重载{push_back(ch);//复用尾插函数return *this;}string& operator+=(const char* str)//尾插字符串--运算符重载{append(str);//复用尾插函数return *this;}void clear(){_str[0] = '\0';_size = 0;}const char* c_str()const{return _str;}~string()//析构函数,释放_str申请的空间{delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;//在堆上开辟一块空间用于存放字符串size_t _size;//记录字符串的总字符数size_t _capacity;//记录所开辟空间的大小static size_t npos;//定义一个静态成员变量};ostream& operator<<(ostream& out, const string& str)//流插入{cout << str.c_str()<< endl;return out;}istream& operator>>(istream& in, string& str)//流提取{str.clear();//每次输入数据的时候把之前数据清空char ch;ch = in.get();size_t i = 0;char buff[128];//用buff作为一个缓冲区的概念,减少每次+=字符串的消耗while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[128] = '\0';//因为是一个字符的往buff里面输入,因此buff是没有\0的,要手动添加str += buff;//数组满了就一次性+=到stri = 0;}ch = in.get();}if (i != 0)//把数组剩余的数据+=到str中{buff[i] = '\0';str += buff;}return in;}
}size_t ZH::string::npos = -1;//-1的正整数表示一个40多亿的数int main()
{ZH::string st1("ssssssssssssssssssssssssss");cin >> st1;cout << st1 << endl;return 0;
}

        运行结果:

        这里注意库里面的流提取是每次输入新的数据时,旧的数据会直接清空,因此每次调用流提取函数时会先调用clear()函数清空之前的数据。

结语:

        以上就是关于string类的实现和讲解,只有了解了string底层是如何实现的,才能帮助我们进一步了解和使用string类。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

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

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

相关文章

centos7部署minio单机版

一、目标 在centos7上部署minio单机版 二、centos7部署minio 1、下载minio mkdir /usr/local/minio cd /usr/local/minio wget https://dl.minio.io/server/minio/release/linux-amd64/minio chmod x minio 2、新建minio存储数据的目录 mkdir -p /data/minio/data3、新建…

ASP.NETCore WebAPI 入门 杨中科

ASP.NETCore WebAPI入门1 回顾 mvc开发模式 前端代码和后端代码是混在一个项目之中 WEB API 1、什么是结构化的Http接口。Json。 2、Web API项目的搭建。 3、Web API项目没有Views文件夹。 4、运行项目&#xff0c;解读代码结构。 5、【启用OpenAPI支持】→>swagger,在界…

Spring的IOC解决程序耦合

目录 1.配置项目 1.1配置pom.xml 1.2Spring常用功能的Jar包依赖关系 1.3简单代码 2.IOC 2.1.applicationContext.xml 2.2.测试 3.DI 3.1概述 3.2.构造函数注入 3.3set方法注入 3.4自动注入 3.5注入集合类型的属性 1.配置项目 1.1配置pom.xml <?xml version&…

【算法每日一练]-动态规划(保姆级教程 篇14) #三倍经验 #散步 #异或和 #抽奖概率

目录 今日知识点&#xff1a; 金字塔的正反dp两种方案&#xff0c;转移方程取决于dp的具体含义 取模实现循环走m步回到原点的方案 在统计上升子序列的时候使用最小结尾元素进行标记&#xff0c;一举两得 将亏本的概率转换各种情况的方案&#xff0c;然后统计亏本的情况的方…

数字孪生技术详解

在线工具推荐&#xff1a;3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 数字孪生技术正在迅速彻底改变企业的运营方式。借助数字孪生技术&#xff0c…

【Nginx】在线安装与离线安装

目录 1、下载nginx news 1.2、 安装包 2、 在线安装 第一步&#xff0c;安装pcre 第二步&#xff0c;安装openssl 、zlib 、 gcc 依赖 第三步&#xff0c;安装nginx 第四步&#xff0c;关闭防火墙&#xff0c;访问nginx ​编辑 3、 离线安装 第一步 安装pcre 第二步…

【KingbaseES】实现MySql函数Median

本方法只支持在聚合函数窗口中调用 不支持在GROUP BY中使用&#xff0c;使用plsql写的玩意新能都会稍微差一些 建议使用原生方法修改 CREATE OR REPLACE FUNCTION _final_median(numeric[])RETURNS numeric AS $$SELECT AVG(val)FROM (SELECT valFROM unnest($1) valORDER BY …

vue中动态出来返回的时间秒数,在多少范围显示多少秒,多少范围显示分,小时等等

在Vue中&#xff0c;你可以使用计算属性&#xff08;computed property&#xff09;或过滤器&#xff08;filter&#xff09;来根据动态返回的时间秒数来显示不同的时间单位&#xff0c;比如秒、分、小时等等。 下面是一个使用计算属性的示例&#xff1a; <template>&l…

【Python小游戏】消消乐丨喜羊羊与灰太狼(完整代码)

文章目录 写在前面喜羊羊与灰太狼PyGame入门消消乐注意事项写在后面写在前面 本期内容:基于pygame实现喜羊羊与灰太狼版消消乐小游戏 实验环境 python3.11及以上pycharmpygame安装pygame的命令: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pygame喜羊羊与灰…

毛虫目标检测数据集VOC格式550张

毛虫&#xff0c;一种令人惊叹的生物&#xff0c;以其独特的外貌和习性&#xff0c;成为了自然界中的一道亮丽风景。 毛虫的外观非常特别&#xff0c;身体呈圆柱形&#xff0c;表面覆盖着许多细小的毛发&#xff0c;这使得它们在叶子上伪装得非常好。它们的头部有一对坚硬的颚…

select for update会锁表还是行锁还是其它

select for update含义 select查询语句是不会加锁的&#xff0c;但是 select for update除了有查询的作用外&#xff0c;还会加锁呢&#xff0c;而且它是悲观锁哦。至于加了是行锁还是表锁&#xff0c;这就要看是不是用了索引/主键啦。 没用索引/主键的话就是表锁&#xff0c…

SpringCloud-高级篇(十)

&#xff08;1&#xff09;单节点Redis问题 缓存大家都不陌生&#xff0c;在企业里面只要做缓存都会用到Redis&#xff0c;我们在使用的时候都是做的单节点部署&#xff0c;单节点部署是存在一些问题的&#xff0c;分布式缓存正是Redis的集群&#xff0c;正是为了解决单节点部署…

大数据毕业设计:租房推荐系统 python 租房大数据 爬虫+可视化大屏 计算机毕业设计(附源码+文档)✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

广播及代码实现

广播&#xff08;Broadcast&#xff09;是一种网络通信方式&#xff0c;它允许一台设备向网络中的所有其他设备发送消息。广播通常用于在网络上传递一些信息&#xff0c;让所有设备都能接收并处理。在广播中&#xff0c;通信的目标是整个网络而不是特定的单个设备。 向子网中…

【JAVA】Java 中 Set集合常用方法

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 常用方法 代码示例 结语 我的其他博客 前言 Java中的Set接口提供了一种不允许包含重复元素的集合。常用的实现类有HashS…

把苹果手机上的备忘录转为长图片,分享给别人方法教程

在这个信息爆炸的时代&#xff0c;手机备忘录几乎成了我随身携带的“记忆宝库”。每当我脑海中闪现出一个想法、灵感或是需要记住的重要事项&#xff0c;我都会第一时间打开苹果手机的备忘录&#xff0c;将它们一一记录下来。备忘录的简洁界面和高效操作总能让我在忙碌的生活中…

正负样本分配策略simOTA

simOTA是YOLOX中提出的 正负样本分配策略&#xff08;OTA, SimOTA&#xff0c;TAS&#xff09; OTA源于2021年cvpr的论文&#xff0c;使训练和验证的标签有着更好的对应关系。 yolov5没有用到&#xff0c;只有一种loss&#xff1a; from utils.loss import ComputeLoss comput…

线性代数-第五课,第六课,第七课,第八课

第五课 判断某向量是否可由某向量组线性表示 把向量组组成一个行列式&#xff0c;计算行列式的秩 把所有向量放在一起构成一个行列式&#xff0c;计算行列式的秩 如果两个行列式的秩相等&#xff0c;表示可以线性表示&#xff0c;写答案的格式如下 线性表示&#xff1a;bk…

大模型应用实践:AIGC探索之旅

随着OpenAI推出ChatGPT&#xff0c;AIGC迎来了前所未有的发展机遇。大模型技术已经不仅仅是技术趋势&#xff0c;而是深刻地塑造着我们交流、工作和思考的方式。 本文介绍了笔者理解的大模型和AIGC的密切联系&#xff0c;从历史沿革到实际应用案例&#xff0c;再到面临的技术挑…

二、串行FLASH文件系统FatFs移植

经过上一节的分析&#xff0c;我们对文件系统有一定的理解了&#xff0c;这一节给大家介绍怎么把FatFs文件系统的这些代码移植到STM32S上&#xff0c;然后STM32利用这一些代码或者函数&#xff0c;以文件的格式对FLASH进行读写数据。 实则对diskio.c提供一些函数接口。 首先将…