C++ string模拟实现

目录

        一.默认成员函数

        二.迭代器

        三.增

        四.容量

        五.[]的重载

        六.比较运算符的重载

        七.删

        八.查 

        九.改

        十.其他

        十一.输入输出函数


string是C++用来表示字符串的类,下面我们来模拟实现一个string类的增删查改。

        一.默认成员函数

        4个主要默认成员函数,需要注意构造函数的初始化列表

        //默认构造函数
        string(const char* str = "")//缺省值为空
            :_capacity(strlen(str))
            , _size(_capacity)
        {
            _str = new char[_capacity + 1];//多开1存放'\0'
            strcpy(_str, str);//拷贝内容
        }

        //拷贝构造函数
        string(const string& s)
            :_str(nullptr)//VS会对内置进行处理,但其他编译器不会,防止未初始化_str导致delete崩溃
            , _size(0)
            , _capacity(0)
        {
            //传统写法
            /*_str = new char[s._size + 1];//多开1空间是存放'\0'
            strcpy(_str, s._str);
            _size = s._size;
            _capacity = s._capacity;*/

            //现代写法
            string tmp(s._str);
            swap(tmp);
        }

        //赋值重载函数
        string& operator=(string s)//传值传参已经拷贝了
        {
            //传统写法 参数为(const string& s)
            /*if (this != &s)
            {
                _str = new char[s._size + 1];//多开1空间是存放'\0'
                strcpy(_str, s._str);
                _size = s._size;
                _capacity = s._capacity;
            }
            return *this;*/

            //现代写法 参数为(string s)
            if (this != &s)
            {
                swap(s);
            }
            return *this;
        }

        //析构函数
        ~string()
        {
            delete[] _str;//释放_str指向的空间
            _str = nullptr;
            _size = 0;
            _capacity = 0;
        }

        二.迭代器

        string的迭代器就是原生指针包装成的。

        typedef char* iterator;//迭代器
        typedef const char* const_iterator;//const迭代器
        
        //迭代器是左闭右开
        iterator begin()
        {
            return _str;//指向第一个字符
        }

        iterator end()
        {
            return _str + _size;//指向'\0'
        }

        const_iterator begin() const
        {
            return _str;
        }

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

        三.增

        这里只实现了增加字符和字符串的函数,不过基本涵盖到了大部分情况,对于增加string类的函数和增加字符串的基本一样。

        void push_back(char c)//增加字符
        {
            if (_size + 1 > _capacity)//判断是否超出当前容量
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);//扩两倍,需注意原始容量为0的情况
            }
            _str[_size] = c;//写入字符
            _size++;//增加当前字符个数
            _str[_size] = '\0';//补上'\0'
        }

        string& operator+=(char c)//增加字符
        {
            push_back(c);//复用即可
            return *this;
        }

        void append(const char* str)//增加字符串
        {
            size_t len = strlen(str);//算出增加的字符串的长度
            if (_size + len > _capacity)//判断是否超出当前容量
            {
                reserve(_size + len);//扩容
            }
            for (size_t i = 0; i < len; i++, _size++)
            {
                _str[_size] = str[i];//追加字符串
            }
            _str[_size] = '\0';//补上'\0'
        }

        string& operator+=(const char* str)//增加字符串
        {
            append(str);//复用即可
            return *this;
        }

        void clear()//清除string,只是修改_size
        {
            _size = 0;
            _str[_size] = '\0';
        }

        void swap(string& s)//交换两个string类的成员变量
        {
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
        }

        const char* c_str() const//返回_str的函数
        {
            return _str;
        }

        四.容量

        下面是两个扩容函数和一些基本函数。

        size_t size() const//返回当前string类中的字符个数
        {
            return _size;
        }

        size_t capacity() const//返回当前string类的容量大小
        {
            return _capacity;
        }

        bool empty() const//返回string是否为空
        {
            return _size == 0;
        }

        void resize(size_t n, char c = '\0')//让string类的_size变成n,多的会用c补足,少的会截断
        {
            if (n < _size)//如果是少的,直接截断
            {
                _size = n;
                _str[_size] = '\0';
            }
            else if (n > _size)
            {
                if (n > _capacity)//判断目标容量是否超出当前容量
                {
                    reserve(n);//是就扩容
                }
                for (size_t i = _size; i < n; i++)
                {
                    _str[i] = c;//用c补足n和_size之间的位置
                }
                _size = n;
                _str[_size] = '\0';//补充'\0'
            }
        }

        void reserve(size_t n)//容量扩容到n,小于当前容量就不理会
        {
            if (n > _capacity)//有点类似realloc扩容
            {
                char* tmp = new char[n + 1];//多开1空间存放'\0'
                strcpy(tmp, _str);
                delete[] _str;
                _capacity = n;
                _str = tmp;
                _str[_size] = '\0';
            }
        }

        五.[]的重载

        要针对const对象和非const对象写两个[]的重载,做到读和写分离。

        char& operator[](size_t index)//返回引用说明可以修改
        {
            assert(index < _size);//断言查询的下标有没有越界
            return _str[index];
        }

        const char& operator[](size_t index) const
        {
            assert(index < _size);//断言查询的下标有没有越界
            return _str[index];
        }

        六.比较运算符的重载

        比较运算符可以实现两个其他复用,复用和我之前的日期类完全一样。这里我比较大小使用了strcmp函数,这个函数在我之前的博客当中也有讲过实现原理和模拟实现,所以这里就不具体说明了。

        bool operator<(const string& s)//strcmp函数前者小于后者就会返回负数相等就会返回0,前者大于后者就会返回大于正数
        {
            return strcmp(this->_str, s._str) < 0;
        }

        bool operator==(const string& s)
        {
            return !strcmp(this->_str, s._str);
        }

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

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

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

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

        七.删

        删要诺数据,需要注意下标,稍有不慎就会越界或者出错。

        string& erase(size_t pos, size_t len)//删除pos位置后len个字符
        {
            assert(pos <= _size);//断言有没有越界
            if (pos + len >= _size)//如果要删除pos之后的所有的字符就像resize一样直接截断
            {
                _size = pos;
                _str[_size] = '\0';
            }
            else
            {
                //从前往后走,把右边的字符挪到左边去,这样不会出现覆盖的问题
                size_t end = pos + len;//这里要注意下标
                while (end <= _size)
                {
                    _str[end - len] = _str[end];
                    end++;
                }
                _size = pos;//更新当前字符的数量
            }
            return *this;
        }

        八.查 

        查找的函数有查找一个字符以及一个字符串,查找字符串复用了strstr函数,这个在我之前的博客有讲解以及实现。

        

        size_t find(char c, size_t pos = 0) const//找第一个出现在string中的c,找到了就返回下标,否则就返回npos(size_t -1)
        {
            for (size_t i = pos; i < _size; i++)
            {
                if (_str[i] == c)//找到就返回下标
                    return i;
            }
            return npos;
        }

        size_t find(const char* s, size_t pos = 0) const//返回子串s在string中第一次出现的位置的下标
        {
            char* res = strstr(_str + pos, s);//在前者字符串中找后者字符串,返回找到的字符串的指针(前者),没有找到就会返回空指针
            if (res != nullptr)
                return res - _str;//堆中的数据是从下往上的!!!,所以是res-_str
            return npos;//没找到就返回npos
        }

        九.改

        insert函数是在指定下标处插入一个字符或字符串,需要注意挪数据的方向和下标。

        string& insert(size_t pos, char c)//在pos位置插入字符c
        {
            assert(pos <= _size);//断言有没有越界
            if (_size + 1 > _capacity)//判断有没有超过当前容量
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);//注意当前容量为0的情况
            }
            //从后往前走,从左往右挪数据
            size_t end = _size + 1;//注意起始位置的下标,这里是指向'\0'的下一个位置
            while (end > pos)//这里需注意如果end指向'\0'就得是end>=pos,假如pos为0就会死循环,因为pos和end都是size_t,就算把end改为int也会死循环,因为隐式类型转换,end和pos相比还是会转换成size_t
            {
                _str[end] = _str[end - 1];//从左往右挪数据
                end--;
            }
            _size++;//增加当前字符数量
            _str[pos] = c;//插入字符
            return *this;
        }

        string& insert(size_t pos, const char* str)//在pos位置插入字符串
        {
            assert(pos <= _size);//断言有没有越界
            size_t len = strlen(str);//记录str的长度
            if (_size + len > _capacity)//判断有没有超过当前容量
            {
                reserve(_size + len);//扩容,因为不知道len长度和2*_capacity谁长,所以干脆直接扩容插入后的长度
            }
            //这里和insert字符基本一样,挪数据的间隔不是1而是len
            size_t end = _size + len;//注意下标
            while (end > pos)
            {
                _str[end] = _str[end - len];//从左往右挪数据
                end--;
            }
            for (size_t i = 0; i < len; i++, pos++)
            {
                _str[pos] = str[i];//插入字符串
            }
            _size += len;//增加当前字符数量
            return *this;
        }

        十.其他

        substr函数就是截取一段字符串。

        string substr(size_t pos = 0, size_t len = npos) const//从pos位置往后len字符截取字符串
        {
            string s;//创建字符串
            size_t end = pos + len;//假设end是pos之后len个字符
            if (len == npos || pos + len >= _size)//如果len或len+pos的位置超过了当前字符数量就令end指向'\0'
            {
                end = _size;
                len = _size - pos;
            }
            s.reserve(len);//提前开好空间,防止多次扩容造成性能浪费
            for (size_t i = pos; i < end; i++)
            {
                s += _str[i];//截取字符串
            }
            return s;//返回
        }

        十一.输入输出函数

        cout的实现比较简单,就是打印字符串的每一个字符,cin稍微复杂。这里是实现成全局函数,并且是string类的友元。

ostream& _string::operator<<(ostream& _cout, const _string::string& s)
{
    for (auto ch : s)//范围for直接打印
    {
        _cout << ch;
    }
    return _cout;
}

istream& _string::operator>>(istream& _cin, _string::string& s)
{
    char buff[129];//开一个字符数组
    char ch;
    ch = _cin.get();//从输入流中读取字符
    size_t size = 0;//记录buff数组存了几个字符
    while (ch != ' ' && ch != '\n')//没遇到空格和换行就继续
    {
        buff[size++] = ch;//读入字符
        if (size == 128)//如果读到上限就写入到s里
        {
            buff[129] = '\0';//记得要补上'\0'
            s += buff;
            size = 0;//重置size
        }
        ch = _cin.get();//读取字符
    }
    buff[size] = '\0';//buff可能还有数据,写入到s中即可
    s += buff;
    return _cin;
}

        以上就是string的简单模拟实现,如有问题可在评论区提出。

你可能感兴趣的:(c++,开发语言)