构造函数需要尽可能将成员在初始化列表中初始化,string类的成员这里自定义的和顺序表相似,有_str , _size , _capacity , 以及一个静态成员 npos ,构造函数这里实现两种,一种是传参为常量字符串的,一种是不进行传参直接实例化的,这里可以使用缺省参数。
string(const char* str = ""):
_size(strlen(str))
{
assert(str);
_capacity = _size == 0 ? 3 : _size;//一开始时开多几个空间,避免后续一些越界问题
_str = new char[_capacity+1];//给多一个位置存放‘\0’
strcpy(_str,str);
}
拷贝构造得注意参数给的时候,要用引用传参,不然会无限递归
string(const string& s):
_size(s._size),
_capacity(s._capacity)
{
_str = new char[_capacity+1];
strcpy(_str,s._str);
}
正常用delete释放空间,参数置空即可
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
这里由于底层是顺序表,因此迭代器的实现可以直接用指针,需要注意的是,在使用迭代器时,有时候需要用到const修饰,因此两种都要实现,构成函数重载
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;
}
反向迭代器的实现还没学,暂时先空着...
关于成员变量,我们需要提供一些接口去给用户合理的访问部分参数
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
关于增加字符,这里模拟实现几个基本的接口
在模拟实现增加字符前,要先考虑扩容的问题,可以先实现reserve,后续对其进行复用扩容
void reserve(size_t n)//扩容
{
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 + 1 > _capacity)
{
reserve(_capacity*2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* s)
{
size_t len = strlen(s);
if(_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str+_size,s);
_size += len;
}
resize改变size的大小,当size改变后比原有的capacity大时,会进行扩容,但不会进行缩容,resize除了改变大小,还会在其余的空间里填上指定字符,要是不给定字符则默认为‘\0’
void resize(size_t n,char ch = '\0')
{
if(n > _size)
{
reserve(n);
while(_size < n)
{
_str[_size++] = ch;
}
_str[_size] = '\0';
}
else
{
_size = n;
_str[_size] = '\0';
}
}
从指定位置pos插入字符或者字符串,这里需要构成函数重载,思路是用一个end,从后往前的将数据依次往后挪(不要漏掉‘\0’)然后空出来的位置再进行插入,在插入字符串时,挪动数据那一块的判断相对复杂,需要画图去对边界条件进行判断,需要注意的是,end的类型要定义成size_t,为了避免整形提升,还需要注意end<0时,不会变成-1,所以需要避免让end小于0的情况,也就是头插的情况,需要额外加判断或者采用从前面往后搬数据,end指向后面
void insert(size_t pos,char ch)
{
assert(pos < _size);
size_t end = _size+1;
if(_size + 1 > _capacity)
{
reserve(_capacity*2);
}
while(end < pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos,const char* s)
{
assert(pos < _size);
size_t len = strlen(s);
if(_size + len > _capacity)
{
reserve(_size+len);
}
size_t end = _size + len;
while(end < pos+len-1)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str+pos,s,len);
_size+=len;
}
npos=-1,但npos是无符号整形,因此是整形的最大值,函数功能是从指定位置往后删指定长度的数据,若是不给长度,则默认全删了,删到‘\0’停下。
void erase(size_t pos,size_t len = npos)
{
assert(pos < _size);
size_t end = pos;
size_t count = 0;
while(end < _size && len)
{
end++;
len--;
count++;
}
while(end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= count;
}
清除所有数据
void clear()
{
_size = 0;
_str[_size] = '\0';
}
查找函数这里模拟实现一个接口,要支持查找字符或者字符串,返回相对应的下标,需要构成函数重载
查找字符
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;
}
查找字符串
size_t find(const char* s,size_t pos = 0)
{
char* pstr = strstr(_str+pos,s);
if(pstr == nullptr)
{
return npos;
}
return pstr - s;
}
修改操作可以通过删除添加实现,实际价值不大,因此没有专门的接口实现,这里实现一个常用的交换操作,比起库里面直接使用的swap,这里省去了拷贝构造的过程,效率上会高很多
void swap(string& s)
{
std::swap(_str,s._str);
std::swap(_size,s._size);
std::swap(_capacity,s._capacity);
}
char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* s)
{
append(s);
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;
}
bool operator>(const string& s)const
{
return strcmp(_str,s._str) > 0 ;
}
bool operator==(const string& s)const
{
return strcmp(_str,s._str) == 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);
}
bool operator!=(const string& s)const
{
return !(*this == s);
}
ostream& operator<<(ostream& out,const string& s)
{
for(size_t i = 0;i
输入重载需要注意,字符串的截取,如果直接用in截取,则无法截取到空格和回车,因此用借用in的内部接口get(),而且为了避免多次扩容减低效率,可以先开一个字符串数组,将每次在缓存区内获取的字符存到字符串数组中,当字符串满了或者是获取完字符后,再一次性输入到string变量中
istream& operator>>(istream& in,string& s)
{
s.clear();
char ch = in.get();
char tmp[100];
size_t i = 0;
while(ch !=' ' && ch !='\n')
{
if(i<100)
{
tmp[i++] = ch;
ch = in.get();
tmp[i] = '\0';
}
else
{
s+=tmp;
i = 0;
}
}
if(i != 0)
{
s+=tmp;
}
return in;
}
本篇模拟实现了string的部分常用的基本接口,从原理上去学习了string类的相关知识