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

news2024/7/2 3:51:19

文章目录

  • 前言
  • 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.coloradmin.cn/o/482231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TCP之报文格式解析

TCP网络协议是较常用的&#xff0c;也基本上都会接触&#xff0c;那么来简单了解下它吧。TCP 是一种面向连接的、可靠的传输协议&#xff0c;它能够将数据分成一些小块&#xff0c;并通过 Internet 进行传输。在 TCP 中&#xff0c;数据被分割成一些称为 TCP 报文段&#xff08…

JetBrains 公布 WebStorm 2023.2 路线图

JetBrains 已公布了 WebStorm 2023.2 版本的路线图&#xff0c;以便用户可以率先了解到官方的规划以及能够预览一下未来能够用上的新功能。 主要聚焦于以下内容&#xff1a; 稳定的新 UI。这是此版本中的优先事项之一。CSS 嵌套支持。WebStorm 2023.2 计划将添加对 CSS 嵌套功能…

TensorRT:自定义插件学习与实践 002:实现GELU

代码连接:https://github.com/codesteller/trt-custom-plugin TensorRT版本的选择 教程代码对应的版本TensorRT-6.0.1.8,我尝试使用TensorRT-7.2.3.4也能通过编译 set_ifndef(TRT_LIB /usr/local/TensorRT-7.2.3.4/lib) set_ifndef(TRT_INCLUDE /usr/local/TensorRT-7.2.3.4…

是不是在为 API 烦恼 ?好用免费的api接口大全呼之欲出

前期回顾 “ ES6 —— 让你的JavaScript代码从平凡到精彩 “_0.活在风浪里的博客-CSDN博客Es6 特性https://blog.csdn.net/m0_57904695/article/details/130408701?spm1001.2014.3001.5501 &#x1f44d; 本文专栏&#xff1a;开发技巧 先说本文目的&#xff0c;本文会分…

有效日志管理在软件开发和运营中的作用

作者&#xff1a;Luca Wintergerst, David Hope, Bahubali Shetti 当今存在的快速软件开发过程需要扩展和复杂的基础架构和应用程序组件&#xff0c;并且操作和开发团队的工作不断增长且涉及多个方面。 有助于管理和分析遥测数据的可观察性是确保应用程序和基础架构的性能和可靠…

JavaScript实现输入数值,判断是否为(任意)三角形的代码

以下为实现输入数值&#xff0c;判断是否为&#xff08;任意&#xff09;三角形的代码和运行截图 目录 前言 一、实现输入数值&#xff0c;判断是否为三角形 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 二、实现输入数值&#xff0c;判断是否为…

PLC模糊控制模糊PID(梯形图实现+算法分析)

博途PLC的模糊PID控制详细内容请查看下面的博客文章: Matlab仿真+博途PLC模糊PID控制完整SCL源代码参考(带模糊和普通PID切换功能)_博途怎么实现模糊pid_RXXW_Dor的博客-CSDN博客模糊PID的其它相关数学基础,理论知识大家可以参看专栏的其它文章,这里不再赘述,本文就双容…

01背包问题个人剖析

背包问题 文章目录 背包问题1 01背包问题1.1 问题阐述1.2 问题分析 背包问题中我最初的一些疑惑 1 01背包问题 我参考了文献背包九讲。https://github.com/tianyicui/pack/raw/master/V2.pdf 背包九讲的作者是ACM大牛崔天翼。 1.1 问题阐述 有 N N N件物品和一个容量为 V V …

Java程序猿搬砖笔记(十二)

文章目录 PostConstruct注解Mybatis的mapper-locations配置JsonFormat实现原理IDEA String Manipulation插件使用及设置快捷键在Windows中测试服务器端口是否开放Centos开放端口Nginx常用配置详解Nginx里面的路径定位关键词root、aliaszuul里面的prefix 和 strip-prefix学习解决…

