【C++初阶7-string实现】xxx坐享其成,xxx苦不堪言

news2024/11/19 22:46:10

前言

本期分享C++string类的模拟实现(参考SGI 30实现),不套类模版,降低学习成本, 进一步加深理解。

属性介绍

名称具体成员
动态字符数组char* _s
存储有效数据的容量size_t capacity
有效数据的个数size_t size
nposstatic const size_t npos

方法介绍

要实现的接口大概以下:

  • 迭代器
    • begin
    • end
  • 基本成员函数
    • constructor(构造)
    • destructor(析构)
    • copy constructor(拷贝构造)
    • assignment operator overload(赋值运算符重载)
  • capacity
    • capacity
    • size
    • reserve
    • resize
    • empty
  • modify
    • push_back
    • pop_back
    • append
    • insert
    • erase
    • find
    • operator +=
    • clear
    • swap
    • c_str
  • access
    • operator []
  • 非成员函数重载
    • operator >>
    • operator <<

实现

声明

避免和库的冲突,我们需要实现在自己的命名空间内:

//string.hpp
#include <iostream>
using std:: cin;
using std:: cout;
using std:: endl;
using std:: ostream;
using std:: istream;
using std:: swap;

namespace bacon
{

class string
{
    friend ostream& operator<<(ostream& out, const string& s);
    friend istream& operator>>(istream& in, bacon::string& s);
    
public:
    typedef char* iterator;
    
    //迭代器
    iterator begin() const;//闭
    iterator end() const;//开
    
    //构造
    //用char*构造
    string(const char* s = "");
    //拷贝构造
    string(const string& s);
    //析构
    ~string();
    //赋值重载
		//string& operator=(const string& s);//传统写法
    string& operator=(string s);//现代写法
    

    
    //capacity
    size_t size() const;
    size_t capacity() const;
    void reserve(size_t n);
    void resize(size_t n, char c = '\0');
    bool empty() const;
    
    
    //modify
    void push_back(char ch);
    void pop_back();
    void append(const char* s);
    
    string& insert(size_t pos, char ch);
    string& insert(size_t pos, const char* s);
    string& erase(size_t pos, size_t n = npos);
    size_t find(const char& ch, size_t pos = 0) const;
    size_t find(const char* s, size_t pos = 0) const;
    
    string& operator+=(char ch);
    string& operator+=(const char* s);
    string& operator+=(const string& s);
    
    void clear();
    void swap(string& s);
    const char* c_str() const;
    
    //access
    char& operator[](size_t i);
    const char& operator[](size_t i) const;
    
private:
    char* _s;
    size_t _size;//有效数据个数,_s[size]就是\0的位置
    size_t _capacity;//存放有效数据的空间大小,开辟时多开一个预留给\0 
    static const size_t npos = -1;
};

}
  • 构造函数要给缺省值,来覆盖空string的情况

    • “\0”:字符串,有俩\0

    • “”:字符串,有一个\0

  • 类外实现,使用类内的东西需要指定类域,如类型、函数名等。

  • 缺省参数仅声明时给即可。

【接口需要实现const还是非const版本,或是二者都要?】

从功能上分辨:

  • 功能只需要读:const
  • 功能只需要写:非const
  • 功能读写都需要:cosnt + 非const

基本成员函数

//---------------迭代器---------------
string::iterator string::begin() const
{
    return _s;
}

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

//---------------构造---------------
string::string(const char* s)
{
    //开空间
    _size = strlen(s);
    _capacity = _size;
    _s = new char[_capacity + 1];//有效数据空间 + 预留\0空间
    
    //赋值
    strcpy(_s, s);
}

//---------------析构---------------
string::~string()
{
    delete[] _s;
    _s = nullptr;
    _size = _capacity = 0;
}

//---------------拷贝构造---------------
//传统写法
//string::string(const string& s)
//{
//      _s = new char[s._capacity + 1];
//    _capacity = s._capacity;
//
//    strcpy(_s, s._s);
//    _size = s._size;
//}

//现代写法:复用构造
string::string(const string& s)
    :_s(nullptr),
     _size(0),
     _capacity(0)
{
    string tmp(s._s);//构造(开了自己的空间)
    swap(tmp);
}

