• <C++> 模拟实现string


    目录

    前言

    一、模拟实现string

    1. 成员变量

    2. 构造函数

    2.1 构造函数

    2.2 重载默认构造 

    2.3 合并

    3. 析构函数 

    4. 拷贝构造函数 

    5. c_str

    6. size

    7. operator[ ] 

    7.1 普通版

    7.2 const版本 

    8. 迭代器—iterator 

    8.1 普通版iterator

    8.2 const版本iterator

    9. 尾插

    10. operator+=

    11. insert

    11.1 插入n个字符 

    11.2 插入字符串 

    12. erase 

    12.1 从pos位置开始删除

    13. find

    13.1 查找字符

    13.2 查找字符串

    14. substr 

    15. reseve && resize

    16. operator<< 流插入

    17. operator>> 流提取

    18. string比较

    19. operator= 赋值运算符重载

     二、整体代码

    总结


    前言

            学习了string的用法,为了提升自身能力,我们来模拟实现string的部分功能


    一、模拟实现string

    初始化列表初始化顺序是声明顺序!

    我们将自己实现的string放在自己的命名空间内,防止与std中的string冲突

    1. namespace my_string
    2. {
    3. class string
    4. {
    5. };
    6. };

    1. 成员变量

    1. private:
    2. char* _str;
    3. size_t _size;
    4. size_t _capacity;

    2. 构造函数

     2.1 构造函数

    构造函数功能:为字符串开空间,并根据传入的参数进行初始化操作。

            此处会出现一些问题,当我们使用初始化列表时,每个人的初始化顺序可能不同,并且成员变量的声明顺序也可能不同,编写完代码后,发现出错,代码不能跑,这就是初始化列表的知识点——初始化列表的初始化顺序由成员变量的声明顺序决定。

            并且为了少次使用strlen这一O(N)级函数,我们可以只将一个变量在初始化列表初始化,其他成员在函数内赋值(以达到少次使用strlen函数),这是因为这些成员变量是内置类型(内置类型在列表或构造函数体内初始化区别不大),如果是自定义类型则不能在内部赋值,因为即使不写,它们仍然会被自动调用各自的构造函数。

            这里我们将全部的成员变量都放在函数体内赋值,以便代码清晰。

    1. namespace my_string
    2. {
    3. class string
    4. {
    5. public:
    6. string(const char* str)
    7. {
    8. _size = strlen(str);
    9. _capacity = _size;
    10. //加1,存放\0
    11. _str = new char[_capacity + 1];
    12. strcpy(_str, str);
    13. }
    14. private:
    15. size_t _size;
    16. size_t _capacity;
    17. char* _str;
    18. };
    19. };

    2.2 重载默认构造 

            那么,当我们不传参构造,只实例化时,编译器找不到默认构造函数,所以我们要重载一个默认构造函数

            根据监视,可以看到C++的string在无参构造时,它是有一个‘\0'的,所以我们在初始化_str不能置空要至少开一个空间,来存储 '\0'

    1. string(const char* str)
    2. :_size(0)
    3. ,_capacity(0)
    4. ,_str(new char[1])
    5. {
    6. _str[0] = '\0';
    7. }

     2.3 合并

    我们可以使用缺省值,将两个构造函数合并

    这里的问题在于,如何设置缺省值?是 '\0' ,还是 nullptr, 还是 "\0"

    1. '\0' 不可以,问题在于左边类型为char*,而'\0'是字符,两者类型不同
    2. nullptr 也不可以,问题在于后面的strlen直接失效,并且没有 '\0'
    3. "\0" 不太合适,因为 “” 内默认有\0,所以缺省值应为“”
    1. string(const char* str = "")
    2. {
    3. _size = strlen(str);
    4. _capacity = _size;
    5. _str = new char[_capacity + 1];
    6. strcpy(_str, str);
    7. }

    注意:该构造函数代码之后会在operator<<处再次修改 

    3. 析构函数 

    • 释放new出的空间,并将_size与_capacity置空即可
    1. ~string()
    2. {
    3. delete[] _str;
    4. _str = nullptr;
    5. _size = _capacity = 0;
    6. }

    4. 拷贝构造函数 

    1. //拷贝构造
    2. string(const string& s)
    3. {
    4. _str = new char[s._capacity + 1];
    5. strcpy(_str, s._str);
    6. _size = s._size;
    7. _capacity = s._capacity;
    8. }

    注意:该构造函数代码之后会在operator<<处再次修改  

    5. c_str

    •  模拟string中的c_str函数功能,返回c形式的字符串,即返回 _str
    • 在函数后面加上const,可以使被const修饰的对象也可调用该函数
    1. const char* c_str() const
    2. {
    3. return _str;
    4. }

    6. size

    •  返回_size即可
    1. size_t size() const
    2. {
    3. return _size;
    4. }

    7. operator[ ] 

    7.1 普通版

    • 为了可读可写,我们使用引用返回,因为返回的对象是数组内的元素,生命周期不在operator[ ]函数内,所以可以使用引用返回
    1. char& operator[](size_t pos)
    2. {
    3. assert(pos < _size);
    4. return _str[pos];
    5. }

    7.2 const版本 

    • const版本只能读,与普通版本形成函数重载,调用函数时,编译器会调用最匹配的函数
    1. //const版本 只读
    2. const char& operator[](size_t pos) const
    3. {
    4. assert(pos < _size);
    5. return _str[pos];
    6. }

    8. 迭代器—iterator 

    8.1 普通版iterator

    • 对于string类,迭代器可以使用指针实现,即迭代器就是指针(针对string类)
    1. //迭代器,在string这里就是指针
    2. typedef char* iterator;
    3. iterator begin()
    4. {
    5. return _str;
    6. }
    7. iterator end()
    8. {
    9. return _str + _size;
    10. }

     测试:

    1. #include
    2. #include"string模拟.h"
    3. using namespace my_string;
    4. using std::cout;
    5. using std::endl;
    6. void test()
    7. {
    8. my_string::string s1("hello world");
    9. cout << s1.c_str() << endl;
    10. string::iterator it = s1.begin();
    11. while (it != s1.end())
    12. {
    13. cout << *it << " ";
    14. ++it;
    15. }
    16. cout << endl;
    17. for (auto ch : s1)
    18. {
    19. cout << ch << "";
    20. }
    21. cout << endl;
    22. }
    1. hello world
    2. h e l l o w o r l d
    3. hello world

    可以看到,我们在写完迭代器之后,范围for就可以直接使用了,因为它就是利用迭代器实现的,底层与使用迭代器遍历几乎一样。 

    如果我们把end(),改为End(),那么范围for就不可用了。所以,范围for是傻瓜式替换,找到类中的begin(),end(),字母错一个它都实现不了。

    8.2 const版本iterator

    • 当对象被const修饰,那么迭代器就要加上const,保证值不被修改
    1. typedef const char* const_iterator;
    2. const_iterator begin() const
    3. {
    4. return _str;
    5. }
    6. const_iterator end() const
    7. {
    8. return _str + _size;
    9. }
    1. string::const_iterator cit = s1.begin();
    2. while (cit != s1.end())
    3. {
    4. cout << *cit << endl;
    5. ++cit;
    6. }
    7. cout << endl;

     9. 尾插

    1. 判断是否要扩容对于push_back() 函数可以进行2倍扩容对于append() 函数,要进行大小判断,如果_size + strlen(str) > _ capacity 那么至少扩容到_size + len 的容量大小。
    2. 对于扩容操作,我们实现reserve() 函数,进行扩容处理
    1. void reserve(size_t n)
    2. {
    3. if (n > _capacity)
    4. {
    5. //多开一个空间留给 '\0'
    6. char* tmp = new char[n + 1];
    7. strcpy(tmp, _str);
    8. delete[] _str;
    9. _str = tmp;
    10. _capacity = n;
    11. }
    12. }
    13. void push_back(char ch)
    14. {
    15. if (_size == _capacity)
    16. {
    17. reserve(_capacity == 0 ? 4 : _capacity * 2);
    18. }
    19. _str[_size] = ch;
    20. ++_size;
    21. _str[_size] = '\0';
    22. }
    23. void append(const char* str)
    24. {
    25. size_t len = strlen(str);
    26. if (_size + len > _capacity)
    27. {
    28. reserve(_size + len);
    29. }
    30. strcpy(_str + _size, str);
    31. _size += len;
    32. }

     10. operator+=

    • 实现尾插之后,operator+=的实现就非常简单了,+=是有返回值的,返回的是string类型
    • 可以返回*this,因为*this是对象,对象的生命周期不在operator+=中,所以可以返回*this
    1. string& operator+=(char ch)
    2. {
    3. push_back(ch);
    4. return *this;
    5. }
    6. string& operator+=(const char* str)
    7. {
    8. append(str);
    9. return *this;
    10. }

     11. insert

    1. 先判断下标是否合法
    2. 再判断是否要扩容
    3. 移位,移位时有“坑”,因为是size_t 类型,大于等于0,-1是最大正数,如果end改为int类型也不行,因为会发生隐式类型转换。这里有多种解决方案:可以强制类型转换、可以将end从end+n开始,进行end - n 赋给 end ,这样就不会到下标为0的地方、用npos判断(npos使用静态成员函数,赋值为-1,表示最大值)
    4. 插入数据
    5. 更新_size

    11.1 插入n个字符 

    1. void insert(size_t pos, size_t n, char ch)
    2. {
    3. //size_t大于0,不用判断
    4. assert(pos <= _size);
    5. if (_size + n > _capacity)
    6. {
    7. //至少扩容到 _size + n
    8. reserve(_size + n);
    9. }
    10. size_t end = _size;
    11. //size_t因为不小于0,当pos为0时,会陷入死循环
    12. //这里有多种方案,可以强制类型转换、可以将end从end+n开始、用npos判断
    13. while (end >= pos && end != npos)
    14. {
    15. _str[end + n] = _str[end];
    16. --end;
    17. }
    18. for (size_t i = 0; i < n; i++)
    19. {
    20. _str[pos + i] = ch;
    21. }
    22. _size += n;
    23. }

    11.2 插入字符串 

    • 同理,将n替换为len即可
    1. void insert(size_t pos, const char* str)
    2. {
    3. assert(pos <= _size);
    4. size_t len = strlen(str);
    5. if (_size + len > _capacity)
    6. {
    7. reserve(_size + len);
    8. }
    9. size_t end = _size;
    10. while (end >= pos && end != npos)
    11. {
    12. _str[end + len] = _str[end];
    13. --end;
    14. }
    15. for (size_t i = 0; i < len; i++)
    16. {
    17. _str[pos + i] = str[i];
    18. }
    19. _size += len;
    20. }

    12. erase 

    12.1 从pos位置开始删除

    1. 先判断下标合法
    2. 再判断是不是要删到尾部,如果是,直接把pos处改为 '\0' ,并修改_size值即可
    3. 如果不是,就是在内部删除,和insert操作类似,移动
    1. void erase(size_t pos, size_t len = npos)
    2. {
    3. assert(pos < _size);
    4. if (len == npos || pos + len >= _size)
    5. {
    6. _str[pos] = '\0';
    7. _size = pos;
    8. _str[_size] = '\0';
    9. }
    10. else
    11. {
    12. size_t begin = pos + len;
    13. //等于size,把\0也移动过去
    14. while (begin <= _size)
    15. {
    16. _str[pos++] = _str[begin++];
    17. }
    18. _size -= len;
    19. }
    20. }

     13. find

    13.1 查找字符

    1. size_t find(char ch, size_t pos = 0)
    2. {
    3. assert(pos < _size);
    4. for (size_t i = pos; i < _size; i++)
    5. {
    6. if (_str[i] == ch)
    7. return i;
    8. }
    9. //找不到,返回npos
    10. return npos;
    11. }

     13.2 查找字符串

    1. size_t find(const char* str, size_t pos = 0)
    2. {
    3. assert(pos < _size);
    4. const char* ptr = strstr(_str, str);
    5. if (ptr)
    6. {
    7. return ptr - _str;
    8. }
    9. else
    10. {
    11. return npos;
    12. }
    13. }

    14. substr 

    • 注意for循环中 i < pos + n,别误写为 i < n
    1. string substr(size_t pos, size_t len = npos)
    2. {
    3. assert(pos < _size);
    4. size_t n = len;
    5. if (len == npos || pos + len > _size)
    6. {
    7. n = _size - pos;
    8. }
    9. string tmp;
    10. tmp.reserve(n);
    11. //注意i < pos + n
    12. for (size_t i = pos; i < pos + n; i++)
    13. {
    14. tmp += _str[i];
    15. }
    16. return tmp;
    17. }

    15. reseve && resize

    1. void reserve(size_t n)
    2. {
    3. if (n > _capacity)
    4. {
    5. //多开一个放'\0'
    6. char* tmp = new char[n + 1];
    7. //strcpy(tmp, _str);
    8. memcpy(tmp, _str, _size + 1);
    9. delete[] _str;
    10. _str = tmp;
    11. _capacity = n;
    12. }
    13. }

    1. void resize(size_t n, char ch = '\0')
    2. {
    3. if (n < _size)
    4. {
    5. _size = n;
    6. _str[_size] = '\0';
    7. }
    8. else
    9. {
    10. //利用reserve进行扩容判断
    11. reserve(n);
    12. for (size_t i = _size; i < n; i++)
    13. {
    14. _str[i] = ch;
    15. }
    16. _size = n;
    17. //由于ch不一定是\0,而且i < n没到最后的\0位置
    18. _str[_size] = '\0';
    19. }
    20. }

    16. operator<< 流插入

    流插入,有多少字符就输出多少字符,中间有'\0'也不终止

    注意将该函数放在命名空间域中,还是全局函数

    1. //流插入,有多少字符就输出多少字符,中间有'\0'也不终止
    2. std::ostream& operator<<(std::ostream& out, const string& s)
    3. {
    4. //要访问私有变量才用友元
    5. for (auto i : s)
    6. {
    7. out << s[i];
    8. }
    9. return out;
    10. }

            直接使用 c_str 和 << 在一般情况是相同的,但是c_str是c字符串,遇到\0自动终止而string则是依靠size来判断,size有多大,operator<<就打印多长 

    例如 :

    1. void test()
    2. {
    3. //与c的字符串不同,c的字符串遇到\0终止,而string是直到size终止
    4. string ss("hello world");
    5. ss += '\0';
    6. ss += "LLLLLLLLLL";
    7. cout << ss.c_str() << endl;
    8. cout << ss << endl;
    9. }
    10. int main()
    11. {
    12. test();
    13. return 0;
    14. }
    1. hello world
    2. hello wordl LLLLLLLLLL

            可以看到第二个打印有空格,空格表示了 '\0'

            所以,有了\0,之前用的str系列的函数就需要修改了,因为str类型的函数遇到\0自动结束,而string是遇到size才停止,将之前的str函数改为memcpy函数

    • 对于构造函数,可以不修改,因为本身构造时,用的就是c字符串,strlen以及strcpy都不需要修改,这里修改strcpy是为了与下面一致
    1. //构造
    2. string(const char* str = "")
    3. {
    4. _size = strlen(str);
    5. _capacity = _size;
    6. _str = new char[_capacity + 1];
    7. memcpy(_str, str, _size + 1);
    8. }
    9. //拷贝构造
    10. string(const string& s)
    11. {
    12. _str = new char[s._capacity + 1];
    13. memcpy(_str, s._str, s._size + 1);
    14. _size = s._size;
    15. _capacity = s._capacity;
    16. }
    • 对于reserve也需要更改
    • 对于append可以不更改,因为追加的是c字符串,为了一致性,改为memcpy,拷贝到时候都默认加一个1,因为strcpy默认拷贝\0
    1. void append(const char* str)
    2. {
    3. size_t len = strlen(str);
    4. if (_size + len > _capacity)
    5. {
    6. reserve(_size + len);
    7. }
    8. //strcpy(_str + _size, str);
    9. memcpy(_str + _size, str, len + 1);

    17. operator>> 流提取

    1. //流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据
    2. std::istream& operator>>(std::istream& in, string& s)
    3. {
    4. //不能写成 in >> s.str,因为s可能没空间来存放数据
    5. char ch;
    6. in >> ch;
    7. while (ch != ' ' && ch != '\n')
    8. {
    9. s += ch;
    10. in >> ch;
    11. }
    12. return in;
    13. }

            上面的代码有错误,当我们输入连续的字符时,不能读到空格和换行,就会陷入死循环,这是in本身的设定,以空格和换行符来区分多个数据,所以我们要使用 get 来提取终端数据 ——in.get(),可以读空格和换行

            当我们不想以空格为分隔符时,可以将 ch != '  ' 判断条件去掉

    1. //流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据
    2. std::istream& operator>>(std::istream& in, string& s)
    3. {
    4. //不能写成 in >> s.str,因为s可能没空间来存放数据
    5. char ch = in.get();
    6. while (ch != ' ' && ch != '\n')
    7. {
    8. s += ch;
    9. ch = in.get();
    10. }
    11. return in;
    12. }

    对于cin操作,如果多次cin,应该会覆盖原先数据 ,即我们需要实现清理工作clear()

    1. void clear()
    2. {
    3. _str[0] = '\0';
    4. _size = 0;
    5. }
    6. //流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据
    7. std::istream& operator>>(std::istream& in, string& s)
    8. {
    9. s.clear();
    10. //不能写成 in >> s.str,因为s可能没空间来存放数据
    11. char ch = in.get();
    12. while (ch != ' ' && ch != '\n')
    13. {
    14. s += ch;
    15. ch = in.get();
    16. }
    17. return in;
    18. }

            优化:循环里的+=效率不高,因为如果字符串过长可能经历多次扩容。我们当然可以提前reserve开一定空间,但是当字符串过短,那么如果空间开的过大就浪费了。

            在这里,我们开一个128的char数组,暂存从终端提取的数据,积累一些数据,当数据超过数组大小后,尾插 \0 后将该数组+=上去,并将下标置为0,再次提取数据。

            当数据不够128时,跳出循环,所以要再循环外判断数组内是否还存有数据,再进行+=

    1. std::istream& operator>>(std::istream& in, string& s)
    2. {
    3. s.clear();
    4. //不能写成 in >> s.str,因为s可能没空间来存放数据
    5. char ch = in.get();
    6. char buff[128];
    7. int i = 0;
    8. while (ch != ' ' && ch != '\n')
    9. {
    10. buff[i++] = ch;
    11. if (i == 127)
    12. {
    13. buff[i] = '\0';
    14. s += buff;
    15. i = 0;
    16. }
    17. ch = in.get();
    18. }
    19. if (i != 0)
    20. {
    21. buff[i] = '\0';
    22. s += buff;
    23. }
    24. return in;
    25. }

            但是对于前有空格和换行,后面输入的数据就进不去,所以再进行优化,加一个while循环,将前空格和换行去除。

    1. //流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据
    2. std::istream& operator>>(std::istream& in, string& s)
    3. {
    4. s.clear();
    5. //不能写成 in >> s.str,因为s可能没空间来存放数据
    6. char ch = in.get();
    7. //处理前缓冲区-流-前面的空格或者换行
    8. while (ch == ' ' || ch == '\n')
    9. {
    10. ch = in.get();
    11. }
    12. char buff[128];
    13. int i = 0;
    14. while (ch != ' ' && ch != '\n')
    15. {
    16. buff[i++] = ch;
    17. if (i == 127)
    18. {
    19. buff[i] = '\0';
    20. s += buff;
    21. i = 0;
    22. }
    23. ch = in.get();
    24. }
    25. if (i != 0)
    26. {
    27. buff[i] = '\0';
    28. s += buff;
    29. }
    30. return in;
    31. }

    18. string比较

    1. string按照ASCII进行比较大小
    2. 不能用strcmp,因为当遇到 例如:hello\0xxxxx 和 hello\0yyyyy 时,比较遇到 \0 就结束了,所以我们可以使用memcmp或手写
    3. 加const防止改变this值,以及可以使const对象调用该函数
    1. //方法一:
    2. bool operator<(const string& s) const
    3. {
    4. int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
    5. return ret == 0 ? _size < s._size : ret < 0;
    6. }
    7. 方法二:
    8. //bool operator<(const string& s)
    9. //{
    10. // //return strcmp(_str, s._str) < 0;由于\0,所以不能用strcmp
    11. // size_t i1 = 0;
    12. // size_t i2 = 0;
    13. // while (i1 < _size && i2 < s._size)
    14. // {
    15. // if (_str[i1] < s._str[i2])
    16. // return true;
    17. // else if (_str[i1] > s._str[i2])
    18. // return false;
    19. // else
    20. // {
    21. // ++i1;
    22. // ++i2;
    23. // }
    24. // }
    25. // //在循环之外,只有一种情况是true
    26. // //return i1 == _size && i2 != s._size;
    27. // //或者判断size大小
    28. // return _size < s._size;
    29. //}

     其他比较复用即可

    1. bool operator==(const string& s) const
    2. {
    3. //先比较size过滤各种情况
    4. return _size == s._size
    5. && memcmp(_str, s._str, _size < s._size) == 0;
    6. }
    7. bool operator<=(const string& s) const
    8. {
    9. return (*this < s || *this == s);
    10. }
    11. bool operator>(const string& s) const
    12. {
    13. return !(*this <= s);
    14. }
    15. bool operator>=(const string& s) const
    16. {
    17. return !(*this < s);
    18. }
    19. bool operator!=(const string& s) const
    20. {
    21. return !(*this == s);
    22. }

     19. operator= 赋值运算符重载

     对于赋值,为深拷贝,则需要释放原空间,再开空间进行赋值

    方法一: 开空间,赋值,再释放,赋值

    1. //赋值运算符重载——深拷贝
    2. string& operator=(const string& s)
    3. {
    4. if (this != &s)
    5. {
    6. //先开空间,赋值,再释放,赋值
    7. char* tmp = new char[s._capacity];
    8. memcpy(tmp, s._str, s._size + 1);
    9. delete[] _str;
    10. _str = tmp;
    11. _size = s._size;
    12. _capacity = s._capacity;
    13. }
    14. return *this;
    15. }

    方法二:交换

            先根据s创建一个临时对象,再将临时对象和this的各个数据进行交换,这时this就指向了拷贝对象,临时对象tmp就指向了原空间,tmp出了作用域自动销毁,完美解决 

    1. //方法二:swap交换
    2. void swap(string& s)
    3. {
    4. std::swap(s._str, _str);
    5. std::swap(s._size, _size);
    6. std::swap(s._capacity, _capacity);
    7. }
    8. string& operator=(const string& s)
    9. {
    10. if (this != &s)
    11. {
    12. //不能直接交换两个对象,会发生栈溢出、无穷递归
    13. //std::swap(tmp, *this); 一直调用赋值运算符重载
    14. string tmp(s);
    15. //this->swap(tmp);
    16. swap(tmp);
    17. }
    18. //tmp是局部对象,出函数自动销毁
    19. return *this;
    20. }

     方法三:再次优化,将形参改为string tmp,直接拷贝构造tmp,出函数作用域直接析构,所以,对于赋值运算符重载,需要先进行拷贝构造

    1. void swap(string& s)
    2. {
    3. std::swap(s._str, _str);
    4. std::swap(s._size, _size);
    5. std::swap(s._capacity, _capacity);
    6. }
    7. //方法三:改变行参,直接拷贝构造tmp
    8. string& operator=(string tmp)
    9. {
    10. swap(tmp);
    11. return *this;
    12. }

    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    由此又引出拷贝构造的现代写法 (即使用交换来实现功能),但是由于\0,这个拷贝构造并不能正确实现拷贝构造功能,所以还是采用原拷贝构造。

    1. string(const string& s)
    2. :_str(nullptr)
    3. ,_size(0)
    4. ,_capacity(0)
    5. {
    6. //使用初始化列表,避免对象各成员变量为nullptr
    7. string tmp(s._str);
    8. swap(tmp);
    9. }

     

     二、整体代码

    my_string.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. namespace my_string
    6. {
    7. class string
    8. {
    9. public:
    10. //迭代器,在string这里就是指针
    11. typedef char* iterator;
    12. typedef const char* const_iterator;
    13. iterator begin()
    14. {
    15. return _str;
    16. }
    17. iterator end()
    18. {
    19. return _str + _size;
    20. }
    21. const_iterator begin() const
    22. {
    23. return _str;
    24. }
    25. const_iterator end() const
    26. {
    27. return _str + _size;
    28. }
    29. //构造
    30. string(const char* str = "")
    31. {
    32. _size = strlen(str);
    33. _capacity = _size;
    34. _str = new char[_capacity + 1];
    35. memcpy(_str, str, _size + 1);
    36. }
    37. //拷贝构造
    38. string(const string& s)
    39. {
    40. _str = new char[s._capacity + 1];
    41. memcpy(_str, s._str, s._size + 1);
    42. _size = s._size;
    43. _capacity = s._capacity;
    44. }
    45. 赋值运算符重载——深拷贝
    46. //string& operator=(const string& s)
    47. //{
    48. // if (this != &s)
    49. // {
    50. // char* tmp = new char[s._capacity];
    51. // memcpy(tmp, s._str, s._size + 1);
    52. // delete[] _str;
    53. // _str = tmp;
    54. // _size = s._size;
    55. // _capacity = s._capacity;
    56. // }
    57. // return *this;
    58. //}
    59. //方法二:swap交换
    60. void swap(string& s)
    61. {
    62. std::swap(s._str, _str);
    63. std::swap(s._size, _size);
    64. std::swap(s._capacity, _capacity);
    65. }
    66. //string& operator=(const string& s)
    67. //{
    68. // if (this != &s)
    69. // {
    70. // //不能直接交换两个对象,会发生栈溢出、无穷递归
    71. // //std::swap(tmp, *this); 一直调用赋值运算符重载
    72. // string tmp(s);
    73. // //this->swap(tmp);新手才写this o.0
    74. // swap(tmp);
    75. // }
    76. // //tmp是局部对象,出函数自动销毁
    77. // return *this;
    78. //}
    79. //方法三:改变行参,直接拷贝构造tmp
    80. string& operator=(string tmp)
    81. {
    82. swap(tmp);
    83. return *this;
    84. }
    85. //析构
    86. ~string()
    87. {
    88. delete[] _str;
    89. _str = nullptr;
    90. _size = _capacity = 0;
    91. }
    92. const char* c_str() const
    93. {
    94. return _str;
    95. }
    96. size_t size() const
    97. {
    98. return _size;
    99. }
    100. char& operator[](size_t pos)
    101. {
    102. assert(pos < _size);
    103. return _str[pos];
    104. }
    105. //const版本 只读
    106. const char& operator[](size_t pos) const
    107. {
    108. assert(pos < _size);
    109. return _str[pos];
    110. }
    111. void reserve(size_t n)
    112. {
    113. if (n > _capacity)
    114. {
    115. //多开一个放'\0'
    116. char* tmp = new char[n + 1];
    117. //strcpy(tmp, _str);
    118. memcpy(tmp, _str, _size + 1);
    119. delete[] _str;
    120. _str = tmp;
    121. _capacity = n;
    122. }
    123. }
    124. void resize(size_t n, char ch = '\0')
    125. {
    126. if (n < _size)
    127. {
    128. _size = n;
    129. _str[_size] = '\0';
    130. }
    131. else
    132. {
    133. //利用reserve进行扩容判断
    134. reserve(n);
    135. for (size_t i = _size; i < n; i++)
    136. {
    137. _str[i] = ch;
    138. }
    139. _size = n;
    140. //由于ch不一定是\0,而且i < n没到最后的\0位置
    141. _str[_size] = '\0';
    142. }
    143. }
    144. void push_back(char ch)
    145. {
    146. if (_size == _capacity)
    147. {
    148. reserve(_capacity == 0 ? 4 : _capacity * 2);
    149. }
    150. _str[_size] = ch;
    151. ++_size;
    152. _str[_size] = '\0';
    153. }
    154. void append(const char* str)
    155. {
    156. size_t len = strlen(str);
    157. if (_size + len > _capacity)
    158. {
    159. reserve(_size + len);
    160. }
    161. //strcpy(_str + _size, str);
    162. memcpy(_str + _size, str, len);
    163. _size += len;
    164. }
    165. string& operator+=(char ch)
    166. {
    167. push_back(ch);
    168. return *this;
    169. }
    170. string& operator+=(const char* str)
    171. {
    172. append(str);
    173. return *this;
    174. }
    175. void insert(size_t pos, size_t n, char ch)
    176. {
    177. //size_t大于0,不用判断
    178. assert(pos <= _size);
    179. if (_size + n > _capacity)
    180. {
    181. //至少扩容到 _size + n
    182. reserve(_size + n);
    183. }
    184. size_t end = _size;
    185. //size_t因为不小于0,当pos为0时,会陷入死循环
    186. //这里有多种方案,可以强制类型转换、可以将end从end+n开始、用npos判断
    187. while (end >= pos && end != npos)
    188. {
    189. _str[end + n] = _str[end];
    190. --end;
    191. }
    192. for (size_t i = 0; i < n; i++)
    193. {
    194. _str[pos + i] = ch;
    195. }
    196. _size += n;
    197. }
    198. void insert(size_t pos, const char* str)
    199. {
    200. assert(pos <= _size);
    201. size_t len = strlen(str);
    202. if (_size + len > _capacity)
    203. {
    204. reserve(_size + len);
    205. }
    206. size_t end = _size;
    207. while (end >= pos && end != npos)
    208. {
    209. _str[end + len] = _str[end];
    210. --end;
    211. }
    212. for (size_t i = 0; i < len; i++)
    213. {
    214. _str[pos + i] = str[i];
    215. }
    216. _size += len;
    217. }
    218. void erase(size_t pos, size_t len = npos)
    219. {
    220. assert(pos < _size);
    221. if (len == npos || pos + len >= _size)
    222. {
    223. _str[pos] = '\0';
    224. _size = pos;
    225. _str[_size] = '\0';
    226. }
    227. else
    228. {
    229. size_t begin = pos + len;
    230. //等于size,把\0也移动过去
    231. while (begin <= _size)
    232. {
    233. _str[pos++] = _str[begin++];
    234. }
    235. _size -= len;
    236. }
    237. }
    238. string substr(size_t pos, size_t len = npos)
    239. {
    240. assert(pos < _size);
    241. size_t n = len;
    242. if (len == npos || pos + len > _size)
    243. {
    244. n = _size - pos;
    245. }
    246. string tmp;
    247. tmp.reserve(n);
    248. //注意i < pos + n
    249. for (size_t i = pos; i < pos + n; i++)
    250. {
    251. tmp += _str[i];
    252. }
    253. return tmp;
    254. }
    255. size_t find(char ch, size_t pos = 0)
    256. {
    257. assert(pos < _size);
    258. for (size_t i = pos; i < _size; i++)
    259. {
    260. if (_str[i] == ch)
    261. return i;
    262. }
    263. //找不到,返回npos
    264. return npos;
    265. }
    266. size_t find(const char* str, size_t pos = 0)
    267. {
    268. assert(pos < _size);
    269. const char* ptr = strstr(_str, str);
    270. if (ptr)
    271. {
    272. return ptr - _str;
    273. }
    274. else
    275. {
    276. return npos;
    277. }
    278. }
    279. void clear()
    280. {
    281. _str[0] = '\0';
    282. _size = 0;
    283. }
    284. //方法一:
    285. bool operator<(const string& s) const
    286. {
    287. int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
    288. return ret == 0 ? _size < s._size : ret < 0;
    289. }
    290. 方法二:
    291. //bool operator<(const string& s)
    292. //{
    293. // //return strcmp(_str, s._str) < 0;由于\0,所以不能用strcmp
    294. // size_t i1 = 0;
    295. // size_t i2 = 0;
    296. // while (i1 < _size && i2 < s._size)
    297. // {
    298. // if (_str[i1] < s._str[i2])
    299. // return true;
    300. // else if (_str[i1] > s._str[i2])
    301. // return false;
    302. // else
    303. // {
    304. // ++i1;
    305. // ++i2;
    306. // }
    307. // }
    308. // //在循环之外,只有一种情况是true
    309. // //return i1 == _size && i2 != s._size;
    310. // //或者判断size大小
    311. // return _size < s._size;
    312. //}
    313. bool operator==(const string& s) const
    314. {
    315. //先比较size过滤各种情况
    316. return _size == s._size
    317. && memcmp(_str, s._str, _size < s._size) == 0;
    318. }
    319. bool operator<=(const string& s) const
    320. {
    321. return (*this < s || *this == s);
    322. }
    323. bool operator>(const string& s) const
    324. {
    325. return !(*this <= s);
    326. }
    327. bool operator>=(const string& s) const
    328. {
    329. return !(*this < s);
    330. }
    331. bool operator!=(const string& s) const
    332. {
    333. return !(*this == s);
    334. }
    335. private:
    336. size_t _size;
    337. size_t _capacity;
    338. char* _str;
    339. static size_t npos;
    340. };
    341. size_t string::npos = -1;
    342. //流插入(向终端插入数据),有多少字符就输出多少字符,中间有'\0'也不终止
    343. std::ostream& operator<<(std::ostream& out, const string& s)
    344. {
    345. //要访问私有变量才用友元
    346. for (auto ch : s)
    347. {
    348. out << ch;
    349. }
    350. return out;
    351. }
    352. //流提取(从终端提取数据),这里的s不能使用const,因为要往s里存放数据
    353. std::istream& operator>>(std::istream& in, string& s)
    354. {
    355. s.clear();
    356. //不能写成 in >> s.str,因为s可能没空间来存放数据
    357. char ch = in.get();
    358. //处理前缓冲区前面的空格或者换行
    359. while (ch == ' ' || ch == '\n')
    360. {
    361. ch = in.get();
    362. }
    363. char buff[128];
    364. int i = 0;
    365. while (ch != ' ' && ch != '\n')
    366. {
    367. buff[i++] = ch;
    368. if (i == 127)
    369. {
    370. buff[i] = '\0';
    371. s += buff;
    372. i = 0;
    373. }
    374. ch = in.get();
    375. }
    376. if (i != 0)
    377. {
    378. buff[i] = '\0';
    379. s += buff;
    380. }
    381. return in;
    382. }
    383. };

    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    三、写时拷贝 

            我们再次回顾深浅拷贝问题

    浅拷贝问题:

            1.析构两次

            2. 一个对象修改会影响另一个

            仔细分析,我们能发现,深拷贝效率不如浅拷贝,因为还要开辟另一块空间。此时,有了浅拷贝的改进:

    1. 对于第一个问题:进行引用计数 ,每有一个对象指向该空间,计数+1,每析构一次,计数-1,直至最后为0时,真正的析构(最后走的人关灯)
    2. 对于第二个问题,如果计数不是1,则进行深拷贝,再修改值(有博弈的成分,如果有只拷贝不修改值的情况,那么就赚了;对于需要修改值的,那么就进行深拷贝,不赚不赔)

     

    想了解更多关于写时拷贝的内容,可以看大佬的文章

    C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell 

    sizeof (s)是多少呢?

            并不是12,是28(32位平台),这是由于vs2019优化,给了buf数组缓存,以空间换时间

    • 当 size < 16 那么就没必要开空间,直接存在buf数组内
    • 当size >= 16 字符串存在_ptr指向的堆空间


    总结

            我们模拟实现的string,具有部分功能,还有一部分没有实现,有兴趣的同学可以参照std的string再进行模拟实现,下节我们将讲解vector并进行实现。

            最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

  • 相关阅读:
    项目管理逻辑:项目如何算是做完?什么是项目管理中的PPP模式?
    一款兼容Win和Mac的iOS设备管理软件iMazing 3 for Windows新功能介绍
    【Linux】vim_gcc_动静态库
    【PAT甲级 - C++题解】1096 Consecutive Factors
    云耀服务器L实例部署Discuz!Q论坛|华为云云耀云服务器L实例评测使用体验
    安全漏洞分类之CNNVD漏洞分类指南
    用python实现在卷积神经网络中,如何计算各层感受野的大小
    别再到处乱放配置文件了!试试我司使用 7 年的这套解决方案,稳的一秕
    mac下nodejs的卸载与安装
    JQuery 高级使用 高阶函数 委托模式 全局css变量
  • 原文地址:https://blog.csdn.net/prodigy623/article/details/133580419