成员//成员函数
成员 (初始化)
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
const static size_t npos = -1;//足够大 18446744073709551615
};
1.首先需要一个char* 来指向储存的字符串内容
2.需要_size来控制字符串大小
3.需要_capacity来控制空间大小
begin/end
iterator begin()const { return _str; }
iterator end()const { return _str + _size; }
const_iterator cbegin()const { return _str; }
const_iterator cend()const { return _str + _size; }
首先实现begin( ) 和end( ) cbegin( )和 cend( )函数,这样就可以支持范围for的使用了
size/capacity
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
直接返回成员 _size 和 _capacity就可以了
注意要加const否则会权限放大
reverse
void reverse(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
tmp = nullptr;
_capacity = n;
}
}
扩容函数:与之前的vector同理,先开一个临时空间,再把本身的内容拷贝到新空间里
这里用到strcpy
destination(目标) source(资源),将资源中的内容拷贝到目标字符串中
c_str//substr
const char* c_str()
{
return _str;
}
string substr(size_t pos, size_t len = npos)
{
assert(pos >= 0 && pos < _size);
size_t end = pos + len;
if (len == npos || pos + len > _size)
{
end = _size;
}
string str;
str.reverse(end-pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
c_str:返回string里储存的 char* 内容,所以这里直接返回成员 _str 就可以了
substr :从本身的字符串中的 pos位置向后len长度的内容拷贝到新的sting对象 返回拷贝的对象(就相当于取到这个字符串的一段)如果len过长的话,默认拷贝到本身的最后一位
然后遍历赋值返回新的字符串就可以完成任务
find
size_t find(char ch,size_t pos=0)//从pos后边位置开始find
{
assert(pos >= 0 && pos < _size);
for (size_t i = pos; i < _size; i++)//遍历
{
if (_str[i] == ch)
{
return i;
}
}
return npos;// 没找到 18446744073709551615
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos >= 0 && pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
这里两个重载(一个find char ,一个find const char *)
遍历判断就可以完成查找
构造函数
/*string()
:_str(nullptr)
, _size(0)
, _capacity(0)
{
}*/
由于我们在声明成员的时候已经进行了默认初始化,所以这里的构造函数什么都不需要做
这里如果写上初始化列表的话,会报错:多个构造函数,所以这里直接屏蔽掉
拷贝构造
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
直接将 str 中的内容strcpy到 _str 中,这里strlen可以得到str的长度
//传统写法
/*string(const string& str)
{
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_capacity = _capacity;
}*/
string(const string& str)
{
string tmp = str;
swap(tmp);
}
void swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
拷贝新的string时道理可以和拷贝const char * 时一样,但是也可以与vector那里一样 直接交换临时资源
注意:
string tmp = str;
需要重载operator = (),要不然会报错(后面谈operator的时候介绍)
析构
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
void clear()
{
_size = 0;
_str[0] = '\0';
}
这里把clear也在这里提到 由于字符串会识别 '\0' 来当作字符串的结尾,所以直接把第一个元素设置成' \0 '就可以了
析构:释放_str指向的内容,再把成员清零,完成释放
插入元素
push_back( )
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reverse(newcapacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
尾插:首先检查容量够不够,如果不够则需要扩容,这里扩容机制与之前vector一致直接复用reverse( )因为_str[_size]位置时最后一个元素的后面一个位置一般时'\0'所以直接 _str[_size]=ch完成尾插 不要忘了再把最后一个位置的后一位设置成' \0 '(这也是为什么capacity需要多开一个的原因——用来存'\0')
append( )
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reverse(_size + len);
}
strcpy(_str + _size, str);
_size += len;
_str[_size] = '\0';
}
追加:在原字符串后边加上一个新的字符串(const char *类型)(这里只重载第一个)
先检查容量,防止容量不够,然后直接把传入的字符串拷贝到_str+_size位置,最后加上'\0'
insert
void insert(size_t pos, char ch)
{
assert(pos >= 0 && pos < _size);
if (_size == _capacity)//扩容同push_back
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reverse(newcapacity);
}
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 >= 0 && pos < _size);
size_t len = strlen(str);//扩容同append
if (_size + len > _capacity)
{
reverse(_size + len);
}
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
insert:插入在_str[pos]位置插入新的元素(不管时const char* 还是char)
首先检查容量!!! 再挪动pos后边的元素,往后挪动1位或多位,最后把需要插入的内容赋值到腾出来的空间中,完成插入 (这里重载了插入字符和字符串两种)
删除元素
erase( )
void erase(size_t pos, size_t len = npos)
{
assert(pos >= 0 && pos < _size);
if (len == npos || pos + len > _size)//如果需要删除长度过长
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + len + pos);//把pos后边len以后的内容拷贝到pos后
_size -= len;
}
}
如果len过长,就直接把pos以后的内容全部删除 只需要把pos位置改成'\0'即可
如果需要删除中间一段,把后边的内容覆盖需要删除的即可 直接strcpy
operator
=
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;
}
原理与拷贝构造一致(二者可以替换)
[ ]
const char& operator[](size_t pos)const
{
assert(pos >= 0 && pos <= _size);
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos >= 0 && pos <= _size);
return _str[pos];
}
直接返回pos位置的引用就可以了,这里重载了 const 版本和 非const 版本
+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
+=:在原字符串后边追加新的元素即可,这里没有重载追加string类型(取出string中的_str复用即可)这里直接复用push_back( )和append( )
>>输入
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128] = {'\0'};
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;
}
这里不能写在类里边,需要写在类外,用buff来接收输入的char,然后将buff中储存的内容追加到 string 中完成输入
<<输出
ostream& operator<<(ostream& out, const string& s)
{
for (auto e : s)
{
out << e;
}
return out;
}
遍历string 然后依次打印每一个元素即可 (这里用到了范围for ,需要支持begin() 和
end( ) ,否则范围for会报错) !
头文件
#include<assert.h>
#include<string.h>
我们需要保证成员函数中的pos在0-_size之间,否则会越界,这里直接暴力断言
我们用到的strlen strcpy strstr strncpy等函数都在string.h 中