主页 > 其他  > 

STL之string类的模拟实现

STL之string类的模拟实现

目录

1. string的成员变量

2. string的成员函数 

2.1 string类的c_str()和swap()函数

2.2 string类的构造 

 2.3 string类的拷贝构造

2.3.1传统写法:

2.3.2现代写法: 

2.4string类的运算符重载 

 2.4.1传统写法:

2.4.2现代写法 

2. 5 string析构函数 

2.6 string的迭代器 

2.7 string的容量操作 

2.7.1. 有效长度与容量大小 

2.7.2. 容量操作 

2.8. string的访问操作 

2.9 string的修改操作 

2.9.1. 字符串的添加

2.9.2. 字符串的删除 

3.0 find()与substr()函数。

3.1. string的非成员函数


💓 博客主页:C-SDN花园GGbond

⏩ 文章专栏:玩转c++

为了让我们更加深入理解string,接下来我们将模拟实现一个·简易版的string。而为了和STL库中的string以示区分,我们将使用命名空间HTD对其封装。 

1. string的成员变量

string简单来说就是一个被封装可动态增长的字符数组,这与我们在数据结构中学的串非常类似,所以我们可以借助实现串的思路来大致模拟string的结构。 

下面是string的成员变量:

namespace betty { class string { public: //... private: char* _str;//存储的字符串 size_t _size;//当前有效字符的个数 size_t _capacity;//当前容量的大小,方便扩容 }; }

注意的是\0既不占据有效长度的大小,也不占据容量的大小。 

2. string的成员函数  2.1 string类的c_str()和swap()函数

在实现基本的构造,拷贝构造,赋值运算符重载,析构,和其他成员函数之前我们先实现c_str() 便于后续在类外使用_str成员变量来打印字符串的测试和swap()便于进行实现拷贝构造和赋值运算符重载现代写法

注意:非成员swap()函数在底层实际也是调用成员函数swap()函数

const char* c_str()const { return _str; } //交换字符串 void swap(string& s) { std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向 std::swap(_size, s._size); std::swap(_capacity, s._capacity); }

2.2 string类的构造  class string { public: //无参构造 /*string() { _str = nullptr; _size = _capacity = 0; }*/ string(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } private: char* _str; size_t _size; size_t _capacity; };

 2.3 string类的拷贝构造 2.3.1传统写法:

传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝 

//拷贝构造(深拷贝) string(const string& s) :_size(s._size) , _capacity(s._capacity) { _str = new char[s._capacity + 1]; strcpy(_str, s._str); } 2.3.2现代写法: 

现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)

string(const string&s) :_str(nullptr) { string tmp(s._str); swap(tmp); } 2.4string类的运算符重载   2.4.1传统写法:

传统的思路就是,先释放掉原空间,开跟s一样的空间   拷贝字符串,更改新的_szie,_capacity

//赋值运算符重载传统写法 string& operator=(const string& s) { if (this != &s) { delete[]_str; _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } return *this; }

注意:要注意自赋值情况!!否则s就被释放了 

2.4.2现代写法 

 现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。 

//赋值运算符重载现代写法 string& operator=(string s) { swap(s); return *this; } 2. 5 string析构函数 

