0


【C++】string模拟实现

在这里插入图片描述

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

目录

前言

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

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

🔥string类的接口函数

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

string.h

,一份

string.cpp


看看string.h的内容:

#define_CRT_SECURE_NO_WARNINGS1#pragmaonce#include<iostream>#include<cstring>#include<cassert>using std::cout;using std::cin;using std::endl;namespace ForcibleBugMaker
{classstring{public:// 交换,非成员函数friendvoidswap(string& s1, string s2);// 定义迭代器typedefchar* iterator;typedefconstchar* const_iterator;//迭代器获取
        iterator begin();
        iterator end();
        const_iterator begin()const;
        const_iterator end()const;// string默认成员函数string(constchar* str ="");string(const string& s);
        string&operator=(string tmp);~string();// 获取只读字符串constchar*c_str()const;// 获取容量
        size_t size()const;
        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);
        string&operator+=(char ch);
        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);// 查找字符或字串
        size_t find(char ch, size_t pos =0);
        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对象字串
        string substr(size_t pos, size_t len);// 交换函数,成员函数voidswap(string& str);// 清除串中内容voidclear();private:char* _str =nullptr;
        size_t _size =0;
        size_t _capacity =0;// 常量成员conststatic size_t npos;};// 交换函数,非成员函数voidswap(string& s1, string s2);// 流插入和流提取重载,非成员函数
    std::ostream&operator<<(std::ostream& so,const string& str);
    std::istream&operator>>(std::istream& is, string& str);}

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

<cstring>


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

🔥string类的模拟实现

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

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

swap交换

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

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

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

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

非成员函数的swap:

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

默认成员函数

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

构造函数:

string::string(constchar* str):_size(strlen(str)){
    _capacity = _size +1;
    _str =newchar[_capacity +1];strcpy(_str, str);}

string::string(const string& s){
    _str =newchar[s._capacity +1];strcpy(_str, s._str);
    _size = s._size;
    _capacity = s._capacity;}

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

'\0'

析构函数:

string::~string(){delete[] _str;
    _str =nullptr;
    _capacity =0;
    _size =0;}

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

赋值运算符重载:

string的赋值属于深拷贝。

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

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

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

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

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

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

迭代器

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

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

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

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

获取迭代器的接口:

string::iterator string::begin(){return _str;}

string::iterator string::end(){return _str + _size;}

string::const_iterator string::begin()const{return _str;}

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

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

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

在这里插入图片描述

获取容量和内容信息

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

constchar* string::c_str()const{return _str;}

size_t string::size()const{return _size;}

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对象的内存管理。

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

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

尾插字符和字符串

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

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

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

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

string& string::operator+=(constchar* str){append(str);return*this;}

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

string str("hello world!");char ch ='T';constchar* s ="hhhhhh";
cout << str << endl;

str += ch;
cout << str << endl;

str += s;
cout << str << endl;

在这里插入图片描述

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

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

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

使用案例:

string str("hello world!");char ch ='T';constchar* s ="hhhhhh";
cout << str << endl;

str.insert(3, ch);
cout << str << endl;

str.insert(3, s);
cout << str << endl;

str.erase(3,strlen(s));
cout << str << endl;

在这里插入图片描述

find查找

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

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;}

size_t string::find(constchar* sub, size_t pos){constchar* ptr =strstr(_str, sub);return ptr - _str;}

使用案例:

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

在这里插入图片描述

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

比大小运算符重载

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

strcmp

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可以获取所需对象字串。

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

清除clear

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

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

流插入和流提取

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

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

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

cin

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

cin.get()


使用案例:

string str;
cin >> str;
cout << str << endl;

在这里插入图片描述

常量成员npos

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

const size_t string::npos =-1;

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

结语

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


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

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

还没有评论