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

从零开始实现 std::string:让你更深入地了解字符串的本质

文章目录

  • 前言
  • string类 的模拟实现
    • 一,搭建框架
    • 二,重载输入输出操作符 ‘<<’ ‘>>’
      • 1. 重载操作符 ‘<<’
      • 2.重载操作符 ‘>>’
        • 且看方式一
        • 来看方式二
    • 三,实现构造函数
      • 方式一
      • 方式二
    • 四,实现拷贝构造和重载赋值操作符
        • 传统的写法
          • 1.拷贝构造
          • 2. 重载赋值操作符
        • 现代写法
          • 1.拷贝构造
          • 2. 重载赋值操作符
    • 五,实现string 类相关的函数
      • 1. reserve函数
      • 2.resize函数
      • 3. insert函数
        • 1.插入字符
        • 2.插入字符串
      • 4.push_back函数
      • 5. append函数
      • 6.操作符 += 重载
      • 7.erase函数
      • 8.clear函数
      • 9.find函数
      • 10.substr函数
      • 11.操作符 > == >= <= < != 重载
    • **源码:**
  • 后记

前言

本文将讲述怎么模拟实现string类,有些同学可能会问了,我要实现这个有什么用?会用不就可以了吗?

你没有错,但是我们通过模拟实现string类可以帮助我们更加深入的了解字符串具体是怎么一回事?它的内部结构是怎么样的?如果以后我们写程序,碰到字符串某个地方报错,也能很快排查出问题哦~

🕺作者: 迷茫的启明星
专栏:《C++初阶》

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

持续更新中~

string类 的模拟实现

一,搭建框架

我们首先把string类大概样子搭建出来,它不外乎构造函数、拷贝函数,析构函数、还有一些基于C语言实现的方便我们操作字符串的函数,那么我们就来搭建一下,需要提醒的是:一些注意事项我会放在代码的注释中便于理解。

#pragma once
#include <cstring>
#include <iostream>
#include <cassert>
using namespace std;

namespace hxq
{
    class string
    {
        public:
        typedef char* iterator;
        typedef const char* const_iterator;

        iterator begin()
        {
            return _str;//返回首字符地址
        }

        iterator end()
        {
            //字符串一个字符占一个字节
            return _str + _size; 
        }
        //当然也有const类型
        const_iterator begin() const
        {
            return _str;//返回首字符地址
        }

        const_iterator end() const
        {
            //字符串一个字符占一个字节
            return _str + _size;//返回尾巴的地址  
        }

		const char* c_str() const
        {
            return _str;//返回字符串首地址
        }
        
        size_t size() const
        {
            return _size;//返回字符串长度
        }
        size_t capacity() const
        {
            return _capacity;//返回string对象空间大小
        }

        //字符串类(String)中的 operator[] 运算符重载函数,
        //用于访问字符串中指定位置上的字符(元素),
        //并返回该字符的引用。
        const char& operator[](size_t pos) const
        {
            assert(pos < _size);

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

            return _str[pos];
        }

        //析构函数,释放资源
        ~string()
        {
            delete[] _str;
            _str = nullptr;
            _size=_capacity=0;
        }


        //一般来说我们尽量把参数放在一起
        private:
        size_t _capacity;//实际空间
        size_t _size;//字符串长度(不包括'\0')
        char* _str;//首字符地址
        public:
        const static size_t npos=-1;
        //特殊值
        //使用const static 语法修饰
        //npos是最后一个字符的位置
    };
}

二,重载输入输出操作符 ‘<<’ ‘>>’

1. 重载操作符 ‘<<’

将字符逐个输到输出流中

ostream& operator<<(ostream& out, const string& s)
    {
        for (char i : s)
        {
            out << i;
        }

        return out;
    }

2.重载操作符 ‘>>’

且看方式一

按照一般思路,我们设计一个循环读取字符直到遇到空格或者回车停止

于是就有了以下实现

    istream& operator>>(istream& in ,string& s)
    {
        char ch;
        ch=in.get();
        s.reserve(128);//预开128个字符的空间
        while(ch!=' '&&ch!='\n')
        {
            s+=ch;
            ch=in.get();
        }
        return in;
    }

