云计算百科
云计算领域专业知识百科平台

【C++】String的语法及常用接口底层模拟实现

【C++】String的语法及常用接口底层模拟实现

  • 一、string类(了解)
  • 二、string类的常用接口
    • 2.1 string类对象的常见构造
    • 2.2 string类对象的容量操作
    • 2.3 string类对象的访问及遍历操作
    • 2.4 string类对象的修改操作
    • 2.5 string类非成员函数
  • 三、string类常用接口的模拟实现
    • 3.1 初建构造
    • 3.2 赋值重载
    • 3.3 返回容量大小与【】
    • 3.4 reserve,resize
    • 3.5 插入(push_back,append,operator+=,insert)
    • 3.6 删除(erase),清空(clear)
    • 3.7 查找(find)
    • 3.8 返回子串(substr)
    • 3.9 比较符号(<,>,==,<=,>=,!=)
    • 3.10 迭代器(iterator)
    • 3.11 流插入(<<),流提取(>>)
  • 四、总结

一、string类(了解)

  • string是表示字符串的字符串类
  • 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  • string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
  • 不能操作多字节或者变长字符的序列。在使用string类时,必须包含#include头文件以及using namespace std;
    • C++ 的 std::string 是一个动态字符数组,支持多种操作,且是可变的。在使用string类时,必须包含#include头文件以及using namespace std;

    二、string类的常用接口

    2.1 string类对象的常见构造

    • 函数名称 ——-功能说明
    • string() ——– 构造空的string类对象,即空字符串
    • string(const char * s) ——– 用C-string来构造string类对象
    • string(size_t n, char c) ——- string类对象中包含n个字符c
    • string(const string&s) ——– 拷贝构造函数

    void Teststring()
    {
    string s1; // 构造空的string类对象s1
    string s2("hello bit"); // 用C格式字符串构造string类对象s2
    string s3(s2); // 拷贝构造s3
    }

    2.2 string类对象的容量操作

    • 函数名称 ——- 功能说明
    • size ——- 返回字符串有效字符长度
    • length ——- 返回字符串有效字符长度
    • capacity ——-返回空间总大小
    • empty ——- 检测字符串释放为空串,是返回true,否则返回false
    • clear ——- 清空有效字符
    • reserve ——- 为字符串预留空间
    • resize ——- 将有效字符的个数该成n个,多出的空间用字符c填充

    在这里插入图片描述

    注意: 1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。 2. clear()只是将string中有效字符清空,不改变底层空间大小。 3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。 [注意]:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。 4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

    2.3 string类对象的访问及遍历操作

    • 函数名称 ——- 功能说明
    • operator[] ——- 返回pos位置的字符,const string类对象调用
    • begin+ end ——- begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
    • 范围for ——- C++11支持更简洁的范围for的新遍历方式

    在这里插入图片描述

    2.4 string类对象的修改操作

    • 函数名称 ——- 功能说明
    • push_back ——- 在字符串后尾插字符c
    • append ——- 在字符串后追加一个字符串
    • operator+= ——- 在字符串后追加字符串str
    • c_str ——- 返回C格式字符串
    • find + npos ——- 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
    • rfind ——- 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
    • substr ——- 在str中从pos位置开始,截取n个字符,然后将其返回 在这里插入图片描述

    注意: 1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。 2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

    2.5 string类非成员函数

    • 函数名称 ——- 功能说明
    • operator+ ——- 尽量少用,因为传值返回,导致深拷贝效率低
    • operator>> ——- 输入运算符重载
    • operator<< ——- 输出运算符重载
    • getline ——- 获取一行字符串
    • <、>、=、>=、<=、== ——- 大小比较

    三、string类常用接口的模拟实现

    3.1 初建构造

    • 我们运用一个字符数组来存string,私有部分包括字符大小size,和空间容量capacity,_str字符数组
    • 构造函数的形参运用缺省参数,当不传参时,将会变为空串,运用strlen得到传入的字符串大小,再new一个容量较大的字符数组,让_str指向这个数组,最后用memcpy,将传入的字符串内容拷贝给_str
    • 拷贝构造思路与构造函数思路相同
    • 析构函数将_str释放,再将size与capacity置为0
    • 再写一个c_str接口,方便打印查看,与c语言适配

    class string
    {
    public:
    const static size_t npos = 1;
    //设置一个npos后面要用,类型为size_t所以这里的npos是一个非常大的整数

    //构造函数
    string(const char* str = "")
    {
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    memcpy(_str, str, _size + 1);
    }
    //拷贝构造
    string(const string& s)
    {
    _str = new char[s._capacity + 1];
    memcpy(_str, s._str, s._size + 1);
    _size = s._size;
    _capacity = s._capacity;
    }
    //析构函数
    ~string()
    {
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
    }
    //c_str
    const char* c_str()const
    {
    return _str;
    }
    private:
    size_t _size;
    size_t _capacity;
    char* _str;
    };

    3.2 赋值重载

    先写一个自定义的交换string的函数,将传入的形参与this的数据交换,最后返回*this

    //赋值重载
    void swap(string& s)
    {
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
    }
    string& operator=(string tmp)
    {
    swap(tmp);
    return *this;
    }

    3.3 返回容量大小与【】

    //size,capacity
    size_t size()const
    {
    return _size;
    }
    size_t capacity()const
    {
    return _capacity;
    }
    //[]模拟实现
    char& operator[](size_t pos)
    {
    assert(pos < _size);
    return _str[pos];
    }

    const char& operator[](size_t pos)const
    {
    assert(pos < _size);
    return _str[pos];
    }

    3.4 reserve,resize

    • reserve:如果需要的空间n大于原空间,则要进行扩容,创建一个临时变量tmp指向new的新空间,运用memcpy将原数据拷贝给tmp,将原来_str所在空间释放,再将_str只向tmp所指向的空间,size不变,capacity变为扩容后的n
    • resize:如果是缩小,n小于原来的size,直接在下标为n的位置置为\\0。如果是扩大,先用reserve扩容,再将ch放入字符数组

    //reserve模拟实现
    void reserve(size_t n)
    {
    if (n > _capacity)
    {
    char* tmp = new char[n + 1];
    memcpy(tmp, _str,_size + 1);
    delete[]_str;
    _str = tmp;
    _capacity = n;
    }
    }
    //resize模拟实现
    void resize(size_t n, char ch = '\\0')
    {
    if (n < _size)
    {
    _size = n;
    _str[_size] = '\\0';
    }
    else
    {
    reserve(n);

    for (size_t i = _size; i < n; i++)
    {
    _str[i] = ch;

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

    3.5 插入(push_back,append,operator+=,insert)

    • 插入的主要思想,就是空间不够就扩容,然后将字符放入数组,写出push_back和append,就可以复用写出+=,insert

    //push_back模拟实现
    void push_back(char ch)
    {
    if (_size == _capacity)
    {
    //2倍扩容
    reserve(_capacity == 0 ? 4 : _capacity * 2);

    }
    _str[_size] = ch;
    _size++;
    _str[_size] = '\\0';
    }

    //append模拟实现
    void append(const char* str)
    {
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
    reserve(_size + len);
    }
    memcpy(_str + _size, str, len + 1);

    _size += len;
    }

    //+=模拟实现
    string& operator+=(char ch)
    {
    push_back(ch);
    return *this;
    }
    string& operator+=(const char* str)
    {
    append(str);
    return *this;
    }

    //insert模拟实现
    void insert(size_t pos, size_t n, char ch)
    {
    assert(pos <= _size);
    if(_size + n > _capacity)
    {
    reserve(_size + n);
    }
    size_t end = _size;
    while (end >= pos && end != npos)
    {
    //当end==-1时,因为类型为size_t 所以会变为整数的最大值
    //也就是npos,所以多加一个条件控制whlie
    _str[end + n] = _str[end];
    end;
    }
    for (int i = 0; i < n; i++)
    {
    _str[i + pos] = ch;
    }
    _size += n;
    }
    void insert(size_t pos, const char* str)
    {
    assert(pos <= _size);

    size_t len = strlen(str);
    if (len + _size > _capacity)
    {
    reserve(len + _size);
    }
    size_t end = _size;
    while (end >= pos && end != npos)
    {
    _str[end + len] = _str[end];
    end;
    }
    for (size_t i = 0; i < len; i++)
    {
    _str[pos + i] = str[i];
    }
    _size += len;
    }

    3.6 删除(erase),清空(clear)

    • erase:实现的从某个位置开始删除,删除长度为 len 的字符。len有一个缺省参数,为npos(npos是一个很大的数,也就是不传参给 len 的话,默认删除到最后)。如果 len 本就很大,删除的长度超过从pos开始所剩余的长度,那么默认也是pos后的删除完。
    • claer:与erase不同,它是将整个字符串全部清空

    //erase模拟实现
    void erase(size_t pos, size_t len = npos)
    {
    assert(pos < _size);

    if (len == npos || len + pos >= _size)
    {
    _str[pos] = '\\0';
    _size = pos;
    }
    else
    {
    size_t end = len + pos;
    while (end <= _size)
    {
    _str[pos++] = _str[end++];
    }
    _size -= len;
    }
    }
    //clear模拟实现
    void clear()
    {
    _str[0] = '\\0';
    _size = 0;
    }

    3.7 查找(find)

    从pos位置开始查找,查找的内容可能是一个字符,也可能是一个子串。如果找到,则返回其下标。没找到就返回npos。

    //find模拟实现
    size_t find(char ch, size_t pos = 0)
    {
    assert(pos < _size);

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

    }
    return npos;
    }
    size_t find(const char* str, size_t pos = 0)
    {
    assert(pos < _size);

    const char* ptr = strstr(_str + pos, str);
    if (ptr)
    {
    return ptr _str;

    }
    else
    {
    return npos;
    }

    }

    3.8 返回子串(substr)

    从某个位置开始查找,查找长度为 len 的字符。len有一个缺省参数,为npos(npos是一个很大的数,也就是不传参给 len 的话,默认返回到最后)。如果 len 本就很大,返回子串的长度超过从pos开始所剩余的长度,那么默认也是pos后的子串全部返回。

    //substr模拟实现
    string substr(size_t pos = 0, size_t len = npos)
    {
    assert(pos < _size);

    size_t n = len;
    if (len == npos || len + pos >=_size)
    {
    n = _size pos;
    }

    string tmp;
    tmp.reserve(n);
    for (size_t i = pos; i < pos + n; i++)
    {
    tmp += _str[i];
    }
    return tmp;
    }

    3.9 比较符号(<,>,==,<=,>=,!=)

    字符串的比较并非比较其长度,而是与其相同位置字符的大小有关,也就是我们所说的字典序。我们这里只需要实现其中的两个,其他均可复用。

    bool operator<(const string& s)const
    {
    //先用memcmp比较两个字符串较短的部分(前半段长度相等的部分)
    //如果相等,ret为0,
    //则size小的字符串小
    int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
    // "hello" "hello" false
    // "helloxx" "hello" false
    // "hello" "helloxx" true
    return ret == 0 ? _size < s._size : ret < 0;
    }
    bool operator==(const string& s)const
    {
    return _size == s._size
    && memcmp(_str, s._str, _size) == 0;
    }
    bool operator<=(const string& s)const
    {
    return *this < s || *this == s;
    }
    bool operator>
    (const string& s)const
    {
    return !(*this <= s);
    }
    bool operator>=(const string& s)const
    {
    return !(*this < s);
    }
    bool operator!=(const string& s)const
    {
    return !(*this == s);
    }

    3.10 迭代器(iterator)

    这里的begin()就是返回的字符串的首元素地址,end()返回的是字符串最后一个元素的后一个地址!

    public:
    typedef char* iterator;
    typedef const char* const_iterator;
    //迭代器模拟实现
    iterator begin()
    {
    return _str;
    }

    iterator end()
    {
    return _str + _size;
    }

    const_iterator begin() const
    {
    return _str;
    }

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

    3.11 流插入(<<),流提取(>>)

    //流插入,流提取
    ostream& operator<<(ostream& out, const string& s)
    {
    for (auto ch : s)
    {
    out << ch;
    }
    return out;
    }
    istream& operator>>
    (istream& in, string& s)
    {
    //这里我们采用一个一个字符的输入

    //覆盖之前输入数据
    s.clear();

    char ch = in.get();
    //处理前缓冲区前面的空格或者换行
    while (ch == ' ' || ch == '\\n')
    {
    ch = in.get();
    }

    //每次输入的字符大小不确定,如果提前用reserve开空间可能导致浪费
    //因此先用一个大小为128字符数组存输入的字符,当数组满时再放入string
    char buff[128];
    int i = 0;

    while (ch != ' ' && ch != '\\n')
    {
    buff[i++] = ch;
    if (i == 127)
    {
    buff[i] = '\\0';
    s += buff;
    i = 0;
    }

    ch = in.get();
    }

    if (i != 0)
    {
    buff[i] = '\\0';
    s += buff;
    }

    return in;
    }

    四、总结

    string是C++中比较重要的且是入门时必须所学的容器。在平常中使用的频率较高,所以我们不仅要掌握其简单的用法,更应该去了解其底层的实现。这有助于我们后续的使用和理解。string常用的语法和接口的底层实现,这些都是我们应该熟练掌握的内容。

    关于string类的讲解就到这里,希望对你有所帮助,感谢观看ovo!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【C++】String的语法及常用接口底层模拟实现
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!