当前位置:首页 » 《关注互联网》 » 正文

【C++之STL】摸清 string 的模拟实现(中)

17 人参与  2024年11月26日 16:01  分类 : 《关注互联网》  评论

点击全文阅读


string的模拟实现系列文章:

模拟实现上模拟实现中模拟实现下

文章目录

5. 调整操作5. 1 `push_back()`5. 2 `append()`5. 3 `operator+=()`5. 4 `insert()`5. 5 `erase()`5. 5. 1 `npos`5. 6 `swap()`5. 6. 1 为什么要实现成员函数 `swap` 6. 访问操作6. 1 `operator[]`6. 2 `front()`和`back()`


5. 调整操作

5. 1 push_back()

往字符串后面加一个字符。

注意检查容量是否足够,还有添加'\0'

void string::push_back(char c){    // 检查是否需要扩容    if (_size == _capacity)    {        // 这里采用2倍扩容,或者你也可以1.5倍扩容        size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;        reserve(newcapacity);        _capacity = newcapacity;    }    // 插入字符和 \0    _str[_size++] = c;    _str[_size] = '\0';}

5. 2 append()

append()有很多重载,但是这里我们只实现插入字符串的和插入多个相同字符的,其他的可以自行尝试。

追加字符串
void string::append(const char* str){    // 要断言 str 不为空指针    assert(str);    size_t len = strlen(str);    // 扩容    if (_capacity < len + _size)    {        size_t newcapacity = len + _size > 2 * _capacity ? len + _size : 2 * _capacity;        reserve(newcapacity);        _capacity = newcapacity;    }    // 追加新的数据,strcpy会拷贝\0,所以不需要手动加    strcpy(_str + _size, str);    _size += len;}
追加多个相同字符
void string::append(size_t n, char c){    // 扩容    if (_capacity < n + _size)    {        size_t newcapacity = n + _size > 2 * _capacity ? n + _size : 2 * _capacity;        reserve(newcapacity);        _capacity = newcapacity;    }    // 插入数据    for (int i = 0; i < n; i++)    {         //push_back(c);//这里也可以对push_back()进行复用        _str[_size++] = c;    }    // 如果是对push_back进行复用,这里就不用再手动加'\0'了    _str[_size] = '\0';}

5. 3 operator+=()

operator+=有两个功能:

插入字符,相当于push_back()插入字符串,相当于append()

可以在不同的重载中复用不同的函数。

为了保证运算符能连续运算,还要注意返回*this

// 追加字符串,复用append()string& string::operator+=(const char* str){    append(str);    return *this;}// 追加字符,复用push_back()string& string::operator+=(char c){    push_back(c);    return *this;}

5. 4 insert()

string& insert(size_t pos, char c);string& insert(size_t pos, const char* str);

pos位置(下标)插入字符或者字符串,需要把pos位置之后的所有元素都向后挪动,因此效率上可能比较差。

在挪动的时候,要特别注意不要发生越界!

挪动时也要注意不要让先挪动的数据盖住还没挪动的数据,应该从后往前依次挪动来避免这一情况。

    string& string::insert(size_t pos, char c)    {        assert(pos <= _size);        // 扩容        if (_capacity < _size + 1)        {            size_t newcapacity = _size + 1 > 2 * _capacity ? 1 + _size : 2 * _capacity;            reserve(newcapacity);            _capacity = newcapacity;        }        for (size_t i = _size; i > pos; i--)        {            //不能改成_str[i + 1] = _str[i]的形式            _str[i] = _str[i - 1];        }        // 记得加'\0'        _str[++_size] = '\0';        _str[pos] = c;        return *this;    }    string& string::insert(size_t pos, const char* str)    {        assert(pos <= _size);        size_t len = strlen(str);// 扩容        if (_capacity < len + _size)        {            size_t newcapacity = len + _size > 2 * _capacity ? len + _size : 2 * _capacity;            reserve(newcapacity);            _capacity = newcapacity;        }// 挪动数据        for (size_t i = _size + len; i >= len; i--)        {            _str[i] = _str[i - len];        }// 插入字符串        for (size_t i = 0; i < len; i++)        {            _str[i + pos] = str[i];        }        _size += len;        // 加'\0'        _str[_size] = '\0';        return *this;    }

5. 5 erase()

string& erase(size_t pos, size_t len = npos);

5. 5. 1 npos

npos 是一个静态成员常量值,对于 size_t 类型的元素具有最大可能的值。

当此值用作字符串成员函数中长度时,表示“直到字符串的末尾”。

作为返回值,它通常用于指示没有匹配项。

此常量定义为值 -1,因为 size_t 是无符号整型,所以它是此类型的最大可能可表示值。\

其定义为:

    const static size_t npos = -1;

那么回到erase的模拟实现:

删除一段数据,就是把后面的直接拉到前面进行覆盖就行了。

但是要注意,要从pos位开始被覆盖,如果从最后面开始的话,可能会覆盖还没挪动的数据。

string& string::erase(size_t pos, size_t len){    // 如果会把pos位置之后的所有元素全部删除,就不需要挪动数据,直接在pos位置加'\0'就行了   if (len >= _size - pos - 1)   {       _str[pos] = '\0';       _size = 0;   }   else   {       // 挪动数据       // 从 pos + len 位开始向前挪动       for (size_t i = pos + len; i <= _size; i++)       {           _str[i - len] = _str[i];       }       _size -= len;       // 加'\0'       _str[_size] = '\0';   }   return *this;}

5. 6 swap()

尽管在算法库中已经有了一个swap()函数,但是string类中依然实现了swap,并且有两个,一个是正常的成员函数,还有一个是stdswap函数的重载。

我们先看成员函数这一个:

void swap(string& s)

只需要交换thiss的三个成员变量就可以了:

void string::swap(string& s){    // 交换的时候还可以直接使用 std 中的 swap 进行交换    std::swap(_str, s._str);    std::swap(_size, s._size);    std::swap(_capacity, s._capacity);}

再看对std中的swap的重载:

注意为了防止重定义,要把声明和定义分离在.h.cpp文件中,不能在头文件中直接实现定义。

这个函数放在全局,不放入命名空间,这样当全局有匹配的函数时,就不会在命名空间中搜索函数了,更何况库中的还是函数模板。

// string.hvoid swap(test::string& s1, test::string& s2);// string.cppvoid swap(test::string& s1, test::string& s2){    s1.swap(s2);  // 调用类的成员函数 swap}

5. 6. 1 为什么要实现成员函数 swap

既然算法库中已经实现了一个swap,而且如果你尝试的话,会发现这个swap也是可以成功交换两个string类型的,但是为什么我们不使用呢?

因为算法库中的swap是通过模板实现的,一般实现为:

template <class T>     void swap (T& a, T& b){  T c(a);     a=b;     b=c;}

可以看到,算法库中的swap是通过创建临时变量来进行交换的,对于一个自定义类型,发生拷贝是一件很可能严重影响效率的事,如果这个string类中存储了非常多的数据,就会大大拖慢程序的运行。

并且实际上string类型之间的交换完全不需要创建临时变量,只需要交换所有的成员变量就可以了,这样一比较,算法库提供的swapstring类的交换会产生无法接受的损耗,所以在库中要想方设法避免程序员使用到原本的模板生成的函数。

6. 访问操作

6. 1 operator[]

下标访问操作符,就是直接返回_str中对应位置的元素的引用

但是这里要提供两个重载,因为string类如果被const修饰的话,直接返回引用会发生权限放大,导致报错,所以还要提供返回值也被const修饰的重载。

因为下标访问操作符本身

char& string::operator[](size_t index){    return *(_str + index);}// 注意这里构成重载的原因是最后的那个const,它修饰的是this指针,返回值类型不同不能构成重载const char& string::operator[](size_t index)const{    return *(_str + index);}

6. 2 front()back()

分别返回第一个和最后一个元素的引用,出于和下标访问操作符相同的原因,也要提供const版本的。

char& string::front(){    return _str[0];}const char& string::front()const{    return _str[0];}char& string::back(){    return _str[_size - 1];}const char& string::back()const{    return _str[_size - 1];}

下接: 模拟实现下

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章


点击全文阅读


本文链接:http://zhangshiyu.com/post/192367.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1