 们实现析构函数,只需要清理资源即可

//析构函数 ~string() { delete[]_str; _size = _capacity = 0; } 2.6 string的迭代器 

首先我们来模拟实现一下迭代器iterator,而在string中迭代器iterator就是一个指针。所以我们直接使用typedef实现

 

typedef char* iterator; typedef const char* const_iterator;

接下来我们来实现begin()与end(),其中begin()指向的是字符串的起始位置即_str,而end()指向有效字符最后的下一位即\0的位置。 

iterator begin() { return _str; } iterator end() { return _str + _size; }

实现完普通迭代器之后,我们可以顺便重载一个const_iterator的版本。 

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

我们知道在string中还有一个反向迭代器,这个我们在之后会统一实现。 

2.7 string的容量操作  2.7.1. 有效长度与容量大小 

首先我们先实现返回字符串有效长度的size() 与容量大小的capacity()。并且为了适配const对象,最后用const修饰this指针。

size_t size() const { return _size; } size_t capacity() const { return _capacity; } 2.7.2. 容量操作  首先我们实现判断字符串是否为空的empty()以及清理字符串的clear()。其中emty() 不需要修改,可以加上const void clear() { _str[0] = '\0'; _size = 0;//clear()函数只会将字符串的内容设置为空,而不会改变其容量不考虑capacity } bool empty()const { return _size == 0; }

接下来我们来实现扩容函数reserve()与·resize(),其中reserve()最简单,只要新容量大于旧容量就发生扩容。 

void string::reserve(size_t n) { if (n > _capacity) { char*tmp= new char[n + 1]; memcpy(tmp, _str, _size); delete[]_str; _str = tmp; _capacity = n; } } void string::resize(size_t n) { if (n > _size) { if (n > _capacity) { reserve(n); } memset(_str + _size, '\0', n - _size); } _size = n; _str[_size] = '\0'; } 2.8. string的访问操作 

为了符合我们C语言访问数组的习惯,我们可以先重载operator[]。当然我们也要提供两种不同的接口:可读可写与可读不可写。并且使用引用返回,减少不必要的拷贝。 

// 可读可写 char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } // 可读不可写 const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }

同理我们也可以实现front()与back()函数。 

// 可读可写 char& front() { return _str[0]; } char& back() { return _str[_size - 1]; } // 可读不可写 const char& front()const { return _str[0]; } const char& back()const { return _str[_size - 1]; } 2.9 string的修改操作  2.9.1. 字符串的添加

首先我们将实现两个常用的修改函数:push_back()与append()

void push_back(char c) { // 如果数据满了,则需要进行扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = c; _str[_size] = '\0'; } // 追加字符串 void append(const char* str) { int len = strlen(str);// 获取字符串的长度 // 如果大于原来容量,则就需要扩容 if (_size + len > _capacity) { reserve(_size + len); } // 将字符串拷贝到末尾的_size位置 memcpy(_str + _size, str, len + 1); _size += len; }

而后我们可以复用前两个函数实现operator+=()。 

//追加一个字符 string& operator+=(char ch) { push_back(ch); return *this; } //追加一个字符串 string& operator+=(const char* s) { append(s); return *this; }

最后我们来实现随机插入insert()函数。将pos位置和pos后所有字符移动len个单位,如果为字符len=1,否则len=字符串长度 

//添加一个字符 void insert(size_t pos, char ch) { //防止越界访问 assert(pos <= _size); //检查是否需要扩容 if (_size == _capacity) { size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newCapacity); } size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; _size++; } //添加一个字符串 void insert(size_t pos, const char* s) { //防止越界访问 assert(pos <= _size); //检查是否需要扩容 size_t len = strlen(s); if (_size+len > _capacity) { reserve(_size+len); } size_t end = _size + len; while (end > pos) { _str[end] = _str[end - len]; --end; } memcpy(_str + pos, s, len); } 2.9.2. 字符串的删除 

 字符串的删除我们需要实现pop_back()与erase()两个函数。

void pop_back() { _str[_size - 1] = '\0'; --_size; }

而随机删除erase()需要再定义一个静态类成员变量npos来实现,它为无符号数的-1,一般为整型的最大值

// 类内声明 static size_t npos; // 类外初始化 size_t string::npos = -1;

将pos位置后所有字符往前移动len个单位,如果为字符 len=1,否则len=字符串长度 

void erase(size_t pos, size_t len = npos) { assert(pos < _size); //判断是否将后面字符之间删除完 if (len == npos || pos + len >= _size) { _str[0] = '\0'; _size = pos; } else { //往前移len个字符 size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; ++begin; } _size -= len; } } 3.0 find()与substr()函数。 const char* c_str()const { return _str; } //找字符 size_t find(char ch, size_t pos = 0) const { for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } //找字符串 size_t find(const char* s, size_t pos = 0) const { const char* p = strstr(_str + pos, s); if (p) { return p - _str; } return npos; } //截取一段字符串 string substr(size_t pos, size_t len = npos) { string s; size_t end = pos + len; //判断是否截取到最后 if (len == npos || pos + len >=_size) { len = _size - pos; end = _size; } //提前开辟空间 s.reserve(len); for (size_t i = pos; i < end; i++) { s += _str[i]; } return s; } 3.1. string的非成员函数 bool operator<(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) < 0; } bool operator==(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str())==0; } bool operator<=(const string& s1, const string& s2) { return s1.c_str() == s2.c_str() || s1.c_str() < s2.c_str(); } bool operator>(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) > 0; } bool operator>=(const string& s1, const string& s2) { return s1.c_str() == s2.c_str() || s1.c_str() >s2.c_str(); }

 接下来让我们实现流插入operator<<()与流提取operator>>()。但是我们要注意普通istream对象无法提前空格与\n。这是我们就需要一个函数get()来提取

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(); char buf[128]; int i = 0; while (ch != '\n')//以换行为分隔符 { buf[i++] = ch; // 为\0留空间 if (i == 127) { buf[i] = '\0'; s += buf; i = 0; } ch = in.get(); } //将buf中剩余数据直接填入 if (i != 0) { buf[i] = '\0'; s += buf; } return in; }

标签:

STL之string类的模拟实现由讯客互联其他栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“STL之string类的模拟实现