0


【C++】string模拟实现

在这里插入图片描述

🔥个人主页: Forcible Bug Maker
🔥专栏: STL || C++

目录

前言

本篇博客主要内容:实现string类的基本功能

string使用很快就讲完了,但是光会用string还不够,在面试中,面试官总喜欢让我们自己来模拟实现string类,包括string类的构造、拷贝构造、赋值运算符重载以及析构函数等等内容。所以,我认为string类的自实现还是有必要讲一下的。

🔥string类的接口函数

我们本次并不会将string类的所有接口函数逐一讲解,讲一些常用的和重点的。本次string的实现分成了两个文件,一份是

  1. string.h

,一份

  1. string.cpp


看看string.h的内容:

  1. #define_CRT_SECURE_NO_WARNINGS1#pragmaonce#include<iostream>#include<cstring>#include<cassert>using std::cout;using std::cin;using std::endl;namespace ForcibleBugMaker
  2. {classstring{public:// 交换,非成员函数friendvoidswap(string& s1, string s2);// 定义迭代器typedefchar* iterator;typedefconstchar* const_iterator;//迭代器获取
  3. iterator begin();
  4. iterator end();
  5. const_iterator begin()const;
  6. const_iterator end()const;// string默认成员函数string(constchar* str ="");string(const string& s);
  7. string&operator=(string tmp);~string();// 获取只读字符串constchar*c_str()const;// 获取容量
  8. size_t size()const;
  9. size_t capaity()const;// []获取元素重载char&operator[](size_t pos);constchar&operator[](size_t pos)const;// 开辟空间voidreserve(size_t n);// 尾插字符或字符串voidpush_back(char ch);voidappend(constchar* str);
  10. string&operator+=(char ch);
  11. string&operator+=(constchar* str);// 插入字符或字符串voidinsert(size_t pos,char ch);voidinsert(size_t pos,constchar* str);//删除字符串voiderase(size_t pos =0, size_t len = npos);// 查找字符或字串
  12. size_t find(char ch, size_t pos =0);
  13. size_t find(constchar* str, size_t pos =0);// string对象比较booloperator>(const string& str)const;booloperator==(const string& str)const;booloperator>=(const string& str)const;booloperator<(const string& str)const;booloperator<=(const string& str)const;booloperator!=(const string& str)const;// 获取string对象字串
  14. string substr(size_t pos, size_t len);// 交换函数,成员函数voidswap(string& str);// 清除串中内容voidclear();private:char* _str =nullptr;
  15. size_t _size =0;
  16. size_t _capacity =0;// 常量成员conststatic size_t npos;};// 交换函数,非成员函数voidswap(string& s1, string s2);// 流插入和流提取重载,非成员函数
  17. std::ostream&operator<<(std::ostream& so,const string& str);
  18. std::istream&operator>>(std::istream& is, string& str);}

在实现string类添加和拷贝的一些函数中,使用了C语言中的一些库函数,以此方便实现,这些函数在C++中统一存放在

  1. <cstring>


如果对C语言的一些字符串函数不太了解,可以看看我之前写的一篇博客:C语言-字符串函数,相信会对你有所帮助。

🔥string类的模拟实现

接下来进入主要内容,按照string.h的接口开始实现。

以下接口都是放在命名空间里的,不同文件的相同命名空间在编译时会自动合并。

swap交换

如果你仔细观察,会发现存在两个swap交换函数,一个string中的成员函数,另一个是非成员函数。
在std的默认swap当中,是这样的:
在这里插入图片描述

  1. template<classT>voidswap( T& a, T& b ){
  2. T c(a); a=b; b=c;}

当我们使用这个成员函数进行交换时,会造成拷贝消耗,我们提供对应的非成员函数重载是为了防止C++程序员掉坑。
成员函数的swap:

  1. void string::swap(string& str){
  2. std::swap(_str, str._str);
  3. std::swap(_size, str._size);
  4. std::swap(_capacity, str._capacity);}

非成员函数的swap:

  1. voidswap(string& s1, string& s2){
  2. std::swap(s1._str, s2._str);
  3. std::swap(s1._size, s2._size);
  4. std::swap(s1._capacity, s2._capacity);}

默认成员函数

默认成员函数,就是不提供编译器自动会生成的一些函数。我们这里实现构造函数,析构函数和赋值运算符重载

构造函数:

  1. string::string(constchar* str):_size(strlen(str)){
  2. _capacity = _size +1;
  3. _str =newchar[_capacity +1];strcpy(_str, str);}
  4. string::string(const string& s){
  5. _str =newchar[s._capacity +1];strcpy(_str, s._str);
  6. _size = s._size;
  7. _capacity = s._capacity;}

构造函数提供了两个,分别支持了字符串构造和string对象构造。_str指向通过new开辟空间,这个空间需要比实际的capacity大,因为需要在字符串末尾多存放一个

  1. '\0'

析构函数:

  1. string::~string(){delete[] _str;
  2. _str =nullptr;
  3. _capacity =0;
  4. _size =0;}

这个没什么好说,释放空间,指针置空,_size和_capacity置0。

赋值运算符重载:

string的赋值属于深拷贝。

拷贝分为深拷贝和浅拷贝:
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享
深拷贝:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

浅拷贝:
在这里插入图片描述
深拷贝:
在这里插入图片描述
如果中规中矩的来写(传统版赋值重载):

  1. String&operator=(const String& s){if(this!=&s){char* pStr =newchar[strlen(s._str)+1];strcpy(pStr, s._str);delete[] _str;
  2. _str = pStr;}return*this;}

但如果你能对之前实现的代码进行复用,你会发现这个过程可以简化非常多(现代版赋值重载)

  1. string& string::operator=(string tmp){// 调用成员函数的swap,交换*this和tmpswap(tmp);return*this;}

在C++的编写中,学会复很有必要。

迭代器

之前讲过,迭代器不一定是指针,但是你可以把它想象成指针,可以通过正常的运算操作来控制其指向的元素。在string中,我们使用指针来模拟实现迭代器。

迭代器在不同编译器下的实现方式有所不同,比如VS下的迭代器就不是一个指针,而是一个类模板。

在这里插入图片描述
在类中需要typedef一下,可以看到我们所实现的string迭代器的本质:

  1. // 定义迭代器typedefchar* iterator;typedefconstchar* const_iterator;

获取迭代器的接口:

  1. string::iterator string::begin(){return _str;}
  2. string::iterator string::end(){return _str + _size;}
  3. string::const_iterator string::begin()const{return _str;}
  4. string::const_iterator string::end()const{return _str + _size;}

这时,我们已经可以使用我们自己的迭代器了:

  1. string str("hello world!");
  2. string::iterator it = str.begin();while(it != str.end()){
  3. cout <<*it <<" ";++it;}
  4. cout << endl;

在这里插入图片描述

获取容量和内容信息

这些都是返回str类型状态和内容的函数。

  1. constchar* string::c_str()const{return _str;}
  2. size_t string::size()const{return _size;}
  3. size_t string::capaity()const{return _capacity;}// 运算符重载char& string::operator[](size_t pos){assert(pos < _size);return _str[pos];}constchar& string::operator[](size_t pos)const{assert(pos < _size);return _str[pos];}

都比较简单易懂。

reserve预留空间

在开始字符串增删查改之前,有必要介绍这个函数,使用它可以更好的控制我们string对象的内存管理。

  1. void string::reserve(size_t n){if(_capacity < n){char* tmp =newchar[n +1];strcpy(tmp, _str);delete[] _str;
  2. _str = tmp;
  3. _capacity = n;}}

当n大于当前容量_capacity的时候,进行空间的开辟,将_str中的内容拷贝到新空间中去,同时delete释放旧空间。

尾插字符和字符串

接口函数,一个是push_back,一个是append。

  1. void string::push_back(char ch){if(_size >= _capacity){reserve(_capacity ==0?4: _capacity *2);}
  2. _str[_size]= ch;++_size;
  3. _str[_size]='\0';}void string::append(constchar* str){
  4. size_t len =strlen(str);if(_capacity < _size + len){reserve(_size + len);}strcpy(_str + _size, str);
  5. _size += len;}

这两个成员函数都使用了reserve来预留空间。
同时还需要有 运算符重载+= 来实现尾插,用重载的运算符执行这样的操作才是最爽的。

  1. string& string::operator+=(char ch){push_back(ch);return*this;}
  2. string& string::operator+=(constchar* str){append(str);return*this;}

其实不用再实现一遍了,直接复用就行。
可以简单看一下使用的效果:

  1. string str("hello world!");char ch ='T';constchar* s ="hhhhhh";
  2. cout << str << endl;
  3. str += ch;
  4. cout << str << endl;
  5. str += s;
  6. cout << str << endl;

在这里插入图片描述

字符或字符串的插入和删除

这里的逻辑稍微有些复杂,而且还有几个比较容易掉的坑。

  1. // 字符的插入void string::insert(size_t pos,char ch){assert(pos <= _size);if(_size >= _capacity){reserve(_capacity ==0?4: _capacity *2);}// 注意这里为什么要+1,size_t是无符号整型,没有负值//当有符号和无符号比较时,统一会被转成无符号
  2. size_t end = _size +1;while(end > pos){
  3. _str[end]= _str[end -1];--end;}
  4. _str[pos]= ch;++_size;}// 字符串的插入void string::insert(size_t pos,constchar* str){assert(pos <= _size);
  5. size_t len =strlen(str);if(len ==0)return;if(_size + len > _capacity){reserve(_size + len);}// 注意这里为什么要+len,size_t是无符号整型,没有负值//当有符号和无符号比较时,统一会被转成无符号
  6. size_t end = _size + len;while(end > pos + len -1){
  7. _str[end]= _str[end - len];--end;}memcpy(_str + pos, str, len);
  8. _size += len;}// 字符串的删除void string::erase(size_t pos, size_t len){assert(pos < _size);// 当pos+len过大,超过_size,则取到末尾if(len >= _size - pos){
  9. _str[pos]='\0';
  10. _size = pos;}else{strcpy(_str + pos, _str + pos + len);
  11. _size -= len;}}

