• 模拟实现string【C++】


    文章目录

    • 全部的实现代码放在了文章末尾
    • 准备工作
      • 包含头文件
      • 定义命名空间和类
        • 类的成员变量
    • 构造函数
      • 默认构造
      • 拷贝构造
    • 重载赋值拷贝函数
    • 析构函数
    • 迭代器和获取迭代器
      • 迭代器
      • 获取迭代器
    • resize【调整size】
      • 图解
    • reserve【调整capacity】
    • empty【判断串是否为空】
    • operator[]
    • append
    • push_back
    • operator+=
    • insert【在pos之前插入字符串】
    • erase【删除从pos开始的len个字符】
    • swap
    • find
    • substr
    • operator+
    • compare
    • 比较运算符重载
      • operator>
      • operator==
      • operator<
      • operator>
      • operator<=
      • operator!=
    • c_str
    • operator<<【输出运算符重载】
    • operator>>【输入运算符重载】
    • 全部代码
      • mystring.h
      • mystring.cpp

    全部的实现代码放在了文章末尾

    准备工作

    创建三个文件,一个头文件mystring.h,两个源文件mystring.cpp tesr.cpp
    因为是简单实现,所以只实现了string里放char这一种实现,就没有用模板了

    1. mystring.h:存放包含的头文件,命名空间的定义
    2. mystring.cpp:存放成员函数和命名空间中的函数的定义
    3. test.cpp:存放main函数,以及测试代码

    包含头文件

    1. iostream:用于输入输出
    2. string.h:C语言的头文件,用于使用其中的操作字符数组的函数
    3. assert.h:用于使用报错函数

    定义命名空间和类

    在文件mystring.hmystring.cpp中都定义上一个命名空间mystring
    mystring.h中类的声明放进命名空间,把mystring中的函数实现也放进命名空间

    注意:
    不同源文件的同名的命名空间经过编译链接之后可以合成在一起

    类的成员变量

    在这里插入图片描述


    构造函数

    默认构造

    在这里插入图片描述

    给默认构造的str加上缺省值(“”)就可以实现不传参数时就是空串了


    拷贝构造

    因为成员str申请了堆区空间,所以要手写拷贝构造,并且要实现深拷贝
    在这里插入图片描述


    重载赋值拷贝函数

    因为成员str申请了堆区空间,所以要手写赋值拷贝,并且要实现深拷贝
    在这里插入图片描述
    为什么要防止自己给自己赋值?
    【this指针指向接收赋值的对象,所以只要this传入的参数的地址相等就是自己赋值给自己】

    因为要深拷贝,所以要把一个对象中的所有成员都拷贝一遍,时间复杂度是
    O(N),所以有必要防止自己给自己赋值


    析构函数

    因为成员str申请了堆区空间,所以要手写析构,不能用编译器给的默认析构

    在这里插入图片描述


    迭代器和获取迭代器

    迭代器

    因为存放字符的空间的地址是连续的,且只是简单模拟
    所以我用了char*作为普通迭代器,const char*作为const迭代器

    直接把char*重命名为iterator,把const char*重命名为const_iterator就完成了迭代器的实现


    获取迭代器

    在这里插入图片描述
    因为使用了char*作为迭代器
    所以str就是第一个有效元素,str+size就是最后一个有效字符的下一个位置(\0)

    又因为const修饰的对象只能调用const修饰的成员函数
    所以如果是const修饰的对象调用begin()和end()的时候,就会自动调用到const修饰的begin和end.
    在这里插入图片描述


    resize【调整size】

    void string::resize(size_t n,char c='\0')
    {
        if (n > _capacity) 当要调整的容量n大于最大容量时,就要扩容
        {
            char* tmp = new char[n +1];申请size+1个空间,那多出来的1是给'\0'strcpy(tmp, _str);拷贝字符串(把右参数拷贝给左参数)
    
            delete[] _str;释放扩容前str指向的空间
    
            _str = tmp;完成扩容
    
            for (; _size <n; _size++)把多出来的有效字符用c补上
            {
                _str[_size] = c;
            }
    
            _str[_size] = '\0'; 补完之后加上字符串结束标志'\0'
    
            _capacity = n;更新最大容量
        }
        else
        {
            if (n > _size)如果调整之后的size  大于  原来的size
                          就要把少(n-size)的有效字符用c补上
            {
                for (; _size < n; _size++)把多出来的有效字符用c补上
                {
                    _str[_size] = c;
                }
                _str[_size] = '\0';补完之后加上字符串结束标志'\0'
            }
            else 如果调整之后的size  小于  原来的size
                就要把多的字符删除
            {
                _size = n;调整有效字符大小
    
                _str[_size] = '\0';直接把n位置改成'\0'即可删除多余字符
            }
        }
    }
    

    图解

    在这里插入图片描述

    在这里插入图片描述


    reserve【调整capacity】

    在这里插入图片描述


    empty【判断串是否为空】

    在这里插入图片描述


    operator[]

    返回值要是char&这样才能像字符数组一样访问和修改串中的字符
    在这里插入图片描述


    append

    在这里插入图片描述

    在这里插入图片描述


    push_back

    可以直接服用append
    在这里插入图片描述


    operator+=

    也可以直接服用append

    在这里插入图片描述


    insert【在pos之前插入字符串】

    在这里插入图片描述


    erase【删除从pos开始的len个字符】

    在这里插入图片描述


    swap

    因为存放字符串的空间是在堆区开辟的,用成员str去指向的
    所以直接交换两个对象的str中存放的地址就可以完成存储的字符串的交换了

    不需要拷贝
    在这里插入图片描述

    在这里插入图片描述


    find

    在这里插入图片描述


    substr

    在这里插入图片描述


    operator+

    直接复用operator+=
    因为+不改变字符串本身,所以要用一个临时对象存储+=之后的字符,再用传值返回即可

    在这里插入图片描述


    compare

    在这里插入图片描述


    比较运算符重载

    operator>

    复用compare
    在这里插入图片描述


    operator==

    复用compare
    在这里插入图片描述


    operator<

    复用operator>operator==
    在这里插入图片描述


    operator>

    复用operator>operator==
    在这里插入图片描述


    operator<=

    复用operator>

    在这里插入图片描述


    operator!=

    复用operator==
    在这里插入图片描述


    c_str

    作用是获取string对象中的str成员。

    一般是要在C++中使用C语言函数时使用,因为C语言不支持string,所以只能用字符指针代替一下

    在这里插入图片描述

    operator<<【输出运算符重载】

    重载了之后就可以
    直接用cout输出string实例化的对象了

    string a(“aaaaa”);
    cout<<a<<endl;
    

    在这里插入图片描述


    operator>>【输入运算符重载】

    重载了之后就可以
    直接用cin把字符串输入到string实例化的对象里面了

    istream是输入流对象,比如我们常用的  cin  就是  istream实例化的对象
    
    istream& operator>>(istream& is, string& obj)
    {
        char str[100];  存储  输入的  每一行的字符
    
        int sum = 0;  使用sum记录输入的  每一行  的字符个数
        char c = 0;
    
        \n是换行符(回车),所以  没遇到  \n就  没换行
        while ((c = is.get()) != '\n')  一次读取一个字符
        {
            if (sum == 99)  当sum等于99时说明  str数组存不下了
            {
                str[sum] = '\0';  末尾加上了\0才是字符串
    
                obj += str;  使用+=把str数组中的字符先加上去
    
                sum = 0;  sum置成0,继续记录
            }
            else  否则
            {
                把读取到的字符存进str数组里
                str[sum++] = c;
            }
        }
    
        sum!=0说明最后输入的最后一行字符个数  小于99个,没有+=if (sum != 0)
        {
            str[sum] = '\0';  末尾加上了\0才是字符串
    
            obj += str;  使用+=把str数组中的字符先加上去
        }
        return is;  为支持链式编程,返回  输入流对象  的引用
    }
    

    全部代码

    mystring.h

    #include
    #include
    #include
    using namespace std;
    
    namespace mystring
    {
        class string
        {
        public:
            typedef char* iterator;
            typedef const char* const_iterator;
            
    
            string(const string& obj);
            string(const char* str = "");
            string& operator=(const string&obj);
    
            ~string();
    
            iterator begin();
            const_iterator begin() const;
    
            iterator end();
            const_iterator end() const;
    
            size_t size() const;
            size_t capacity() const;
            void resize(size_t n, char c='\0');
    
            void reserve(size_t n = 0);
    
            void clear();
            bool empty() const;
    
            char& operator[](size_t pos);
            const char& operator[](size_t pos)const;
    
            string& append(const string& obj);
            string& append(char ch);
            void push_back(char c);
            string& operator+=(const string& obj);
            string& operator+=(char c);
            string& assign(const string& str);
            string& insert(size_t pos, const string& str);
            string& insert(size_t pos,char c);
            string& erase(size_t pos = 0, size_t len = npos);
            void swap(string& str);
            const char* c_str() const;
            size_t find(const string& str, size_t pos = 0) const;
            size_t find(char c, size_t pos = 0) const;
            string substr(size_t pos = 0, size_t len = npos) const;
            int compare(const string& str) const;
            string operator+(const string& obj)const;
            string operator+(char c)const;
            bool operator>(const string&obj)const;
            bool operator<(const string& obj)const;
            bool operator>=(const string& obj)const;
            bool operator<=(const string& obj)const;
            bool operator==(const string& obj)const;
            bool operator!=(const string& obj)const;
           
        private:
            char* _str;//指向存放字符串的堆区空间的指针
            size_t _size;//字符串的有效字符个数
            size_t _capacity;//字符串的最大容量
    
            static const size_t npos;//理论上可以存储的最大字符个数
        };
        ostream& operator<< (ostream& os, const string& obj);
        istream& operator>>(istream& is,string& obj);
    }
    

    mystring.cpp

    #include"string.h"
    
    namespace mystring
    {
        const size_t string::npos = -1;
    
        string::string(const string& obj)//因为成员在堆区申请了空间所以要写深拷贝的拷贝构造
        {
            _str = new char[obj._size + 1];// 申请size + 1个空间,那多出来的1是给'\0'的
    
    
            strcpy(_str, obj._str);//使用该函数把字符串把obj中的字符串拷贝过来
    
    
            _capacity = obj._capacity;
    
    
            _size = obj._size;
        }
    
        string::string(const char* str) //让str的缺省值为""(空字符串)
    
            :_size(strlen(str))//构造函数会先走成员初始化列表,借此先计算出size
        {
             assert(str != nullptr);//防止传入的字符指针是空的
    
             _str = new char[_size + 1];//申请size+1个空间,那多出来的1是给'\0'的
    
             strcpy(_str, str);//使用该函数把字符串str中的字符拷贝过来
    
             _capacity = _size;//设置初始最大容量和size一样大
        }
    
        string& string::operator=(const string&obj)
        {
            if (this != &obj)//防止自己赋值给自己
            {
                delete[] _str;//先释放接收赋值之前str指向的堆区空间
                              //因为接收赋值之后,str中存放的地址就被覆盖了
    
                _str = new char[obj._size + 1];//申请size+1个空间,那多出来的1是给'\0'的
    
                strcpy(_str, obj._str);//拷贝字符串(把右参数拷贝给左参数)
    
                _size = obj._size;
    
                _capacity = obj._capacity;
            }
            return *this;
        }
    
    
        string::~string()
        {
            delete[] _str;//释放str指向的堆区空间
    
            _str = nullptr;
    
            _size = 0;
    
            _capacity = 0;
        }
    
    
        string::iterator string::begin()//普通起始迭代器
        {
            return _str;
        }
    
        string::const_iterator string::begin() const//const起始迭代器
        {
            return _str;
        }
    
        string::iterator  string::end()//普通结束迭代器
        {
            return _str + _size;
        }
    
        string::const_iterator  string::end() const//const结束迭代器
        {    
            return _str + _size;
        }
    
        
        size_t  string::size() const
        {
            return _size;
        }
    
    
        void string::resize(size_t n,char c)
        {
            if (n > _capacity)//当要调整的size,n大于最大容量时,就要扩容
            {
                char* tmp = new char[n +1];//申请size+1个空间,那多出来的1是给'\0'的
    
                strcpy(tmp, _str);//拷贝字符串(把右参数拷贝给左参数)
    
                delete[] _str;//释放扩容前str指向的空间
    
                _str = tmp;//完成扩容
    
                for (; _size <n; _size++)//把多出来的有效字符用c补上
                {
                    _str[_size] = c;
                }
    
                _str[_size] = '\0'; //补完之后加上字符串结束标志'\0'
    
                _capacity = n;//更新最大容量
            }
            else
            {
                if (n > _size)//如果调整之后的size  大于  原来的size
                              //就要把少(n-size)的有效字符用c补上
                {
                    for (; _size < n; _size++)//把多出来的有效字符用c补上
                    {
                        _str[_size] = c;
                    }
                    _str[_size] = '\0';//补完之后加上字符串结束标志'\0'
                }
                else//如果调整之后的size  小于  原来的size
                    //就要把多的字符删除
                {
                    _size = n;//调整有效字符大小
    
                    _str[_size] = '\0';//直接把n位置改成'\0'即可删除多余字符
                }
            }
        }
    
    
        size_t string::capacity() const
        {
            return _capacity;
        }
    
    
        void string::reserve(size_t n)//注意:n是指元素个数,不是字节数
        {
            if (n > _capacity)//当要调整的容量n大于capacity时,才扩容
            {
                char* tmp = new char[n + 1];//申请size+1个空间,那多出来的1是给'\0'的
    
                strcpy(tmp, _str);//拷贝字符串(把右参数拷贝给左参数)
    
                delete[] _str;//拷贝之后再释放旧空间
    
                _str = tmp;//指向新开辟的空间
    
                _capacity = n;//更改最大容量
            }
        }
    
    
        void string::clear()
        {
            _size = 0;
            _str[0] = '\0';
        }
    
    
    
        bool string::empty() const
        {
            return _size == 0;//如果size==0就为真,就会return true
                              //如果size!=0就为假,就会return false
        }
    
    
        char& string::operator[](size_t pos)
        {
            assert(pos<_size);//防止越界访问
    
    
    
            return _str[pos];//因为存放字符的空间是连续的
                             //所以直接像数组一样,使用pos位置的字符就可以
        }
    
        //const修饰的对象会自动调用下面这个
        const char& string::operator[](size_t pos)const
        {
            assert(pos < _size);//防止越界访问
    
    
            return _str[pos];
        }
    
        //在串尾加上一个string对象或者  字符串【可以隐式类型转换为string对象】
        string& string::append(const string& obj)
        {
            if (_capacity < obj._size + _size)//如果容量不够了
            {
                reserve(obj._size + _size);//就扩容
            }
    
            //从指定地址开始  拷贝字符串(把右参数拷贝给左参数)
            strcpy(_str + _size, obj._str);
    
            _size += obj._size;//改变有效字符个数
    
            return *this;
        }
    
        //在串尾加上一个字符
        string& string::append(char ch)
        {
            if (_capacity <_size+1)//如果容量不够了
            {
                reserve(_size + 1);//就扩容
            }
    
            _str[_size++] = ch;
    
            _str[_size] = '\0';//\0被ch覆盖了,再加回来
    
            return *this;
        }
    
    
        void string::push_back(char c)
        {
    
            append(c);//复用append,尾插一个字符c
    
        }
    
        string& string::operator+=(const string& obj)
        {
            append(obj);//复用append,尾插一个string对象/字符串
    
            return *this;
        }
    
        string& string::operator+=(char c)
        {
            append(c);//复用append,尾插一个字符c
    
            return *this;
        }
    
    
    
        string& string::assign(const string& obj)
        {
            *this = obj;
            return *this;
        }
    
    
    
        string& string::insert(size_t pos, const string& obj)
        {
            if (_capacity < obj._size + _size)//如果容量不够了
            {
                reserve(obj._size + _size);//就扩容
            }
    
            //从要插入的pos位置开始,把pos和其之后的字符都
            //向后移动  传入的对象的size个位置
            for (int i = _size; i >=(int) pos; i--)
            {
                _str[i + obj._size] = _str[i];
            }
    
            //把字符串插入进去
            for (int i = pos,j=0; i<obj._size+pos; i++,j++)
            {
                _str[i] = obj._str[j];
            }
    
            _size += obj._size;
            _str[_size] = '\0';//再补上\0
            return *this;
        }
    
        string& string::insert(size_t pos, char c)
        {
            if (_capacity < _size + 1)
            {
                reserve(_size + 1);
            }
            for (int i = _size; i >=(int) pos; i--)
            {
                _str[i + 1] = _str[i];
            }
            _str[pos] = c;
            return *this;
        }
    
    
        string& string:: erase(size_t pos , size_t len)
        {
            if (len > _size - pos)//如果len大于pos及其之后的字符的长度  或者 len==npos
            {
                _str[pos] = '\0';//就直接把pos及其之后的字符  全部删除
    
                _size = pos;//把size更改成新的有效长度
            }
            else//否则
            {
                //把从pos开始的字符都  用它+len之后的字符进行覆盖,即可完成删除
                for (int i = pos; i <=_size-len; i++)
                {
                    _str[i] = _str[i + len];
                }
    
                _size -= len;//把size更改成新的有效长度
            }
            return *this;
        }
    
    
        void string::swap(string& obj)
        {
            //使用库里面的swap把 两个对象的成员变量交换即可
            std::swap(_str, obj._str);
            std::swap(_size, obj._size);
            std::swap(_capacity, obj._capacity);
        }
    
        //获取string对象中的  str成员
        const char* string::c_str() const
        {
            return _str;
        }
    
        //从pos位置开始查找字符串
        size_t string::find(const string&obj, size_t pos) const
        {
            const char* p = NULL; //p为找到的字符串的首地址
    
            //使用string.h里面的strstr查找子串,如果  找不到  就返回NULL
            if ((p=strstr(_str + pos, obj._str)) == NULL)
            {
                return npos;//找不到  就返回  npos
            }
            else
            {
                return p - _str;//返回下标,p为找到的字符串的  首地址
                                //p-字符数组的首地址str  等于str到p之前的元素个数-1,即下标
            }
        }
    
    
    
        size_t string::find(char c, size_t pos ) const
        {
            for (int i = pos; i < _size; i++)
            {
                if (_str[i] == c)
                    return i;
            }
            return npos;
        }
    
        //把从pos开始的长度为len的子串  作为一个新的字符串返回
        string string::substr(size_t pos, size_t len) const
        {
            if (len > _size - pos)//如果len大于pos及其之后的所有 字符的长度或者len==npos
            {
                string tmp(_str + pos);//直接用默认构造把pos及其之后的字符全部  做新串返回
    
                return tmp;
            }
            else//否则
            {
                //申请长度为len+1的空间,多出的1是给\0的
                char* p = new char[len + 1];
    
                strncpy(p, _str+pos, len);//把从pos开始的长度为len的子串拷贝给p
    
                p[len] = '\0';
    
                string tmp(p);//用默认构造创建出新字符串
    
                return tmp;
            }
        }
    
        //比较两个字符串的大小
        //返回值大于0就是  左>右
        // 返回值等于0就是  左=右
        // 返回值小于0就是  左<右
        int string::compare(const string& obj) const
        {
            //使用string.h里面的  strcmp即可完成判断
            return strcmp(_str, obj._str);
        }
    
    
        string string::operator+(const string& obj)const
        {
            string tmp(*this);//拷贝构造出一个临时对象
    
            tmp += obj;//让临时对象去+=,就不会改变字符串自己了
    
            return tmp;//传值返回
        }
    
    
    
        string string::operator+(char c)const
        {
            string tmp(*this);//拷贝构造出一个临时对象
    
            tmp += c;
    
            return tmp;
        }
    
    
        bool string::operator>(const string& obj)const
        {
            if (compare(obj) > 0)//返回值大于0就是  左>右
                                 //即调用函数的对象>传入的对象
                return true;
            else
                return false;
        }
    
    
        bool string::operator<(const string& obj)const
        {
            if (!(*this >= obj))// < 就是>=取反
            {
                return true;
            }
            else
                return false;
        }
    
        bool string::operator>=(const string& obj)const
        {
            //大于等于  是  大于或者等于
            if (*this > obj || *this == obj)
    
                return true;
            else
    
                return false;
        }
    
        bool string::operator<=(const string& obj)const
        {
            //小于等于就是  不大于,即大于取反
            if (!(*this > obj))
                return true;
            else
                return false;
        }
    
    
        bool string::operator==(const string& obj)const
        {
            if (compare(obj) == 0)//返回值大于0就是  左=右
                                  //即调用函数的对象=传入的对象
                return true;
            else
    
                return false;
        }
    
    
        bool string::operator!=(const string& obj)const
        {
            //不等于就是  等于取反
            if (!(*this==obj))
    
                return true;
            else
    
                return false;
        }
    
        //ostream是输出流对象,比如我们常用的cout就是  ostream实例化的对象
        ostream& operator<< (ostream& os, const string& obj)
        {
    
            const char* p = obj.c_str();//获取string对象中的 str成员
    
            os << p;//可以用字符指针直接输出  它指向  的字符串
    
            return os;//为支持链式编程,返回输出流对象的引用
        }
    
        //istream是输入流对象,比如我们常用的  cin  就是  istream实例化的对象
        istream& operator>>(istream& is, string& obj)
        {
            char str[100];//存储  输入的  每一行的字符
    
            int sum = 0;//使用sum记录输入的  每一行  的字符个数
            char c = 0;
    
            //\n是换行符(回车),所以  没遇到  \n就  没换行
            while ((c = is.get()) != '\n')//一次读取一个字符
            {
                if (sum == 99)//当sum等于99时说明  str数组存不下了
                {
                    str[sum] = '\0';//末尾加上了\0才是字符串
    
                    obj += str;//使用+=把str数组中的字符先加上去
    
                    sum = 0;//把sum置成0,继续记录
                }
                else//否则
                {
                    //把读取到的字符存进str数组里
                    str[sum++] = c;
                }
            }
    
            //sum!=0说明最后输入的最后一行字符个数  小于99个,没有+=上
            if (sum != 0)
            {
                str[sum] = '\0';//末尾加上了\0才是字符串
    
                obj += str;//使用+=把str数组中的字符先加上去
            }
            return is;//为支持链式编程,返回  输入流对象  的引用
        }
    }
    

  • 相关阅读:
    Edge在IE模式下加载网页 - Edge设置IE兼容性
    三面头条+四面阿里+五面腾讯拿offer分享面经总结,最终入职阿里
    PyQt5 拖拽与剪贴板
    C语言,关于字节对齐的一些问题
    基于openGauss的五子棋AI项目
    选择文件如何控制,已经选择了一个文件再选时就必须也在当前文件夹选 js
    Kotlin 协程 (7/7篇) - 在Android中的使用
    消息队列-Kafka-基础架构
    自动化运维——ansible (五十三) (02)
    (二)基于kubernetes(1.25.2) 进行基础prometheus监控
  • 原文地址:https://blog.csdn.net/2301_80058734/article/details/140004256