但是在测试的时候出现这样的问题

当string对象有默认值时,输入的字符串会加在后面

就像这样:

在这里插入图片描述

结果:

在这里插入图片描述

不仅如此,当输入字符串很长,不断+=,频繁扩容,代码效率很低

来看方式二

我们开辟一个缓存字符数组来存储输入的字符串

达到条件就把缓存空间最后一个值赋值为‘\0’

再尾加缓存空间,也就是尾加字符串

    istream& operator>>(istream& in,string& s)
    {
        s.clear();

        char ch;
        ch=in.get();

        const size_t N = 32;
        char buffer[N];
        size_t i =0;

        //如果没有遇到空格或者回车就一直读取到buffer缓存区中
        while(ch != ' '&&ch != '\n')
        {
            buffer[i++] = ch;
            //当它满了就让s尾加buffer
            //注意看buffer经过处理也是一个字符串
            if(i == N-1)
            {
                buffer[i]='\0';
                s+=buffer;
                i=0;
            }
            ch=in.get();
        }
        //读取buffer中剩余字符
        buffer[i]='\0';
        s+=buffer;

        return in;
    }

三,实现构造函数

如何构造一个string的对象呢?

得考虑到三个成员变量:

  • _str 字符串的地址
  • _size 字符串长度
  • _capacity 字符串空间

我们需要将它们初始化

于是以下代码就出现了

string()
    :_str(new char[1])//先预存一个'\0'的空间
        ,_size(0)
        ,_capacity(0)
    {
        _str[0]='\0';
    }

有些同学就会想不能这样实现吗?

string()
    :_str(nullptr)//它没有给\0 预留空间
        ,_size(0)
        ,_capacity(0)
    {}

不能这样子,它无法初始化空对象

但是如果构造函数有参数,这就不适用了,还要重载一个有参的构造函数,我们为什么不实现一个默认参数为“\0”的构造函数呢?

方式一

//string(const char* str = "\0")//二者都可以
string(const char* str = "")//""相当于"\0"
    :_str(new char[strlen(str)+1])
    , _size(strlen(str))
    , _capacity(strlen(str))
{
	strcpy(_str, str);	
}

但是我们会发现它的初始化的时候调用了三次strlen函数是不是看上去就很拉?
于是就有同学想这样了↓

string(const char* str = "")
    :_size(strlen(str))
    ,_capacity(_size)
    ,_str(new char[_capacity+1])
{
	strcpy(_str,str);
}

不能这样,初始化的顺序是按照成员变量的顺序依次进行的

_str => _capacity => _size

在初始化_str的时候,_capacity是随机值,就不可行

但是我们可以这样,把初始化放在里面,就不会受到它的限制

方式二

string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];

    strcpy(_str, str);
}

四,实现拷贝构造和重载赋值操作符

传统的写法

1.拷贝构造

s2(s1)

按照我们之前对拷贝构造的理解

我们需要将s1的’_str’,‘_size’,'_capacity’依次赋值给s2的成员变量

注意:capacity要加1,为‘\0’预留位置

实现:

string(const string& s)
    :_str(new char[s._capacity+1])
        ,_size(s.size())
        ,_capacity(s._capacity)
    {
        strcpy(_str,s._str);
    }
2. 重载赋值操作符

假如说是:s1=s3

思路:

  • 新开辟一个空间tmp,将s3._str赋值给tmp
  • 将s1的_str删除
  • 将tmp的值给s1