//---------------赋值重载---------------
//传统写法
//string& string::operator=(const string& s)
//{
//    if(this != &s)
//    {
//        char* tmp = new char[s._capacity + 1];
//        strcpy(tmp, s._s);
//
//        delete[] _s;
//        _s = tmp;
//        _size = s._size;
//        _capacity = s._capacity;
//    }
//    return *this;
//}

//现代写法
string& string::operator=(string s)//s是实参的一份临时拷贝,给已存在实参初始化:调用拷贝构造
{
    swap(s);
    return *this;
}

成员_s是动态字符数组,由于后续需要释放,不能浅拷贝(直接按字节拷贝),否则指向同一块空间,析构时先后释放两次,出错。

在这里插入图片描述

这也代表涉及拷贝的 拷贝构造 和 赋值重载 都需要实现深拷贝(开辟自己的空间)

【拷贝构造的传统写法和现代写法】

开空间最好不要用C的内存函数,毕竟我们在玩自定义类型,虽然当前的char*可以用,但容易造成“自定义类型忘记初始化”的问题,且往后实现别的也不行了,new才是真理。

传统写法:不复用
string::string(const string& s)
{
  	_s = new char[_capacity + 1];
    _size = s._size;
    _capacity = s._capacity;

    strcpy(_s, s._s);
}

在这里插入图片描述

现代写法:复用构造
string::string(const string& s)
    :_s(nullptr),
     _size(0),
     _capacity(0)
{
    string tmp(s._s);//构造
    swap(tmp);
}
  • 必须将_s置空,否则tmp析构野指针出错
  • 复用构造,tmp深拷贝:开辟空间,拷贝内容
  • 交换成员,_s坐享其成,啥也不用干就拿到空间和数据,tmp苦不堪言,不仅要去开空间拷贝,还要帮_s释放空间
