前言

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

属性介绍

名称 具体成员
动态字符数组 char* _s
存储有效数据的容量 size_t capacity
有效数据的个数 size_t size
npos static 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
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);//现代写法//capacitysize_t size() const;size_t capacity() const;void reserve(size_t n);void resize(size_t n, char c = '\0');bool empty() const;//modifyvoid 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;//accesschar& 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 <iosteam>
using namespace std;
#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也要强转成intint 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;elsereturn 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;
}

其他两种结构

vs2019下的结构是这样的:

  • char _buf[16] : <=15时使用
  • char* _ptr : >15时使用
  • size_t size
  • size_t reserve: 15

正常对象内都是只存属性,但这里采用了空间换时间的方法:
数据少就存string自己身上,数据大才去另开空间

g++的结构是这样的:
仅有一个指针,指向的堆空间内有如下字段

  • size_type _M_length
  • size_type _M_capacity
  • _Atomic_word _M-refcount

看一个现象:

#include <iostream>
#include <stdio.h>
#include <string>using namespace std;int main()
{string s1 = "1111";string s2(s1);cout << sizeof s1 << endl;cout << sizeof s2 << endl;printf("%p\n", s1.c_str());printf("%p\n", s2.c_str());}
[bacon@VM-12-5-centos 2]$ ./a.out
8
8
0x1ca0c38
0x1ca0c38

其实这种结构,拷贝构造时默认浅拷贝。
多次析构怎么解决?
引用计数解决,析构只减计数,计数为1时才析构
这块共享的数据被写入时,就会发生写时拷贝

int main()
{string s1 = "1111";string s2(s1);cout << sizeof s1 << endl;cout << sizeof s2 << endl;printf("%p\n", s1.c_str());printf("%p\n", s2.c_str());//写:发生写时拷贝++s2[0];printf("%p\n", s1.c_str());printf("%p\n", s2.c_str());
}
[bacon@VM-12-5-centos 2]$ ./a.out
8
8
0x1ddcc38
0x1ddcc38
0x1ddcc38
0x1ddcc68

其实是一种拖延战术:不写就赚(不用拷贝),写了不亏(和直接拷贝一样)。


整体代码

//
//  string.h
//  string_3
//
//  Created by Bacon on 2022/12/6.
//#ifndef string_h
#define string_hnamespace 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);//现代写法//capacitysize_t size() const;size_t capacity() const;void reserve(size_t n);void resize(size_t n, char c = '\0');bool empty() const;//modifyvoid 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;//accesschar& 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;elsereturn 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,期待与你共同进步

下期见~

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

  1. 【C++ 初阶】string底层框架模拟实现

    目录 一. 前言 二. 浅拷贝与深拷贝优缺点 1. 浅拷贝 2. 深拷贝 3. 深拷贝现代版 4. 写时拷贝 三. string框架搭建 1. 框架定义 2. 构造函数 3. 析构函数 4. 赋值重载 ...

  2. 初级C++STL:初阶模板 | String的使用

    文章目录 初级模板简介 函数模板 模板参数的匹配原则 类模板 STL标准模板库 string string类对象的访问及遍历操作 string类对象的容量操作 string类对象的修改操作 +=.in ...

  3. 学习笔记:C++初阶【C++入门、类和对象、C/C++内存管理、模板初阶、STL简介、string、vector、list、stack、queueu、模板进阶、C++的IO流】

    文章目录 前言 一.C++入门 1. C++关键字 2.命名空间 2.1 C语言缺点之一,没办法很好地解决命名冲突问题 2.2 C++提出了一个新语法--命名空间 2.2.1 命名空间概念 2.2.2 ...

  4. 【初阶】unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流...

    [初阶]unity3d官方案例_太空射击SpacingShooter 学习笔记 显示分数时,如何让函数之间相互交流 一.关于 显示分数时,如何让函数之间相互交流 这是一个非常好的逻辑问题 1 思路:主 ...

  5. 《JavaEE初阶》HTTP协议和HTTPS

    <JavaEE初阶>HTTP协议和HTTPS 文章目录 <JavaEE初阶>HTTP协议和HTTPS HTTP协议是应用层协议: 使用Fiddler抓取HTTP请求和响应: F ...

  6. 【C初阶】C初阶考试题

    ⭐博客主页:️CS semi主页 ⭐欢迎关注:点赞收藏+留言 ⭐系列专栏:C语言初阶 ⭐代码仓库:C Advanced 家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们 ...

  7. linux公共基础-初阶

    Linux Public Basics公共基础 备注:本文以倒叙方式编辑,各版块内容从下至上.比如先编写的" Linux文件系统",而后是"系统管理命令整理", ...

  8. [C语言] 平淡的日子里泛起光 初阶最后的战役

    //细节讲一下操作符 //1.算术操作符 #include<stdio.h> int main() {//float number = 6 / 5;// 结果是1:系统自动认为整数相除// ...

  9. C语言初阶——5.字符串

    C语言初阶--5.字符串 1. 字符串操作 1.1 字符串遍历 可以通过数组方式遍历字符串. char* str="Hello World"; for(int i = 0;'\0' ...

  10. 数据结构初阶(4)(OJ练习【判断链表中是否有环、返回链表入口点、删除链表中的所有重复出现的元素】、双向链表LinkedList【注意事项、构造方法、常用方法、模拟实现、遍历方法、顺序表和链表的区别)

    接上次博客:数据结构初阶(3)(链表:链表的基本概念.链表的类型.单向不带头非循环链表的实现.链表的相关OJ练习.链表的优缺点 )_di-Dora的博客-CSDN博客 目录 OJ练习 双向链表--Li ...

最新文章

  1. 中科院 工程硕士专业课 复试考试前的辅导安排
  2. Hibernate 乐观锁和悲观锁
  3. Lesson11 vSphere VUM
  4. python画轨迹曲线-matplotlib绘制随机行走轨迹图
  5. 给element-ui的steps加点击事件
  6. 分享一些好用的 Chrome 扩展
  7. linux卡在x windows,Linux下显卡配置错误 无法进入X Windows的解决
  8. yii多表查询--学习随笔
  9. 平台式可复用的应用集成能力,助您敏捷、高效的完成企业数字化转型
  10. 计算机应用第7章在线测试,《计算机应用基础》第07章在线测试
  11. 计算实际例子_【科普】机器学习的核心计算:距离+统计?
  12. 电脑PC端实现微信多开
  13. Labelling tools 的环境配置
  14. 贵州大学计算机学院院长,贵州大学计算机科学与信息学院第七届研究生会干部选举大会...
  15. unity2D:视觉差Parallex
  16. 2021年10月语音合成和语音识别论文月报
  17. 生活随机 - 下雨天的心情有烦恼有感动有确幸
  18. 标段(包)分类 字典sql
  19. rocketMQ系列1
  20. 手把手教你做Android聊天机器人

热门文章

  1. 《惢客创业日记》2019.06.08(周六) 在“桑拿房”里录音
  2. 河南一高校副校长美出圈,被称校领导颜值天花板
  3. 《精益数据分析》:通过数据分析驱动用户增长
  4. 品牌公关中如何做好展会布置与策划?
  5. 小学计算机第二册教学计划,小学语文第二册教学计划
  6. geotools 实例
  7. HANA / TeraData 日期年加减1
  8. vscode 官网无法下载 解决方法
  9. 学习笔记(03):MySQL数据库从入门到搞定实战-DDL之数据表
  10. iOS 右滑手势返回上一级