string& operator=(const string&s)
{
    if(this!=&s)//判断是否是自己给自己赋值
    {
        //新开一个空间来存s3的值
        char* tmp = new char[s._capacity+1];//多开一个给'\0'
        strcpy(tmp,s._str);
        //把delete[] _str放在开空间后面,避免空间不足,会友好一些
        delete[] _str;//删除原来s1的值
        //将tmp的值给s1
        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

现代写法

1.拷贝构造

还有一种现代写法
这是一种有着资本主义或者说老板思维,让员工干活
具体怎么回事呢?
s2(s1)
我们可以新建一个string对象,并且以s1._str来初始化它
也就是说利用了前文的构造函数string(const char* str = “”)
再将”员工“的’_str’,‘_size’,'_capacity’与this的相交换
这样我们就可以借助这个”员工“来实现拷贝构造
需要注意的是,这里需要进行初始化为nullptr,0,0的操作
因为被调用的员工被处理(析构)的时候_str不能是随机空间(脏数据)(感兴趣的同学可以试着自己试试)

string(const string&s)
    :_str(nullptr)
        ,_size(0)
        ,_capacity(0)
    {
        string tmp(s._str);
        swap(_str,tmp._str);
        swap(_size,tmp._size);
        swap(_capacity,tmp._capacity);
    }

但是它似乎有点不太美观,我们将他放到一个函数中

void swap(string& tmp)//这是我们定义的函数
{
    //↓这是库里面的函数,我们以后再讲
    ::swap(_str,tmp._str);
    ::swap(_size,tmp._size);
    ::swap(_capacity,tmp._capacity);
}
string(const string&s)
    :_str(nullptr)
        ,_size(0)
        ,_capacity(0)
    {
        string tmp(s._str);
        swap(tmp);
        //相当于this->swap(tmp)
    }
2. 重载赋值操作符

再想想,刚刚的赋值操作符是不是也有相似之处呢?

我们只要和拷贝构造那样”雇佣“一个”员工“帮助我们实现即可

可以让代码非常简洁,就像这个式子:s1=s2

我们不需要考虑后面那个值的下场,因为它只是走个过场

所以直接调用swap函数交换它们的’_str’,‘_size’,‘_capacity’

string& operator=(string s)
{
    swap(s);
    return *this;
}

五,实现string 类相关的函数

1. reserve函数

在 C++ 的 string 类中,reserve() 是一个成员函数,

它用于在字符串中预分配一定的内存空间,以提高字符串操作的效率。

具体来说,reserve() 函数的功能是控制字符串对象的容量(capacity),

也就是分配给字符串对象的内存空间大小。当字符串对象的长度超过容量时,

会导致再次分配内存空间,从而影响性能。

因此,通过使用 reserve() 函数可以提前预留一定的内存空间,

以避免频繁地重新分配内存,从而提高程序的运行效率。

实现思路:

  • 如果需要分配的值大于字符串的_capacity就申请空间tmp
  • 将原来的字符串按字节拷贝给tmp
  • 删除原来的字符串的值
  • 将tmp的地址给_str

代码:

void reserve(size_t n)
{
    if(n > _capacity)
    {
        char* tmp=new char[n+1];
        strcpy(tmp,_str);
        delete[] _str;

        _str = tmp;
        _capacity = n;
    }
}

2.resize函数

在 C++ 的 string 类中,resize() 是一个成员函数,

它用于重新设置字符串的长度(size),并可以选择是否填充新添加的字符。

具体来说,resize() 函数的功能是控制字符串对象的长度和内容。

当需要改变字符串对象的长度时,可以使用 resize() 函数来进行操作。

如果新的长度小于或等于原长度,则会删除超出部分的字符;

如果新的长度大于原长度,则会在末尾添加新的字符。

实现思路:

  • 分为两种情况
    • 如果新的长度大于原长度就申请空间再将新的字符添加即可
    • 如果新的长度小于或等于原长度,则会删除超出部分的字符

代码:

void resize(size_t n, char ch = '\0')
{
    if (n > _size)
    {
        reserve(n);
        for (size_t i = _size; i < n; ++i) {
            _str[i] = ch;
        }
        _str[n]='\0';
        _size = n;
    }
    else
    {
        _str[n]='\0';
        _size = n;
    }
}

3. insert函数

在 C++ 的 string 类中,insert() 是一个成员函数,

它用于将一个字符串或字符序列插入到另一个字符串的指定位置。

具体来说,insert() 函数的功能是在字符串对象的指定位置插入一个字符串或字符序列。

可以通过传递不同的参数组合来选择需要插入的内容和插入位置。

在这里我们仅实现两种常用的

1.插入字符

实现思路:

insert(size_t pos,char ch)
  • pos>字符串长度报错
  • 如果空间不够扩容
  • 将pos位置后面的字符往后挪

代码:

//使用引用,提高工作效率
string& insert(size_t pos,char ch)
{
    assert(pos<=_size);

    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    size_t end = _size + 1;
    while(end>pos)
    {
        _str[end]=_str[end-1];
        --end;
    }

    _str[pos] = ch;
    ++_size;
    return *this;
}

2.插入字符串

实现思路:

insert(size_t pos,const char* str)
  • 和插入单个字符类似
  • pos>字符串长度报错
  • 如果空间不够扩容
  • 将pos位置后面的字符往后挪
  • 注意挪的长度是插入字符串的长度

代码:

string& insert(size_t pos,const char* str)
{
    assert(pos<=_size);
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }

    size_t end = _size + len;
    while(end>=pos+len)
    {
        _str[end]=_str[end - len];
        --end;
    }

    strncpy(_str + pos,str,len);
    _size += len;

    return *this;
}

4.push_back函数

尾插

实现思路:

push_back(char ch)
  • 方式一:判断空间是否足够,在最后设置需要增加字符,再在其后加上‘\0’即可
  • 方式二可以直接使用前面的insert函数进行尾插即可

代码:

//方式一
void push_back(char ch)
{
    if(_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }

    _str[_size]=ch;
    ++_size;
    _str[_size] = '\0';
}
//方式二
void push_back(char ch)
{
    insert(_size,ch);
}

5. append函数

用来将一个字符串追加到另一个字符串的末尾,使其成为一个更长的字符串。

实现思路:

append(const char*str)
  • 方式一:判断原字符串空间是否足够,直接将追加的字符串拷贝在它后面
  • 方式二:使用前面的insert函数尾插字符串

代码:

//方式一
void append(const char*str)
{
    //方式一
    size_t len = strlen(str);

    if (_size+len>_capacity)
    {
        reserve(_size+len);
    }
    strcpy(_str+_size,str);
    //还有一个函数是strcat(_str,str);
    //但是思考一下它的实现方式就会发现它首先要找'\0',那么就不高效
    _size+=len;
}
//方式二
void append(const char*str)
{
    insert(_size,str);
}

6.操作符 += 重载

相当于尾插字符和尾插字符串

实现思路:

string& operator+=(char ch)
  • 尾插字符利用push_back函数
string& operator+=(const char* str)
  • 尾插字符串利用append函数

代码:

//加字符
string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}
//加字符串
string& operator+=(const char* str)
{
    append(str);
    return *this;
}