【为什么要自己实现swap?】
void string::swap(string& s)
{
    std::swap(_s, s._s);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

template <class T> void swap ( T& a, T& b )
{
  T c(a); a=b; b=c;
}

前者巧妙交换成员,后者粗暴交换对象, 多了三次深拷贝。

【赋值运算符重载的传统写法和现代写法】

传统写法:不复用
string& string::operator=(const string& s)
{
    if(this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._s);

        delete[] _s;
        _s = tmp;
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}
现代写法:复用拷贝构造
string& string::operator=(string s)//s是实参的一份临时拷贝,调用拷贝构造
{
    swap(s);
    return *this;
}
  • 复用拷贝构造:传值传参,调用拷贝构造产生临时对象,s有自己的空间
  • 交换的思路和上面一样,_s坐享其成,tmp苦不堪言

现代写法的思路:找工具人。

拷贝构造的工具人是自己构造的,赋值重载的工具人是路边拉来的。


但是我们测试的时候还得调试来看,不方便,我们实现一下打印相关的接口:

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

//---------------access---------------
char& string::operator[](size_t i)//可修改
{
    assert(i < _size);
    return _s[i];
}

const char& string::operator[](size_t i) const//不可修改
{
    assert(i < _size);
    return _s[i];
}

//---------------流插入运算符重载---------------
ostream& operator<<(ostream& out, const bacon::string& s)
{
    for(size_t i = 0 ; i<s.size(); i++)
    {
        out << s[i];
    }
    return out;
}
#include "string.h"
using namespace::bacon;

void t1()
{
    string s1("bacon's string");//构造
    string s2(s1);//拷贝构造
    string s3 = s1;//赋值重载
    
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    
    //迭代器 + []重载遍历
    bacon::string::iterator it1 = s1.begin();
    while(it1 != s1.end())
    {
        ++(*it1);
        ++it1;
    }
    cout << s1 << endl;
    
    //范围for遍历
    for(auto& e : s1)
    {
        --e;
    }
    cout << s1 << endl;
    
}//析构
 
int main(int argc, const char * argv[])
{
    t1();
    return 0;
}
bacon's string
bacon's string
bacon's string
cbdpo(t!tusjoh
bacon's string

可以看到,有了begin和end方法,范围for也能用了——但当我们将begin的名字改成Begin

在这里插入图片描述

说begin未定义,那肯定就是调用了begin,底层实际上很像宏或实例化。


capacity

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

size_t string::capacity() const
{
    return _capacity;
}

void string::reserve(size_t n)
{
    if(n > _capacity)
    {
        char* tmp = new char[n + 1];//预留\0
        _capacity = n;
        
        strcpy(tmp, _s);
        delete[] _s;
        _s = tmp;
    }
}

void string::resize(size_t n, char c)
{
    //n < size:删除数据
    //n > size:多出的部分填充c(若需要,扩容)
    if(n > _size)
    {
        if(n > _capacity)
            reserve(n + 1);
        
        for(size_t i = _size; i < n; ++i)
            _s[i] = c;
    }
    _size = n;
    _s[_size] = '\0';
}

bool string::empty() const
{
    return _size == 0;
}
  • reserve
    • 不缩容:开销大。**C++中已经不再用C的内存函数,否则容易导致“自定义类型未初始化”的问题。没有realloc这种函数,我们每次扩容都是异地扩——开空间,拷贝,销毁。
    • 当知道将来要用多大空间,可以提前reserve,免得频繁异地扩。如s1将来要用100个char的空间,s1.reserve(100);
  • n < size:删除数据
    n > size:多出的部分填充c(若需要,扩容)

modify

几个轻松愉快的接口:

void string::push_back(char ch)
{
    if(_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2*_capacity);
    
    _s[_size++] = ch;
    _s[_size] = '\0';
}

void string::pop_back()
{
    assert(!empty());
    
    _s[--_size] = '\0';
}

void string::append(const char* s)
{
    size_t len = strlen(s);
    
    if(_size + len > _capacity)
        reserve(_size + len + 1);//字符串长度不定,二倍扩容不一定够(手动计算)
    
    strcpy(_s + _size, s);//覆盖原\0,strcpy会把src的\0一起拷到dst
    _size += len;
}

老伙计insert

插入字符

若我们想

在"helloworld"的5下标位置,插入’ ‘,需要先挪动腾出位置,再插入’ ’

在这里插入图片描述

string& string::insert(size_t pos, char ch)
{
    assert(pos <= _size);//pos == _size:尾插
    if(_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2*_capacity);

    //挪动数据,腾出1个空间(需挪\0)
    //[pos, _size] ==> [pos + 1, _size + 1]
    size_t end = _size;
    while(end >= pos)
    {
      	//[pos+1, _size+1] = [pos, _size]
        _s[end + 1] = _s[end];
        --end;
    }

    _s[pos] = ch;
    ++_size;

    return *this;
}

上面的实现有没有什么问题?

有的,比如要头插的时候:

当pos == 0,对于

while(end >= pos)
和
_s[end + 1] = _s[end];

期望的最后一次挪动:

end = 0;
_s[1] = _s[0];

但此次挪动后,size_t的end自减始终>=0,无法跳出循环。说白了就是,

碰到了小于0的位置,使得size_t影响逻辑。

怎么解决呢?

【1.硬干:强制类型转换】
string& string::insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if(_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2*_capacity);

    //方案1:end改成int类型,但还是不能直接解决
  	//int会向size_t提升(二者比较了,精度低==>精度高),pos也要强转成int
    int end = _size;
    while(end >= (int)pos)
    {
      	//挪动数据
   	   	//[pos+1, _size+1] = [pos, _size] 腾出1个位置
        _s[end + 1] = _s[end];
        --end;
    }

    _s[pos] = ch;
    ++_size;

    return *this;
}

当pos == 0,对于

while(end >= (int)pos)
和
_s[end + 1] = _s[end];

期望的最后一次挪动:

end = 0;
_s[1] = _s[0];

此次挪动后,int的end自减<0,跳出循环,符合效果。

但不符合原意了:作为下标的end和pos不会小于0,其类型就应该是size_t。

【2.巧妙避开:不碰<0的位置】
string& string::insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if(_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2*_capacity);

    //方案2:end不设计成拿数据的位置,而设计成放数据的位置,避免访问<0的位置
    size_t end = _size + 1;
    while(end > pos)
    {
      	//挪动数据
      	//[pos, _size+1] = [pos, _size] 腾出1个位置
        _s[end] = _s[end - 1];
        --end;
    }

    _s[pos] = ch;
    ++_size;

    return *this;
}

当pos == 0,对于

while(end > pos)
和
_s[end] = _s[end - 1];

期望的最后一次挪动:

end = 1;
_s[1] = _s[0];

此次挪动后,size_t的end自减==0,跳出循环。

这样一来,成功挪动, 又没触碰到危险的地方。

插入字符串

