从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(中)

简介: 从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法

从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(上):https://developer.aliyun.com/article/1513672

3. string的迭代器

在上上篇中,我们首次讲解迭代器,为了方便理解,我们当时解释其为像指针一样的类型。

实际上,有没有一种可能,它就是一种指针呢?

遗憾的是,迭代器并非指针,而是类模板。 只是它表现地像指针,模拟了指针的部分功能。

string迭代器的实现非常简单,它就是一个 char* 的指针罢了。

后面我们讲解 list 的时候它的迭代器又不是指针了,又是自定义类型了。

所以迭代器是一个像指针的东西,有可能是指针有可能不是指针。

3.1 string迭代器的实现

实现迭代器的 begin() 和 end() :

    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }

const 迭代器就是可以读但是不可以写的迭代器,实现一下 const 迭代器:

    typedef const char* const_iterator;
    const_iterator begin() const 
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }

3.2 迭代器和范围for再思考

迭代器的底层是连续的物理空间,给原生指针++解引用能正好贴合迭代器的行为,能做到遍历。

但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。

但是,强大的迭代器通过统一的封装,无论是树、链表还是数组……

它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处。

我们上一章提到过范围for遍历string,我们能不能直接用在我们写的迭代器上:

  void test_string3()
  {
    string s1("hello world");
 
    string::iterator it = s1.begin();
    while (it != s1.end())
    {
      cout << *it << " ";// 读
      it++;
    }
    cout << endl;
 
    it = s1.begin();
    while (it != s1.end())
    {
      (*it)++;// 写,++的优先级比*高,+=就低,可以 *it += 1;
      cout << *it << " ";
      it++;
    }
    cout << endl;
 
    for (auto& e : s1)
    {
      cout << e << " ";
    }
    cout << endl;
  }

我们也妹写范围 for 啊,怎么就能直接用了?

所以范围 for 根本就不需要自己实现,你只要把迭代器实现好,范围 for 直接就可以用。

范围 for 的本质是由迭代器支持的,编译时范围 for 会被替换成迭代器。

这么一看,又是自动加加,又是自动判断结束的范围 for,好像也没那么回事儿。

4. string的增删查改函数实现

4.1 reserve() 的实现


我们先实现一下 reserve 增容:

这里可以检查一下是否真的需要增容,万一接收的 new_capacity 比 _capacity 小,就不动。

    void reserve(size_t new_capacity)
    {
      if (new_capacity > _capacity)
      {
        char* tmp = new char[new_capacity + 1];// 开新空间
        strcpy(tmp, _str);// 搬运
        delete[] _str; // 释放原空间
 
        _str = tmp;// 没问题,递交给_str
        _capacity = new_capacity;// 更新容量
      }
    }

这里我们之前讲数据结构用的是 realloc,现在我们熟悉熟悉用 new,

还是用申请新空间、原空间数据拷贝到新空间,再释放空间地方式去扩容。

我们的 _capacity 存储的是有效字符,没算 \0,所以这里还要 +1 为 \0 开一个空间。

4.2 push_back() 的实现

    void push_back(const char ch)
    {
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
 
      _str[_size++] = ch;// 在_size位置放字符后++
      _str[_size] = '\0';// 易漏
    }

首先检查是否需要增容,如果需要就调用我们上面实现的 reserve 函数,

参数传递可以用三目操作符,防止容量是0的情况,0乘任何数都是0从而引发问题的情况。

测试一下效果如何:

4.3 append() 的实现

append 是追加字符串的,首先我们把要追加的字符串长度计算出来,

然后看容量够不够,不够我们就交给 reserve 去扩容,扩 _size + len,够用就行。

    void append(const char* str)
    {
      int len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
 
      strcpy(_str + _size, str);// 首字符+_size大小就是\0位置
      _size += len;
    }

这里我们甚至都不需要用 strcat,因为它的位置我们很清楚,不就在 _str + _size 后面插入吗。

用 strcat 还需要遍历找到原来位置的 \0,麻烦且效率低,strcat 函数我们以后尽量都不用。

4.4 operator+= 的实现

这就是我们一章说的 "用起来爽到飞起" 的 += ,因为字符和字符串都可以用 += 去操作。

所以我们需要两个重载版本,一个是字符的,一个是字符串的。

我们不需要自己实现了,直接复用 push_back 和 append 就好了:

    string& operator+=(const char ch)
    {
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }

测试:

  void test_string4() 
  {
    string s1("hello world");
    cout << s1.c_str() << endl;
 
    s1.push_back('!');
    cout << s1.c_str() << endl;
 
    s1.push_back('R');
    cout << s1.c_str() << endl;
 
    s1.append("abcd");
    cout << s1.c_str() << endl;
 
    s1 += 'e';
    s1 += "fgh";
    cout << s1.c_str() << endl;
  }

4.5 insert() 的实现

insert:字符

    string& insert(size_t pos, const char ch)
    {
      assert(pos < _size);
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
 
      for (size_t i = _size + 1;i > pos; --i)// 挪动数据,+1是挪动\0
      {
        _str[i] = _str[i - 1];
      }
 
      _str[pos] = ch;
      ++_size;
      return *this;
    }

insert:字符串

    string& insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      int len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
 
      for (size_t i = _size + len ;i > pos + len - 1; --i)// 挪动数据,画图注意边界,参考上面inser字符的len == 1
      {
        _str[i] = _str[i - len];// 首先看\0 _size+len-len就是\0的位置
      }
 
      strncpy(_str + pos, str, len);
      _size += len;
      return *this;
    }

测试:

  void test_string4() 
  {
    string s1("hello world");
    cout << s1.c_str() << endl;
 
    s1.push_back('!');
    cout << s1.c_str() << endl;
 
    s1.push_back('R');
    cout << s1.c_str() << endl;
 
    s1.append("abcd");
    cout << s1.c_str() << endl;
 
    s1 += 'e';
    s1 += "fgh";
    cout << s1.c_str() << endl;
 
    s1.insert(0, 'x');
    s1.insert(6, 'T');
    cout << s1.c_str() << endl;
 
    s1.insert(6, "PPPPPPPPPP");
    cout << s1.c_str() << endl;
    s1.insert(0, "PPPPPPPPPP");
    cout << s1.c_str() << endl;
  }

(测试后push_back 和 append 直接复用还能测试一波)

+=又是复用push_back 和 append 的,直接套娃开始:

    void reserve(size_t new_capacity)
    {
      if (new_capacity > _capacity)
      {
        char* tmp = new char[new_capacity + 1];// 开新空间
        strcpy(tmp, _str);// 搬运
        delete[] _str; // 释放原空间
 
        _str = tmp;// 没问题,递交给_str
        _capacity = new_capacity;// 更新容量
      }
    }
 
    void push_back(const char ch)
    {
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
 
      _str[_size++] = ch;// 在_size位置放字符后++
      _str[_size] = '\0';// 易漏
 
      //insert(_size, ch);
    }

4.6 resize() 的实现

我们为了扩容,先实现了 reverse,现在我们再顺便实现一下 resize。

这里再提一下 reverse 和 resize 的区别:

resize 分给初始值和不给初始值的情况,所以有两种:


库里面也是这么实现的。

但是我们上面讲构造函数的时候说过,我们可以使用全缺省的方式,这样就可以二合一了。

resize 实现的难点是要考虑种种情况,我们来举个例子分析一下:

如果欲增容量比 _size 小的情况:

因为标准库是没有缩容的,所以我们实现的时候也不考虑去缩容。我们可以加一个 \0 去截断。

如果预增容量比 _size 大的情况:

resize 是开空间 + 初始化,开空间的工作我们就可以交给已经实现好的 reserve,

然后再写 resize 的初始化的功能,我们这里可以使用 memset 函数。

    void resize(size_t new_capacity, const char ch = '\0')
    {
      if (new_capacity > _size)// 插入数据
      {
        reserve(new_capacity);
        //for (size_t i = _size; i < new_capacity; ++i)
        //{
        //  _str[i] = ch;
        //}
        memset(_str + _size, ch, new_capacity - _size);// 上面的for循环即memset的功能
        _str[new_capacity] = '\0';
        _size = new_capacity;
      }
      else// 删除数据
      {
        _str[new_capacity] = '\0';
        _size = new_capacity;
      }
    }

从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(下):https://developer.aliyun.com/article/1513674?spm=a2c6h.13148508.setting.14.5e0d4f0eaZIrWK

目录
相关文章
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
85 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
66 2
|
1月前
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
66 0
|
3月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
33 1
|
3月前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
62 0
|
3月前
|
存储 编译器 程序员
深度剖析C++string(上篇)(2)
深度剖析C++string(上篇)(2)
49 0
|
3月前
|
存储 Linux C语言
深度剖析C++string(上篇)(1)
深度剖析C++string(上篇)(1)
36 0
|
4月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
50 0
java基础(13)String类
|
3月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
75 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
3月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
70 2