7.erase函数

从pos开始删除长度为len的字符串,默认npos即为删到尾

实现思路:

erase(size_t pos , size_t len = npos)
  • pos>字符串长度报错
  • 如果是最后一个或者大于字符串的长度,那么就在pos这个位置上设为’\0’
  • 否则将位置[pos+len]后面的字符串给到[pos]位置,复制结束时再在尾巴上给一个’\0’

代码:

void erase(size_t pos , size_t len = npos)
{
    assert(pos<_size);
    //如果是最后一个或者大于字符串的长度,那么就在pos这个位置上设为'\0'
    if(len == npos || pos + len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        //将位置[pos+len]后面的字符串给到[pos]位置
        //复制结束时再在尾巴上给一个'\0'
        strcpy(_str + pos,_str+pos+len);
        _size -=len;
    }
}

8.clear函数

字符清空,长度置为0

实现思路:

clear()
  • 利用字符串以‘\0’结尾的性质
  • 直接将字符串第一个位置置为‘\0’
  • 再将长度置为0

代码:

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

9.find函数

从pos位置开始寻找返回子串在字符串中第一次出现的位置,如果没有找到,则返回 std::string::npos

实现思路:

//找某个字符
size_t find(char ch,size_t pos = 0) const
  • 遍历字符串,寻找匹配字符
//找匹配的字符串
size_t find(const char* sub,size_t pos = 0)const
  • 利用C语言库中的strstr函数
  • strstr 函数返回的指针指向的是源字符串中第一次出现子串的位置

