std::string的模拟实现
目录
string的构造函数
无参数的构造函数
根据字符串初始化
用n个ch字符初始化
用一个字符串的前n个初始化
拷贝构造
用另一个string对象的pos位置向后len的长度初始化
[ ]解引用重载
迭代器的实现
非const版本
const版本
扩容reserve和resize
reserve
resize
push_back和append
push_back
append
+=运算符重载
insert插入
erase删除
find
substr
流插入和流提取
比较
赋值运算符重载
补充
string是库中用于储存字符串的类,其底层实际上是顺序表。
namespace cfl
{
class string
{
public:
private:
char* _str; //指向储存字符串的起始位置
size_t _size; //存储有效字符个数
size_t _capacity; //存储开辟的空间总个数
static size_t npos;
};
size_t string::npos = -1;
}
string中有一个静态变量是npos类型是size_t,但是定义是却定义为-1,所以npos强转为了无符号整形,也就是能存储size_t类型的最大值,其用来表示整个字符串。
string的构造函数
参考文献:string::string - C++ Referencehttps://legacy.cplusplus.com/reference/string/string/string/
string的构造一共有中其中有一种的实现需要迭代器,在下面实现迭代器的时候讲解,先完成前6种。
无参数的构造函数
//无参数的构造函数
string()
:_str(new char('\0')) //此处不直接设置nullptr,将其设置为空字符串
,_size(0)
,_capacity(0)
{}
根据字符串初始化
//根据字符串初始化的构造函数
string(const char* str)
:_size(strlen(str))
, _capacity(strlen(str))
{
_str = new char[_size]; //此处也可以在上面进行初始化,但是要注意顺序
memcpy(_str, str, _size + 1); //_size+1是要拷贝'\0'
}
用n个ch字符初始化
//用n个ch字符初始化的构造函数
string(size_t n, const char ch)
:_str(new char[n+1]) //要开辟n+1个空间是为了存储'\0'
, _size(n)
, _capacity(n)
{
memset(_str,ch, n);
_str[_size] = '\0';
}
用一个字符串的前n个初始化
//用一个字符串的前n个初始化的构造函数
string(const char* str, size_t n)
:_str(new char[n + 1])
, _size(n)
, _capacity(n)
{
memcpy(_str, str, n);
_str[_size] = '\0';
}
拷贝构造
//拷贝构造函数
string(const string& s)
{
_capacity = s._capacity;
_str = new char[_capacity + 1];
_size = s._size;
memcpy(_str, s._str, _size);
_str[_size] = '\0';
}
用另一个string对象的pos位置向后len的长度初始化
//用另一个string对象的pos位置向后len的长度初始化的构造函数
string(const string& s, size_t pos, size_t len = npos) //len给一个缺省值是npos,代表整个字符串
{
if (len == npos || len + pos >= s._size)
{
len = s._size - pos;
_capacity=len;
_str = new char[_capacity + 1];
_size = len;
_str[_size] = '\0';
}
else
{
_capacity = len;
_str = new char[_capacity + 1];
_size = len;
_str[_size] = '\0';
}
}
[ ]解引用重载
string类对象可以像数组一样通过下标访问是因为解引用运算符重载。
char& operator[](size_t pos)
{
return _str[pos];
}
迭代器的实现
迭代器实际上可以看成一个指针,但是其与指针有一些区别。范围for的底层逻辑就是迭代器,实现了迭代器,范围for才能使用。
非const版本
//迭代器的实现
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() //begin就是返回起始位置的指针
{
return _str;
}
iterator end()
{
return _str + _size; //返回最后一个字符下一个位置。
}
const版本
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
扩容reserve和resize
reserve
reserve扩容要帮助,传过去的参数比当前空间大才扩容,比当前空间小则不做处理。
void reserve(size_t n)
{
if(n>_capacity)
{
//先开辟新的空间,再将数据拷贝回来
char* tmp= new char[n + 1];
memcpy(tmp, _str, _size + 1);
_str = tmp;
_capacity = n;
}
}
resize
resize和reverse扩容有所区别,传给resize的空间小于_size会将n后面的所有数据删除,resize还会对像开辟的空间进行初始化。
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
reserve(n);
for (int i = _size; i < n; i++)
_str[i] = ch;
_str[_capacity] = '\0';
}
}
push_back和append
push_back
尾插直接向顺序表结尾插入数据,尾插是也要考虑扩容。
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
append
appened是直接在尾部插入字符串。
void append(const char* s)
{
size_t len = strlen(s);
if (_capacity == _size || len + _size > _capacity)
{
reserve(len + _size);
}
memcpy(_str + _size, s, len + 1);
_size = _size + len;
}
+=运算符重载
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
insert插入
insert有两种:在pos位置插入n个字符ch,在pos插入字符串。
void insert(size_t pos, size_t n, char ch)
{
assert(pos < _size);
//判断空间够不够
if (n + _size < _capacity)
{
reserve(n + _size);
}
//将数据向后移
size_t end = _size;
while (end >= pos)
{
_str[end + n] = _str[end];
--end;
}
//插入数据
for (int i = 0; i < n; i++)
_str[pos + i] = ch;
}
void insert(size_t pos, const char* str)
{
assert(pos < _size);
size_t len = strlen(str);
if (_size == _capacity || len + _size > _capacity)
{
reserve(len + _size);
}
int end = _size;
while (end >= pos)
{
_str[end + len] = _str[end];
--end;
}
for (int i = 0; i < len; i++)
_str[pos + i] = str[i];
}
erase删除
从pos位置删除len的长度。
void erase(size_t pos, size_t len = npos)
{
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
int end = pos+len;
while (end < _size)
{
_str[end - len] = _str[end];
++end;
}
_size -= len;
_str[_size] = '\0';
}
}
find
从pos位置向后找字符ch。
size_t find(size_t pos, const char ch)
{
assert(pos < _size);
int cur = pos;
while (cur < _size)
{
if (_str[cur] == ch)
return cur;
++cur;
}
return npos; //表示没找到
}
substr
返回冲pos位置开始取n个字符的子字符串。
string substr(size_t pos, size_t n)
{
int end = pos + n;
if (end > _size)
{
end = _size;
}
string s;
for (int i = pos; i < end; i++)
s += _str[i];
return s;
}
流插入和流提取
流插入和流提取要放在类外定义,具体原因在<类和对象>该文种有讲解。
//流插入
ostream& operator<<(ostream& out, const cfl::string& s)
{
for (auto e : s)
out << e; //使用循环for可以防止s中间有\0导致停止
return out;
}
istream& operator>>(istream& in, cfl::string& s)
{
//每次插入一个字符会导致每次尾插都要开空间
//设置一个数组,先将字符插入到数组中,再将数组插入到s中
char tmp[128];
char ch = in.get();//此处使用in.get可以读取输入流中的空格
int i = 0;
while (ch != '\n')
{
tmp[i++] = ch;
if (i == 127)
{
tmp[127] = '\0';
i = 0;
s += tmp;
}
ch = in.get();
}
tmp[i] = '\0';
s += tmp;
return in;
}
注意:流插入重载和流提取重载都是string的友元函数。
比较
此处仅实现>和==,其他比较可以直接套用他们两个。
bool operator<(const string& s)
{
size_t end1 = _size;
size_t end2 = s._size;
int i = 0;
while (i < end1 && i < end2)
{
if (_str[i] < s._str[i])
return true;
else if (_str[i] > s._str[i])
return false;
i++;
}
if (i < end1)
return true;
return false;
}
bool operator==(const string& s)
{
if (_size != s._size)
return false;
int i = 0;
while (i < _size)
{
if (_str[i] != s._str[i])
return false;
i++;
}
return true;
}
赋值运算符重载
void swap(string& s)
{
std::swap(this->_str, s._str);
std::swap(this->_capacity, s._capacity);
std::swap(this->_size, s._size);
}
string& operator=(string tmp)
{
swap(tmp); //此处的tmp是临时变量,又是原对象的拷贝
//所以直接将tmp与*this,交换,实现赋值
//因为tmp是临时变量出作用域后会将*this的空间释放
return *this;
}
补充
补充一些不是太常用的string的成员函数。
1)string::shrink_to_fit()由于将空间缩至有效数据处;
2)string::at(size_t n)返回n处的字符,相当于s1[n];
3)string::replace()替换类中的数据;
4)string::rfind()查找,但是是从后往前找;
5)size_t find_first_of(string& str,size_t pos=0),从pos位置向后找,找到str中的任意一个字符就返回;
6)size_t find_end_of(string& str,size_t pos=npos),已5)不同的是该函数从后往前找:
7)getline(cin,str),读取流中的数据,可指定读取到什么的时候停止,默认是换行符;
8)string to_string()可以将非字符串转化为字符串。