【三十天精通Vue 3】第二十四天 Vue3 移动端适配和响应式布局

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、 移动端适配概述1.1 为什么需要移动端适配&#xff1f;1.…

3.5 并行存储器

学习步骤&#xff1a; 如果我要学习并行存储器&#xff0c;我会采取以下几个步骤&#xff1a; 了解并行存储器的基本概念和原理。学习并行存储器的前提是要对存储器的基本原理有所了解&#xff0c;包括存储器的分类、工作原理、读写时序等。 学习并行存储器的特点和应用。并行…

Java每日一练(20230502)

目录 1. 二叉搜索树的最近公共祖先 &#x1f31f;&#x1f31f; 2. 随机分组问题 &#x1f31f; 3. K 个一组翻转链表 &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练…

Vue 框架入门介绍

前言 前端时间工作任务没有那么忙&#xff0c;在技术总监沟通中他认为我自己花点时间做技术扩展&#xff0c;由于项目中用到前端部分功能&#xff0c;框架用的是Vue&#xff0c;本身项目中和前端同时接触比较多&#xff0c;而且公司有现成的项目可以供我去练习&#xff0c;所以…

存储资源调优技术——SmartMigration智能数据迁移技术

目录 基本概念 工作原理 注意事项 基本概念 智能数据迁移技术是业务迁移的关键技术 在不中断主机业务的情况下&#xff0c;实现源LUN上的业务完整--业务相关的所有数据 迁移到目标LUN上 工作原理 业务数据同步 创建SmartMigration&#xff0c;源LUN和目标LUN之间建立Pair关系&a…

RabbitMq、Kafka、RocketMq整理

MQ的主要作用:异步提高性能、解耦提高扩展性、削峰。 一、常见中间件对比 Kafka、RocketMq和RabbitMq最大的区别就是:前两个是分布式存储。 1.1、ActiveMq 优点:1)完全支持jms规范的消息中间件 ,2)提供丰富的api, 3)多种集群构建模式。 缺点:)在高并发的场景下,性能可…

计算机视觉——day88 读论文:基于驾驶员注意视野的交通目标检测与识别

基于驾驶员注意视野的交通目标检测与识别 II. RELATED WORKSA. 通用对象检测B. 交通标志检测与识别C. 车辆检测D.行人检测E. 交通灯检测 III. PROPOSED METHODA. The RoadLAB DatasetB. 驾驶员注视定位C. 目标检测阶段模型A模型B D.数据扩充E.综合检测结果F.物体识别阶段 IV. 实…

【Git】Git(分布式项目管理工具)在Windows本地/命令行中的基本操作以及在gitee中的操作,使用命令行、window,进行提交,同步,克隆

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

​【五一创作】基于mysql关系型实现分布式锁

看完该文预计用时&#xff1a;15分钟 看之前应具体的技术栈&#xff1a;springboot mysql nginx&#xff08;了解即可&#xff09; 目录 0.写在前面 1. 从减库存聊起 1.1. 环境准备 1.2. 简单实现减库存 1.3. 演示超卖现象 1.4. jvm锁问题演示 1.4.2. 原理 1.5. 多服务问…

Linux CentOS本地搭建Web站点,并实现公网访问

文章目录 前言1. 本地搭建web站点2. 测试局域网访问3. 公开本地web网站3.1 安装cpolar内网穿透3.2 创建http隧道&#xff0c;指向本地80端口3.3 配置后台服务 4. 配置固定二级子域名5. 测试使用固定二级子域名访问本地web站点 转载自cpolar文章&#xff1a;Linux CentOS本地搭建…

ChatGPT提示词工程(六):Expanding扩展

目录 一、说明二、安装环境三、扩展&#xff08;Expanding&#xff09;1. 自定义自动回复客户电子邮件2. 提醒模型使用客户电子邮件中的详细信息3. 参数 temperature 一、说明 这是吴恩达 《ChatGPT Prompt Engineering for Developers》 的课程笔记系列。 本文是第六讲的内容…