使用案例:

  1. string str("hello world!");char ch ='T';constchar* s ="hhhhhh";
  2. cout << str << endl;
  3. str.insert(3, ch);
  4. cout << str << endl;
  5. str.insert(3, s);
  6. cout << str << endl;
  7. str.erase(3,strlen(s));
  8. cout << str << endl;

在这里插入图片描述

find查找

查找C语言查找字符串的库也有提供,可以直接使用。

  1. size_t string::find(char ch, size_t pos){for(size_t i = pos; i < _size; i++){if(_str[i]== ch)return i;}return npos;}
  2. size_t string::find(constchar* sub, size_t pos){constchar* ptr =strstr(_str, sub);return ptr - _str;}

使用案例:

  1. string str("hello world!");
  2. cout << str.find('w')<< endl;
  3. cout << str.find("wor")<< endl;

在这里插入图片描述

缺省参数在函数声明那里。

比大小运算符重载

C语言中,有一个按能按字典序将字符串比大小的函数——

  1. strcmp

  1. bool string::operator>(const string& str)const{returnstrcmp(_str, str._str)>0;}bool string::operator==(const string& str)const{returnstrcmp(_str, str._str)==0;}bool string::operator>=(const string& str)const{return*this> str ||*this== str;}bool string::operator<(const string& str)const{return!(*this>= str);}bool string::operator<=(const string& str)const{return!(*this> str);}bool string::operator!=(const string& str)const{return!(*this== str);}

