string的简单实现中一些函数的实现可以复用一些其他的函数来实现;比较重要的是在实现是深浅拷贝问题
目录
流插入和流提取运算符的重载(跟swap一样是string的友元)
string的结构
class string
{
private:
typedef char* iterator;//普通迭代器
typedef const char* const_iterator;//const迭代器
char* _str;
size_t _size;//长度,大小
size_t _capacity;//容量
static const size_t npos = -1;//npos相当于一个很大的数
};
为啥用char* 指针就可以充当迭代器?
因为可以直接通过char*来访问string中的字符数组。在库中的实现可能不是用的char*。
实现构造和析构
string(const char* s = "");//构造 默认参数即使不传参也可以构造一个空字符串
string(const string& s);//拷贝构造
~string();//析构
为什么需要显示实现析构函数?
因为_str指向的空间是new出来的,是资源,所以需要用delete手动释放该资源。
string::string(const char* s)
:_size(strlen(s))
{
_str = new char[_size + 1];
strcpy(_str, s);
_capacity = _size + 1;
}
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
string::~string()
{
delete[] _str;
_size = _capacity = 0;
}
实现没有什么就是简单的对字符串的操作。
reserve扩容
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n];
strcpy(tmp, _str);
delete[] _str;//释放原来的空间
_str = tmp;
_capacity = n;
}
}
注意一定要释放原来的空间否则会内存泄漏
操作简图
容量
size_t string::size()
{
return _size;
}
size_t string::capacity()
{
return _capacity;
}
size_t string::size()const//const修饰的string会调用该函数
{
return _size;
}
size_t string::capacity()const
{
return _capacity;
}
push_back和append
void string::push_back(char ch)
{
//扩容
if (_size == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size++] = ch;
//别忘了在最后加上'\0'
_str[_size] = '\0';
}
string& string::append(const char* s)
{
int len = strlen(s);
//扩容
if (_size + len >= _capacity)
{
reserve(_size + len+1);//需要多扩一个字节来存放'\0'
}
strcpy(_str + _size, s);
_size = _size + len;
return *this;//返回调用append这个函数的string对象本身
}
注意:向string中插入新的数据,就有可能需要扩容,扩容正好可以复用之前实现的reserve函数。
insert和erase的实现
string& string::insert(size_t pos, const char* s)
{
assert(pos >= 0 && pos < _size);//pos这个要插入的位置要合法
int len = strlen(s);
//扩容
if (_size + len >= _capacity)
{
reserve(_size + len +1);
}
int end = _size + len;
//将pos位置之后的字符整体向后移动len(插入字符串的长度)个字节
while (end - len >= pos)
{
_str[end] = _str[end - len];
end--;
}
//插入字符串
for (int i = 0; i < len; i++)
{
_str[pos + i] = s[i];
}
//strcpy(_str+pos,s);也可以直接这样写
_size = _size + len;
return *this;
}
string& string::insert(size_t pos,char ch)
{
assert(pos >= 0 && pos < _size);
if (_size + 1 >= _capacity)
reserve(_size + 1 +1);
int end = _size;
//将pos位置之后的字符整体向后移动1个字节
while (end >= pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
string& string::erase(size_t pos, size_t len)
{
//如果长度很长就直接将pos位置设置为'\0'
if (pos + len > _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//如果长度不是很长,那么就pos+len位置之后的字符整体前移
for (size_t i = pos; i+len <= _size; i++)
{
_str[i] = _str[i + len];
}
_size-=len;
}
return *this;
}
swap的实现(不是成员函数但是string类的友元)
void swap(string& x, string& y)
{
std::swap(x._str, y._str);
std::swap(x._capacity, y._capacity);
std::swap(x._size, y._size);
}
在标准库中有实现的一个swap的函数模板
//标准库中的swap的实现
template<class T>
void swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
如果使用std::swap来交换两个字符串效率不高:tmp需要调用拷贝构造,重新拷贝一份a;将b赋值给a和将tmp赋值给b都会调用赋值运算符,重新开辟空间,释放掉原来的空间。总结,需要完成三次深拷贝,性能不高。
而为string专门实现的swap就只需要交换资源就可以。效率很高。
赋值运算符的重载和+=运算符的重载
string& 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& string::operator=(string s)
{
swap(*this,s);//直接调用swap交换两个*this和s两个资源就可以了
return *this;
}
直接将临时变量的资源与*this对象的资源互换,并且临时变量在销毁时也会delete释放资源。
跟上面的代码效率差不多,但如果是自己赋值自己,那么下面就会进行一次深拷贝,而上面啥也不做,效率不如上面。
c_str和substr
//返回字符串首元素地址
const char* string::c_str()
{
return _str;
}
//返回字符串中的一段字符构造的string
string string::substr(size_t pos, size_t len)
{
string tmp;
//len的长度很长直接将pos后的所有字符用来构造string
if (pos + len >= _size)
{
for (int i = pos; i <= _size; i++)
{
tmp += _str[i];
}
return tmp;
}
int i;
for (i = 0; i < len; i++)
{
tmp += _str[pos+i];
}
tmp += '\0';
return tmp;
}
这里的substr的实现直接复用了+=运算符
关系运算符的重载
bool string::operator==(string& s)
{
return strcmp(_str, s._str)==0;
}
bool string::operator!=(string& s)
{
return !(*this == s);
}
bool string::operator>(string& s)
{
return strcmp(_str, s._str) > 0;
}
bool string::operator>=(string& s)
{
return (*this == s) || (*this > s);
}
bool string::operator<(string& s)
{
return !(*this >= s);
}
bool string::operator<=(string& s)
{
return (*this < s) || (*this == s);
}
有的运算符的重载可以复用已经实现的运算符
流插入和流提取运算符的重载(跟swap一样是string的友元)
istream& operator>>(istream& in,string& s)
{
s.clear();
char ch = in.get();//get从流(string)中读入一个char
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
ostream& operator<<(ostream& out,const string& s)
{
for (int i = 0; i <s._size; i++)
{
out << s[i];
}
return out;
}
完整的string实现在gitee中: