这学习我有三不学
昨天不学,因为昨天是个过去
明天不学,因为明天还是个未知数
今天不学,因为我们要活在当下,我就是玩嘿嘿~
–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–
目录
一、string类的模拟实现
1.成员函数(Member functions)
1.1 构造函数(constructor)
1.2 析构函数(destructor)
1.3 赋值拷贝函数(operator=)
2.迭代器(iterators)
3.容量(Capacity)
4.元素访问(Element access)
5.调节器(Modifiers)
6.字符操作(String operation)
7.成员常量(Member constant)npos实现
8.非成员函数重载(Non-member function overload)
二、完结撒❀
前言:
模拟string类的实现对于我们学习认识string类会有更加深刻的理解,还没学过string类的老铁建议可以先看学习一下我的上一篇博客讲解:C++ 之 string类 详细讲解,再来进行模拟实现。
一、string类的模拟实现
在上篇博客中讲解了string类的常用接口,这篇博客带大家模拟实现一下string类的一些常用接口。
string类查阅文档
我们根据上面文档所规划的接口分类为大家进行部分模拟实现,大家可以先简单看一下上面文档。
1.成员函数(Member functions)
1.1 构造函数(constructor)
● 无参构造函数 string() 实现:
string()
//:_str(nullptr)
:_str(new char[1])//不能赋空指针,因为直接c_str会出错
, _size(0)
, _capacity(0)
{
_str[0] = '\0';
}
● 有参(字符串)构造函数 string(const char* str = "") 实现:
//string(const char* str = nullptr)错
//string(const char* str = '\0')错
string(const char* str = "")//常量字符串默认结尾含有\0
//该构造函数可以替代上面无参构造函数
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
● 拷贝构造函数 string (const string& s) 实现:
//s2(s1)
string (const string& s)
{
_str = new char[s._capacity+1];//完成深拷贝,+1 存放\0使用
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
1.2 析构函数(destructor)
● ~string()实现:
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
1.3 赋值拷贝函数(operator=)
● operator=实现:
//s2 = s1
string& operator=(const string& s)
{
char* tmp = new char[s._capacity + 1];//多开辟的1个空间用来存储\0
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;//支持连续赋值
}
2.迭代器(iterators)
● begin实现:
在string类中迭代器中begin表示的就是字符串的首地址的指针,但并不是所有迭代器都是由指针来实现的。
这里使用迭代器的对象有两种,一种是const修饰的,一种是const没有修饰的,所以begin实现应有const修饰,和const不修饰两种:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
● end实现:
同理,end实现也一样:
typedef char* iterator;
typedef const char* const_iterator;
iterator end()
{
return _str + _size;
}
const_iterator end() const
{
return _str + _size;
}
这里可以再说一下,范围for的实现也是基于迭代器begin和end所实现的,大家可以在汇编代码中就可以看到。
而对于const修饰的对象和const没有修饰的对象分别对应使用的范围for其内部实现也是const修饰的begin,end和const没有修饰的begin,end两种范围for。
3.容量(Capacity)
● size实现:
size_t size() const
{
return _size;
}
● capacity实现:
size_t capacity() const
{
return _capacity;
}
● resize实现:
由于resize函数的实现可能需要对数组进行扩容,所以我们先实现一下reserve函数:
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n+1];//多开1个位置给\0
strcpy(tmp, _str);
delete[] _str;//!!!
_str = tmp;
_capacity = n;
}
}
注意:reserve只有在n大于当前有效空间是才会进行开辟,当n小于当前有效空间不会进行任何操作。
resize实现:
void resize(size n,const char* c)
{
if(n>_size)
{
reserve(n);
for(size i=size; i<n;i++)
{
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
else
{
_str[n] = '\0';
_size = n;
}
}
● clear实现:
注意:clear只是清楚当前所存数据,而不是销毁空间
void clear()
{
_size = 0;
_str[0] = '\0';
}
4.元素访问(Element access)
● operator[]实现:
operator[]的实现功能就是访问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];
}
5.调节器(Modifiers)
● insert实现:
插入字符或字符串那么肯定会涉及到空间不够是否需要扩容的问题,解决了之后剩下的就是将插入位置之后的字符串向后移动插入字符或字符串大小的位置,为要插入的字符或字符串流出插入空间,之后再将字符或字符串插入即可:
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
//扩容。。。reserve
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//后移字符串方案1
//int end = _size;
//while (end >= (int)pos)//!!!一个运算符两边操作数类型不同的时候发生类型提升(范围小的像范围大的提升 这里有符号向无符号提升)
//{
// _str[end + 1] = _str[end];
// --end;
//}
//后移字符串方案2
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* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos < end - len + 1)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
● append实现:
既然已经实现了insert,那么我们直接赋用insert实现append,push_back,operator+=即可:
void append(const char* str)
{
//size_t len = strlen(str);
//if (len + _size > _capacity)
//{
// //扩容。。。
// reserve(_size + len);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
● push_back实现:
void push_back(const char ch)
{
//if (_size == _capacity)
//{
// //扩容。。。reserve
// reserve(_capacity == 0 ? 4 : _capacity * 2);
//}
//_str[_size] = ch;
//_str[_size + 1] = '\0';
//++_size;
insert(_size, ch);
}
● operator+=实现:
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
● erase实现:
当len要清除的字符长度大于等于总字符长度减去pos起始位置时就要将pos起始位置后面的字符全都清除,即_str[pos] = '\0'即可。
void erase(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
if (pos == npos || pos >= _size - len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
6.字符操作(String operation)
● c_str实现:
const char* c_str() const
{
return _str;
}
● find实现:
size_t find(const char c, size_t pos = 0) const
{
assert(pos < _size);
//从pos位置开始遍历字符串
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
size_t find(const char* sub, size_t pos = 0) const
{
assert(pos < _size);
const char* p = strstr(_str+pos, sub);
if (p)
{
return p - _str;//指针-指针为两指针之间的距离
}
return npos;
}
● substr实现:
与erase分析相似,当要截取的len长度的字符大于等于总字符长度_size减起始位置pos时,就要将pos后面的字符全部截取进行返回:
string substr(size_t pos = 0, size_t len = npos) const
{
string sub;
if (len >= _size - pos)
{
for (size_t i = pos; i < _size; i++)
{
sub += _str[i];
}
}
else
{
for (size_t i = pos; i < pos + len; i++)
{
sub += _str[i];
}
}
return sub;
}
7.成员常量(Member constant)npos实现:
在标准库中,npos为静态全局变量,在npos文档中就有说明,其值应为size_t类型的-1。
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
public:
static size_t npos;//类内声明
};
size_t string::npos = -1;//类外定义
8.非成员函数重载(Non-member function overload)
● swap实现:
在C++库中是有swap函数的,我们可以直接调用使用,但是对于自定义类型来说就比如string类,其直接调用库中的swap函数进行交换的话会调用3次拷贝构造+1次析构(库中的swap实现方式是创建一个新的临时变量tmp进行两个值的交换),这样消耗会很大,所以对于自定义类型我们不推荐直接调用库里面的swap函数。
那么我们该如何实现swap函数呢?我们可以直接对自定义类型里面的内置成员变量进行交换即可满足两者自定义类型的交换,将swap函数定义为非成员函数是因为要满足swap(s1,s2);交换的格式。
内部成员函数:
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
非成员函数(全局函数):
void swap(string& s1, string& s2)
{
s1.swap(s2);//直接调用成员函数即可
}
● operator<<实现:
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;//实现连续打印
}
● operator>>实现:
istream& operator>>(istream& in, string& s)
{
s.clear();//输入变量值之前需要将变量原先所存的值给清除
char ch;
ch = in.get();//可以获取空格' ',cin不支持获取空格
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;//实现连续赋值
}
● getline实现:
istream& getline(istream& in, string& s)
{
s.clear();
char c;
c = in.get();
//栈区开辟空间,栈区开辟空间比堆区开辟空间高
char ch[128];//提高效率
size_t i = 0;
while (c != '\n')
{
ch[i++] = c;
if (i == 127)
{
ch[127] = '\0';
s += ch;
i = 0;
}
c = in.get();
}
if (i > 0)
{
ch[i] = '\0';
s += ch;
}
return in;
}
上面所实现的都是string类的常用的一些接口,其他一些没有实现的大家感兴趣的话可以查阅其他资料自行模拟实现一下
二、完结撒❀
如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