只需要实现前两个,后面的复用就行。

获取子串

使用substr可以获取所需对象字串。

  1. string string::substr(size_t pos, size_t len){if(pos + len >= _size){
  2. string sub(_str + pos);return sub;}else{
  3. string sub;
  4. sub.reserve(len);for(size_t i =0; i < len; i++){
  5. sub += _str[pos + i];}return sub;}}

清除clear

可以将对象内容都删除,但_capacity保持不变。

  1. void string::clear(){
  2. _str[0]='\0';
  3. _size =0;}

流插入和流提取

这两个函数之前在Date类部分实现的时候,定义为了Date类的友元。但是,流插入和流提取其实可以不定义为string类的友元。

  1. // 流插入
  2. std::ostream&operator<<(std::ostream& os,const string& str){for(int i =0; i < str.size(); i++)
  3. os << str[i];return os;}// 流提取
  4. std::istream&operator>>(std::istream& is, string& str){
  5. str.clear();char ch = is.get();while(ch !=' '&& ch !='\n'){
  6. str += ch;
  7. ch = is.get();}return is;}

string对象读取的截断是空格换行,但是

  1. cin

对象默认是读不到这两个字符的,这就需要使用另一种读取方式:

  1. cin.get()


使用案例:

  1. string str;
  2. cin >> str;
  3. cout << str << endl;

在这里插入图片描述

常量成员npos

常量成员的定义需要实现在类的外部,像这样:

  1. const size_t string::npos =-1;

常量的内容是整型(浮点型和字符类型等都不行)时,也可以作为缺省参数进行定义。

结语

本篇博客主要介绍了string类常用接口的实现,包括默认成员函数,迭代器,字符和字符串的插入删除等等内容。
后续博主还会继续分享与STL相关的内容,感谢大家的支持。♥


本文转载自: https://blog.csdn.net/2303_79329831/article/details/139028720
版权归原作者 Forcible Bug Maker 所有, 如有侵权,请联系我们删除。

“【C++】string模拟实现”的评论:

还没有评论