C++中string的简单实现
string的简单实现中一些函数的实现可以复用一些其他的函数来实现;比较重要的是在实现是深浅拷贝问题
目录
string的结构
实现构造和析构
reserve扩容
容量
push_back和append
insert和erase的实现
swap的实现(不是成员函数但是string类的友元)
赋值运算符的重载和+=运算符的重载
c_str和substr
关系运算符的重载
流插入和流提取运算符的重载(跟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;}//返回字符串中的一段字符构造的stringstring string::substr(size_t pos, size_t len){string tmp;//len的长度很长直接将pos后的所有字符用来构造stringif (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)中读入一个charwhile (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中:
刷题记录: 用来调试刷题是写的代码 (gitee.com)