C++string|遍历、模拟实现、赋值拷贝现代写法

文章目录

  • 1、遍历
    • [1、1 循环+下标](#1、1 循环+下标)
    • [1、2 迭代器遍历](#1、2 迭代器遍历)
    • [1、3 范围for遍历](#1、3 范围for遍历)
  • 2、string模拟实现
    • [2、1 构造和销毁](#2、1 构造和销毁)
      • [2、1、1 成员声明](#2、1、1 成员声明)
      • [为什么static const修饰整型成员要在声明位置就初始化?](#为什么static const修饰整型成员要在声明位置就初始化?)
      • [2、1、2 C 串构造](#2、1、2 C 串构造)
      • [2、1、3 拷贝](#2、1、3 拷贝)
      • [2、1、4 赋值运算重载](#2、1、4 赋值运算重载)
      • [2、1、5 析构](#2、1、5 析构)
    • [2、2 迭代器](#2、2 迭代器)
      • [2、2、1 迭代器类型](#2、2、1 迭代器类型)
      • [2、2、2 迭代器实现](#2、2、2 迭代器实现)
    • [2、3 容量](#2、3 容量)
      • [2、3、1 size和capactiy](#2、3、1 size和capactiy)
      • [2、3、2 empty 判空](#2、3、2 empty 判空)
      • [2、3、3 reserve 预留空间](#2、3、3 reserve 预留空间)
      • [2、3、4 resize 重新定义有效空间大小](#2、3、4 resize 重新定义有效空间大小)
    • [2、4 访问 operator[]](#2、4 访问 operator[])
    • [2、5 修改器](#2、5 修改器)
      • [2、5、1 push_back](#2、5、1 push_back)
      • [2、5、2 append](#2、5、2 append)
      • [2、5、3 operator+=](#2、5、3 operator+=)
      • [2、5、4 insert](#2、5、4 insert)
      • [2、5、5 erase](#2、5、5 erase)
      • [2、5、6 clear](#2、5、6 clear)
    • [2、6 字符操作](#2、6 字符操作)
      • [2、6、1 c_str](#2、6、1 c_str)
      • [2、6、2 find](#2、6、2 find)
    • [2、7 比较运算符重载](#2、7 比较运算符重载)
    • [2、8 流插入和流提取](#2、8 流插入和流提取)
      • [2、8、1 流插入](#2、8、1 流插入)
      • [2、8、2 流提取](#2、8、2 流提取)
    • 3、现代拷贝赋值写法

经过前面两期调用了一遍string接口,是时候实现我们自己的string了。在实现之前,这里补充一下遍历的知识。

1、遍历

1、1 循环+下标

试想一下,当我们遍历数据,对于线性的数据我们可以用循环+下标来访问。string的底层是顺序表,也是线性的那么也是可以用上述方式。

代码演示:

cpp 复制代码
	string s = "hello string";

	string s = "hello string";

	//for遍历
	for (int i = 0; i < s.length(); i++)
	{
		cout << s[i] << " ";
		//可修改
		//注意不支持 *(s + i) 访问
	}

	cout << endl;

	//for遍历 + 修改
	for (int i = 0; i < s.length(); i++)
	{
	 	//ASCII码+1
		s[i] += 1;
		cout << s[i] << " ";
	}

1、2 迭代器遍历

STL也提供了一种新的遍历方式:迭代器遍历。遍历不仅支持线性的还支持非线性的,为STL容器提供了统一的遍历方式。

cpp 复制代码
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << "$";
		//可修改
		it++;
	}

1、3 范围for遍历

C++11引入了一种新的遍历方式范围for,它减少了遍历的代码量,其底层就是迭代器遍历。

cpp 复制代码
	//范围for遍历,底层迭代器
	//auto	自动推导类型
	for (auto ch : s)
	{
		cout << ch << "#";
		//ch可修改,但不影响s,ch是数据的拷贝
	}

	cout << endl;

2、string模拟实现

由于string的接口较多,这里只实现一些重要且常用的。

展开标准库(using namespace std) 含有 string 头文件会和我们的 string 起冲突,建议定义自己的命名空间放我们的 string。

2、1 构造和销毁

2、1、1 成员声明

string 的成员属于私有,在类外不能用随便访问,注意npos也是成员,npos由 static const 修饰,其初始化在成员声明中

cpp 复制代码
    private:

        char* _str;

        size_t _capacity;

        size_t _size;
        
        static const size_t npos = -1;

为什么static const修饰整型成员要在声明位置就初始化?

一般而言,成员变量的的声明和初始化是分离的,而static const成员声明位置位置已经初始化好了,其原因是为了满足一些特殊情况,如定义数组的边界等,这是特例。

2、1、2 C 串构造

cpp 复制代码
        string(const char* str = "")
        {
        		//返回\0之前的字节数
            _size = strlen(str);
            //_size + 1 多一个空间存\0
            _str = new char[_size + 1];
            strcpy(_str, str);
            _capacity = _size;
        }

2、1、3 拷贝

string有资源的申请,编译器的浅拷贝不满足我们的需求,我们要手动实现深拷贝。

cpp 复制代码
        //拷贝
        string(const string& s)
        {
            _str = new char[s._capacity + 1];
            strcpy(_str, s._str);
            _size = s._size;
            _capacity = s._capacity;
        }

2、1、4 赋值运算重载

cpp 复制代码
        //赋值
        string& operator=(const string& s)
        {
            delete[] _str;
            if (&s != this)
            {
                _str = new char[s._capacity + 1];
                strcpy(_str, s._str);
                _size = s._size;
                _capacity = s._capacity;
            }
            return *this;
        }

2、1、5 析构

cpp 复制代码
        //析构
        ~string()
        {
                delete[] _str;
                _str = nullptr;
        }

2、2 迭代器

2、2、1 迭代器类型

迭代器并不是一个类型,而是封装的一个标识符,string 的迭代器可以用字符指针来封装。

cpp 复制代码
    public:

        typedef char* iterator;

2、2、2 迭代器实现

迭代器实现非常的简单,我们可以直接返回对应位置的指针。

cpp 复制代码
        iterator begin()
        {
            return _str;
        }

        iterator end()
        {
            return _str + _size;
        }

        iterator begin() const
        {
            return _str;
        }

        iterator end() const
        {
            return _str + _size;
        }

2、3 容量

2、3、1 size和capactiy

cpp 复制代码
        size_t size()const
        {
            return _size;
        }

       size_t capacity()const
       {
           return _capacity;
       }

2、3、2 empty 判空

cpp 复制代码
        bool empty()const
        {
            return _size == 0;
        }

2、3、3 reserve 预留空间

cpp 复制代码
    void string::reserve(size_t n)
    {
        if (n > _capacity)
        {
            char* tmp = new char[n + 1];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
            _capacity = n;
        }
    }

2、3、4 resize 重新定义有效空间大小

cpp 复制代码
//声明   c 填充字符
//void resize(size_t n, char c = '\0');
void string::resize(size_t n, char c)
{
	  //需要填充
    if (n > _size)
    {   //扩容
        if (n > _capacity)
        {
            reserve(n > 2 * _capacity ? n : 2 * _capacity);
        }
        for (size_t i = _size; i < n; i++)
        {
            _str[i] = c;
        }

    }
    _size = n;
    //补\0
	 _str[n] = '\0';
    
}

2、4 访问 operator[]

cpp 复制代码
        char& operator[](size_t index)
        {
            return *(_str + index);
        }

        const char& operator[](size_t index)const
        {
            return *(_str + index);
        }

2、5 修改器

2、5、1 push_back

cpp 复制代码
    void string::push_back(char c)
    {
        if (_size == _capacity)
        {
            reserve(_capacity = _capacity == 0 ? 4 : 2 * _capacity);
            char* tmp = new char[_capacity];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
        }
        _str[_size++] = c;
        _str[_size] = '\0';
    }

2、5、2 append

cpp 复制代码
    void string::append(const char* str)
    {
        size_t len = strlen(str);
        size_t n = len + _size;
        if (n > _capacity)
        {
            
            reserve(_capacity + len > 2 * _capacity ? _capacity + len : 2 * _capacity);
        }
        strcpy(_str + _size, str);
        _size += len;
    }

2、5、3 operator+=

cpp 复制代码
    string& string::operator+=(const char* str)
    {
        append(str);
        return *this;
    }

2、5、4 insert

cpp 复制代码
  //插入字符
  string& string::insert(size_t pos, char c)
  {
      assert(pos <= _size);
      size_t i = _size + 1;
      if (_size == _capacity)
      {

          reserve(2 * _capacity);
      }
      while (pos < i)
      {
          _str[i] = _str[i - 1];
          i--;
      }
      _size++;
      _str[pos] = c;
      return *this;
  }

  //插入 C 串
  string& string::insert(size_t pos, const char* str)
  {
      assert(pos <= _size);
      size_t n = strlen(str);
      size_t i = _size + n;

      //检查容量
      if (_size + n > _capacity)
      {
          reserve(_size + n > 2 * _capacity ? _size + n : 2 * _capacity);
      }

      //挪移
      while (pos + n < i + 1)
      {
          _str[i] = _str[i - n];
          i--;
      }
      _size += n;
      
      memcpy(_str + pos, str, n);
     
      return *this;
  }

2、5、5 erase

cpp 复制代码
        // 删除pos位置上的元素,并返回该元素的下一个位置
            string& erase(size_t pos, size_t len = npos);

            string& string::erase(size_t pos, size_t len)
    {
        assert(pos < _size);
        if (len == 0 || pos == _size)
        {
            return *this;
        }

        if (len > _size - pos)
        {
            len = _size - pos;
        }
        
        for (size_t i = pos ; i < _size - len; i++)
        {
            _str[i] = _str[i + len];
        }
        
        _size -= len;

        _str[_size] = '\0';

        return *this;
    }

2、5、6 clear

cpp 复制代码
        void clear()
        {
            _size = 0;
            *_str = '\0';
        }

2、6 字符操作

2、6、1 c_str

cpp 复制代码
        const char* c_str()const
        {
            return _str;
        }

2、6、2 find

cpp 复制代码
        // 返回c在string中第一次出现的位置

        size_t find(char c, size_t pos = 0) const;

        // 返回子串s在string中第一次出现的位置

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


    size_t string::find(char c, size_t pos) const
    {
        assert(pos < _size);

        if (_size <= pos)
        {
            return npos;
        }
        for (size_t i = pos; i < _size; i++)
        {
            if(_str[i] == c)
            {
                return i;
            }
        }
        return npos;
    }

    size_t string::find(const char* s, size_t pos) const
    {
        assert(pos < _size);
        char* ch = strstr(_str + pos, s);
        if (ch == nullptr)
        {
            return npos;
        }

        for (size_t i = 0; i < _size; i++)
        {
            if (_str + i == ch)
            {
                return i;
            }
        }

        return npos;
    }

2、7 比较运算符重载

cpp 复制代码
        bool operator<(const string& s);

        bool operator<=(const string& s);

        bool operator>(const string& s);

        bool operator>=(const string& s);

        bool operator==(const string& s);

        bool operator!=(const string& s);

//比较
bool string::operator<(const string& s)
{
    return strcmp(this->c_str(), s.c_str()) < 0;
}

bool string::operator<=(const string& s)
{
    return *this == s || *this < s;
}

bool string::operator>(const string& s)
{
    return !(*this <= s);
}


bool string::operator>=(const string& s)
{
    return (*this > s);
}

bool string::operator==(const string& s)
{
    return strcmp(this->c_str(), s.c_str()) == 0;
}

bool string::operator!=(const string& s)
{
    return !(*this == s);
}

2、8 流插入和流提取

2、8、1 流插入

cpp 复制代码
    ostream& operator<<(ostream& _cout, const string& s)
    {
        //要用const版本的begin和end
        for (auto ch : s)
        {
            _cout << ch;
        }


        return _cout;
    }

2、8、2 流提取

如果我们直接用C的字符串来取数据,是不行的,因为遇到 '\n' 和 ' ' ,就结束了。因此用 get 函数取,get函数的作用是取一个字符,没有结束标志。但一个一个地尾插效率低,而外开一个数组可以提高效率。

cpp 复制代码
    istream& operator>>(istream& _cin, string& s)
    {
        s.clear();

        const int N = 256;
        char arr[N];
        size_t i = 0;

        char ch;
        ch = _cin.get();


        while (ch != ' ' && ch != '\n')
        {
            arr[i++] = ch;
            if (i == N - 1)
            {
                arr[i] = '\0';
                s += arr;
                i = 0;
            }

            ch = _cin.get();
        }
        if (i >= 0)
        {
            arr[i] = '\0';
            s += arr;
        }

        return _cin;
    }

3、现代拷贝赋值写法

cpp 复制代码
        //交换
        void swap(string& s)
        {
            std::swap(this->_str, s._str);
            std::swap(this->_size, s._size);
            std::swap(this->_capacity, s._capacity);
        }

				//现代拷贝
        string(const string& s)
        {
            string tmp(s._str);
            swap(tmp);
        }

				//现代赋值
        string& operator=(string s)
        {
            swap(s);
            return *this;
        }
相关推荐
计算机安禾1 小时前
【c++面向对象编程】第14篇:多态(一):虚函数——实现“一个接口,多种方法”
开发语言·c++
tellmewhoisi2 小时前
单独抽取用户服务(请求不通):feign添加拦截器(添加token)
java·开发语言
Hua-Jay2 小时前
OpenCV联合C++/Qt 学习笔记(十七)----凸包检测、直线检测及点集拟合
c++·笔记·qt·opencv·学习·计算机视觉
basketball6162 小时前
C++ Lambda 表达式完全指南
开发语言·c++·算法
不知名的老吴2 小时前
C++中emplace函数的不适场景总结(三)
开发语言·c++·算法
Java面试题总结2 小时前
Go 里什么时候可以“panic”?
开发语言·后端·golang
rit84324992 小时前
基于MATLAB平台的指纹识别系统实现
开发语言·matlab
2401_892070982 小时前
C++ 缓存线程池(CachedThreadPool):原理、实现、对比
c++·缓存·缓存线程池
沐知全栈开发2 小时前
TypeScript String
开发语言