《string的模拟实现》

news/2024/4/20 2:31:44/文章来源:https://blog.csdn.net/m0_58950498/article/details/130351503

本文主要介绍库里面string类的模拟实现

文章目录

  • 前言
  • 一、string的构造函数
      • ①无参的构造函数
      • ②带参的构造函数
      • ③修改构造函数
  • 二、析构函数
  • 三、拷贝构造
  • 四、赋值重载
  • 五、返回size 、capacity和empty
  • 六、[]的运算符重载
  • 七、迭代器
    • ① 正向迭代器。
    • ② 正向const迭代器
  • 八、string比较大小(运算符重载)
  • 九、扩容
    • ① reserve扩容
    • ② resize扩容
  • 十、尾插数据
    • ①push_back
    • ②append
  • 十一、+=的运算符重载(替换尾插数据的函数)
  • 十二、在固定为插入,删除(insert和erase)
    • ①插入一个字符
    • ② 插入一个字符串
    • ③ 删除
  • 十三、交换swap
  • 十四、find
    • ① 找字符
    • ②找子串
  • 十五、流插入,流提取
    • ① 流插入
    • ②流提取
  • 十六、全部源码
    • string.h
    • test.c


前言

首先创建两个文件

string.h :用来定义和声明类里面的成员函数和成员变量,以及测试函数。使用命名空间包裹
test.cpp:用来运行代码


一、string的构造函数

由于我们要模拟实现string,为了不和库里面的string冲突,我们需要使用命名空间将其封装起来。
其实底层其实和我们的顺序表很类似,也是三个成员变量(字符指针)

//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{class string{public:private:char* _str;size_t _size;size_t _capacity;};
};

我们先把准备工作做好,在test.cpp里面写一个主函数,然后包含这个string.h头文件。

注意:我们还要包含#include <iostream> 以及using namespace std,因为我们后面需要在头文件的命名空间里面写测试函数,需要用到cout!!

#include <iostream>
using namespace std;#include "string.h"int main()
{mwq::test1();return 0;
}

①无参的构造函数

现在我们开始写一个无参的构造函数

//无参构造函数
string():_str(nullptr), _size(0),_capacity(0)
{}

其实对于我们的无参构造函数看着没什么问题,其实有点问题。那我举一个例子大家看看什么问题。
我们前面了解c_str(返回C形式的字符串),我们如果要实现这个要求我们返回这个字符串的地址。

//返回C形式的字符串
const char* c_str()const
{return _str;
}

好了现在我们写一个test1测试一下(直接在命名空间里面写就行了)
在test.cpp里

#include <iostream>
using namespace std;#include "string.h"int main()
{mwq::test1(); //test1在string.h文件里面的mwq命名空间下return 0;
}

在string.h里

//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{class string{public://无参构造函数string():_str(nullptr), _size(0),_capacity(0){}//返回C形式的字符串const char* c_str(){return _str;}private:char* _str;size_t _size;size_t _capacity;};void test1(){string s1;	 cout << s1.c_str() << endl;
};

注意:这里使用了cout,所以test.cpp里面必须有#include<iostream>以及using namespace std
因为头文件包含了#include<iostream>以及using namespace std,当我们预处理时候会将头文件展开在链接时,cout就会去他的定义,如果我们没有定义就会报错
所以在test.cpp里面必须写#include<iostream>以及using namespace std(cout是在这个里面定义的)

我们如果想打印我们的字符串,就会发现直接报错,那么出现了什么问题呢?
在这里插入图片描述

大家先思考一下
我们s1进行无参的构造函数,并且在初始化列表中初始化了空指针。我们先通过s1找到了c_str的成员函数,这时候是没有崩溃的,并且将空指针返回也是没有问题的。问题就出在我们通过这个空指针打印,因为cout在打印字符串时候会进行解引用,因为要找\0,这时就涉及到了空指针的解引用。就会出现问题。

②带参的构造函数

现在我们开始写一个带参的构造函数

string(const char* str):_str(str),_size(strlen(str)),_capacity(strlen(str))
{}

在这里插入图片描述

其实他也是有问题的
我们设置成带参数,str的类型是const char*,但是我们成员变量_str是char*类型,我们在初始化列表中传参数的时候就涉及到权限的放大。

那有没有解决办法呢?
我们可以想到一种解决办法就是将我们的成员参数也设置为const。
在这里插入图片描述

这样虽然可以运行,但是,这样的操作无疑是局限的,那么我们后续的操作就都不可以改变我们的_str。(因为你变成了const的类型)

例如:我们再实现是个[]运算符重载去访问这个字符串

char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}

此时去访问s2的数据并试图修改就会报错,这里是因为我们的_str是const,但是返回是char,
在这里插入图片描述
那我们再修改一下返回值,将返回值修改成const char&
在这里插入图片描述
还是不可以的,其实这个报错很容易看懂,因为我们的返回的是const char,这个是不能修改的
所以:我们的这个带参的构造函数是有问题的。