代码:

//找某个字符
size_t find(char ch,size_t pos = 0) const
{
    assert(pos<_size);

    for(size_t i= pos;i<_size;++i)
    {
        if(ch == _str[i])
        {
            return i;
        }
    }
    return npos;
}
//找匹配的字符串
size_t find(const char* sub,size_t pos = 0)const
{
    assert(sub);
    assert(pos<_size);
    //strstr 函数返回的指针指向的是源字符串中第一次出现子串的位置
    const char* ptr = strstr(_str+pos,sub);
    if(ptr == nullptr)
    {
        return npos;
    }
    else
    {
        return ptr-_str;
    }

}

10.substr函数

用于从字符串中提取子串

实现思路:

string substr(size_t pos,size_t len = npos) const
  • 新建一个string对象
  • 从pos位置开始,截取长度为len的字符串

代码:

string substr(size_t pos,size_t len = npos) const
{
    assert(pos<_size);
    size_t realLen = len;
    if (len == npos || pos+len>_size)
    {
        realLen = _size - pos;
    }

    string sub;
    for (size_t i = 0; i < realLen; ++i) {
        sub+=_str[pos+i];
    }
    return sub;
}

11.操作符 > == >= <= < != 重载

实现思路:

利用strcmp函数判断大小

代码:

bool operator>(const string&s)const
{
    return strcmp(_str,s._str)>0;
}
bool operator==(const string& s) const
{
    return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s) const
{
    return *this>s||*this == s;
}

bool operator<=(const string& s) const
{
    return !(*this>s);
}
bool operator<(const string& s) const
{
    return !(*this >= s);
}
bool operator!=(const string& s) const
{
    return !(*this == s);
}

到此我们就模拟了string类的大部分功能,还有一些都是相关的函数,我就不再过多赘述了,这里面最重要的就是构造函数和拷贝构造,一定要理解清楚!

源码:

namespace hxq
{
    class string
    {
        //框架
    public:
        typedef char* iterator;
        typedef const char* const_iterator;

        iterator begin()
        {
            return _str;//返回首字符地址
        }

        iterator end()
        {
            return _str + _size;  //字符串一个字符占一个字节
        }
        //当然也有const类型
        const_iterator begin() const
        {
            return _str;//返回首字符地址
        }

        const_iterator end() const
        {
            return _str + _size;  //字符串一个字符占一个字节
        }
       
        string(const char* str = "")
        {
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];

            strcpy(_str, str);
        }


        //拷贝构造
        //s2(s1)
        //传统写法
//        string(const string& s)
//            :_str(new char[s._capacity+1])
//            ,_size(s.size())
//            ,_capacity(s._capacity)
//        {
//            strcpy(_str,s._str);
//        }
//        //假设说是这样的式子:s1=s3
//        string& operator=(const string&s)
//        {
//            if(this!=&s)//判断是否是自己给自己赋值
//            {
//                //新开一个空间来存s3的值
//                char* tmp = new char[s._capacity+1];//多开一个给'\0'
//                strcpy(tmp,s._str);
//                //把delete[] _str放在开空间后面,避免空间不足,会友好一些
//                delete[] _str;//删除原来s1的值
//                //将tmp的值给s1
//                _str = tmp;
//                _size = s._size;
//                _capacity = s._capacity;
//            }
//            return *this;
//        }

//        string(const string&s)
//                :_str(nullptr)
//                ,_size(0)
//                ,_capacity(0)
//        {
//            string tmp(s._str);
//            swap(_str,tmp._str);
//            swap(_size,tmp._size);
//            swap(_capacity,tmp._capacity);
//        }


        //但是它似乎有点不太美观,我们将他放到一个函数中
        //再简化
        void swap(string& tmp)//这是我们定义的函数
        {
            //↓这是库里面的函数,我们以后再讲
            ::swap(_str,tmp._str);
            ::swap(_size,tmp._size);
            ::swap(_capacity,tmp._capacity);
        }
        string(const string&s)
            :_str(nullptr)
            ,_size(0)
            ,_capacity(0)
        {
            string tmp(s._str);
            swap(tmp);
            //相当于this->swap(tmp)
        }

