C++—string类的模拟实现
目录
1、string构造和析构函数
1.1 构造
1.2 拷贝构造
1.3 赋值构造
1.4 析构
2、对const对象获取容器的大小和容量的处理
3、遍历访问接口的实现
3.1 下标访问
3.2 迭代器
4、改变容量(reserve&resize)
5、插入、删除、查找
5.1 push_back、append、+=
5.2 insert
5.3 erase
5.4 find
5.4 截取子串
6、流插入和流提取
参考代码
1、string构造和析构函数
1.1 构造
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
构造函数参数用缺省值,这样的话空串也能构造。
根据C++中string的特性,这里的_size和_capacity都不算'\0'。
为了兼容C语言,_str的空间要多开一个,用于储存'\0'。
1.2 拷贝构造
//现代写法中需要用到swap函数,建议一开始就实现
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
{
_str = nullptr;
string tmp(s._str);
swap(tmp);
}
这种处理方法是通过构造一个临时对象,将这个临时对象的成员与*this交换。
拷贝构造要先将_str初始化为nullptr,避免交换后tmp._str变成随机值,导致找不到这块空间,析构出错。
1.3 赋值构造
赋值构造与上面的拷贝构造类似,也是通过一个临时变量完成赋值,避免了自己给自己赋值的情况。
string& operator=(string s)
{
swap(s);
return (*this);
}
1.4 析构
析构函数没什么好说的,按照常规思路写即可
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
2、对const对象获取容器的大小和容量的处理
除了普通对象,我们还需要考虑const对象对接口的调用。
为了解决const对象的调用,我们通常为这类函数this指针加上const修饰。
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
const char* c_str()const
{
return _str;
}
//清理字符
void clear()
{
_size = 0;
_str[0] = '\0';
}
//string 的判空
bool empty()const
{
return (_size == 0);
}
3、遍历访问接口的实现
3.1 下标访问
//const对象,不能修改,仅读
const char& operator[](size_t pos) const
{
assert(pos <= _size);
return _str[pos];
}
//普通对象,访问和修改都需要,可读可写
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
需要重载operator[]函数,也需要注意const对象的处理。
3.2 迭代器
//迭代器
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迭代器底层其实就是字符指针。
既然这里支持迭代器,那么肯定有人又想使用范围for了,那么这里能实现范围for吗?
这里是可以使用的,但当我们换一个迭代器名字,不用begin()和end()时。
这里报错的原因是因为范围for底层只认begin()和end(),更改名字后无法使用,也就是说如果自己实现的迭代器不叫begin()和end(),范围for就无法使用。
4、改变容量(reserve&resize)
//修改字符串长度和容量
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char c = '\0')
{
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
else
{
_str[n] = '\0';
_size = n;
}
}
reserve函数的实现注意事项
1、容量调整条件
在请求的容量大于当前容量时才扩容。若请求的容量小于或等于当前容量,直接忽略,不会缩容。2、内存分配大小
分配的内存大小为 请求的容量 + 1,额外的一个字节用于存储末尾的\0,用来支持c_str()的实现。
resize函数的实现注意事项
1、容量不足时需要扩容
若n大于当前容量,先调用reserve扩容。
2、增大字符串长度时
将新增部分(_size到n-1)填充指定字符(默认'\0'),需要在n处写入'\0',确保字符串可以正确结束。
3、缩短字符串长度时
直接更新_size并在n处写入\0,无需修改其他数据。
5、插入、删除、查找
5.1 push_back、append、+=
//插入删除
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
5.2 insert
//在pos位置插入字符
string& 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++;
return (*this);
}
//在pos位置插入字符串
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return (*this);
}
5.3 erase
//删除在pos位置的元素
string& erase(size_t pos, size_t len)
{
assert(pos < _size);
//若要删除的字符数比后面剩余的字符数多
//就把pos位置其后面的字符都删完
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return (*this);
}
5.4 find
//查找字符在string中第一次出现的位置
size_t find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
//查找字符串s在string中第一次出现的位置
size_t find(const char* str, size_t pos)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
5.4 截取子串
string substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t end = pos + len;
if (len == npos || pos + len >= _size)
{
end = _size;
}
string str;
str.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
6、流插入和流提取
//流插入和流提取
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
//以128为一组,避免多次扩容
char buff[128];
char ch = in.get();
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;
}
流插入运算符 (operator<<) 的实现的注意事项
1、输出内容范围
输出字符串时,应严格按照_size确定输出长度(而非依赖\0终止符),因为自定义的string类可能包含中间的空字符(\0)。
2、直接访问数据指针
需要直接访问内部数据指针data_,并确保其以'\0'终止(为了兼容c_str)。
流提取运算符 (operator>>) 的实现注意事项
1、跳过空白字符
需要按照标准行为,默认跳过输入流的前面的空白字符。
2、动态内存管理
读取字符时需动态扩展内存(类似于vector的push_back机制)。
使用临时缓冲区逐步读取,避免频繁分配内存。
3、终止条件
遇到空白字符或流结束时停止读取。
参考代码
#include<iostream>
#include<assert.h>
using namespace std;
namespace MyString
{
class string
{
public:
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//构造
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
/*string(const string& s)
{
_capacity = s._capacity;
_size = s._size;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}*/
//拷贝现代写法
string(const string& s)
{
_str = nullptr;
string tmp(s._str);
swap(tmp);
}
//赋值构造
string& operator=(string s)
{
swap(s);
return (*this);
}
//析构
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//给const修饰的对象写的获取_size和_capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
const char* c_str()const
{
return _str;
}
void clear()
{
_size = 0;
_str[0] = '\0';
}
bool empty()const
{
return (_size == 0);
}
//遍历访问接口的实现
//const对象,不能修改,仅读
const char& operator[](size_t pos) const
{
assert(pos <= _size);
return _str[pos];
}
//普通对象,访问和修改都需要,可读可写
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
//迭代器
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);
}
//修改字符串长度和容量
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char c = '\0')
{
//n > _size,扩容后用c填满容器
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
else
{
//n <= _size的情况,直接把下标n的位置改为'\0',修改_size即可
_str[n] = '\0';
_size = n;
}
}
//插入删除
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, 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 == _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++;
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);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return (*this);
}
string& erase(size_t pos, size_t len)
{
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);
}
size_t find(char ch, size_t pos)
{
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)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
//查找子串
string substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t end = pos + len;
if (len == npos || pos + len >= _size)
{
end = _size;
}
string str;
str.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
//只有const static整型类型,指针成员变量才可以在类中定义
const static size_t npos = -1;
};
//流插入和流提取
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128];
char ch = in.get();
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;
}
}