当前位置: 首页 > news >正文

string的自主实现

string的自主实现

  • 需要注意的地方
      • 1.string 的构造
      • 2.string的拷贝和赋值重载
        • 2.1拷贝
        • 2.2赋值重载
      • 3. push_back
      • 4.迭代器的访问,和for范围
      • 5.末尾插入字符串append
      • 6. 中间插入字符和字符串
      • 7.earse删除数据
      • 8.substr裁剪字符串
      • 9. find,查找字符串
      • 10.流插入<<和流提取>>
      • 11.运算符重载==,>,>=
      • 12.swap

需要注意的地方

1.string 的构造

对于无参的构造,初始化_str,我们并不能初始化为空,因为输出,末尾没有\0,会输出错误,cout会解引用,翻译成字符,但遇到的是nullptr,会出现崩溃。

string::string():_str(new char[1]{ '\0' })//因为c_str是返回\0结尾的字符串//_str(nullptr)如果这样写,cout会崩溃一直没有\0, _size(0), _capacity(0){}string::string(const char* str) {//常量字符串遇到第一个\0就中止,所以可以用strcpy_size = strlen(str);//不能用sizeof因为那个计算的是指针的大小_str = new char[_size + 1];//要多分配一个空间,因为strlen没有算\0,如果不加1,后续可能出现问题_capacity = _size;strcpy(_str, str);}

这就是构造的方式

2.string的拷贝和赋值重载

2.1拷贝

对于string s1(s2),我们不能单纯的使用编译器生成的拷贝函数,因为这样只是简单的浅拷贝,两个指向同一块空间,因为结束时,会自动调用析构,对同一个空间进行两次析构,会出现错误,所以要进行深拷贝