        string& operator=(string s)
        {
            swap(s);
            return *this;
        }
        const char* c_str() const
        {
            return _str;
        }

        //一些函数
        //分配空间
        void reserve(size_t n)
        {
            if(n > _capacity)
            {
                char* tmp=new char[n+1];
                strcpy(tmp,_str);
                delete[] _str;

                _str = tmp;
                _capacity = n;
            }
        }
        //重新设置长度,后面参数如果有的话就是加n个ch
        void resize(size_t n, char ch = '\0')
        {
            if (n > _size)
            {
                reserve(n);
                for (size_t i = _size; i < n; ++i) {
                    _str[i] = ch;
                }
                _str[n]='\0';
                _size = n;
            }
            else
            {
                _str[n]='\0';
                _size = n;
            }
        }
        //插入字符
        //使用引用,提高工作效率
        string& insert(size_t pos,char ch)
        {
            assert(pos<=_size);

            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : _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);
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                reserve(_size + len);
            }

            size_t end = _size + len;
            while(end>=pos+len)
            {
                _str[end]=_str[end - len];
                --end;
            }

            strncpy(_str + pos,str,len);
            _size += len;

            return *this;
        }
        void push_back(char ch)
        {
            //方式一
//            if(_size == _capacity)
//            {
//                reserve(_capacity == 0 ? 4 : _capacity * 2);
//            }
//
//            _str[_size]=ch;
//            ++_size;
//            _str[_size] = '\0';
            //方式二
            insert(_size,ch);
        }

        void append(const char*str)
        {
            //方式一
            size_t len = strlen(str);

            if (_size+len>_capacity)
            {
                reserve(_size+len);
            }
            strcpy(_str+_size,str);
            //strcat(_str,str);
            //思考一下它的实现方式就会发现它首先要找'\0',那么就不高效
            _size+=len;
            //方式二
            insert(_size,str);
        }

        //加字符
        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }
        //加字符串
        string& operator+=(const char* str)
        {
            append(str);
            return *this;
        }

        //删除
        //从pos开始删除长度为len的字符串,默认npos即为删到尾
        void erase(size_t pos , size_t len = npos)
        {
            assert(pos<_size);
            //如果是最后一个或者大于字符串的长度,那么就在pos这个位置上设为'\0'
            if(len == npos || pos + len >= _size)
            {
                _str[pos] = '\0';
                _size = pos;
            }
            else
            {
//                将位置[pos+len]后面的字符串给到[pos]位置
//                复制结束时再在尾巴上给一个'\0'
                strcpy(_str + pos,_str+pos+len);
                _size -=len;
            }
        }
        //想到字符串是以'\0'判断结束的,于是_str[0] = '\0',再把_size设为0结束
        void clear()
        {
            _str[0] = '\0';
            _size = 0;
        }
        //找某个字符
        size_t find(char ch,size_t pos = 0) const
        {
            assert(pos<_size);

            for(size_t i= pos;i<_size;++i)
            {
                if(ch == _str[i])
                {
                    return i;
                }
            }
            return npos;
        }
        //找匹配的字符串
        size_t find(const char* sub,size_t pos = 0)const
        {
            assert(sub);
            assert(pos<_size);
            //strstr 函数返回的指针指向的是源字符串中第一次出现子串的位置
            const char* ptr = strstr(_str+pos,sub);
            if(ptr == nullptr)
            {
                return npos;
            }
            else
            {
                return ptr-_str;
            }

        }
        //截取字符串
        string substr(size_t pos,size_t len = npos) const
        {
            assert(pos<_size);
            size_t realLen = len;
            if (len == npos || pos+len>_size)
            {
                realLen = _size - pos;
            }

            string sub;
            for (size_t i = 0; i < realLen; ++i) {
                sub+=_str[pos+i];
            }
            return sub;
        }

        bool operator>(const string&s)const
        {
            return strcmp(_str,s._str)>0;
        }
        bool operator==(const string& s) const
        {
            return strcmp(_str, s._str) == 0;
        }
        bool operator>=(const string& s) const
        {
            return *this>s||*this == s;
        }

        bool operator<=(const string& s) const
        {
            return !(*this>s);
        }
        bool operator<(const string& s) const
        {
            return !(*this >= s);
        }
        bool operator!=(const string& s) const
        {
            return !(*this == s);
        }
        //框架
        size_t size() const
        {
            return _size;
        }
        size_t capacity() const
        {
            return _capacity;
        }
        const char& operator[](size_t pos) const
        {
            assert(pos < _size);

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

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


    //一般来说我们尽量把参数放在一起
    private:
        size_t _capacity;//实际空间
        size_t _size;//字符串长度(不包括'\0')
        char* _str;//首字符地址
    public:
        const static size_t npos=-1;
        //特殊值
        //使用const static 语法修饰
        //npos是最后一个字符的位置




    };
    //重载操作符<<  按照字符逐字节输到输出流中
    ostream& operator<<(ostream& out, const string& s)
    {
        for (char i : s)
        {
            out << i;
        }

        return out;
    }
    //方式一:输入字符串很长,不断+=,频繁扩容,效率很低
    //而且会出现这样的情况,当string对象有默认值时,输入的字符串会加在后面