综上所述:我们的有无参数的构造函数的问题如下:

  1. 无参构造函数:不能初始化为nullptr,因为我们打印字符串需要解引用
  2. 带参构造函数:不能用str直接初始化,因为类型不匹配我们的_str要设置为char*类型,因为后续还要修改。

③修改构造函数

我们先改我们带参数的构造函数,我们的直接将我们的常量字符串给他,肯定不可以,那么我们就自己开一块空间,将字符串的内容拷贝过来。这样我们自己开辟的空间就可以自己随意修改,而不是像我们上面将成员变量修改为const。

我们开辟空间,就没有必要在初始化列表开空间,就直接在函数中开就可以。具体的细节我们开代码注释。

string(const char* str):_size(strlen(str))
{//为什们将capacity的初始化也放到函数中?//在初始化列表,初始化的顺序跟定义的顺序有关,//如果我们先定义的capacity,我们size还没定义就是随机值。_capacity = _size;//因为capacity不包括字符的结束标准 \0 //所以在开辟空间的时候,我们多开一位。_str = new char [_capacity + 1];//将我们字符串的内容拷贝过来。strcpy(_str, str);
}

修改无参数的构造函数,我们防止空指针的解引用,我们可以也给他开一个字节的空间,放\0,也就是字符串的结束标志,表示空字符串。

string()//我们初始化为什们要用new[]呢?//为了跟我们带参构造函数保持一致,//在析构的时候用一样的类型。delete[] _str;:_str(new char[1]), _size(0),_capacity(0)
{_str[0] = '\0'; //字符串结束标志,不写的话,直接打印s1.c_str()会崩溃,因为找不到\0
}

我们在类和对象中,我们知道,无参的构造函数可以用缺省参数代替,所以我们将带参数的构造函数用缺省参数,就可以将这两个合二为一。

//下面两种形式都可以,我们""里面就有\0。
//string(const char* str = "\0")
string(const char* str = ""):_size(strlen(str))
{_capacity = _size == 0 ? 3 : _size; //防止一开始_capacity = 0,对后面的代码有影响_str = new char [_capacity + 1];strcpy(_str, str);
}

二、析构函数

这个很简单

~string()
{
//注意的点就是,delete一定要和new保持一致delete[]_str;_str = nullptr;_size = _capacity = 0;
}

三、拷贝构造

编译器会自动生成我们的拷贝构造,但是我们在类和对象的章节学过,它完成的是浅拷贝。
对于我们的字符串,我们_str是一个指针,指向一块数组空间(new出来的),当我们完成浅拷贝的时候,我们拷贝出来的指针只是将_str的四个字节拷贝了过去,所以两块空间用指针指向同一块空间。

当我们进行析构函数的时候,我们同一块空间析构了两次,这显然是由问题的,所以会直接报错,我们不能直接用默认拷贝构造,我们要自己写。

深拷贝:

//拷贝构造
string(const string& s): _size(s._size), _capacity( s._capacity)
{//防止new失败,我们先创建一个临时变量开辟空间//拷贝完后,在给_strchar* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);_str = tmp;
}

四、赋值重载

这个需要考虑的情况比较多,我们看图

这个也是默认成员函数,编译器会自动生成,跟拷贝构造一样进行浅拷贝,和我们上面出现的情况一样,所以我们要进行深拷贝,自己写一个赋值重载。
在这里插入图片描述

我们可以看到,上面有三个问题

  1. 首先就得自己写一个深拷贝,和上面拷贝构造问题一样
  2. 当新空间>原空间时,需要扩容,还需要小心扩容失败了
  3. 当新空间=原空间,直接扩容
  4. 当新空间<原空间,直接拷贝过去会造成资源浪费

基于上面的这么多复杂的原因,我们编译器直接统一处理,直接new一个空间temp,将需要拷贝的数据拷给temp空间,然后释放原空间,再让原空间指向temp即可解决。

注意:这里为了满足连续赋值,我们使用引用返回

