本章代码gitee仓库:string模拟实现
文章目录
- 0. 搭个框子
- 1. 成员变量
- 2. 构造函数 & 析构函数 & 拷贝构造
- 3. 字符串访问
- c_str
- operator[]
- 4. 申请空间
- 5. 增删查改
- 增加字符/字符串
- push_back
- append
- operator+=
- insert
- 删除
- 查找 & 修改
- 6. 字符串比较
- 7. 深浅拷贝
- 8. 流插入 & 流提取
- 9. 写时拷贝(补充知识)
要模拟实现string
,我们就要定义出一个自己的类域,然后引用一些头文件(这个可以边写边加,我这边就直接全写上了)
#include
#include
#include
//我不是很喜欢全部展开,用到什么展开什么就行
//using namespace std;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::ostream;
using std::istream;
namespace mystring
{
class string
{
public:
// 方法
private:
// 成员变量
public:
const static size_t npos;
}
const size_t string::npos = -1; //size_t类型最大值
}
string
就像一个动态顺序表,记录当前字符的数量、空间的容量、字符串
size_t _size; // 大小
size_t _capacity; //容量
char* _str; // 字符串
开口就的时候,多开一个为\0
留一个空间,缺省参数""
代表不传参的时候,默认\0
string(const char* str = "") //构造函数
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1]; //为'/0'留一个空间
memcpy(_str, str, _size + 1);
}
string(const string& s) //拷贝构造
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s.size();
_capacity = s._capacity;
}
~string() //析构函数
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
代码边写边测,写一个输出函数,方便测试代码
const char* c_str()const
{
return _str;
}
下标访问,重载[ ]
,也方便查看字符串
char& operator[](size_t pos) //读写
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const //只读
{
assert(pos < _size);
return _str[pos];
}
预留空间的时候,每次都多开一个,为\0
准备空间
防止出现xxxx\0xxx
这样的字符,strcpy
字符拷贝\0
拷贝不过去;所以使用直接使用memcpy
内存拷贝按要求拷贝完毕
void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
增添字符串首先要先看空间还够不够,不够就扩容
string
不提供头插,头插效率太低了
添加完毕之后,_size
自增一下,结尾注意添加\0
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 12 : _capacity * 2);
}
_str[_size] = c;
_str[++_size] = '\0';
}
尾部增添字符串
string& append(const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(len + _size);
}
memcpy(_str + _size, s, len + 1);
_size += len;
return *this;
}
用的最多的还是+=
,所以重载+=
,本质上还是push_back
和append
,这个看着更加舒服一点
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
insert
指定位置插入,某种意义上也可以说是实现了头插
string& insert(size_t pos, size_t n, char c)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
reserve(n + _size);
}
//防止整形提升
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
end--;
}
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(len + _size);
}
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
return *this;
}
string& erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
return *this;
}
能找到这个字符,就代表可以修改
string
类也提供了resize
,这个可以修改字符串
size_t find(const char c, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
size_t find(const char* s, size_t pos = 0) const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, s);
if (ptr)
{
//子字符串在原始字符串中的索引位置
return ptr - _str;
}
return npos;
}
string substr(size_t pos = 0, size_t len = npos) const
{
assert(pos < _size);
size_t n = len;
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i <pos+ n; i++)
{
tmp += _str[i];
}
return tmp;
}
void resize(size_t n, char c = '\0')
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
}
bool operator<(const string& s) const
{
bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
// "123" "123" f
// "1234" "123" f
// "123" "1234" t
return ret == 0 ? _size < s._size : ret;
}
bool operator==(const string& s) const
{
return _size == s._size && memcmp(_str, s._str,_size) == 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 || *this == s;
}
重载=
赋值运算符,需要实现深拷贝,如果没有写,编译器默认实现浅拷贝,这不符合要求
深拷贝要做到,将赋值的数据给目标,然后再将原目标原本指向的空间释放
//正常思路 全部手动完成
/*string& operator= (const string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
memcpy(tmp, str._str, str._size + 1);
_size = str._size;
_capacity = str._capacity;
}
return *this;
}*/
void swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
/*string& operator= (const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}*/
//自动化写法, 交互完毕之后,自动调用析构函数,释放原目标指向的空间
string& operator= (string tmp)
{
swap(tmp);
return *this;
}
最开始写的c_str
,是C语言样式的,遇见\0
就停止,而string
类是将对象字符全部输出
//清理对象
void clear()
{
_str[0] = '\0';
_size = 0;
}
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
//清理原始对象
s.clear();
char ch=in.get();
//清理缓冲区
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
//避免频繁扩容
char buff[128];
int i = 0;
while (ch!=' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
写时拷贝是一种技术优化。用于在共享资源的情况下减少不必要的数据复制。它通常用于实现像字符串、向量等数据结构的副本,以避免在副本创建时立即复制整个数据。相反,只有在原始数据被修改时,才会执行实际的复制操作。
比如说在string
类赋值的时候,Vs2022采用的就是深拷贝,每次都额外开空间
而gcc/g++
,则采用的是写时拷贝
那以上就将string
类的基本功能就实现完毕了,完整的看我仓库代码吧
本期分享就到这里咯,我们下期再见,如果还有下期的话