//    istream& operator>>(istream& in ,string& s)
//    {
//        char ch;
//        ch=in.get();
//        s.reserve(128);
//        while(ch!=' '&&ch!='\n')
//        {
//            s+=ch;
//            ch=in.get();
//        }
//        return in;
//    }
    //方式二
    //设计一个缓存数组
    istream& operator>>(istream& in,string& s)
    {
        s.clear();

        char ch;
        ch=in.get();

        const size_t N = 32;
        char buffer[N];
        size_t i =0;

        //如果没有遇到空格或者回车就一直读取到buffer缓存区中
        while(ch != ' '&&ch != '\n')
        {
            buffer[i++] = ch;
            //当它满了就让s尾加buffer
            //注意看buffer经过处理也是一个字符串
            if(i == N-1)
            {
                buffer[i]='\0';
                s+=buffer;
                i=0;
            }
            ch=in.get();
        }
        //读取buffer中剩余字符
        buffer[i]='\0';
        s+=buffer;

        return in;
    }
}

后记

感谢大家支持!!!

respect!

下篇见!

在这里插入图片描述


http://www.kler.cn/a/16956.html

相关文章:

  • 建筑施工特种作业人员安全生产知识试题
  • 某app最新版 vmp算法分析一
  • 【Vue】Vue3.0(十九)Vue 3.0 中一种组件间通信方式-自定义事件
  • Unity3D学习FPS游戏(12)敌人检测和攻击玩家
  • 【C++】new操作符的使用说明
  • 如何用WordPress和Shopify提升SEO表现?
  • OpenCV学习小记
  • 递归思路讲解
  • C/C++开发神器CLion全新发布v2023.1——新软件包管理解决方案
  • python语法入门到面向过程编程(七)
  • QML动画分组(Grouped Animations)
  • 6. 计算机网络
  • synchronized用法加锁原理
  • 深入浅出C++ ——异常
  • Ubuntu 安装和配置 Samba服务开启共享文件夹
  • 【光伏预报/太阳能预报】上海道宁与Solargi为您提供开发地理数据库模拟工具和网络服务
  • 关于项目移植过程中,如何在不修改java源程序的情况下,如何适应新环境下的mysql
  • web小游戏开发:华容道(完)
  • 功能齐全的 ESP32 智能手表,具有多个表盘、心率传感器硬件设计
  • Bizcharts 3.0 到 4.0 升级部分问题记录
  • 现代CMake高级教程 - 第 5 章:链接第三方库
  • spring1:核心和设计思想
  • Vue3中双向数据绑定与Pinia实践+JS数据引用的循环修改问题
  • 【Docker_windows】安装Docker桌面版
  • 2023哪款蓝牙耳机性价比高?200左右高性价比蓝牙耳机推荐
  • windows11 安装多个mysql8