//赋值重载
string& operator=(string& s)
{//防止自己和自己赋值if (this != &s){// 防止new失败,我们先创建一个临时变量开辟空间//拷贝完后,在给_strchar* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[]_str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}

五、返回size 、capacity和empty

就是将我们的size直接返回,需要注意的一点就是我们返回size,一般都是不可修改的,不可修改一般就设置成const
这几个我们都不希望他修改,统一用const修饰,这样普通对象可以调用,const对象也可以调用。

//获取字符串元素个数
size_t size()const
{return _size;
}//返回空间容量大小
size_t capacity()const
{return _capacity;
}//判空
bool empty()const
{return _size == 0;
}

六、[]的运算符重载

这个也是比较容易,就是返回字符串的位置,我们可以通过引用返回改变我们字符串的值。
但是当我们想要打印字符串的时候,我们并不想修改字符串,我们就可以用const修饰,让其构成运算符重载。让我们的程序更高效

//[]运算符重载char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//不可修改的[]const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}

七、迭代器

我们暂时将其考虑为指针,就可以。
像用指针一样,用迭代器。我们先写一个正向迭代器。

① 正向迭代器。

//迭代器,//我们先知道怎么用就可以
typedef char* itterator;
itterator begin()
{return _str;
}
itterator end()
{return _str + _size;
}

用迭代器遍历字符串。

//测试迭代器
void test_string4()
{string s1("hello world");string::itterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//范围for的底层就是迭代器for (auto ch : s1){cout << ch << " ";}cout << endl;
}

② 正向const迭代器

//正向const迭代器
typedef const char* const_itterator;
const_itterator begin() const
{return _str;
}
const_itterator end() const
{return _str + _size;
}

八、string比较大小(运算符重载)

注意:这里我们不需要修改字符串,所以使用const修饰

我们比较的不是两个对象的大小,而是两个字符串。使用strcmp即可

int strcmp ( const char * str1, const char * str2 );
str1<str2结果小于0
str1=str2结果等于0
str1>str2结果大于0

		//s1>s2bool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}//s1==s2bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}//s1>=s2bool operator>=(const string& s) const{return strcmp(_str, s._str) >= 0;}//s1<s2bool operator<(const string& s) const{return !(*this >= s);}//s1!=s2bool operator!=(const string& s) const{return !(*this == s);}//s1<=s2bool operator<=(const string& s) const{return !(*this > s);}

九、扩容

扩容我们知道有两种方式:一种是reserve,另外一种是resize

注意:reserve和resize都不支持缩容

① reserve扩容

首先介绍一下reserve

void reserve (size_t n = 0);

函数功能:将空间增加为n个,不会改变有效元素的个数(_size)

  1. 当参数n小于原空间时,啥也不干,空间大小不变
  2. 当参数n大于原空间时,将空间大小增加为n+1(为\0留一个空间)
void reserve(size_t n)
{//reserve 不支持缩容//所以我们自己加个条件if (n > _capacity){//给\0开一个空间char* tmp = new char[n + 1];strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}
}

② resize扩容

void resize (size_t n);
void resize (size_t n, char c);

函数功能:都是将字符串的有效字符改变到n个。不同的是当字符个数增多时:resize(n)用\0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

详细解释:

  1. 当n<size时,void resize (size_t n);将有效字符个数缩减为n个,全部初始化为\0
  2. 当size <= n <= capacity时,将有效字符个数增加为n个
    void resize (size_t n);将n-size个字符初始化为\0
    void resize (size_t n, char c);,将将n-size个字符初始化为字符c
    其实这种情况就是在有效字符结尾后面添加\0或者字符c
  3. n>capacity,将容量扩展至n+1个空间将n-size个字符初始化为\0或者字符c
    void resize (size_t n);将n-size个字符初始化为\0
    void resize (size_t n, char c);,将将n-size个字符初始化为字符c
    我们发现2和3其实差不多,3就先扩容在初始化后面的字符

综上所述:其实我们会发现,void resize (size_t n);,其实有点多余,我们直接使用缺省参数知识将
void resize (size_t n, char c = '\0');即可。

		//不写第二个参数默认改为\0,使用缺省参数就可以将其合二为一//1:n <= size//2:size < n <= capacity//3:n>capacityvoid resize(size_t n, char c = '\0'){if (n < _size){memset(_str, c, n);_size = n;_str[_size] = '\0';}else{//if (n < _capacity)//{//	memset(_str + _size, c, n - _size); //size后面的n个字符//	_size = n;//	_str[_size] = '\0';//}//else //扩容//{//	reserve(n);//	memset(_str + _size, c, n - _size); //size后面的n个字符//	_size = n;//	_str[_size] = '\0';//}//第3种情况和第2种差不多if (n > _capacity){reserve(n);//扩容}memset(_str + _size, c, n - _size); //size后面的n个字符_size = n;_str[_size] = '\0';//将即为填上\0,防止出错}}

注意:我们以后改变字符串的时候,

  1. 都要考结尾有没有\0,防止出错
  2. 记得改变字符串的size

十、尾插数据

增加数据有两种方式,
一种是push_back,尾插一个字符
一种是append,尾插一个字符串

①push_back

		//尾插字符string& push_back(char ch){if (_size + 1 > _capacity){//if (_capacity == 0)//{//_capacity = 3;//}reserve(_capacity * 2); //二倍扩容,}_str[_size] = ch;++_size; //不要忘记改变size了_str[_size] = '\0'; //结尾加上\0return *this;}

注意:我们这里会发现,当_size + 1 > _capacity时,我们才扩容至capacity * 2,但是我们构造函数如果一开始为0的话,这里显然就会出问题了,直接把空间干为0了。

解决办法:

  1. 我们在构造函数里面处理,一开始就给capacity一个空间,防止为0
  2. 我们可以在这个再判断一下,如果capacity为0,那我们就给他一个空间也可以

②append

我们这里处理扩容和push_back不一样,我们插多少,扩多少,因为如果原数组太小,知识扩二倍之后还可能放不下

//增加一个字符串
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){//如果原数组太小,二倍之后还可能放不下reverse(_size + len);}strcpy(_str + _size, str);_size += len;//不要忘记改变size了//最后加上\0_str[_size] = '\0';

十一、+=的运算符重载(替换尾插数据的函数)

这个超级容易,其实就是直接复用我们的push_back和append。
利用运算符重载直接写两个即可

string& operator+=(char ch)
{push_back(ch);return *this;
}
string& operator+=(const char* str)
{append(str);return *this;
}

十二、在固定为插入,删除(insert和erase)

这时我们应该用insert
在我们的插入也分为插入一个字符,和插入一个字符串。

其实思路大致相同,都是先判断是否需要扩容,然后挪数据,插数据

注意:这里挪数据时,很容易出错
在这里插入图片描述
总结:以后挪数据都将结尾定位最后一个位置的下一个位置,然后挪动。

①插入一个字符

		//再posstring& insert(size_t pos, char ch){//1:判断是否需要扩容assert(pos <= _size && pos >= 0);if (_size + 1 > _capacity){reserve(_capacity * 2);}//2:挪数据size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}//3:插数据_str[pos] = ch;++_size;//不要忘记改变size了return *this;}

② 插入一个字符串

string& insert(size_t pos, const char* str){assert(pos <= _size && pos >= 0);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//提前开空间}//挪动数据(包括\0)//循环size-pos+1次size_t end1 = _size;size_t end2 = _size + len;for (size_t i = 0; i <= _size - pos; ++i){_str[end2--] = _str[end1--];}//插入数据strncpy(_str + pos, str, len);_size += len;//不要忘记改变size了return *this;}

我们这里解释一下挪数据的思路:我们只要考虑挪多少次就行了
在这里插入图片描述

插入数据直接使用strncpy将新的字符串从pos位置插入即可
char * strncpy ( char * destination, const char * source, size_t num );

③ 删除

string& erase (size_t pos = 0, size_t len = npos);
函数功能:就是删除从pos位置开始数,长度为len的字符(包括pos)

  1. 当len=npos时(npos是整形的最大值)或者要删除的len大于pos后面字符串的长度,直接将pos以及pos后面的所有字符删除,并且给pos位置赋值为\0
  2. 当len小于pos后面的字符串长度时候1,直接挪数据覆盖即可

注意:别忘了改变size

		//将pos位置的字符,横跨len个长度的串删除(包括pos)string& erase(size_t pos, size_t len = npos){//要删除的个数比pos之后的字符串长if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}

十三、交换swap

将我们两个指针进行交换。
所以比我们库里的交换函数快,少了一次拷贝构造,和两次赋值重载。

//交换
void swap(string& s)
{std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);
}

十四、find

就是在指定位置找。但是查找有两种方式
一种是查找字符
第二种是查找子串

函数原型:
size_t find(char c, size_t pos = 0)const;从pos位置(包括pos位置)开始找字符
size_t find(const char* str, size_t pos = 0)const;从pos位置(包括pos位置)开始找字符串

函数功能:找字符或者字符串,找到返回pos位置,找不到npos

① 找字符

size_t find(char ch, int pos = 0)
{assert(pos <= _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}

②找子串

这里我们使用strstr
函数功能:函数就是在一个字符串中找另外一个字符串是否存在,找到了就返回匹配的字符串的首元素地址,找不到就返回空指针

函数原型:
const char * strstr ( const char * str1, const char * str2 );返回const型指针,对其解引用不能修改
char * strstr ( char * str1, const char * str2 );

//查找一个子串
size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);char * p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{//指针相减就是中间的个数return p - _str;}
}

十五、流插入,流提取

① 流插入

我们的流插入不一定就是友元函数,我们也可以用迭代器,和[]来找到他的数据

//流插入
ostream& operator<<(ostream& out, const string s)
{for (int i = 0; i < s.size(); i++){out << s[i];}return out;
}

②流提取

我们看下面这个流提取,并分析他为什么是错的

istream& operator>>(istream& in, string s)
{s.clear();char ch;in >> ch;while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}return in;
}