在这里插入图片描述

string& string::insert(size_t pos, const char* s)
{
    assert(pos <= _size);
    reserve(_capacity == 0 ? 4 : 2*_capacity);
    
    size_t len = strlen(s);
    size_t end = _size + len;
    while(end >= pos+len)//最后一次需要_s[pos+len] = _s[pos]
    {
      	//挪动数据,腾出len个空间(需挪\0)
        //[pos+len, _size+len] = [pos, _size] len个位置
        _s[end] = _s[end - len];
        --end;
    }
    
    memcpy(_s + pos, s, len);//用strcpy会拷\0,不是想要的效果
    _size += len;
    
    return *this;
}

但这样还是有坑:但pos==0 && len == 0,对空string插入空串,–end又是“size_t无法小于0打破循环”的问题。

同样避开这个场景:

while(end > pos + len - 1)
{
    _s[end] = _s[end - len];
    --end;
}

对于这种控制数组下标的问题,有一个办法:

需求 + 挪动方式 + 始末下标

比如插入字符串,

  • 需求:pos到pos+len的位置需要腾出来给s,也就代表[pos, _size]要移动到[pos+len, _size+len]

  • 挪动方式:总体向后移动,从后开始,给一个end作为放数据的位置,则end-len为拿数据的位置

  • 始末下标:依照挪动方式可以确定,

    end作为放数据的位置要从_size+len开始,最后一次需要_s[pos+len] = _s[pos],即

    end∈[_size + len, pos + len]

简单说,

  • [pos, _size]要移动到[pos+len, _size+len]

  • end为放数据的位置,end-len为拿数据的位置

  • end∈[_size + len, pos + len]

代码对应以上三点实现

写出while ==> 写出挪动方式 ==> 控制下标

就完事。

对于下标控制的越界问题:

把各个下标能取到的奇怪值都代一下

这里“对空string插入空串”就是代了pos和len都是0的情况。

老伙计erase

string& string::erase(size_t pos, size_t n)
{
    assert(pos < _size);
    
    if(n == npos || pos + n >= _size)
    {
        _s[pos] = '\0';
        _size = pos;
    }
    else
    {
        //[1, 2, 3, 4, 5] pos = 1, n = 2
        //[1, 4, 5]
        //[pos+n, _size] ==> [pos, _size-n]
        
//        size_t begin = pos;
//        while(begin <= _size - n)
//        {
//            _s[begin] = _s[begin + n];
//            ++begin;
//        }
        
        strcpy(_s + pos, _s + pos + n);
        _size -= n;
    }
    
    return *this;
}
  • 如果后面都要删,直接放\0
  • 否则老实挪动覆盖
size_t string::find(const char& ch, size_t pos) const
{
    assert(pos < _size);
    
    for(size_t i = 0; i < _size; ++i)
    {
            if(_s[i] == ch)
            return i;
    }
    
    return -1;
}

size_t string::find(const char* s, size_t pos) const
{
    assert(pos < _size);
    
    const char* ret = strstr(_s + pos, s);
    
    if(nullptr == ret)
        return -1;
    else
        return ret - _s;//元素个数/下标
}

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

