学习专栏:
《进击的C++》
关于STL容器的学习,我会采用模拟实现的方式,以此来更加清楚地了解其底层原理和整体架构。而string类更是有100多个接口函数,所以模拟实现的时候只会调重点和常见的函数进行实现,以此加强对重点函数的掌握。
string类中包含了
同时,还包含了一个static修饰的静态成员变量npos,赋值为-1,因其类型为无符号整型,则表示最大值。
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
size_t string::npos = -1;
标准的静态成员变量,是在类内声明,类外定义。但是,这里设计出了一种奇怪的语法,加上const修饰,就可以在类内声明加定义。
static const size_t npos = -1;
细节:
\0
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s)
:_size(s._size)
, _capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
细节:
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
delete[] _str;
_str = tmp;
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
迭代器的实现和编译器有关,不同的编译器有不同的实现方式。这里简单的用指针来实现迭代器。
同时,重载了普通迭代器和const迭代器。
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
迭代器遵循左闭右开的原则,begin指向首元素,end指向末元素的下一位。
typedef char* iterator;
typedef const char* const_iterator;
iterator end()
{
return _str + _size;
}
const_iterator end() const
{
return _str + _size;
}
悄悄告诉你:范围for的底层实现,就是运用了迭代器。
为了方便的访问元素,我们重载了[ ]运算符。同时,也分为普通版本和const版本,对应不同string类的权限。
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
获取当前有效数据个数
细节:const修饰,保证普通和const类型string类都能访问
size_t size() const
{
return _size;
}
获取当前最大有效容量
细节:同上
size_t capacity() const
{
return _capacity;
}
改变当前_capacity(将其变为指定大小n)
细节:
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
改变当前_size(将其变为指定大小n),分为三种情况:
\0
void resize(size_t n, char ch = '\0')
{
if(n > _size)
{
reserve(n);
memset(_str + _size, ch, n - _size);
}
_size = n;
_str[_size] = '\0';
}
尾插一个字符
细节:
\0
void push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
尾插(追加)一个字符串
细节:
\0
也拷贝过去void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
悄悄说一句:其实这个函数写成push_back的重载函数更好哦~
为了更加方便地使用尾插,我们重载了+=运算符,这样无论尾插字符或者字符串都极为方便。
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
在指定位置插入一个字符
细节:
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
其实,步骤2的字符后移,可以使用memmove函数(专门处理重叠空间的移动)
memmove(_str + pos + 1, _str + pos, _size + 1 - pos);
在指定位置插入一个字符串
细节:
\0
)string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end + len - 1] = _str[end - 1];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
同样,步骤2的字符后移 len 格,也可以使用memmove函数。
memmove(_str + pos + len, _str + pos, _size + 1 - pos);
那么,完成了指定位置的插入,我们就可以复用代码,让push_back和append复用insert函数。
void push_back(char ch)
{
insert(_size, ch);
}
void append(const char* str)
{
insert(_size, str);
}
在指定位置删除指定长度的字符串
细节:
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if(len == npos || pos + len >= _size)
{
_size = pos;
_str[_size] = '\0';
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
交换两个string类的值
细节:使用std库中的swap函数,交换各个成员变量的值
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
清空字符串
void clear()
{
_str[0] = '\0';
_size = 0;
}
获取字符串
细节:const修饰,保证普通和const类型string类都能访问
const char* c_str() const
{
return _str;
}
查找指定字符或者字符串,返回其下标
细节:
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);
char* p = strstr(_str, str);
if (p == nullptr)
{
return npos;
}
return p - _str;
}
重载比较关系的运算符
细节:
bool operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
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);
}
重载流插入运算符
细节:遍历字符串,可以采用下标+[ ]的循环形式,也可以使用范围for
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')
{
s += ch;
ch = in.get();
}
return in;
}
以上代码是能够完成功能的实现,但是从效率的角度考虑,还是不够高效。所以,我们可以优化一下:
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
size_t i = 0;
char buf[128] = { 0 };
while (ch != ' ' && ch != '\n')
{
buf[i++] = ch;
if(i == 127)
{
s += buf;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
s += buf;
}
return in;
}
我们来模拟实现string类,不是为了造一个更好的轮子,而是熟练掌握重点函数的功能与应用,顺便巩固之前学习的C++语法。常言道,没学过STL,那你根本没学过C++!C++的梦幻之旅,才刚刚开始……
看到这里了还不给博主扣个: ?? 点赞??收藏 ?? 关注! 💛 💙 💜 ?? 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要! 你们的点赞就是博主更新最大的动力! 有问题可以评论或者私信呢秒回哦。