cin的缓冲区,输入多个数据,两个数据分割就是空格或者换行,编译器认为我们是要多个字符,会将所以我们的空格换行我们就拿不到,它一直在读数据。所以cin和scanf识别不了' '和'\n'

所以我们可以提取数据需要不区分空格的操作符。
正好C++种get就不会区分,每一个字符我们都可以拿到。

int get();
istream& get (char& c);

istream& operator>>(istream& in, string& s)
{char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;或者//char ch;//in.get(ch);while (ch != ' ' && ch != '\n'){s += ch;in.get(ch);}return in;
}

这个代码对于对象里面没有内容是没问题的,但是当对象里面一开始有数据时候就不可以了。
比如这种:

	void test10(){string s1("0123456789");s1 += '\0';s1 += "xxxxxxxx";cout << s1 << endl;cout << s1.c_str() << endl;cin >> s1;cout << s1 << endl;}

在这里插入图片描述

解释:我们调试会发现是扩容的原因,具体是strcpy导致的!!

我们发现中间出现了一堆乱码,因为在输入123后,cin开始拿数据,首先拿到1,尾插数据,没问题。当我们再拿2尾插的时候,此时我们的size+1 > capacity.我们就需要扩容。
在这里插入图片描述
加上1后。此时里面的数据是这个样子的0123456789\0xxxxxxxx1\0
此时我们的capacity=20,size=20,size+1>capacity,需要二倍扩容