void string::swap(string& s)
{
    std::swap(_s, s._s);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

const char* string::c_str() const
{
    return _s;
}

operator overload

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

string& string::operator+=(const char* s)
{
    append(s);
    return *this;
}

string& string::operator+=(const string& s)
{
    append(s.c_str());
    return *this;
}

access

//---------------access---------------
char& string::operator[](size_t i)//可修改
{
    assert(i < _size);
    return _s[i];
}

const char& string::operator[](size_t i) const//不可修改
{
    assert(i < _size);
    return _s[i];
}

非成员函数运算符重载

//---------------非成员函数运算符重载---------------
ostream& operator<<(ostream& out, const bacon::string& s)
{
    for(size_t i = 0 ; i<s.size(); i++)
    {
        out << s[i];
    }
    return out;
}

istream& operator>>(istream& in, bacon::string& s)
{
    s.clear();
    
    //缓冲区
    char buf[128] = {'\0'};
    
    size_t cnt = 0;
    char ch = in.get();
    while(ch != ' ' && ch != '\n' && ch != '\t') //遇到空白字符停下
    {
        //缓冲区满了就追加
        if(cnt == 127) //考虑\0
        {
            s += buf;
            cnt = 0;
        }
        
        buf[cnt++] = ch;
        
        ch = in.get();
    }
    
    //缓冲区内若还有救追加
    if(cnt > 0)
    {
        buf[cnt] = '\0';
        s += buf;
    }
    
    return in;
}

整体代码

//
//  string.h
//  string_3
//
//  Created by Bacon on 2022/12/6.
//

#ifndef string_h
#define string_h

#include <iostream>
using std:: cin;
using std:: cout;
using std:: endl;
using std:: ostream;
using std:: istream;
using std:: swap;

namespace bacon
{

class string
{
    friend ostream& operator<<(ostream& out, const string& s);
    friend istream& operator>>(istream& in, bacon::string& s);
    
public:
    typedef char* iterator;
    
    //迭代器
    iterator begin() const;
    iterator end() const;
    
    //构造
    //用char*构造
    string(const char* s = "");
    //拷贝构造
    string(const string& s);
    //析构
    ~string();
    //赋值重载
//    string& operator=(const string& s);//传统写法
    string& operator=(string s);//现代写法
    

    
    //capacity
    size_t size() const;
    size_t capacity() const;
    void reserve(size_t n);
    void resize(size_t n, char c = '\0');
    bool empty() const;
    
    
    //modify
    void push_back(char ch);
    void pop_back();
    void append(const char* s);
    
    string& insert(size_t pos, char ch);
    string& insert(size_t pos, const char* s);
    string& erase(size_t pos, size_t n = npos);
    size_t find(const char& ch, size_t pos = 0) const;
    size_t find(const char* s, size_t pos = 0) const;
    
    string& operator+=(char ch);
    string& operator+=(const char* s);
    string& operator+=(const string& s);
    
    void clear();
    void swap(string& s);
    const char* c_str() const;
    
    //access
    char& operator[](size_t i);
    const char& operator[](size_t i) const;
    
private:
    char* _s;
    size_t _size;//size就是\0的位置
    size_t _capacity;
    static const size_t npos = -1;
};



//类外实现,使用类内的东西需要指定类域,如类型、函数名等。

//---------------迭代器---------------
string::iterator string::begin() const
{
    return _s;
}

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

//---------------构造---------------
string::string(const char* s)
{
    //开空间
    _size = strlen(s);
    _capacity = _size;
    _s = new char[_capacity + 1];//预留\0空间
    
    //赋值
    strcpy(_s, s);
}

//---------------析构---------------
string::~string()
{
    delete[] _s;
    _s = nullptr;
    _size = _capacity = 0;
}

//---------------拷贝构造---------------
//传统写法
//string::string(const string& s)
//{
//      _s = new char[s._capacity + 1];
//    _size = s._size;
//    _capacity = s._capacity;
//
//    strcpy(_s, s._s);
//}

//现代写法:复用构造
string::string(const string& s)
    :_s(nullptr),
     _size(0),
     _capacity(0)
{
    string tmp(s._s);//构造
    swap(tmp);
}

//---------------赋值重载---------------
//传统写法
//string& string::operator=(const string& s)
//{
//    if(this != &s)
//    {
//        char* tmp = new char[s._capacity + 1];
//        strcpy(tmp, s._s);
//
//        delete[] _s;
//        _s = tmp;
//        _size = s._size;
//        _capacity = s._capacity;
//    }
//    return *this;
//}

//现代写法
string& string::operator=(string s)//s是实参的一份临时拷贝,调用拷贝构造
{
    swap(s);
    return *this;
}

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

size_t string::capacity() const
{
    return _capacity;
}

void string::reserve(size_t n)
{
    if(n > _capacity)
    {
        char* tmp = new char[n + 1];//预留\0
        _capacity = n;
        
        strcpy(tmp, _s);
        delete[] _s;
        _s = tmp;
    }
}

void string::resize(size_t n, char c)
{
    //n < size:删除数据
    //n > size:多出的部分填充c(若需要,扩容)
    if(n > _size)
    {
        if(n > _capacity)
            reserve(n + 1);
        
        for(size_t i = _size; i < n; ++i)
            _s[i] = c;
    }
    _size = n;
    _s[_size] = '\0';
}

bool string::empty() const
{
    return _size == 0;
}

//---------------modify---------------
void string::push_back(char ch)
{
    if(_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2*_capacity);
    
    _s[_size++] = ch;
    _s[_size] = '\0';
}

void string::pop_back()
{
    assert(!empty());
    
    _s[--_size] = '\0';
}

void string::append(const char* s)
{
    size_t len = strlen(s);
    
    if(_size + len > _capacity)
        reserve(_size + len + 1);//字符串长度不定,二倍扩容不一定够(手动计算)
    
    strcpy(_s + _size, s);//覆盖原\0,strcpy会把src的\0一起拷到dst
    _size += len;
}

//string& string::insert(size_t pos, char ch)
//{
//    assert(pos <= _size);//pos == _size:尾插
//    if(_size == _capacity)
//        reserve(_capacity == 0 ? 4 : 2*_capacity);
//
//    //挪动数据,腾出1个空间(需挪\0)
//    //[pos, _size] ==> [pos + 1, _size + 1]
//    size_t end = _size;
//    while(end >= pos)
//    {
//        _s[end + 1] = _s[end];
//        --end;
//    }
//
//    _s[pos] = ch;
//    ++_size;
//
//    return *this;
//}
当pos == 0:end始终>=0,死循环


string& string::insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if(_size == _capacity)
        reserve(_capacity == 0 ? 4 : 2*_capacity);

    //[pos, _size] ==> [pos + 1, _size + 1]
    //方案1:end改成intl类型,但是还是不能直接解决,int会向size_t提升(二者比较了,精度低==>精度高)
    //pos也要强转成int,但这样不符合原意了
//    int end = _size;
    while(end >= pos)
//    while(end >= (int)pos)
//    {
        _s[0] = _s[-1];
//        _s[end + 1] = _s[end];
//        --end;
//    }

    //方案2:end不设计成拿数据的位置,而设计成放数据的位置
    size_t end = _size + 1;
    while(end > pos)
    {
        _s[end] = _s[end - 1];
        --end;
    }

    _s[pos] = ch;
    ++_size;

    return *this;
}

string& string::insert(size_t pos, const char* s)
{
    assert(pos <= _size);
    reserve(_capacity == 0 ? 4 : 2*_capacity);
    
    size_t len = strlen(s);
    //挪动数据,腾出len个空间(需挪\0)
    size_t end = _size + len;
//    while(end > pos + len - 1)
    while(end > pos + len - 1)
    {
        _s[end] = _s[end - len];
        --end;
    }
    
    memcpy(_s + pos, s, len);//用strcpy会拷\0,不是想要的效果
    _size += len;
    
    return *this;
}


string& string::erase(size_t pos, size_t n)
{
    assert(pos < _size);
    
    if(n == npos || pos + n >= _size)
    {
        _s[pos] = '\0';
        _size = pos;
    }
    else
    {
        //[1, 2, 3, 4, 5] pos = 1, n = 2
        //[1, 4, 5]
        //[pos+n, _size] ==> [pos, _size-n]
        
//        size_t begin = pos;
//        while(begin <= _size - n)
//        {
//            _s[begin] = _s[begin + n];
//            ++begin;
//        }
        
        strcpy(_s + pos, _s + pos + n);
        _size -= n;
    }
    
    return *this;
}


size_t string::find(const char& ch, size_t pos) const
{
    assert(pos < _size);
    
    for(size_t i = 0; i < _size; ++i)
    {
            if(_s[i] == ch)
            return i;
    }
    
    return -1;
}

size_t string::find(const char* s, size_t pos) const
{
    assert(pos < _size);
    
    const char* ret = strstr(_s + pos, s);
    
    if(nullptr == ret)
        return -1;
    else
        return ret - _s;//元素个数/下标
}

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

void string::swap(string& s)
{
    std::swap(_s, s._s);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

const char* string::c_str() const
{
    return _s;
}


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

string& string::operator+=(const char* s)
{
    append(s);
    return *this;
}

string& string::operator+=(const string& s)
{
    append(s.c_str());
    return *this;
}




//---------------access---------------
char& string::operator[](size_t i)//可修改
{
    assert(i < _size);
    return _s[i];
}

const char& string::operator[](size_t i) const//不可修改
{
    assert(i < _size);
    return _s[i];
}



//---------------非成员函数运算符重载---------------
ostream& operator<<(ostream& out, const bacon::string& s)
{
    for(size_t i = 0 ; i<s.size(); i++)
    {
        out << s[i];
    }
    return out;
}

istream& operator>>(istream& in, bacon::string& s)
{
    s.clear();
    
    //缓冲区
    char buf[128] = {'\0'};
    
    size_t cnt = 0;
    char ch = in.get();
    while(ch != ' ' && ch != '\n' && ch != '\t') //遇到空白字符停下
    {
        //缓冲区满了就追加
        if(cnt == 127) //考虑\0
        {
            s += buf;
            cnt = 0;
        }
        
        buf[cnt++] = ch;
        
        ch = in.get();
    }
    
    //缓冲区内若还有救追加
    if(cnt > 0)
    {
        buf[cnt] = '\0';
        s += buf;
    }
    
    return in;
}

}


#endif /* string_h */


今天的分享就到这里啦,不足之处望请斧正!

这里是培根的blog,期待与你共同进步

下期见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/82042.html

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

相关文章

【OpenCV 例程 300篇】251. 特征匹配之暴力匹配

『youcans 的 OpenCV 例程300篇 - 总目录』 【youcans 的 OpenCV 例程 300篇】251. 特征匹配之暴力匹配 特征匹配是特征检测和特征描述的基本应用&#xff0c;在在图像拼接、目标识别、三维重建等领域的应用非常广泛。 基于特征描述符的特征点匹配是通过对两幅图像的特征点集合…

基于统一空间方法的动态切换拥挤(DSC)DSC-MOAGDE算法附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

Python编程零基础如何逆袭成为爬虫实战高手之《WIFI破解》(甩万能钥匙十条街)爆赞爆赞~

导语 Hello&#xff0c;大家好呀&#xff01;我是木木子吖&#xff5e; 一个集美貌幽默风趣善良可爱并努力码代码的程序媛一枚。 听说关注我的人会一夜暴富发大财哦~ &#xff08;哇哇哇 这真的爱&#x1f60d;&#x1f60d;&#xff09; 所有文章完整的素材源码都在&#…

dreamweaver网页大作业 我的家乡——南京玄武湖旅游攻略(4页) 学生网页设计作业源码

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

Java项目写好了,如何部署上线?看这篇文章吧

嗨&#xff0c;各位小伙伴大家好&#xff0c;你有没有想壹哥呀&#xff1f;前几天有小伙伴给壹哥留言&#xff0c;说自己的项目写好了&#xff0c;想把项目部署到服务器上&#xff0c;这个该怎么实现呢&#xff1f;那么针对这个问题&#xff0c;今天壹哥就带大家走一遍完整的项…

【云计算与大数据计算】大数据物理、集成、安全架构及阿里云飞天系统架构讲解(超详细)

一、物理架构 物理架构 - 企业大数据系统的各层次系统最终要部署到主机节点中,这些节点通过网络连接成 为一个整体,为企业的大数据应用提供物理支撑 &#xff0c;企业大数据系统由多个逻辑层组成&#xff0c;多个逻辑层可以映射到一个物理节点上&#xff0c;也可以映射到多个物…

nacos--基础--1.1--理论--介绍

nacos–基础–1.1–理论–介绍 1、介绍 是阿里的一个开源产品致力于帮助您发现、配置和管理微服务 1.1、参考资料 https://nacos.io/zh-cn/docs/what-is-nacos.html1.2、功能 服务发现中心&#xff1a;动态服务发现服务注册中心&#xff1a;管理注册服务服务配置中心&#…

行为管理(锐捷智慧教室)

大家好&#xff0c;我是小杜&#xff0c;被师傅“强制”休息两天&#xff0c;感觉整个人都升华了&#xff0c;精神满满的&#xff0c;看来还是需要劳逸结合&#xff0c;一味的高强度精神亢奋的情况下其实是事倍功半......。随着学习的深入&#xff0c;师傅也带着我对公司的业务…

PCIe Dma coherent

目录 1.PCIe Dma coherent前言 2.DMA与Cache 的一致性 2.1一致性问题 2.2Coherent DMA buffers 一致性 2.3DMA Streaming Mapping 流式DMA映射 2.4dma_alloc_coherent的例外 2.5SMMU | IOMMU 3.Linux 内核中 DMA 及 Cache 分析 3.1arm 3.2DMA ZONE 3.3DMA ZONE 的内…

南昌市-中安协-安防工程企业设计施工维护能力评价

安防工程企业设计、施工、维护能力评价是指中国安全防范产品行业协会从本行业实际出发&#xff0c;制定评价标准和实施办法&#xff0c;确定其能力等级&#xff0c;并颁发证书的活动。安防工程企业&#xff08;以下称企业&#xff09;设计、施工、维护能力&#xff08;以下称能…

以太网 传统STP生成树简介、STP工作方式简单介绍

2.10.0 以太网 传统STP生成树&#xff08;简介、工作方式&#xff09; 作用&#xff1a; STP&#xff08;Spanning Tree Prortoco&#xff09;生成树协议&#xff0c;它的出现解决了交换机网络环路的问题。 交换机网络中收到BUM帧的时候&#xff0c;将会进行泛洪的操作&…

CSS 父选择器,:has()

在CSS Selectors 4规范中&#xff0c;CSS 引入了一个名为 的新选择器:has()&#xff0c;它最终让我们可以选择父级。这意味着我们可以选择具有特定元素的父元素。目前Safari和Chrome105已经支持。 父选择器如何在 CSS 中工作 在 CSS 中&#xff0c;如果我们想要选择某些东西&a…

【Pygame小游戏】史上最全:《唐诗三百首》合集,每一首都是精华,果断收藏~(学诗+锻炼记忆+Python诗句填空小程序上线啦)

前言 岁岁年龄岁岁心&#xff0c;不负时光不负卿 哈喽&#xff01;我是你们的栗子同学&#xff0c;今天给大家来点儿有趣的—— 有句话说&#xff1a;“读史使人明智&#xff0c;读诗使人灵秀。”唐诗本来就是中国文化的绚丽瑰宝&#xff0c;是每个人都 该学习的人生必修课。…

蚂蚁三面遭分布式血虐,意外收获史诗级分布式笔记手册,从基础到进阶收获满满

学习分布式系统设计的难题在于&#xff0c;这个过程存在一个环境障碍&#xff0c;工作中只有一些中大规模的互联网企业&#xff0c;才有开发大规模分布式系统的场景和需求。这就造成目前只有少数身在一线互联网公司的架构师和开发者&#xff0c;才有机会接触并掌握分布式系统设…

Spring Security入门学习

认识Spring Security Spring Security 是为基于 Spring 的应用程序提供声明式安全保护的安全性框架。Spring Security 提供了完整的安全性解决方案&#xff0c;它能够在 Web 请求级别和方法调用级别处理身份认证和授权。因为基于 Spring 框架&#xff0c;所以 Spring Security…

树莓派Pico开发板与大功率MOSFET/IGBT器件驱动控制24V直流电机技术实践

摘要&#xff1a;本文在介绍MOSFET器件和IGBT器件作为电子开关基本原理的基础上&#xff0c;讲述了Pico与MOSFET&IGBT器件驱动控制24V直流电机硬件接口技术&#xff0c;最后给出了Pico开发板GP15端口控制24V直流电机启停的MicroPython测试程序。 一、实验设备和元器件清单 …

[附源码]Nodejs计算机毕业设计基于Java的智慧停车软件Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

React 入门:实战案例 TodoList 对组件的 props 进行限制

文章目录安装 prop-types 库给组件的 props 添加限制给 Header 组件添加限制给 List 组件添加限制给 Item 组件添加限制验证 props 限制完整代码Header 组件完整代码List 组件完整代码Item 组件完整代码本文实现对组件的 props 进行属性的类型和必要性的限制。为什么要对 props…

双十二买什么数码产品比较值?入手超值的数码好物盘点

2022年双十二正式开启倒计时模式&#xff0c;最近看到很多人问什么数码产品值得入手。现如今&#xff0c;数码产品已经贯彻在我们生活的方方面面&#xff0c;在此&#xff0c;我来给大家盘点几款入手超值的数码好物&#xff0c;可以当个参考。 一、蓝牙耳机 推荐产品&#xf…

JUC(6) : LockSupport | 优雅的线程通信工具

一、前言 前文介绍了 CompletableFuture 和 线程池的几种对线程的管理方式后&#xff0c;本质上&#xff0c;通过这些工具&#xff0c;可以直接帮我们对线程进行很好的管理和运作&#xff0c;什么时间需要启动哪个线程&#xff0c;以及线程的执行顺序等。毕竟&#xff0c;线程…