string常见功能模拟

news/2025/3/15 4:31:37/

    学到string终于就不用像c语言一样造轮子了,接下来我们就模拟一下string方便我们更好理解string,首先我们都知道库里有个string,所以为了避免我们的string和库里的冲突,要用命名空间my_string将我们写的string包含在内。string的成员变量和我之前写的顺序表差不多,首先是一个指针指向一块存放字符串的空间,还有_size记录该空间内的有效字符个数,不包括\0,然后就是_capcity记录该空间能存放有效字符的个数,当然,实际上由于我们末尾要放一个\0,所以我们在实现的时候实际开辟空间比容量稍微大一点。库里的容量则更加充裕。

     

 一:构造函数,析构函数和赋值运算符重载

1.构造函数,析构函数实现:

#pragma once
#include<iostream>
#include<string>
using namespace std;
namespace my_string
{class string{public://构造函数string(const char*str="")//默认用空串初始化{都是内置类型,在函数体内初始化也挺方便,免得初始化列表的书写顺序和声明顺序不同导致出错int len = strlen(str);计算要开辟的空间_str = new char[len + 1];_capcity = len;_size = len;strcpy(_str, str);_str[_size] = '\0';末尾补\0}~string(){delete[]_str;要注意的是new[]对应delete[],new对应delete,原因不好解释。_size = 0;_capcity = 0;_str = nullptr;}const char* c_str()const将string以c语言的字符串形式返回{return _str;}private:size_t _capcity;size_t _size;char* _str;};
};

2.拷贝构造函数

string(const string& Src)
{_str = new char[Src._size+1];_size = Src._size;_capacity = _size;memcpy(_str, Src._str, Src._size+1);//将\0也一同拷贝
}

3.测试构造函数

void TestString1()
{my_string::string s1("hello world");cout << s1.c_str() <<endl;
}

  首先c._str()函数将string s1以字符串的形式返回,也就是返回指针,并且cout内部有内置类型的输出重载,所以可以将其打印出来

    const修饰对象只会限制成员变量不被修改,却并不限制修改其成员指向的空间,也就是说如果c_str()返回指针不加const修饰,我们可以在外部用该指针修改const对象,这是不合理的,又为了保证const对象和普通对象都可以调用这个函数,所以对c_str()这个函数用了两个const修饰

4.赋值运算符重载


string.hstring& operator=(string tmp)
{swap(tmp);return (*this);//支持连续赋值
}main.c
string s1;
string s2;
s1=s2;	

