C++ string类模拟实现
上一篇讲解了string的使用,当然少不了string的模拟实现实现啦!这里依然是讲解常用接口的模拟实现,话不多说,下面正文直接开始!
@TOC
1. 成员变量
string
本质就是一个存放字符的顺序表,是由指针
、大小
、容量
组成的,并且加入了npos = -1
,来当作size_t
的上限,也起到当作查找是未找到的标准等功能。
namespace sakura //命名空间
{
class string
{
private:
char* _str; //数据指针
size_t _size; //大小
size_t _capacity; //容量
static const size_t npos;
};
}
==注意:==
这里npos
的类型为 static const size_t
,static
修饰后,npos
只能被初始化一次,而加了 const
后,允许在类中赋予缺省值进行初始化,如果不加 const
,则必需到类外手动初始化静态成员,const
修饰的静态变量,只允许整型家族在类中设置缺省值
2. 默认成员函数
2.1 构造和析构
==构造函数==
使用缺省参数,当用户没有传参时,将 string
对象初始化为空串,字符串长度可以利用初始化列表进行初始化
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1]; //预留位置给'/0'
strcpy(_str, str);
}
==析构函数==
析构函数
在释放内存时,是以 delete[]
的形式,所以在申请内存时,即使只申请一个 char
,也要写成 new char[1]
的形式,目的就是为了销毁对应
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity
2.2 拷贝和赋值
==拷贝构造==
此处的拷贝构造为深拷贝,先开辟一块和s空间相同大小的空间,然后拷贝数据到新空间,完成拷贝
string(const string& s)
:_size(s._size)
, _capacity(s._capacity)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
}
注意:申请空间后,要使用strcap
进行数据拷贝,否则操作无效
==赋值重载==
赋值重载的实现首先需要判断是否为同一对象赋值,然后再进行赋值操作
==注意:==
这里不使用_str = new char[s._capacity + 1]
的方式,而是先将数据拷贝到一块新开辟的空间,成功后改变_str
指向,释放掉原空间,这样可以保证若申请空间失败,原空间数据不被破坏
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1]; //防止new失败
strcpy(tmp, s._str);
delete[] _str;
_str = tmp; //改变指针指向
_size = s._size;
_capacity = s._capacity;
}
return *this; //返回避免 a = b = c 的连续赋值情况
}
3. 容量操作
3.1 reserve方法
若传入的capacity
不大于_capacity
,不做任何操作,若大于_capacity
,则正常进行扩容
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; //防止new失败
strcpy(tmp, _str);
delete[] _str;
_str = tmp; //改变指针指向
_capacity = n;
}
}
这里的_str = tmp
并不是使用的赋值重载(operator=)
,因为_str
和tmp
都是类值类型(指针类型),这里只是改变了_str
的指向
3.2 resize方法
大小调整分三种情况:
n < _size
:缩容,直接调整'\0'
的位置改变_size
n > capacity
:扩容并初始化n > _size
&&n < _capacity
:不扩容只进行初始化
void resize(size_t n, char ch = '\0')
{
if (n < _size) //比_size小就是缩容,直接添加终止符号'\0'
{
//删除数据 保留前n个
_size = n;
_str[_size] = '\0';
}
else if (n > _size) //扩容
{
if (n > _capacity)
{
reserve(n);
}
size_t i = _size;
while (i < n)
{
_str[i] = ch;
++i;
}
_size = n;
_str[_size] = '\0';
}
}
3.3 查看大小容量、判空
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty() const
{
return _size == 0;
}
类似获取数据这些,不修改成员变量数据的函数,最好都加上const
4. 数据访问
4.1 下标访问
类中的数据为私有,无法直接访问,但可以通过函数间接访问
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//const版本
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
4.2 迭代器访问
获取原生指针来简单实现迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//const版本
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
5. 数据修改
5.1 push_back方法
这里的push_back
方法对应于顺序表的尾插,相信友友们已经非常熟悉了,判断是否需要阔容后直接插入即可
void push_back(char ch)
{
if (_size + 1 > _capacity) //检查扩容
{
reserve(_capacity * 2); //二倍扩
}
//尾插字符
_str[_size] = ch;
_str[_size] = '\0';
++_size;
}
5.2 append方法
首先是append
尾插n
个字符,这里直接复用push_back
,来尾插n
次即可实现
void append(size_t n, char ch)
{
//提前开空间
if (_size + n > _capacity)
{
reserve(_size + n);
}
while (n--)
{
push_back(ch);
}
}
然后是append
尾插字符串,这里判断是否需要扩容后,直接调用strcpy
或者strcat
即可实现
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
//strcat(_str, str);
_size += len;
}
5.3 insert方法
操作步骤:
- 检查要插入位置的下标是否合法
- 检查是否需要扩容
- 挪动数据 + 插入数据
首先是任意位置插入字符的实现
string& insert(size_t pos, char ch)
{
assert(pos <= _size); //判断下标是否合法
if (_size + 1 > _capacity) //判断扩容
{
reserve(2 * _capacity);
}
//挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
//错误写法
//size_t end = _size;
//while (end >= pos)
//{
// _str[end + 1] = _str[end];
// --end;
//}
//插入数据
_str[pos] = ch;
++_size;
return *this;
}
这里的size_t end = _size
写法错误,是因为end
是size_t
类型,当end = -1
时,会发生整型提升,end = INT_MAX
产生错误
然后是任意位置插入字符串的实现
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 + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//写法二:
//size_t end = _size;
//for (size_t i = 0; i < _size + 1; ++i)
//{
// _str[end + len] = _str[end];
// --end;
//}
//拷贝插入
strncpy(_str + pos, str, len); //只拷贝len个,避免将'\0'拷贝
_size += len;
return *this;
}
有了insert
方法,我们可以直接将其复用给push_back
和append
方法
void push_back(char c)
{
insert(_size, c);
}
void append(const char* str)
{
insert(_size, str);
}
5.4 erase方法
操作步骤:
- 检查下标是否合法
- 判断删除长度,若
len == npos
或pos + len >= _size
则直接终止到pos
处,反之将pos + len
处的字符挪动覆盖到pos
处
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
6. 运算符重载
6.1 +/+=运算符重载
这里的+
和+=
运算符重载,就是借助临时变量来进行字符或字符串的附加操作
string operator+(char ch) const
{
string tmp(*this); //传值返回
tmp.push_back(ch);
return tmp;
}
string operator+(const string& s) const
{
string tmp(*this);
tmp.append(s._str);
return tmp;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
6.2 逻辑判断
string
对象是借助ASCII
值来进行大小的判断的,这里可以直接俄使用strcmp
函数,只需要实现 >
和 ==
,其他逻辑判断都可以复用
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 || s == *this;
}
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);
}
7. 其他
7.1 find方法
传入目标字符/字符串,遍历原字符串,找到返回下标,反之则返回npos
,支持从传入的位置查找,默认从pos = 0
的位置开始查找,查找字符串可以直接调用strstr
库函数
//查找字符
size_t find(char ch, size_t pos = 0) //默认从0下标开始查找
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
{
return i; //找到返回下标
}
}
return npos; //没找到返回npos
}
//查找字符串
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
char* p = strstr(_str + pos, str); //调用strstr库函数(查找子串)
if (p == nullptr)
{
return npos;
}
else
{
return p - _str; //指针 - 指针
}
}
==指针 - 指针就是实际找到的字符串的下标==
7.2 clean方法
clean
方法用于清空字符串,实现只用将字符串结束标准设置到0下标
位置,再将长度置零即可
void clrar()
{
_str[0] = '\0';
_size = 0;
}
7.3 swap方法
调用库中的swap
函数,将string
对象中的三个成员进行交换即可,此时是浅拷贝
,效率很高
void swap(string& s)
{
std::swap(_str, s._str); //交换指针
std::swap(_capacity, s._capacity); /交换容量
std::swap(_size, s._size); //交接大小
}
这里有个问题,为什么不直接交换对象,而是要交换三个成员变量呢?
这是因为直接交换对象的话,会发生多次拷贝构造的操作,而且还是深拷贝,效率很低
7.4 获取原生指针
_str
为私有成员不能直接获取,只需要借助函数间接获取即可
const char* c_str()
{
return _str;
}
8. 流操作
流操作是 string
中的类外成员函数,此时的左操作数为 ostream
或 istream
8.1 流插入
通过迭代器将string
对象的内容输出到屏幕上
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
8.2 流提取
流提取的实现有两个问题需要解决:
- 在获取字符串前,不知道用户输入的字符串长度,无法提前开辟空间,开小了不够,开大了浪费
- 读取数据后,若对象中已有数据,会覆盖原数据
解决方案:
- 借助一个 数组存储数据,当数组装满时,将 数组内容拼接至字符串尾部,原数组则重新开始存储数据
- 调用
clear
函数先清理原对象数据,再进行输入
istream& operator>>(istream& in, string& s)
{
s.clrar(); //清理原有数据
char ch = in.get();
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[127] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
9. 完整代码
#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;
//模拟实现string
namespace sakura
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//string(const char* str = nullptr) //不可以
//string(const char* str = "\0") //会多一个'\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[s._capacity + 1];
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str()
{
return _str;
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
string operator+(char ch) const
{
string tmp(*this); //传值返回
tmp.push_back(ch);
return tmp;
}
string operator+(const string& s) const
{
string tmp(*this);
tmp.append(s._str);
return tmp;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty() const
{
return _size == 0;
}
// 不修改成员变量数据的函数,最好都加上const
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 || s == *this;
}
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);
}
void resize(size_t n, char ch = '\0')
{
if (n < _size) //比_size小就是缩容,直接添加终止符号'\0'
{
//删除数据 保留前n个
_size = n;
_str[_size] = '\0';
}
else if (n > _size) //扩容
{
if (n > _capacity)
{
reserve(n);
}
size_t i = _size;
while (i < n)
{
_str[i] = ch;
++i;
}
_size = n;
_str[_size] = '\0';
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; //防止new失败
strcpy(tmp, _str);
delete[] _str;
_str = tmp; //改变指针指向
_capacity = n;
}
}
void push_back(char ch)
{
if (_size + 1 > _capacity) //检查扩容
{
_capacity == 0 ? _capacity = 1 : 0;
reserve(_capacity * 2); //二倍扩
}
//尾插字符
_str[_size] = ch;
_str[_size] = '\0';
++_size;
}
void append(size_t n, char ch)
{
//提前开空间
if (_size + n > _capacity)
{
reserve(_size + n);
}
while (n--)
{
push_back(ch);
}
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
//strcat(_str, str);
_size += len;
}
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(2 * _capacity);
}
//挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
//错误写法
//size_t end = _size;
//while (end >= pos)
//{
// _str[end + 1] = _str[end];
// --end;
//}
//插入数据
_str[pos] = ch;
++_size;
return *this;
}
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 + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//写法二:
//size_t end = _size;
//for (size_t i = 0; i < _size + 1; ++i)
//{
// _str[end + len] = _str[end];
// --end;
//}
//拷贝插入
strncpy(_str + pos, str, len); //只拷贝len个,避免将'\0'拷贝
_size += len;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
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 + pos, str);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
void clrar()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _capacity;
size_t _size;
static const size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clrar();
char ch = in.get();
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[127] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
C++【STL】之string模拟实现,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!
文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!