在这里插入图片描述

但是,我们这里的扩容使用的是strcpy,他遇到第一个\0就会停下来。导致拷贝过去数据其实是
0123456789\0,后面的都没有拷贝过去
,但是我们的size没有变,仍然是20,只是中间的数字全部是随机值了。我们添加数字2和数字3仍然是在末尾处添加,最后打印发现中间都是乱码
在这里插入图片描述

解决:我们可以使用s.clear(),将字符串清空,这样就不会出现这种问题了。
void clear();

	//会有频繁扩容的问题istream& operator>>(istream& in, string& s){s.clear();char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;或者//char ch;//in.get(ch);while (ch != ' ' && ch != '\n'){s += ch;in.get(ch);}return in;}

但是这个代码其实还可以再优化一下,因为当我们输入的字符串太长时,会发生频繁扩容的问题。这个我们其实也可以解决的

	//使用一个数组,将数据填到数组里面,填满了再加一次,然后将i置为0,再重投开始填数组istream& operator>>(istream& in, string& s){s.clear();char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;char buffer[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buffer[i++] = ch;if (i == 127){buffer[127] = '\0';s += buffer;i = 0;}ch = in.get();}if (i != 0){buffer[i] = '\0';//防止加了上一次后面遗留的数据s += buffer;i = 0;}return in;}

十六、全部源码

string.h

