【C++】:string的模拟实现

编程入门 行业动态 更新时间:2024-10-25 15:19:42

【C++】:<a href=https://www.elefans.com/category/jswz/34/1769667.html style=string的模拟实现"/>

【C++】:string的模拟实现

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关string的模拟实现,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 基本构造

2. 深拷贝

2.1 传统写法

2.2 现代写法

2.3 写时拷贝(了解)

2. 容量相关接口

2.1 size、capacity、clear、empty

2.2 reserve

2.3 resize

3. 迭代器

3.1 operator[ ]

3.2 begin、end

4. 修改相关接口

4.1 push_back、c_str、npos

4.2 operator+=、append

4.3 insert

4.4 erase

5. 运算符重载

6. 流插入和流提取

6.1 流提取

6.2 流插入


1. 基本构造

为了区别库中本来的string,我们可以将模拟实现的string封装在我们自己的命名空间中,string的基本构造构造函数、拷贝构造、析构函数。

 头文件:string.h

#pragma once
#include <iostream>using namespace std;//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{class string{public://构造函数string(const char* str = ""):_size(strlen(str)),_capacity(_size){//根据空间大小为_str开辟空间_str = new char[_capacity + 1];  //多开辟一个存放'\0'//将str拷贝至_strstrcpy(_str, str);}//析构~string(){//匹配使用delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;      //有效元素个数size_t _capacity;  //空间大小};
}

2. 深拷贝

在之前的文章中提到过关于浅拷贝的问题,我们不写编译器默认生成的拷贝构造就是浅拷贝,但是string存在资源的创建的,所以我们得自己实现一个深拷贝的拷贝构造。

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显示给出。一般情况都是按照深拷贝方式提供。

2.1 传统写法

#pragma once
#include <iostream>
using namespace std;
//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{class string{public://构造函数string(const char* str = ""):_size(strlen(str)),_capacity(_size){//根据空间大小为_str开辟空间_str = new char[_capacity + 1];  //多开辟一个存放'\0'//将str拷贝至_strstrcpy(_str, str);}//析构~string(){//匹配使用delete[] _str;_str = nullptr;_size = _capacity = 0;}//传统写法//拷贝构造string(const string& s){_str = new char[s._capacity + 1];_size = s._size;_capacity = s._capacity;strcpy(_str, s._str);}//赋值运算符重载string& operator=(const string& s){if (this != &s){//使用一块新的空间来保存s的数据char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;  //直接将_str重新指向tmp_size = s._size;_capacity = s._capacity;}return *this;}private:char* _str;size_t _size;      //有效元素个数size_t _capacity;  //空间大小};
}

传统写法的代码不容易看懂而且还比较麻烦,我们可以尝试新的方法。

2.2 现代写法

#pragma once
#include <iostream>
using namespace std;//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{class string{public://构造函数string(const char* str = ""):_size(strlen(str)),_capacity(_size){//根据空间大小为_str开辟空间_str = new char[_capacity + 1];  //多开辟一个存放'\0'//将str拷贝至_strstrcpy(_str, str);}//析构~string(){//匹配使用delete[] _str;_str = nullptr;_size = _capacity = 0;}/*//传统写法//拷贝构造string(const string& s){_str = new char[s._capacity + 1];_size = s._size;_capacity = s._capacity;strcpy(_str, s._str);}//赋值运算符重载string& operator=(const string& s){if (this != &s){//使用一块新的空间来保存s的数据char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;  //直接将_str重新指向tmp_size = s._size;_capacity = s._capacity;}return *this;}*///现代写法void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝构造string(const string& s):_str(nullptr),_size(0),_capacity(0){//复用构造函数string tmp(s._str);swap(tmp);}//赋值运算符重载string& operator=(string tmp){//直接交换swap(tmp);return *this;}private:char* _str;size_t _size;      //有效元素个数size_t _capacity;  //空间大小};
}

上述方法是比较简单的,但是还是一种深拷贝的情况,那么可以通过写时拷贝来进行浅拷贝。

2.3 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。


引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

2. 容量相关接口

以下接口都是在string类中实现:

2.1 size、capacity、clear、empty

//清理
void clear()
{_size = 0;_str[_size] = '\0';
}
//有效个数
size_t size() const
{return _size;
}
//容量
size_t capacity() const
{return _capacity;
}
//判空
bool empty()const
{return 0 == _size;
}

2.2 reserve

当要扩容的空间比原来的空间大时才可以进行扩容,重新开辟一块新的空间,先将_str拷贝至新空间,然后再将_str指向的旧空间进行释放,再将_str重新指向新的空间,然后将新空间的_capacity置为新的容量即可:

//预留空间
void reserve(size_t n)
{//当传递的n大于本来的空间才可以扩容if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}

2.3 resize

当传递的n小于原本的_size,只需要将_str中下标为n的地方置为'\0'即可,再将_size改为n,如果大于_size,那么可以复用reserve预留空间,然后将剩余的空间用指定的字符来进行填充,如果没有指定字符,默认使用'\0'来填充,然后在最后面再添加上'\0'来表示末尾。

//修改有效个数
void resize(size_t n, char ch = '\0')
{//小于直接添加'\0'截止if (n <= _size){_str[n] = '\0';_size = n;}else{//大于先预留空间reserve(n);//再将指定的字符依次插入,末尾在添加'\0'while(_size < n){_str[_size] = ch;_size++;}_str[_size] = '\0';}
}

3. 迭代器

3.1 operator[ ]

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

3.2 begin、end

    typedef char* iterator;typedef const char* const_iterator;//非const iterator begin(){return _str;}iterator end(){return _str + _size;}//constconst_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}

4. 修改相关接口

4.1 push_back、c_str、npos

#pragma once
#include <iostream>
#include <assert.h>using namespace std;//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{class string{public:基本构造////...容量接口////...迭代器//...修改接口////尾插void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}//c格式的字符串const char* c_str() const{return _str;}private:char* _str;size_t _size;      //有效元素个数size_t _capacity;  //空间大小public:const static size_t npos;  //静态对象需要在全局定义};
}const size_t string::npos = -1;

4.2 operator+=、append

//追加字符串void append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}strcpy(_str+_size, str);_size += len;}//+=运算符重载string& operator+=(const char ch){//复用尾插push_back(ch);return *this;}string& operator+=(const char* str){//复用appendappend(str);return *this;}

4.3 insert

在pos位置插入字符有两种写法:

1. end表示size位置,end类型为int(有符号整形)

当进行头部插入(pos == 0),如果end类型为size_t(无符号整型),那么end就永远不可能小于pos,如果end类型为int,那么在进行end与pos的比较时,end会进行类型提升,从有符号整型提升为无符号整型,所以为了防止类型提升,需要将pos强制转化为int

//在pos位置插入字符void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//挪动数据int end = _size;   //end类型只能为intwhile (end >= (int)pos)  //防止类型提升,所以进行强转{_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;}

2. end表示size的后一个位置,end的类型为size_t(无符号整型)

//在pos位置插入字符void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//挪动数据size_t end = _size + 1;while (end > pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;}

在pos位置插入字符串同样的也有两种写法,这里就不做演示了。

//在pos位置插入字符串void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(len + _size);}//挪动数据int end = _size;  //end类型只能为intwhile (end >= (int)pos) //防止越界进行强转,防止类型提升{_str[end + len] = _str[end - 1];end--;}strncpy(_str + pos, str, len);_size += len;}

4.4 erase

//删除pos位置void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];begin++;}_size -= len;}}

5. 运算符重载

        bool operator<(const string& s) const{return strcmp(_str, s._str) < 0;}bool operator==(const string& s) const{return strcmp(_str, s._str) == 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);}

6. 流插入和流提取

6.1 流提取

//全局operator<<
//流插入
ostream& operator<<(ostream& out, const string& s)
{for (size_t i = 0; i < s.size(); i++){out << s[i];}/*for (auto ch : s)out << ch;*/return out;
}

6.2 流插入

重载流提取时要注意:库里面的cin与scanf是不读取空格和换行的,即便是我们输入了空格和换行它也不会读取,所以在重载流插入时要使用库里面的get

进行流插入之前先要清理一下字符串,避免上一次的字符串进行混淆,这里还要注意一下,当我们输入的东西很多的时候,它就面临了要不断的进行扩容,如果我们刚开始就进行扩容一点空间,那么还可能存在空间不够用,或者是浪费空间的问题,那么该怎么办呢?

我们可以使用一个字符数组buff当作一个缓存区,将这个buff大小设为129,将输入的字符先储存在这个数组里面,当这个数组满了之后再将这个数组的内容尾插到string中,如果没有满,就将剩下的内容继续尾插,这样就避免了string自己进行多次扩容的问题:

//流提取
istream& operator>>(istream& in, string& s)
{s.clear();   //清理char ch;char buff[129];  //设置缓存区ch = in.get();   //输入int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128)  //满了就往string中尾插{buff[++i] = '\0';s += buff;i = 0;}ch = in.get();}//将剩下的内容插入stringif (i != 0){buff[i] = '\0';s += buff;}return in;
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持! 

更多推荐

【C++】:string的模拟实现

本文发布于:2023-12-03 23:43:55,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1659083.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:string

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!