模拟实现string类,里面包含四个成员变量,第一个是指向字符数组的指针,第二个变量是目前存放了多少个字符,第三个变量为这个字符数组的容量的大小。最后一个为静态成员变量npos。
注意:一个const 修饰的整型,静态成员变量可以在类里面初始化(三者缺一不可),而其他成员变量,要声明定义分离,类中声明,类外定义。
     char* _str;
     size_t _size;
     size_t _capacity;
     const static size_t npos=-1;
  
1,构造函数
可以写一个缺省参数,在实例化对象时没有进行初始化时,可以初始化为缺省参数,缺省参数为""空。另一个需要注意的为,capacity是显示的是实际容量,所以在申请内存的时候有多申请一个来放'\0'。
string(const char* str="")
     :_size(strlen(str))
     ,_capacity(_size)
 {
     _str = new char[_capacity + 1];
     strcpy(_str, str);
 }
2,析构函数
~string() {
     delete[] _str;
     _str = nullptr;
     _size = 0;
     _capacity = 0;
 }
3,拷贝构造
两种方法:
常规:申请一块新的空间,然后将参数字符数组拷贝到这块新空间里。注意:这里也需要申请空间,所以也需要注意申请空间的大小
string(const string& s) {
             _str = new char[s._capacity+1];
             strcpy(_str, s._str);
             _size = s._size;
             _capacity = s._capacity;   }
优化:实例化一个临时对象。然后用参数的字符数组初始化这个临时对象。然后将这里临时对象和*this进行交换。而交换后的临时对象出了作用域将会自动被销毁。这里需要注意,这个临时对象是没有初始化的,有些编译器可能会出问题,保险起见,我们应该将*this初始化,这样两者交换后,已初始化的临时对象销毁将没有问题。
void swap(string& s) {
     std::swap(s._str, _str);
     std::swap(s._size, _size);
     std::swap(s._capacity, _capacity);
 }
string(const string& s) 
     :_str(nullptr)
     ,_size(0)
     ,_capacity(0)
 {
     string tmp(s._str);
     swap(tmp);
 }
4,迭代器
首先定义两种迭代器
typedef char* iterator;
 typedef const char* const_iterator;
iterator begin() {
     return _str;
 }
iterator end() {
     return _str + _size;
 }
 const_iterator begin() const{
     return _str;
 }
const_iterator end() const  {
     return _str + _size;}
5,reserve()
对存放字符的空间进行扩容(扩大_capacity),首先判断,所需要的空间是否大于原有的空间,如果符合要求,则申请一块新的空间(空间大小为n+1),然后将原有的数据拷贝到新的空间中,然后将原本的空间清空,_str指向这个块新空间的地址。
void reserve(size_t n) {
     if (n > _capacity) {
         _capacity = n;
         char* tmp = new char[n+1];
         strcpy(tmp, _str);//把"/0"也拷贝过去
         delete[]_str;
         _str = tmp;
     }
 }
6,push_back()
只需要注意,最后在_size的位置加 '\0'
void push_back(char ch) {
     if (_size == _capacity) {
         reserve(_capacity==0 ? 4 :_capacity * 2);
     }
     _str[_size] = ch;
     _size++;
     _str[_size] = '\0';
 }
7,capacity(),size()
size_t capacity()const {
     return _capacity;
 }
size_t size() const{
     return _size;
 }
8,append()
拼接,首先要判断拼接后的的大小是否开辟的空间会放不下,如果放不下调用reserve,如果放得下在_str + _size插入str
void append(const char* str) {
     size_t len = strlen(str);
     if (_size +len>_capacity) {
         reserve(_size + len);
     }
     strcpy(_str + _size, str);
     _size += len;
 }
9,insert()
分为插入字符和插入字符串
先判断下标的合理性,然后判断是否有空间加入这个字符。然后从字符串的最后 '\0'开始一次向后走一个,直到走到要插入的下标为止,这时将要插入的字符插到该下标

void 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++;
 }
插入字符串与之相类似,只是'\0'向后走要插入的字符串的长度,注意这里不可以用strcpy,因为这个函数会将字符串的'\0'也复制过去,所以应该用函数strncpy,从而指定要复制的长度
void insert(size_t pos, const char* str) {
     assert(pos <= _size);
     size_t len = strlen(str);
     if (_capacity < len + _size) {
         reserve(_size + len);
     }
     size_t end = _size+1;
     while (end>pos)
     {
         _str[end+len] = _str[end - 1];
         end--;
     }
     strncpy(_str + pos,str,len);
     _size +=  len;
}
10,erase()
从下标为pos开始删,删除len个元素,首先判断要开始删的地方加上要删的个数是否超过了_size的长度,如果超过了,则说明从这个下标开始后面的元素都要删。那么就可以直接将该下标的值改为'\0',如果没有超过,那么得到要删除的最后一个元素的下一个,然后将这个元素向前移len长个,后面的元素以此类推向前推移。
void erase(size_t pos,size_t len =npos) {
     assert(pos < _size);
     if (len == npos || pos + len >= _size) {
         _str[pos] = '\0';
         _size = pos;
     }
     else {
         size_t begin = pos + len;
         while (begin<=_size)
         {
             _str[begin-len] = _str[begin];
             begin++;
         }
         _size -= len;
     }
 }