string::string(const string& s) {if (_str!=s._str) {_str = new char[s._capacity + 1];//开同样大的空间再加1,因为capacity不含\0memcpy(_str, s._str, s._size + 1);_capacity = s._capacity;_size = s._size;}}
2.2赋值重载

跟拷贝差不多

string& string:: operator=(const string& s) {char* temp = new char[s._capacity+1];delete[]_str;memcpy(temp, s._str, s._size + 1);this->_str = temp;_size = s._size;_capacity = s._capacity;return *this;}

*** 注意我们要用memcpy,因为strcpy能拷贝\0,遇到\0就停止拷贝了,如果中间有\0,不能拷贝完***

3. push_back

这个我们只需要注意扩容,注意reserve就可以实现扩容,我需要多大的空间,我们先利用reserve阔好,插入后末尾记得加上\0,不然字符串没有结束的标识,末尾会出现汉字

void string::push_back(char c){if (_size == _capacity) {reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = c;//size要更新_str[_size] = '\0';//插入以后要加上\0,不然会崩溃}
void string::reserve(size_t n) {if (n > _capacity) {char* temp = new char[n + 1];memcpy(temp, _str,_size+1);delete[]_str;_str = temp;_capacity = n;//这个不能忘记,不然会出现错误}}

4.迭代器的访问,和for范围

因为迭代器的返回是iterator,我们typedef char* iterator,为什么说迭代器的返回类似于指针,对于有些常量只能调用const函数,所以迭代器还有const函数

iterator begin() {return _str;}iterator end() {return _str + _size;}
const_iterator begin()const {return _str;}const_iterator end()const {return _str + _size;}

范围for的实现

因为范围for的底层就是迭代器,我们实现了迭代器,就可以直接使用范围for。

//迭代器的循环tx::string s1("hello world");//不是const的变量iterator begin = s1.begin();while (begin != s1.end()) {std::cout << *begin;begin++;}std::cout << std::endl;//范围for的循环for (auto e : s1) {std::cout << e;}std::cout << std::endl;//const变量const tx::string s2("wbb is nb");//const成员变量必须调用const成员函数,还有const要写在最前面for (auto c : s2) {std::cout << c;}std::cout << std::endl;}

5.末尾插入字符串append

这里值得注意的是如果我们每次只插入一个字符,那么每次都需要扩容,效率太慢,我们如果插入字符串长度较小,多次插入,我们直接先阔一个大空间,如果插入字符串很长,二倍空间不够,可以直接扩容字符串长度大小
size要及时更新

string& string::append(const char* s) {if (_size+strlen(s) > _capacity) {reserve(std::max(strlen(s)+_size, 2 * _capacity));//max前面要写标准库,即使写了文件名}strcpy(_str + _size, s);//const常量字符串不用可以用strcpy_size += strlen(s);//要加上这个,不然后续_size没有更新,会一直在原地方加字符串return *this;}

6. 中间插入字符和字符串

因为pos是size_t,len也是int,–end,达到-1时会转换成最大的数,导致循环一直继续,

  • 第一种方法是强制转换,吧pos转换成int,从_size+1,移到end=_size
  • 第二中就是把end=_size移动end=_size+1
    插入字符
string& string::insert(size_t pos,size_t n,char c) {assert(pos <= _size);if (_size == _capacity) {reserve(_capacity==0?4:2*_capacity);//strlen只能计算数组指针的长度,不能计算当个字符。}//size_t end = this->size();size_t end = _size+1;while (end > pos) {_str[end] = _str[end-1];//this指针指向的是对象end--;}//size_t end = _size;这种不行无符号,如果为负数也是最大的数//int end = _size;//二目运算符在运算时,不相同的类型,要转成相同的类型进行运算//while (end >= (int)pos) {//	_str[end+1] = _str[end];//	end--;//}_str[pos] = c;_size += 1;return *this;}

插入字符串时,要注意循环条件-1,跟插入单个字符很像
在这里插入图片描述

string& string::insert(size_t pos, const char* s) {assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity) {reserve(std::max(len+_size,2 * _capacity));//max里面形参要是相同类型}size_t end = _size + len;while (end > pos + len-1) {//这里不能写成pos+len这样的话end等于pos+len就停止了,后面插入就会多一个字符格,填入中文_str[end] = _str[end-len];end--;}strncpy(_str + pos, s,len);_size += len;return *this;}

7.earse删除数据

这个简单,如果从pos 位置删完只需要在pos位置加个\0,如果删除len小于后面的长度只需要拷贝到pos 位置

string& string::erase(size_t pos, int len) {//分离,参数不能同时存在assert(pos < _size);if (len == npos || len > _size - pos) {_str[pos] = '\0';_size = pos;}//开始我这里没有加else,以为执行完if直接跳过,必须要加,不然len太大,又会执行,导致数组越界else {memcpy(_str + pos, _str + pos + len,_size-pos-len+1);_size = _size - len;}return *this;}

8.substr裁剪字符串

只需要开一块空间,将字符串memcpy过来

string string::substr(size_t pos, size_t len) {assert(pos <= _size);char* temp = new char[len + 1];memcpy(temp, _str + pos, len);temp[len] = '\0';//len就相当于temp的_size。return temp;}

9. find,查找字符串

我们可以利用c的函数strstr,来查找字符串,但它返回的是char*,我们只需要减去_str,就能知道在哪个位置了,如果字符串中间有\0,strstr就不行了,要有其他算法,strstr对于\0就停止


size_t string::find(const char* s, size_t pos) {assert(pos + strlen(s) < _size);char* ptr = strstr(_str, s);if (ptr) {return ptr - _str;}else {return npos;}}

10.流插入<<和流提取>>

我们开始利用c_str得到字符串的输出,但这样有弊端,如果字符串中间含有\0,c_str遇到\0就停止,所以我们自己实现cout,按照范围for,每个字符都输出直到size个字符都输出完了

std::ostream& operator<<(std::ostream& out, const string& s) {//out是流输入对象的引用。for (auto e : s) {out << e;}return out;}

对于流提取我们不能简单的in>>e,这样字符一个一个的进入,自动跳过空格和换行,导致不能停止,一直输入,因为一直跳过空格和换行,导致我们的循环条件被破坏所以我们可以用get

std::istream& operator>>(std::istream& in, string& s) {//这里s不能用const,因为s要改变,不能是常量s.clear();//s是目标字符串假如s有初值,再输入就会出现错误char ch;char buff[256];int i = 0;//in >> ch;//会自动跳过空格和换行,导致不能停止,一直输出in.get(ch);//可以读取空格和换行,一个字符一个字符的读。while (ch != ' ' && ch != '\n') {buff[i++] = ch;if (i == 255) {buff[i] = '\0';s += buff;i = 0;}//in >> ch;in.get(ch);}if (i > 0) {buff[i] = '\0';s += buff;}return in;}

大家注意我这里加了一个数组,因为我为了减少扩容,如果不加数组,每次+=可能会扩容

上面提到了>>,但是这个不能输入空格,为了方便getline可以输入空格,它跟>>差不多,只是它的结束条件是换行。

std::istream& getline(std::istream& in, string& s, char delim) {s.clear();//s是目标字符串假如s有初值,再输入就会出现错误char ch;char buff[256];int i = 0;//in >> ch;//会自动跳过空格和换行,导致不能停止,一直输出in.get(ch);//可以读取空格和换行,一个字符一个字符的读。while (ch != '\n') {buff[i++] = ch;if (i == 255) {buff[i] = '\0';s += buff;i = 0;}//in >> ch;in.get(ch);}if (i > 0) {buff[i] = '\0';s += buff;}return in;

11.运算符重载==,>,>=

我们写这个只是了解底层,肯定达不到库里面的那么完美,所以这个运算符重载中间有\0的没有仔细考虑,我们可以c 的strcmp来比较

在这里插入图片描述

bool string:: operator==(const string& s) const {//运算符重载是对类类型才有作用。return strcmp(_str, s._str) == 0;//形参是char*}bool string:: operator>(const string& s)const {return strcmp(_str, s._str) > 0;}bool string:: operator>=(const string& s)const {if (*this > s || *this == s) {return true;}return false;}

运算符重载,我们需要类类型成员,而strcmp需要指针,所以我们需要转换一下

12.swap

我们通过官网可以看见三个swap,有string的,有全局的,有库的标准函数的,哪我们应该怎么选择。

string的成员函数
在这里插入图片描述

库里面的
在这里插入图片描述

我们先分析一下差异,库里面,是通过三者来实现交换,如果string的成员通过库里面的就要开空间,进行深拷贝,要进行三次深拷贝,代价太大。而string里面的只是交换数组指针,size,capacity,没有深拷贝,所以string更好。

void string::swap(string& s) {//如果直接算法里面重载的swap,会进行多次深拷贝,浪费效率。std::swap(_str, s._str);//因为如果我们不指定空间域,std,这个swap会调我们实现,但是只有一个参数,会导致错误,要指定命名空间域std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

因为如果我们不指定空间域,std,这个swap会调我们实现,但是只有一个参数,会导致错误,要指定命名空间域.

为了破局,实现全局函数的swap,如果有人调用库里面的,编译器会首先调用全局函数swap,然后调用string的成员函数swap;

http://www.dtcms.com/a/462898.html

相关文章:

  • usart波特率为9600和115200时,发送一句话所耗费的时间分别是多少
  • 灌南住房建设局网站网站建设收费标准不一
  • 前端学习手机网站开发吗黄金网站软件免费
  • 网站建设认知与理解营销平台建设
  • 免费创建属于自己的网站呼和浩特百度seo
  • k8s-pod部署java应用,jvm内存正常,但是pod内存不足oom排查
  • 公司网站建设合同需要交印花税中山专业网站建设模板代理
  • 网站开发需要解决难题项目网络图经常被称为
  • OSPF的高级特性
  • 安徽省建设工程造价管理网站wordpress嵌入php代码
  • 专门做网站的app怎样做网络推广优选豪升网络好
  • 网站空间150m跨境电商平台有哪些?列举5个
  • 怎么做网盘网站高培淇自己做的网站
  • spring ai mcp + 编写自动测试mcp服务端功能
  • 设备管理平台项目部署实验流程
  • 织梦网站文章发布信息模板下载去掉wordpress 上一篇
  • 有铁芯直线电机扰动力建模方法介绍
  • 图解 bulkProcessor(调度器 + bulkAsync() + Semaphore)
  • 监控系统3 - LVGL
  • 20-数组
  • 防水补漏东莞网站建设河北省建设厅办事大厅网站
  • logosc网站怎么做的减少wordpress响应时间
  • ps做的网站在线设计装修的网站
  • Python-UV环境管理实战
  • 江门建设建筑网站一个人做网站难吗
  • 网站开发使用哪些开发语言长沙的网站建设
  • 光效网站制作网站副本
  • 网站推广经典案例网站优化关键词排名自己怎么做
  • 电子商务网站建设实验原理集约化网站群建设
  • RocketMQ如何使用Netty