   此时s1=s3被编译器转为s1.operator(s2), s2是传值传参,要调用拷贝构造,原因我在我的博客类的六大成员函数中曾提及,tmp是s2的深拷贝对象,我们交换了s1和tmp的成员,这样s1指向的空间就是s3深拷贝后的了,也就完成了赋值,最妙的是我们把s1要析构的空间给了局部对象tmp,让其在函数调用结束时销毁,不用我们手动delete[]。

​void swap(string s)
{//复用库的swap函数std::swap(_str, s._str);//直接交换两个对象成员std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

要注意的是我们不是直接调用库里的swap函数交换两个string对象,而是自己写个swap,内部调用库里的swap函数来交换string对象的成员来达到交换对象的目的。原因是库里的swap函数是如下的:

class T
swap(T a,T b)
T c(a);
a=b;
b=c;

当我们调用赋值的时候,用了swap函数,swap内部又是赋值,赋值内部又用swap,无穷递归调用,死循环,千万注意。

二:string的遍历

1:实现迭代器和范围for

在第一次使用迭代器时是std::iterator it,现在我才意识到原来iterator实质上是char*的typedef,下面的begin()函数如果被const修饰,const对象可以调用,非const对象也可以调用,但是这样的话返回类型就只能是const char*了,那对于普通对象来说就无法修改了,所以要再实现一个const修饰的begin()函数,下面还重定义了const char*就是为了服务const对象。

public:typedef char* iterator;typedef const char* const_iterator;char& operator[](int i){return _str[i];}const char& operator[](int i)const{//_size,_str,_cap不可修改,因为this指针此时是const string*const//若是const不修饰char&,_str指向的字符串可修改return _str[i];}iterator begin()  对const对象不适用,所以要再写一个{return _str;}iterator end(){return _str + _size;}不会修改成员变量的成员函数都可以用const修饰,这样就方便const对象和普通对象的调用了。size_t size()const//获取string中的有效字符个数{return _size;}char* c_str()const//将string以c语言的字符串形式返回{return _str;}

  如下为const对象的begin()和end()函数,返回类型是const_char*,为了和char*的重命名作区分,命名为const_iterator,注意:const对象的迭代器不可修改,所以返回的指针要用const修饰,否则该指针同样在外部会被用来修改const对象。

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

2.测试遍历功能

在准备好了成员函数后,就可以测试遍历功能了,下面提供三种遍历方式。

   for (size_t i = 0; i < s1.size(); i++)//测试size()和[]重载{cout << s1[i];//测试[]重载}cout << endl;//迭代器//my_string::string::iterator it = s1.begin();auto it = s1.begin();是不是感觉it的类型名特别长,这个时候auto自动推导类型名的作用就很舒服了while (it != s1.end()){cout << *it;it++;}cout << endl;范围for,范围for在底层被替换成上述的迭代器,而且是按照既定格式来替换的,
如果我们模拟的begin函数名改为Begin,范围for就无法运行,因为范围for被替换后是用begin函数名访问。for (auto ch : s1){cout << ch;}cout << endl;

三:string的添加字符

1.push.back尾插一个字符

void Reserve(size_t n=0)//扩容
{char*tmp = new char[n + 1];//预留一个位置给\0memcpy(tmp, _str, _size);delete[]_str;_str = tmp;_capcity = n;
}

上面的扩容函数中我们用memcpy来把原先存在的字符串复制到tmp中,之所以不用strcpy,是因为我们考虑string中可能会存入"hello \0world'。

void push_back(char c)//尾插一个字符
{if (_size == _capcity)//扩容{Reserve(_capcity==0?4:2*_capcity);当string对象为空串时,给个初始容量其余情况扩大二倍   }_str[_size++] = c;_str[_size] = '\0';//末尾补充\0
}string& operator+=(char s)
{push_back(s);return (*this);
}

2.append尾插一个字符串

void append(const char* s)//尾插一个字符串
{int len = strlen(s);if (_size + len > _capcity)先判断size+len是否会超出容量{Reserve(_size+len);  扩容到_size+len而不是2*_capcity,2*_capcity不一定大于_size+len }else{for (int i = 0; i < len; i++){  _str[_size++] = s[i];//插入字符}}
}+=运算符重载,便于调用,且代码看起来更加规整
string& operator+=(const char* s)
{append(s);return (*this);
}

代码中使用+=运算符重载比直接append和push_back函数更加美观,如下。

s += 'w';
s += 'o';
s += "hello ";
s += "world";对比
s.push_back('w');
s.push_back('o');
s.append("hello");
s.push_back("world");

3.在pos位置添加字符或者字符串

 string& insert(size_t pos,char ch,size_t n)
{assert(pos <= _size);//判断容量if (_size + n > _capacity){Reserve(_size + n);}//挪动数据size_t end = _size;//从\0开始移动while (end>=pos && end!=npos)    end为size_t,当pos=0时,end无法跳出循环,所以添加一个判断条件{_str[end+n] = _str[end];end--;}//插入数据_size += n;while (n){_str[pos++] = ch;n--;}return (*this);
}string& insert(size_t pos,const char*src)
{assert(pos <= _size);int len = strlen(src);//判断容量if (_size + len> _capacity){Reserve(_size + len);}//挪动数据size_t end = _size;//从\0开始移动while (end >= pos && end != npos){str[end + len] = _str[end];end--;}//插入数据_size += len;for (int i = 0; i < len; i++){_str[pos++] = src[i];}return (*this);
}

四:删除string对象的字符

len的缺省值为npos,由于npos为-1,对于无符号数len来说是四十几亿,而实际上字符串都不会这么大,也就表示删除pos位置后全部的字符。

      void erase(size_t pos=0,size_t len=npos)   删除pos位置以及之后len个字符{if (len==npos||pos + len>= _size)//删到末尾{_str[pos] = '\0';_size = pos;}else{挪动覆盖数据size_t begin = pos + len;while (begin <= _size)    将后面未删除字符以pos为起点往后放{                         包括了\0_str[pos++] = _str[begin++];}_size -= len;}}

五:查找字符

1.从指定位置查找字符串,返回字符串第一次出现位置的首字符地址

size_t find(const char*goal,size_t pos=0){if (pos >= _size)return npos;//下标非法,返回-1char*begin = strstr(_str+pos, goal);//复用库函数if (begin == NULL)return npos;//找不到,返回-1return begin - _str;}

2.从指定位置查找字符,返回字符下标

	size_t find(char goal, size_t pos = 0){if (pos >= _size)return npos;//下标非法,返回-1for (size_t i =pos; i < _size; i++)//遍历判断{if (_str[i] == goal)return i;}return npos;//找不到,返回-1}

六:截取字符串

string substr(size_t pos=0,size_t len=npos)
{assert(pos <= _size);string ret;if (len==npos||pos + len > _size)//截取到末尾{此处有个细节是先判断len是否等于npos,可以免得当len=npos时,len+pos出现栈溢出erase函数处同理len = _size - pos;//处理len}for (size_t i = pos; i < len+pos; i++){ret += _str[i];//ret无需处理\0,+=会处理}return ret;
}

七:流插入流提取

   1.流插入

可以选择将流提取和流插入函数放在全局域或者命名空间内,最好直接放在命名空间内,免得冲突,但是放在命名空间的时候,若在命名空间内声明,外写定义,定义处要指定命名空间域,否则函数调用时编译器看到命名空间内有个声明,会认为有个定义,虽然没找到,而全局处的函数定义又没有指定命名空间,就不会认为是命名空间中那段声明对应的函数定义,这样函数调用时就会出现调用不明确。

ostream& my_string::operator<<(ostream& out, const my_string::string& s)
{for (size_t i = 0; i < s.size(); i++){out << s[i]; s[i]调用的是类的公有函数,用公有函数访问string对象的字符不受访问限定符限制}out << endl;return out;
}

2 流提取

istream& my_string::operator>>(istream& in, my_string::string& s)
{s.clear();char ch = 0;ch = in.get();while (ch == ' ' || ch == '\n'){ch = in.get();  处理有效字符前的分隔符}int i = 0;char arr[128] = { 0 };  模拟缓冲区,防止s频繁扩容while (ch != ' ' && ch != '\n')//当ch读到分隔符,一个string对象读取结束{arr[i++] = ch;//先存到数组中去ch = in.get();if (i == 127)//数组满了后,一次性填入s中{arr[i] = '\0';i = 0;s += arr;}}           可能i小于127,此时又读到了分隔符,要在外面判断是否要处理数组中剩下的字符if (i != 0)s += arr;return in;
}

字数有点多,但是string是我们学习c++的关键,对于理解vector和list有着非常大的作用,个人的一些理解希望对大家有帮助。


http://www.ppmy.cn/news/831894.html

相关文章

文件下载功能(简单粗暴)

文件下载功能 // 模板下载 export const modelLoadInterface (data: any) > {return get<Response>(tsureexapp-exchange/config/points/grant/export.json, data, {skipErrorHandler: true,}); };import {modelLoadInterface} from "/services/CommunicationF…

【知识积累】电脑微信卸载后,聊天记录如何恢复

原创&#xff1a;ET最终珍藏版 安装系统前&#xff0c;微信默认安装路径 文件管理的位置&#xff0c;指向D:\Temp 微信会自动在下层创建个WeChat Files文件夹&#xff0c;不必手动创建。 重装系统&#xff0c;或者要把记录迁移到其他电脑时: 备份&#xff1a;C:\Users\用户名…

如何查看已删除的微信聊天记录?教你两招,找到答案

近期&#xff0c;小编在很多的地方和朋友口中谈到的一个问题&#xff0c;如何查看已删除的微信聊天记录&#xff1f;很多人都有这个想法&#xff0c;但不知道如何能去查看到。想查看删除的微信聊天记录或是想恢复自己删除的微信聊天记录&#xff0c;都是需要用上正确的方法才可…

微信删除好友聊天记录还在吗?如何查看已删除的微信聊天记录

微信删除好友聊天记录还在吗&#xff1f;如果您已经把好友删除了&#xff0c;聊天框肯定就不存在了&#xff0c;也就是说聊天记录也不存在。那么&#xff0c;如何查看已删除的微信聊天记录&#xff1f;第一个方法可以在微信设置里进行恢复查看&#xff0c;如果恢复不了&#xf…

怎么导出微信聊天的记录到Word

平时在微信上产生大量的聊天的记录&#xff0c;有时候想要将聊天的记录以Word形式留存下来&#xff0c;但是一条一条聊天的记录复制粘贴会花费大量时间&#xff0c;特别是在聊天的记录数量庞大的情况下&#xff0c;怎么导出微信聊天的记录&#xff1f;小编有一招非常好用&#…

邀请艺人和运动员参加商业活动:费用和邀请策略探究

在商业领域中&#xff0c;邀请知名艺人和运动员参加商业活动已经成为品牌推广和市场营销的重要策略之一。企业举办各种活动&#xff0c;想邀请艺人运动员出席商业活动&#xff0c;怎么跟艺人运动员联系&#xff1f;一般来说和艺人合作有2种方式。一种是直接和艺人的助理联系&am…

亚马逊真的赚钱吗?是坑吗?亚马逊多年卖家真实想法揭秘

亚马逊跨境电商真的能赚钱吗&#xff1f;这是东哥近期收到最多的咨询。理解大家对赚钱的渴望&#xff0c;但凡事都是有利有弊的。至于亚马逊跨境电商能不能赚钱&#xff0c;赚多少&#xff1f;今天东哥就作为一名多年的亚马逊卖家来好好聊聊这件事吧。 亚马逊跨境电商真的能赚钱…

可靠防护,同为科技(TOWE)IP66工业户外防水插座

科技改变生活&#xff0c;随着现代生活水平的提高&#xff0c;人们的户外工作生活日益丰富多彩。以前&#xff0c;在室外除了照明灯光设施&#xff0c;几乎没有什么电器&#xff0c;现如今&#xff0c;忽如一夜春风来&#xff0c;临时景观灯、露台草坪生日聚会、酒吧街、商业街…