目录
变量
size_t _capacity;//对象容量
size_t _size;//对象实际大小
char* _str;//字符指针
const static size_t npos = -1;//定义在类域的静态变量
//这是一种特殊的静态定义方式,只能是size_t类型,其他类型不可
构造函数
有参构造函数
错误想法:直接将const char*的字符串赋给str
_str是char*类型可读可写,str是const char*类型只读(将str赋给_str,修改_str时会导致修改str,这样是矛盾的)权限放大
string(const char* str)
:_str(str)//权限放大
{}
正确想法:构造新对象时,先开辟一块同样大小的空间,将原来字符串str通过函数(strcpy)拷贝到_str。
初始化列表
string(const char* str)
:_size(strlen(str))
,_capacity(_size)
,_str(new char[_size+1])
{
strcpy(_str, str);
}
但注意初始化列表的顺序应该与声明顺序一致,当然也可以不使用初始化列表 ,直接将初始化写在内部。
string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
无参构造函数
string()
{
//_str = nullptr 不能置空,因为解引用时候会报错(比如打印字符串的时需要解引用)
_str = new char[1];
_size = 0;
_capacity = 0;
_str[0] = '\0';
}
全缺省构造函数
综合无参构造函数和有参构造函数。
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
拷贝构造函数
法一:传统写法
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
法二:现代写法
首先重载swap(可以交换string类型的对象),利用构造函数用s构造tmp,交换tmp与this对象
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
析构函数
销毁原来的空间,并将变量置为初值。
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
清空容器。
void clear()
{
_size = 0;
_str[0] = '\0';
}
遍历方法
1.[ ]索引访问
利用传引用返回可以修改返回值,可以直接修改该字符。
而传值返回返回的是一份拷贝,修改的话不会对原来的字符造成影响 。
char& operator[](size_t pos)
{
assert(pos <= _size);
return _str[pos];
}
函数重载,如果返回类型是const char只读类型,则加const修饰成员函数
const char& operator[](size_t pos) const
{
assert(pos <= _size);
return _str[pos];
}
2.迭代器访问
普通迭代器访问
string容器迭代器的本质是原生指针,主要原因是string容器存储数据的空间是连续的,直接通过指针就可以遍历所以数据。
typedef char* iterator;//迭代器本质是一个字符指针
//可以理解为string本质上是一个储存字符的数组(结尾存储‘\0’),
//地址空间是连续的,可以通过指针逐个遍历
iterator begin()
{
return _str;
}
iterator end()//指向的是最后一个数据的下一个,即‘\0’
{
return _str + _size;
}
const迭代器
可以修改指针,但是不能修改指针的内容。只是将指针类型前加const即可。
typedef const char* const_iterator;
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
插入数据
对于数据的扩容
本质上是c语言realloc函数的异地扩容
void reverse(size_t n)//类似于realloc
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
尾部插入数据
首先考虑扩容问题,然后在结尾直接插入一个字符即可。
void push_back(char ch)
{
//检查容量
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reverse(newcapacity);
}
//插入一个字符
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
追加字符串
类似于尾插多个字符,但实现时不是利用尾插实现。
首先是考虑扩容问题,然后有足够容量后,直接用strcpy函数进行尾部插入字符串
void append(const char* str)
{
//检查容量
size_t len = strlen(str);
if (_size + len > _capacity)
{
reverse(_size + len);
}
//尾部插入字符串
strcpy(_str + _size, str);
//strcat(_str, str);//不太好,可以算出尾部,就不用函数去计算
_size += len;
}
strcat函数是追加字符串的函数,但是其内部实现需要计算出原本字符串的尾部再进行追加。
这里可以直接_str+_size计算出尾部的地址,直接用strcpy即可。
中间插入数据
插入一个字符
首先判断是否扩容,然后通过移动字符的方式向中间插入数据
void insert(size_t pos, char ch)
{
assert(pos <= _size);
//考虑容量
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
}
//插入数据
//end的位置就是\0的位置
//size_t end = _size; ×报错
//如果在头部插入,end是无符号的,随着end--,到-1会变成整形最大值,导致死循环
int end = _size;
while (end >= (int)pos)//要强转成int,否则整形提升将end变成无符号的,死循环
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
//当然也可以,把索引改一下,当end为0时跳出循环,防止出现负数情况
//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)
{
reverse(_size + len);
}
//插入字符串
int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
}
删除数据
删除pos位置以后len个长度的字符串(包括pos位置)
分为两种情况
1.删除中间某个位置后的所有值
在pos位置删除len长度的字符串,len的长度大于pos之后的所有字符串长度时,只要把pos位置后面全部删除,就是把pos位置的值变成'\0',_size改成pos的值即可。
2.删除中间某一段数据
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (pos + len > _size)//情况1
{
_str[pos] = '\0';
_size = pos;
}
else//情况2
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
查找
查找单个字符
从pos位置查找某字符。
遍历容器,一一比对,若没有找到则返回整形最大值。
size_t find(char ch, size_t pos = 0)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
查找子串
从pos位置开始向后查找子串,用strstr函数查找子串
size_t find(const char* str, size_t pos = 0)
{
const char* ptr = strstr(_str+pos, str);
if (ptr == nullptr)//没找到
{
return npos;
}
else//找到了
{
return ptr - _str;
}
}
得到子串
得到pos位置向后len长度的子串。
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t end = pos + len;
if (len == npos || pos + len >= _size)//判断是否超出范围,并修正end
{
end = _size;
}
string str;
str.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
操作符重载
+=操作符
直接写出重载的版本,分别是加单个字符,加字符串,直接复用push_back和append。
这里的返回值是传引用返回+=后的结果。
1.尾部插入一字符
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
2.追加字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
=操作符
类似拷贝构造的传统写法
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& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
函数重载,当传非const类型的对象时,选择传值(而不是传引用!),则传入时生成一份拷贝,让这份拷贝直接与this对象进行交换。
string& operator=(string s)
{
swap(s);
return *this;
}
<<操作符
把字符一个一个传给out,这里用了范围for,底层是直接替换成了迭代器。
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
>>操作符
istream& operator>>(istream& in, string& s)//不用const,因为要提取到s中,s可修改
{
s.clear();//先清除s中所有内容
//char ch;
//in >> ch;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;//不能用这种方式传字符,这种方式会跳过'\n'和' ',导致死循环
ch.get();
}
return in;
}
问题:当输入很长的代码时,要多次扩容,开销增大。
改进:提前开空间,开多大合适?不确定。这里通过引入一个临时数组buff,当做一个缓冲区,来缓解多次扩容开空间的问题。
每装满一次buff数组就加到s中。
istream& operator>>(istream& in, string& s)//不用const,要提取到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;
}
杂项
获取c字符串
const char* c_str() const
{
return _str;
}
获取字符串长度
size_t size() const
{
return _size;
}