• C++string的模拟实现


    · CSDN的uu们,大家好。这里是C++入门的第十六讲。
    · 座右铭:前路坎坷,披荆斩棘,扶摇直上。
    · 博客主页: @姬如祎
    · 收录专栏:C++专题

     

    目录

    1. string类的成员变量

    2. 构造函数

    3. 析构函数

    4. const char* c_str() const 

    5. size_t size() const 

    6. char& operator[](size_t pos) 

    7. void reserve(size_ t n)

    8. void push_back(char ch)

    9. void append(const char* s)

    ​编辑

    10. operator+=

    10.1 string& operator+=(char ch)

    10.2 string& operator+=(const char* s)

    11. insert()

    ​编辑

    11.1 void insert(size_t pos, size_t n, char ch)

    11.2 void insert(size_t pos, const char* s)

    12. void erase(size_t pos, size_t len = npos)

    13. find()

    13.1 size_t find(char ch, size_t pos = 0)

    13.2 size_t find(const char* s, size_t pos = 0)

    14. string substr(size_t pos = 0, size_t len = npos)

    15. void resize(size_ t n, char ch = '\0')

     16. void clear()

    17. 比较运算符的重载

    17.1 bool operator<(cons string& s) const

    17.2 bool operator==(cons string& s) const

    17.3 接下来全部都是复用啦

     18. ostream& operator<<(ostream& out, const string& s)

    19. istream& operator>>(istream& in, string& s)

    20. 拷贝构造函数 

    20.1 传统写法

    20.2 现代写法 

    21. void swap(string& s)

    22. 赋值运算符重载

    23. string迭代器的实现 


    在上一讲,我们学习了如何使用string类。这节课我们将用一种叫较为简单的方式模拟实现一个string类。目的是加深对string类的理解。

    1. string类的成员变量

    在string类的使用那一节我们就已经知道了string类其实维护了一个char数组,一个表示char数组实际大小的size和一个表示数组真实容量的capacity,因此,我们模拟实现的string类的成员变量就是这三个啦!

    因为string维护的char数组不是静态的,因此string类维护的是一个char*的指针,size表征有效字符的个数。维护capacity方便扩容。

    1. namespace Tchey
    2. {
    3. class string
    4. {
    5. private:
    6. char* _str;
    7. size_t _size;
    8. size_t _capacity;
    9. };
    10. }

    我们就定义出了一个基础的string类,在string类的定义外套一层明明空间主要是为了和库里面的string冲突哈! 

    2. 构造函数

    我们也是看到string的构造函数是非常多的啊!我们就实现一个:string(const char* s)的版本就行啦!

    该构造函数可以使用一个字符串常量构造一个string对象!我们在用传入的字符串常量构造string对象时,不能将形参s直接赋值给成员变量_str。而是需要在堆上开辟一块空间,然后将字符串常量的数据拷贝过去。同时初始化其他成员变量!

    原因:

    1:形参s指向的空间是常量区,不允许修改,后续对string对象的操作可能报错!

    2:如果构造出来的string对象将空间释放,那么就会释放常量区的空间,非法操作!

    1. string(const char* s)
    2. {
    3. int len = strlen(s); //计算字符串常量的大小
    4. _str = new char[len + 1]; //开辟空间
    5. strcpy(_str, s); //拷贝数据
    6. _size = len; //修改其他成员变量
    7. _capacity = _size;
    8. }

    这里开辟 len + 1的空间是为了 strcpy 拷贝 ‘\0’ 预留的空间哈!为什么要有 '\0' 呢?不是有一个 _size 维护了字符串的大小嘛?那是因为string 有一个 c_str 接口,没有 '\0' 的话,没法兼容C语言啦!

    在定义string对象的时候,我们可能会这么定义!上面的构造函数显然不能行。那怎么办呢?

    string s;

    在string的使用那一节我们讲到,直接这样定义string对象,在内存中其实是在下标为 0 的位置存储了一个 '\0' 的!因此,可以在上面我们写的构造函数给一个缺省参数!像这样:

    1. string(const char* s = "")
    2. {
    3. int len = strlen(s); //计算字符串常量的大小
    4. _str = new char[len + 1]; //开辟空间
    5. strcpy(_str, s); //拷贝数据
    6. _size = len; //修改其他成员变量
    7. _capacity = _size;
    8. }

    这样做是不是既简单右好理解呢?

    3. 析构函数

    构造函数写了,我们就可直接写出来析构函数啦!析构函数很好写:释放维护堆区空间的指针,将_size 和 _capacity 置为 0 即可! 

    1. ~string()
    2. {
    3. delete _str;
    4. _str = nullptr;
    5. _size = 0;
    6. _capacity = 0;
    7. }

    4. const char* c_str() const 

    这个函数返回C风格式的字符串嘛!很简单直接返回_str即可。后面的那个const修饰的是*this哈,代表成员变量不可修改!const对象与非const对象都可以调用!

    1. const char* c_str() const
    2. {
    3. return _str;
    4. }

    5. size_t size() const 

     返回字符串有效字符的数量!返回string类维护的_size即可!

    1. size_t size() const
    2. {
    3. return _size;
    4. }

    6. char& operator[](size_t pos) 

    为了使得我们的程序看起来比较健壮!因此我们可以检查一下pos的合法性,当我们的pos >= _size显然是不合法的!我们assert断言一下就可以啦!返回值就很好处理啦:返回pos位置的字符就行啦!注意到const的string对象也是可以调用operator[]的因此,我们还要实现一个const的版本:const char& operator[] const。方便const对象调用!const对象调用operator[]得到的字符不允许修改!

    1. char& operator[](size_t pos)
    2. {
    3. assert(pos < _size);
    4. return _str[pos];
    5. }
    6. //const版本
    7. const char& operator[](size_t pos) const
    8. {
    9. assert(pos < _size);
    10. return _str[pos];
    11. }

    7. void reserve(size_ t n)

    这个函数是用来修改_str维护字符数组的真实容量的,我们需要做的就是:当n > _capacity的时候,开一块容量为n的空间,然后将原来空间的数据拷贝过去,然后在释放_str,在修改_str的指向,使其维护新开出来的空间!

    1. void reserve(size_t n)
    2. {
    3. if (n > _capacity)
    4. {
    5. char* tmp = new char[n + 1];
    6. memcpy(tmp, _str, _size + 1);
    7. delete[] _str;
    8. _str = tmp;
    9. _capacity = n;
    10. }
    11. }

    这里为什么我们开辟的是 n + 1个字符的空间呢?主要是因为 '\0' 的缘故啊!因为我们的C++必须兼容C语言,所以string对象有效字符的结尾都必须有一个 '\0', 同理memcpy,copy的字节数也要比_size大一,也是因为'\0'的缘故!这里必须要用memcpy不允许使用strcpy,那是因为我们的字符串可能中间出现 '\0',用strcpy就会导致数据拷贝不完整!

    8. void push_back(char ch)

    该函数可以在string的末尾追加一个字符,模拟实现的时候注意扩容逻辑就行了!什么时候扩容:就是当_size >= _capacity 的时候,我们就需要扩容啦!扩容很好办,直接调用我们的reserve接口就行啦!

    还有一个问题就是我们reserve的空间是多大呢?这就取决于你的实现啦!我比较习惯与扩容到原来的两倍!但是有一个魔鬼细节:如果扩容的时候原string的容量就是0 ,扩容成两倍不就是相当于没有扩容嘛,因此我们还需要做一个if条件判断!

    1. void push_back(char ch)
    2. {
    3. if (_size >= _capacity)
    4. {
    5. reserve(_capacity == 0 ? 4 : _capacity * 2);
    6. }
    7. _str[_size++] = ch;
    8. _str[_size] = '\0';
    9. }

    9. void append(const char* s)

    我们知道append的重载版本很多,为了简单,我们选择实现append一个常量字符串的版本。

    首先,我们还是需要判断是否需要进行扩容!我们先求出 常量字符串的长度 len,如果 _size + len > _capacity 就说明需要扩容!最后,我们直接调用 strcpy 将常量字符串中的字符拷贝到str里面就可以啦!strcpy默认是会拷贝 '\0' 的所以没有任何问题哈!注意修改_size的大小哦!

    1. void append(const char* s)
    2. {
    3. int len = strlen(s);
    4. if (_size + len > _capacity)
    5. {
    6. reserve(_size + len);
    7. }
    8. strcpy(_str + _size, s);
    9. _size += len;
    10. }

    10. operator+=

    库里面重载了三个版本,我们实现两个版本就行啦,加等一个字符和加等一个常量字符串。

    10.1 string& operator+=(char ch)

    嘿嘿,很简单,直接调用 push_back() 就行啦!push_back() 会做好一切!

    1. string& operator+=(char ch)
    2. {
    3. push_back(ch);
    4. return *this;
    5. }

    10.2 string& operator+=(const char* s)

    同样地,我们直接调用append接口就行啦!

    1. string& operator+= (const char* s)
    2. {
    3. append(s);
    4. return *this;
    5. }

    11. insert()

    我们看到库里面重载的版本真的很多呢!我们实现两个版本:在pos位置插入n个字符ch和在pos位置插入一个常量字符串。

    11.1 void insert(size_t pos, size_t n, char ch)

    1:检查pos位置的合法性,pos应该是要 <= _size 的。

    2:判断是否需要扩容?当 _size + n > _capacity 就需要扩容啦!至于扩容到多大,取决于你,我们选择效仿上面append的扩容逻辑,就扩容到刚好够!

    3:挪动数据,既然是在pos位置前面插入字符,我们肯定要腾出来 n 个字符的位置撒!不然怎么插入呢!

    我们来看看移动前后的数组对比,再来看如何移动:假设我们要在 pos 位置插入 3 个 'b'

     根据示意图:我们知道要将pos位置及其之后的所有字符向后移动3个下标!我们可以初始化一个变量:end(或者其他名字),指向_size的位置,因为我们的 '\0' 也要移动嘛。然后将下标为 end + n 的字符赋值为下标为 end 指向的字符,我们就完成了字符向后移动 3 个下标,然后让 end--。直到end < pos,因为pos位置的字符也需要移动嘛!

    移动完成之后从pos位置开始向后填充 n 个字符就行啦!

     但是,还是有一个问题!假如 pos == 0,在完成一个字符移动,end--之后与 pos 比较就会出问题。因为 int 类型 与 size_t 类型一起运算的时候会发生整形提升!int 会被提升为 size_t 从而原本 end == -1 整形提升之后 end 就会变成 无符号整形的最大值!从而导致循环无法结束!

    解决办法:

    1:在判断条件处将 pos 强转为 int。

    2. 我们可以将 end == -1 的情况单独拿出来判断。我们在学习string的使用时不是引入了一个 npos 嘛,他就是有符号的 -1 无符号的最大值,现在我们也可以在自己的string类中定义一个。

    静态成员变量的声明和初始化!不可以直接给缺省值:static size_t npos = -1;因为静态成员变量是属于类的!缺省值给的是初始化列表,静态成员不会通过初始化列表初始化!

    有个奇怪的C++语法:const static size_t npos = -1;是能够编译通过的!只不过这个语法仅限于整形家族,是不是特别戳!

    3:我们可以换一种移动字符的方式嘛,将end 初始化为 _size + n 然后将 end - n 的字符赋值给 end 也可以。这样就不会有特殊情况了!只不过代码有点奇怪!

    我们选择第二种解决办法来实现我们的代码!

    1. void insert(size_t pos, size_t n, char ch)
    2. {
    3. assert(pos <= _size);
    4. if (_size + n > _capacity)
    5. reserve(_size + n);
    6. int end = _size;
    7. while (end >= pos && end != npos)
    8. {
    9. _str[end + n] = _str[end];
    10. end--;
    11. }
    12. for (int i = 0; i < n; i++)
    13. _str[pos++] = ch;
    14. _size += n;
    15. }

    11.2 void insert(size_t pos, const char* s)

    有了前面插入n个字符的铺垫,这个函数就很好实现啦!

    1:检查pos的合法性。

    2:计算字符串常量的长度 len,判断len + _size 是否大于 _capacity,如果大于则需要扩容!

    3:移动字符,移动的方法我们还是选择上面的第二种哈!

    4:插入新的字符串!

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

    12. void erase(size_t pos, size_t len = npos)

    这个函数就是删除pos位置后面的 len 个字符!

    1:检查 pos 的合法性。

    2:如果 len 不传 或者 len 传得比较大!就是删除 pos 之后所有的字符!即,当 pos + len >= _size 的时候与不传 len 的时候是等效的!这个时候我们只需要将 pos 位置的字符修改为 '\0' 即可。然后更新 _size;

    3:如果 len 比较小,那么我们就需要移动字符啦!

    假设字符串是:"abcdefg" 我们要删除 pos = 1 后面的 3 个字符。我们可以使用 for 循环,初始化循环变量为 i,然后将 i + len 的字符移动到 i 的位置。很明显循环结束的条件就是当 i > _size - len.

    移动完成之后更新_size 就可以啦!

    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. }
    9. else
    10. {
    11. for (int i = pos; i <= _size - len; i++)
    12. _str[i] = _str[i + len];
    13. _size -= len;
    14. }
    15. }

    13. find()

    find函数我们实现两个版本:1. 从 pos 位置开始查找字符。2. 从 pos 位置开始查找字符串。

    13.1 size_t find(char ch, size_t pos = 0)

    这个函数就很好实现啦。我们先检查一下pos,然后从pos位置开始查找string中有没有这个字符就行啦!找不到返回npos! 

    1. size_t find(char ch, size_t pos = 0)
    2. {
    3. assert(pos < _size);
    4. for (int i = pos; i < _size; i++)
    5. if (_str[i] == ch)
    6. return i;
    7. return npos;
    8. }

    13.2 size_t find(const char* s, size_t pos = 0)

    我们直接套用 C语言的库函数 strstr(),就可以了!注意参数的传入以及返回值的书写。strstr的原型:

    我们要从 pos 位置开始找,因此参数1的实参应该怎么写:_str + pos。

    strstr的返回值是一个char* 我们可以通过 指针减 指针来获得 查找成功的下标!

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

    14. string substr(size_t pos = 0, size_t len = npos)

    从pos位置截取 len 个字符来构造返回一个新的string对象。

    1:检查pos合法性,pos >= _size 都是不合法的!

    2:如果 不传 len 或者说 pos + len > _size,就是截取 pos 之后的所有字符,此时我们可以修正截取的实际字符数量,令 n = _size - pos。

    3:根据实际的字符数量,遍历字符构造字符串返回即可!我们可以在遍历的时候使用 +=。

    1. string substr(size_t pos = 0, 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 s;
    10. for (int i = 0; i < n; i++)
    11. s += _str[pos++];
    12. return s;
    13. }

    15. void resize(size_ t n, char ch = '\0')

    这个函数的使用在 string 使用哪一节是讲过的了!

    1:如果 n < _size 很简单,将_size位置的字符修改为 '\0' 即可!

    2:如果 n >= _size,我们就需要考虑是否要扩容啦!如果 n > _capacity 需要扩容,否则不需要。因此,我们直接调用 reserve(n),在 reserve 的实现中我们是判断了 n 是否大于 _capacity 的,因此不用担心!

    3:用 ch 初始化新的空间即可!

    1. void resize(size_t n, char ch = '\0')
    2. {
    3. if (n < _size)
    4. {
    5. _str[_size] = '\0';
    6. _size = n;
    7. }
    8. else
    9. {
    10. reserve(n);
    11. for (int i = _size; i < n; i++)
    12. _str[i] = ch;
    13. _size = n;
    14. _str[_size] = '\0';
    15. }
    16. }

     16. void clear()

     比较简单,我们只需要将下标为 0 的字符修改为:'\0',将_size 更新为 0 即可!

    1. void clear()
    2. {
    3. _str[0] = '\0';
    4. _size = 0;
    5. }

    17. 比较运算符的重载

    17.1 bool operator<(cons string& s) const

    字符串的比较一直都是按照 字典序 来比较的哈!不是按长度哦!

    当 memcmp 的结果不等于0 的时候,如果结果 < 0,则返回 true;如果结果 > 0 返回false。

    我们来看memcmp == 0的情况:

    1:hello && helloXX,这种情况我们用 memcmp 比较,比较的字节数为两个string 对象中 size()较小的string中有效字符个数所占的字节数。memcmp 的结果为 0,但是因为 helloXX 的hello 后面还有字符,因此 hello < helloXX,返回true。

    2:aaaXX && aaa,同样选择用 memcmp 比较size较小的string的字节数,结果依然是 0,但是因为 aaaXX 的 aaa 后面还有字符,因此 aaaXX > aaa, 返回false。

    3:hello && hello,这种情况 memcmp 的结果依然是 0 ,但是两个string 后面肚饿没有字符,因此 hello == hello, 返回false。

    所以,在 memcmp == 0的情况下,如果 _size < s._size,那么返回 true 否则返回 false。 

    这里你可能发问了,为什么不能用 strcmp 呢?原因就是可能出现这样的字符串:

    aaa\0bbb && aaa\0ccc,strcpy的结果是0,两个string 对象的size也相等,用strcmp得到的结果就是 false,但起始结果是 true。

    1. bool operator<(const string& s) const
    2. {
    3. int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
    4. return ret == 0 ? _size < s._size : ret < 0;
    5. }

    17.2 bool operator==(cons string& s) const

    两个string相等的前提条件就是 他俩的size必须一样,因此如果 size 一样再 memcmp 就行啦!

    1. bool operator==(const string& s) const
    2. {
    3. return _size == s._size && memcpy(_str, s._str, _size);
    4. }

    17.3 接下来全部都是复用啦

    1. bool operator<=(const string& s) const
    2. {
    3. return (*this) < s || (*this) == s;
    4. }
    5. bool operator>(const string& s) const
    6. {
    7. return !((*this) <= s);
    8. }
    9. bool operator>=(const string& s) const
    10. {
    11. return !((*this) < s);
    12. }
    13. bool operator!=(const string& s) const
    14. {
    15. return !((*this) == s);
    16. }

     18. ostream& operator<<(ostream& out, const string& s)

    库里面的 string 是支持 cout << s << endl的。就是因为实现了 流插入运算符的重载!在C++中,ostream的实现是禁用了拷贝构造函数的,因此形参只能写 ostream 的引用。

    还有就是重载流插入和流提取的时候都不要写成成员函数,如果写成成员函数,使用operator<<的顺序就不符合我们的书写习惯啦!一定要写在你定义的namespace内部,不写在你定义的namespace内部函数形参这么写也行:

    ostream& operator<<(ostream& out, const Tchey::string& s)

    因为可能存在 "aaaa\0bbbb" 这样的神奇字符串,因此我们的函数体不能直接写成:

    out << s.c_str(); 

    1. ostream& operator<<(ostream& out, const string& s)
    2. {
    3. for(int i = 0; i < s.size(); i++)
    4. out << s[i];
    5. return out;
    6. }

    19. istream& operator>>(istream& in, string& s)

    这里的函数体千万不敢这样写:

    in >> s.c_str();

    第一:直接这样写并没有为 s 开辟空间,强行写入会引起内存错误,单只程序崩溃的!
    第二:就算你提前开辟了空间,但是我们并不知道用户要输入多少字符哇,提前开多少空间呢?

    因此,我们是不能直接这么搞的,需要一个字符一个字符得读!

    我们定义一个char 变量 ch 用于接收输入的字符,每输入一个字符我们就让 s += ch,直到 ch == ' ', 或者 ch == ‘\0’ 的时候,结束输入(循环)。

    但是这里有一个 魔鬼细节,如果用 cin 输入的话,是无法读入空格和换行的导致死循环,这个可以你自己在编译器上验证,编译器认为空格 和 换行 是用来区分不同的变量的输入的,因此 cin 读不进去 空格 和换行。我们可以用 cin.get() 这样就能读取到 空格 和换行啦。

    于是我们写出了这样的代码:

    没问题了吗?并不是!当我们尝试向一个string对象进行连续的 cin 时,它还是会保留上一次的输入,因此在输入之前还需要清楚 s 原有的字符!

    还不够,我们对比 std::string 发现,输入一连串空格之后再输入其他字符,或者输入一连串换行之后再输入其它字符,是能够正确跳过空格和换行输入有效字符的,因此我们还需要清空缓冲区!

    1. istream& operator>>(istream& in, string& s)
    2. {
    3. //清除s的其他字符
    4. s.clear();
    5. char ch = in.get();
    6. //清空缓冲区
    7. while (ch == ' ' || ch == '\n')
    8. ch = in.get();
    9. while (ch != ' ' && ch != '\n')
    10. {
    11. s += ch;
    12. ch = in.get();
    13. }
    14. return in;
    15. }

    还有一些人认为,如果输入的字符数量很多,可能会导致 s 扩容次数太多,影响效率。因此搞出了一个数组,用来暂时存放输入的字符,等到这个字符满了,或者输入结束,再让 s += 这个字符数组。

    1. istream& operator>>(istream& in, string& s)
    2. {
    3. //清除s的其他字符
    4. s.clear();
    5. char ch = in.get();
    6. //清空缓冲区
    7. while (ch == ' ' || ch == '\n')
    8. ch = in.get();
    9. char buff[128];
    10. int index = 0;
    11. while (ch != ' ' && ch != '\n')
    12. {
    13. if (index == 127)
    14. {
    15. buff[127] = '\0';
    16. s += buff;
    17. index = 0;
    18. }
    19. buff[index] = ch;
    20. ch = in.get();
    21. }
    22. if (index != 0)
    23. {
    24. buff[index] = '\0';
    25. s += buff;
    26. }
    27. return in;
    28. }

    20. 拷贝构造函数 

    哈哈,你肯定想知道为啥拷贝构造函数现在才写!肯定不是因为忘了!哈哈哈!

    string 类中的对象维护了堆区的数据,因此我们要实现深拷贝!深拷贝之前就讲了很多啦!不再赘述!

    20.1 传统写法

    1. string(const string& s)
    2. {
    3. char* tmp = new char[s._capacity + 1];
    4. memcpy(tmp, s._str, s._size + 1); //+1 是为了拷贝\0
    5. delete _str;
    6. _str = tmp;
    7. _size = s._size;
    8. _capacity = s._capacity;
    9. }

    20.2 现代写法 

    现代写法之所以现代就是因为它现代!

    1. string(const string& s)
    2. {
    3. if(this != &s)
    4. {
    5. string tmp(s.c_str());
    6. std::swap(_str, tmp._str);
    7. std::swap(_size, tmp._size);
    8. std::swap(_capacity, tmp._capacity);
    9. }
    10. }

    我们通过 s.c_str() 构造出来一个 tmp 对象,然后通过 swap 函数将 tmp 维护的空间和信息交给 *this 对象。又因为 tmp 是一个临时对象,出了作用域 tmp 就会销毁,调用析构函数,正好将 *this 维护的空间释放掉,简直就是依据两得!有点工具人的味道,哈哈哈!

    21. void swap(string& s)

    这个函数就是用来交换两个对象所维护的空间和信息的,实现这个函数我们能更加方便的使用现代写法! 

    1. void swap(string& s)
    2. {
    3. std::swap(_str, s._str);
    4. std::swap(_size, s._size);
    5. std::swap(_capacity, s._capacity);
    6. }

    22. 赋值运算符重载

    有了现代写法,赋值运算符重载写起来那简直叫一个爽!

    1. //传统写法
    2. string& operator=(const string& s)
    3. {
    4. if (this != &s)
    5. {
    6. char* tmp = new char[s._capacity + 1];
    7. memcpy(tmp, s._str, s._size+1);
    8. delete[] _str;
    9. _str = tmp;
    10. _size = s._size;
    11. _capacity = s._capacity;
    12. }
    13. return *this;
    14. }
    15. //现代写法
    16. string& operator=(string tmp)
    17. {
    18. swap(tmp);
    19. return *this;
    20. }

    23. string迭代器的实现 

    迭代器在string中就是 char* 的指针,迭代器究竟是什么,等我们学到 list 再来理解!

    首先我们要在 string 中定义一个迭代器类型!

    然后我们就逐一来实现 begin() 和end() 起始很简单啊!

    begin():返回 下标为 0 的元素的地址。

    end():返回 _size 下标处的地址。

    *,++,--,都不需要自己实现,因为指针是内置类型,并且 string 的物理空间连续。完全不需要自己动手!

    注意实现两个版本:const 和 非 const 版本!

    迭代器只要实现了,范围 for 就可以使用了,如果范围 for 不知道是什么的老铁可以复习一下:

    21天学会C++:Day8----范围for与nullptr_姬如祎的博客-CSDN博客

    范围for的底层就是 迭代器,范围for会被无脑替换成 迭代器遍历!

    1. iterator begin()
    2. {
    3. return _str;
    4. }
    5. iterator end()
    6. {
    7. return _str + _size;
    8. }
    9. const_iterator begin() const
    10. {
    11. return _str;
    12. }
    13. const_iterator end() const
    14. {
    15. return _str + _size;
    16. }

     到这里,我们就实现了一个属于自己的string,模拟 std::string 实现的方法有很多。模拟只是为了加深对 string 的理解与应用!好啦!不见不散!

     

     

  • 相关阅读:
    NFT: 开启加密艺术时代的无限可能
    Codeanalysis(tca)后端二次开发环境搭建
    Mediapipe Android环境搭建
    十大服装店收银系统有哪些 好用的服装收银软件推荐
    qt连接MySql数据库及增删改查示例
    科比,老大1000天
    项目管理,看这篇就够了,但一定要实操!
    ORB-SLAM3在保存地图时高频率崩溃问题
    头一次见单例模式讲的如此透彻
    嵌入式 Linux 入门(九、Linux 下的磁盘管理)
  • 原文地址:https://blog.csdn.net/m0_73096566/article/details/133932980