#pragma once#include <assert.h>//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{class string{public:typedef char* iterator;  //要放在公共区域typedef const char* const_iterator;//迭代器begin和enditerator begin(){return _str;}iterator end(){return _str + _size;}//const迭代器const iterator begin() const{return _str;}const iterator end() const{return _str + _size;}//string()//	:_str(new char[1])//	, _size(0)//	, _capacity(0)//{//	_str[0] = '\0'; //字符串结束标志,不写的话,直接打印s1.c_str()会崩溃,因为找不到\0//} //string(const char* str)//	: _size(strlen(str))//{//	_capacity = _size;//	_str = new char[_capacity + 1];//	strcpy(_str, str);//}//string(const char* str = nullptr)  不可以//string(const char* str = '\0')//string(const char* str = "\0")//构造函数string(const char* str = ""): _size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造函数(深拷贝)string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(_str, s._str);}//赋值运算符重载,所有情况统一处理,全部新开一个空间,然后将字符串拷贝到新空间,再销毁_str,再将新空间赋值给_strstring& operator=(const string& s){if (this != &s){char* temp = new char[s._capacity + 1]; //定义一个新空间,防止扩容失败,多开一个放\0strcpy(temp, s._str);delete[] _str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;}//[]运算符重载char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//不可修改的[]const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//获取字符串元素个数size_t size()const{return _size;}//返回空间容量大小size_t capacity()const{return _capacity;}//判空bool empty()const{return _size == 0;}//clearvoid clear(){_str[0] = '\0';_size = 0;}//返回C形式的字符串const char* c_str() const{return _str;}//swap(s1, s2);//s1.swap(s2);void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//s1>s2bool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}//s1==s2bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}//s1>=s2bool operator>=(const string& s) const{return strcmp(_str, s._str) >= 0;}//s1<s2bool operator<(const string& s) const{return !(*this >= s);}//s1!=s2bool operator!=(const string& s) const{return !(*this == s);}//s1<=s2bool operator<=(const string& s) const{return !(*this > s);}//尾插字符string& push_back(char ch)//void push_back(char ch){if (_size + 1 > _capacity){if (_capacity == 0){_capacity = 3;}reserve(_capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';return *this;//insert(_size, ch);}//尾插字符串string& append(const char* str)//void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;return *this;//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 <= _size && pos >= 0);if (_size + 1 > _capacity){reserve(_capacity * 2);}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 <= _size && pos >= 0);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//提前开空间}//挪动数据(包括\0)//循环size-pos+1次size_t end1 = _size;size_t end2 = _size + len;for (size_t i = 0; i <= _size - pos; ++i){_str[end2--] = _str[end1--];}//插入数据strncpy(_str + pos, str, len);_size += len;return *this;}//将pos位置的字符,横跨len个长度的串删除(包括pos)string& erase(size_t pos, size_t len = npos){//要删除的个数比pos之后的字符串长if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}//reserve和resize//reserve不支持缩容void reserve(size_t n){if (n > _capacity){char* temp = new char[n + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}//不写第二个参数默认改为\0,使用缺省参数就可以将其合二为一//1:n <= size//2:size < n <= capacity//3:n>capacityvoid resize(size_t n, char c = '\0'){if (n < _size){memset(_str, c, n);_size = n;_str[_size] = '\0';}else{//if (n < _capacity)//{//	memset(_str + _size, c, n - _size); //size后面的n个字符//	_size = n;//	_str[_size] = '\0';//}//else //扩容//{//	reserve(n);//	memset(_str + _size, c, n - _size); //size后面的n个字符//	_size = n;//	_str[_size] = '\0';//}if (n > _capacity){reserve(n);}memset(_str + _size, c, n - _size); //size后面的n个字符_size = n;_str[_size] = '\0';}}size_t find(char c, size_t pos = 0)const{assert(pos >= 0 && pos <= _size);for (size_t i = 0; i < _size; ++i){if (_str[i] == c){return i;}}return npos;}size_t find(const char* str, size_t pos = 0)const{assert(pos >= 0 && pos <= _size);char* p = strstr(_str, str);if (p != nullptr){size_t  pos = p - _str;return pos;}return npos;}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos = -1;};void test1(){string s1;string s2("hello yaya");cout << s1.c_str() << endl;cout << s2.c_str() << endl;s2[0]++;cout << s1.c_str() << endl;cout << s2.c_str() << endl;}void test2(){string s1;string s2("hello yaya");string s3(s2);cout << s1.c_str() << endl;cout << s2.c_str() << endl;cout << s3.c_str() << endl;string s4;s4 = s3;cout << s4.c_str() << endl;}void test3(){string s1("hello yaya");//1:使用[]for (size_t i = 0; i < s1.size(); ++i){cout << s1[i] << " ";}cout << endl;//2:迭代器string::iterator it1 = s1.begin();  //iterator是类型,所以需要typedef一下while (it1 != s1.end()){cout << *it1 << " ";++it1;}cout << endl;//不能修改的版本/*string::const_iterator it2 = s1.begin();while (it2 != s1.end()){(*it2)++;cout << *it2 << " ";++it2;}*///3:范围for,不用实现,因为底层就是调用迭代器for (auto ch : s1){cout << ch << " ";}}void test4(){string s1;string s2("hello yaya");cout << (s1 > s2) << endl;  //0cout << (s1 == s2) << endl;//0cout << (s1 >= s2) << endl;//0cout << (s1 < s2) << endl;//1cout << (s1 != s2) << endl;//1cout << (s1 <= s2) << endl;//1}void test5(){string s1("hello yaya");s1.push_back('a');s1.push_back('b');s1.append("haha");s1 += 'm';s1 += "xxxxxx";}void test6(){string s1("hello yaya");/*s1.insert(0, 'x');cout << s1.c_str() << endl;s1.insert(2, 'x');cout << s1.c_str() << endl;s1.insert(10, 'x');cout << s1.c_str() << endl;*/s1.insert(0, "123"); //第0个位置cout << s1.c_str() << endl;s1.insert(2, "qqq"); //第2个位置cout << s1.c_str() << endl;s1.insert(10, "mmm"); //第3个位置 cout << s1.c_str() << endl;s1.erase(12, 20);cout << s1.c_str() << endl;s1.clear();cout << s1.c_str() << endl;s1.insert(0, "123"); //第0个位置cout << s1.c_str() << endl;}void test7(){string s1("hello yaya");s1.resize(5, 'x');cout << s1.c_str() << endl;s1.resize(20, 'a');cout << s1.c_str() << endl;}void test8(){string s1("hello yaya");cout << s1.find('o', 0) << endl;cout << s1.find("yaya", 0) << endl;}//直接在函数外面定义全局函数,使用迭代器和[]也能找到数据,不一定非要用友元ostream& operator<<(ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i] << "";}return out;/*for (auto ch : s){out << ch << "";}return out; */}//不行,因为字符换字符之间是空格或者换行作为截至的,但是这里面从缓存区读取空格或者换行时,将其看成是一个字符了,导致无休止的输入//istream& operator>>(istream& in,string s)//{//	char ch;//	in >> ch; //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据//	while (ch != ' ' && ch != '\n')//	{//		s += ch;//		in >> ch;//	}//	return in;//}//会有频繁扩容的问题istream& operator>>(istream& in, string& s){s.clear();char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;或者//char ch;//in.get(ch);while (ch != ' ' && ch != '\n'){s += ch;in.get(ch);}return in;}使用一个数组,将数据填到数组里面,填满了再加一次,然后将i置为0,再重投开始填数组//istream& operator>>(istream& in, string& s)//{//	s.clear();//	char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;//	char buffer[128];//	size_t i = 0;//	while (ch != ' ' && ch != '\n')//	{//		buffer[i++] = ch;//		if (i == 127)//		{//			buffer[127] = '\0';//			s += buffer;//			i = 0;//		}//		ch = in.get();//	}//	if (i != 0)//	{//		buffer[i] = '\0';//防止加了上一次后面遗留的数据//		s += buffer;//		i = 0;//	}//	return in;//}void test9(){string s1("hello yaya");cout << s1 << endl;;}void test10(){string s1("0123456789");s1 += '\0';s1 += "xxxxxxxx";cout << s1 << endl;cout << s1.c_str() << endl;cin >> s1;cout << s1 << endl;}};