11,resize()
要将size长度变为n,如果当前的长度大于n那么要删除多出来的元素,如果小于n,要在没有数据的空间中添加'\0'。
首先判断,size是大于n还是小于n。如果大于n,那么就要删除,可以在n下标的地方直接添加'\0'。如果小于n,要先判断空间是否有n这么大,如果没有要先扩容,然后在当前_size位置即以后位置加入'\0',直到下标为n为止
void resize(size_t n, char ch = '\0') {
     if (n<=_size)
     {
         _str[n] = '\0';
         _size = n;
     }
     else {
         reserve(n);
         while (_size<n) {
             _str[_size] = ch;
             _size++;
         } 
         _str[_size] = '\0';
     }
 }
12,find()
寻找字符或者,字符串
size_t find(char ch, size_t pos = 0) {
     for (size_t i = pos; i < _size; i++)
     {
         if (_str[i] == ch) {
             return i;
         }
     }
     return npos;
 }
size_t find(const char* sub, size_t pos = 0) {
     const char* p = strstr(_str+pos, sub);
     if (p)
     {
         return p - _str;
     }
     else {
         return npos;
     }    
 }
13,substr()
首先判断要开始截取的地方加上要截取的长度是否超过了_size的长度,如果超过了,则说明从这个下标开始后面的元素都要被截取。实例化一个对象,将截取的部分+到这个对象的空间里面,然后返回这个对象
string substr(size_t pos, size_t len = npos) {
     string s;
     size_t end = pos + len;
     if (len==npos||end>=_size){
         len = _size - len;
         end = _size;
     }
     s.reserve(len);
     for (size_t i = pos; i < end; i++)
     {
         s += _str[i];
     }
     return s;//拷贝构造+赋值
 }
14,运算符重载
(1)operator[ ],
需要提供两种,一种能读不能写,一种能读也能写
char& operator[](size_t pos) {
     assert(pos < _size);
     return _str[pos];
 }
const char& operator[](size_t pos) const {
     assert(pos < _size);
     return _str[pos];
 }
(2)operator=
常规:我们需要申请一块与参数中字符数组一样大的临时空间。然后将字符数组中的值拷贝到这块临时空间中,清空*this的空间,然后将临时空间的地址赋值给this所指空间的地址。然后将其他变量意义拷贝一下
string& operator=(const string &s) {
             if (*this!=s)
             {
                 char* tmp = new char[s._capacity + 1];
                 strcpy(tmp, s._str);
                 delete[]_str;
                 _str = tmp;
                 _capacity = s._capacity;
                 _size = s._size;
            }
             return *this;
         }
优化:
我们可以实例化一个临时对象,然后利用拷贝构造初始化这个临时对象。然后将临时对象与*this进行交换。
string& operator=(const string& s) {
     if (*this != s)
     {
         string tmp(s);
         swap(tmp);//赋值时,把原本的空间释放了
     }
     return *this;
 }
优化:再传参的时候就实例化一个临时对象
string& operator=(string tmp ) {
     swap(tmp);//赋值时,把原本的空间释放了
     return *this;
 }
(3)operator+=
重载一个+=字符的,重载一个+=字符串的
string& operator+=(char ch) {
     push_back(ch);
     return *this;
 }
string& operator+=(char* str) {
     append(str);
     return *this;
 }
(4)<,==,<=,>,>=,!=
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);
 }
(5)operator<<
注意:这并不是类的成员函数。
之前已经将迭代器实现了,所以可以使用范围for来进行遍历。
ostream& operator<<(ostream& out , const string& s) {
     for (auto ch : s) {
         cout << ch << " ";
     }//因为s是const修饰的,所以要写const相关的函数
     return out;
 }
(6)operator>>
注意:这并不是类的成员函数。
常规:
用get函数去捕获字符,然后+=到引用上,直到遇到“ ”或者“\0”停止。
istream& operator>>(istream& in,  string& s) {
     char ch;
     s.clear();
     ch = cin.get();
     while (ch != ' ' && ch != '\n ')
     {
         s += ch;
         ch = cin.get();
     }
     return in;
 }
优化:建立一个固定大小的数组,将输入的字符先存放到数组中,然后统一将数组中的字符+=到引用中,这样可以防止内存碎片化
istream& operator>>(istream& in, string& s) {
     s.clear();
     char ch;
     ch = in.get();
     char buff [129];
     int i = 0;
     while (ch != ' ' && ch != '\n ')
     {
         buff[i++] = ch;
         if (i==128) {
             buff[i] = '\0';
             s += buff;
             i = 0;
         }
         ch = in.get();
     }
     if (i != 0) {
         buff[i] = '\0';
         s += buff;
     }
     return in;
 }