test.c

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;#include "string.h"int main()
{mwq::test10();return 0;
}

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

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

相关文章

接口自动化测试数据处理:技术人员必备的一项技能

目录 前言&#xff1a; 1.定义测试数据结构 2.从文件中加载测试数据 3.使用faker库生成随机测试数据 4.在测试用例中使用测试数据 总结&#xff1a; 前言&#xff1a; 在进行接口自动化测试时&#xff0c;测试数据的准备和处理是至关重要的一环。测试数据的准确性和完整性…

SAP 已根据规则拒绝服务器触发的操作 报错处理

SAP GUI在读取和写入文件的时候&#xff0c;询问是否给与权限&#xff0c;误操作点了否&#xff0c;导致报错如下 解决步骤如下&#xff1a; 点击选项 在下面的界面中依次点击安全配置---》已定制--》打开安全配置 在下面的界面找到你拒绝的条目&#xff0c;双击 将下图下拉框…

python海龟库教学

海龟库&#xff1a; 海龟绘图 “小海龟”turtle是Python语言中一个很流行的绘制图像的函数库&#xff0c;想象一个小乌龟&#xff0c;在一个横轴为x、纵轴为y的坐标系原点&#xff0c;(0,0)位置开始&#xff0c;它根据一组函数指令的控制&#xff0c;在这个平面坐标系中移动&…

Win11打开移动热点后电脑无法上网怎么办?

Win11打开移动热点后电脑无法上网怎么办&#xff1f;有用户将自己的电脑开启移动热点来使用的时候&#xff0c;发现自己的电脑出现了无法上网的情况。那么为什么开启热点之后&#xff0c;就会无法进行上网呢&#xff1f;来看看以下的解决方法分享吧。 Win11打开移动热点无法上网…

【Python】matplotlib画散点图,并根据目标列的类别来设置颜色区间(含源代码及参数解释)

最近在进行绘图时&#xff0c;遇到了matplotlib画散点图&#xff0c;并根据目标列的类别来设置颜色区间的问题&#xff0c;但是实现的过程较为艰辛。 文章目录 一、数据准备二、第一次尝试&#xff08;失败及其原因&#xff09;2.1 失败2.2 原因 三、第二次尝试&#xff08;成功…

算法记录lday3 LinkedList 链表移除 + 链表构建 + 链表反转reverse

今日任务 ● 链表理论基础 ● 203.移除链表元素 ● 707.设计链表 ● 206.反转链表 链表理论基础 建议&#xff1a;了解一下链接基础&#xff0c;以及链表和数组的区别 文章链接&#xff1a;https://programmercarl.com/%E9%93%BE%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A…

JavaWeb搭建| Tomcat配置| Maven依赖|这一篇就够了(超详细)

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;老茶icon &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;计…

记录自己第一次项目管理(附件:WBS计划与会议纪要模板)

记录自己第一次项目管理 前言 20**年新入职到一家公司&#xff0c;刚到就接到紧急任务&#xff0c;因为上一个后端跑路&#xff0c;现在系统上出现接口报错、假接口的问题&#xff0c;客户又着急验收&#xff0c;所以入职之后&#xff0c;一直在着急改代码。最后因为系统没有…

思科模拟器 | 生成树协议STP、RSTP、HSRP配置

一、生成树协议STP 概念介绍&#xff1a; 生成树协议是一种网络协议&#xff0c;用于在交换机之间建立逻辑上的树形拓扑结构避免产生环路。为了完成这个功能&#xff0c;生成树协议需要进行些配置&#xff0c;包括根桥的选举、端口的状态切换等。 步骤明细&#xff1a; 使用思…

游戏测试的面试技巧

游戏测试的面试技巧 1.自我介绍 回答提示&#xff1a;一般人回答这个问题过于平常&#xff0c;只说姓名、年龄、爱好、工作经验 &#xff0c;这些在简历上都有&#xff0c;其实&#xff0c;企业最希望知道的是求职者能否胜任工作&#xff0c;包括&#xff1a;最强的技能、最深入…

实现PXE批量网络装机及kickstrat无人值守安装(富士山终究留不住欲落的樱花)

一、PXE概述和部署PXE批量装机 1.PXE简介 PXE&#xff08;预启动执行环境&#xff0c;在操作系统之前运行&#xff09;是由Intel公司开发的网络引导技术&#xff0c;c/s架构&#xff0c;允许客户机通过网络从远程服务器下载引导镜像&#xff0c;并加载安装文件或者整个操作系统…

燃气管道定位83KHZ地下电子标识器探测仪ED-8000操作指南

1、电子标识器探测工作 燃气管道定位83KHZ地下电子标识器探测仪ED-8000&#xff0c;探测时周边 3 米范围内不能有其他探测仪&#xff0c;保持探测仪垂直向 下&#xff0c;探测仪的末端距离地面 5~10cm 左右&#xff0c;延估计的埋地管线走向水平移动探测仪。当发现持续信号且信…

RuntimeError: “LayerNormKernelImpl“ not implemented for ‘Long‘解决方法

问题出现的场景&#xff1a; 输入&#xff1a; import torch import torch.nn as nn atorch.randint(10,[3,4]) # atorch.DoubleTensor(a) # aa.double() print(a) layer_normnn.LayerNorm(4) layer_norm(a) 我就是想测试一下经过layernorm之后的输出会变成什么样 但是报错…

量表题如何分析?

量表是一种测量工具&#xff0c;量表设计标准有很多&#xff0c;并且每种量表的设计都有各自的特性&#xff0c;不同量表的特性也决定了测量尺度&#xff0c;在数据分析中常用的量表为李克特量表。李克特量表1932年由美国社会心理学家李克特在当时原有总加量表的基础上进行改进…

eBPF的发展演进---从石器时代到成为神(二)

3. 发展溯源 回顾技术的发展过程&#xff0c;就像观看非洲大草原日出日落一样&#xff0c;宏大的过程让人感动&#xff0c;细节部分引人深思。每天循环不辍&#xff0c;却又每天不同。 BPF的应用早已超越了它最初的设计&#xff0c;但如果要追溯BPF最初的来源&#xff0c;则必…

kubernetes为何需要默认的serviceaccount?

文章目录 什么是k8s的serviceAccount&#xff1f;为什么每一个ns下都有默认的sa&#xff1f;default sa yaml 默认的sa下都会挂一个secret&#xff0c;这个secret是从哪里来的&#xff1f;一道关于RBAC的CKA考题1、创建一个新的 ServiceAccount2、创建一个新的 Role3、创建一个…

2023_8.0.33版windows版MySql安装_配置远程连接_修改设置初始密码---MySql工作笔记0001

MySQL :: Download MySQL Community Server https://dev.mysql.com/downloads/mysql/ 首先去下载mysql 可以看到这里下载第一个就可以了,最新版的8.0.33 这里点击仅仅下载 just start my download 然后解压到一个文件夹,然后配置一下环境变量 然后新建一个my.ini文件 然后把…

【GNN】谱域图卷积

谱域图卷积 1. 谱域卷积的背景知识 1.1 谱域图卷积实现思路 f 1 ( t ) ⋆ f 2 ( t ) F − 1 [ F 1 ( w ) F 2 ( w ) ] f_1(t) \star f_2(t) F^{-1}[F_1(w)F_2(w) ] f1​(t)⋆f2​(t)F−1[F1​(w)F2​(w)] 1.2 如何定义图上的傅里叶变换 经典傅里叶变换&#xff1a; x ( …

速卖通正式推出全托管,卖家竞争进入新阶段

全托管来了&#xff0c;卖家就能安心做甩手掌柜吗&#xff1f; 正式推出全托管 显而易见&#xff0c;越来越多的平台正在转向全托管模式。 近日&#xff0c;速卖通在2023年度商家峰会上&#xff0c;正式推出了全托管服务模式。官方表示&#xff0c;托管是对速卖通平台商家服…

golang微服务项目通用流水线

golang微服务项目通用流水线 工作中随着业务越来越大&#xff0c;微服务的项目也越来越多&#xff0c;最开始的时候是一个服务一个流水线&#xff0c;然后还分了三个环境&#xff0c;也就是一个服务三个流水线&#xff0c;后面就越来越不利于管理维护了&#xff0c;因此&#…