温馨提示:这篇文章已超过767天没有更新,请注意相关的内容是否还可用!
在C语言中,没有专门用来表示字符串的类型。C语言的字符串是一系列以’\0’为结尾的字符的集合。虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与字符串这个类型是分开的,这不太符合C++中面试对象的思想,所以在C++中封装了一个string类,来帮助我们操作字符串。string该如何使用,我这里就不做赘述了,大家可以去看看官方文档呀
string - C++ Reference (cplusplus.com)
string模拟实现
string简单实现
首先我们不考虑string类的增删查改,只是先给string类搭建一个最简单的框架出来。
和C语言中相同,为了存储一个字符串,我们的string类需要一个char*的指针来指向字符像这个对象。作为一个对象,string还需要有构造函数,析构函数和拷贝构造。
class string { private: char *_str; public: string(const char *str) : _str(new char[strlen(str) + 1]) // +1 是给'char& operator[](size_t pos) { assert(pos < strlen()) return _str[pos]; }'留出位置 { strcpy(_str, str); } string(const string &str) : _str(new char[strlen(str._str) + 1]) { strcpy(_str, str._str); } ~string() { if (_str) { delete[] _str; _str = nullptr; } } };
有的朋友可能会疑惑,这里的构造函数和拷贝构造函数为什么不用编译器自动生成的,直接将_str指向原本的字符串就可以了,为什么还要开辟空间呢?
这是因为我们在日常使用中,假如有两个string类 a 和 b,b是由a拷贝构造而来,一般情况下我们在修改b的同时不希望a也被改。此外,如果直接将_str指向原本的字符串会导致的问题是当 a 和 b用完被销毁时,会对同一片空间调用两次析构函数,对同一片空间释放两次。所以在这里,我们需要重新开辟一片空间来给这个string。这也就是所谓的深拷贝。
然后,为了访问string类中的元素,我们需要对运算符[]进行重载。
string完整实现
这样我们就实现了一个简单的string类。
构造函数,析构函数,拷贝构造class string { private: char *_str; size_t _size; size_t _capacity; public: string(const char *str = "") : _size(strlen(str)), _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); } string(const string &str) : _size(str._size), _capacity(str._capacity) { _str = new char[_size + 1]; strcpy(_str, str._str); } ~string() { if (_str) { delete[] _str; _str = nullptr; _size = _capacity = 0; } } };
之前我们实现的一个string类是一个最简单的string类,它没有办法进行增删查改,接下来我们就来一点一点完善它。
要实现增删查改,我们还需要两个变量,一个记录string类当前长度,一个记录string类的容量大小。加入这两个变量后,我们原本的构造函数,拷贝构造和析构函数需要发生一点点变化。
运算符重载//关系运算符的重载 bool operator>(const string &s) { return strcmp(_str, s.c_str()); } bool operator==(const string &s) { return strcmp(_str, s.c_str()) == 0; } bool operator!=(const string &s) { return !(*this == s); } bool operator>=(const string &s) { return *this > s || *this == s; } bool operator<(const string &s) { return !(*this >= s); } bool operator<=(const string &s) { return !(*this > s); } //操作运算符的重载 string &operator=(string& str) { if(*this != str) { char *tmp = new char[str._capacity + 1]; strcpy(tmp,str._str); delete[] _str; _str = tmp; _size = str._size; _capacity = str._capacity; } return *this; } char &operator[](size_t pos) { assert(pos < _size); return *(_str + pos); } const char &operator[](size_t pos) const { assert(pos < _size); return *(_str + pos); }
接下来我们来实现一下,string类的运算符。在实现运算符重载时,我们需要做的只是实现少数几个运算符即可,其他的运算符可复用前面实现的运算符来达到我们想要的效果。
string接口实现size_t size() const { return _size; } size_t capacity() const { return _capacity; } bool empty() const { return 0 == _size; } //后面添加const的目的是为了让所有对象都可以进行访问 void clear() { _str[0] = 'void reserve(size_t n) { if (n > _capacity) //判断是否需要扩容 { char *tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } //resize和reserve的区别在于,reserve只是开空间,而resize还要进行初始化 void resize(size_t n, char c = ''; _size = 0; _capacity = 0; }void push_back(char n) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); //开空间复用reserve } _str[_size++] = n; _str[_size] = '') { if (n > _capacity) { reserve(n); //开空间复用reserve } for (size_t i = _size; i < n; ++i) { _str[i] = c; } _size = n; _str[_size] = 'string &insert(size_t pos, const char *str) { //检查空间是否足够 assert(pos <= _size); size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } //挪动后面的数据 size_t end = _size + len; while (end != pos + len - 1) { _str[end] = _str[end - len]; --end; } //数据插入 strncpy(_str + pos, str, len); _size += len; return *this; }'; }string &eraser(size_t pos, size_t len = npos) //npos为静态变量,值为-1 { assert(pos < _size); if (len == npos || pos + len >= _size) //将位置后的元素全部删除 { _str[pos] = '迭代器的实现'; _size = pos; } else //删除位置后的部分元素 { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size = _size - len; } return *this; }'; }
首先是比较简单的size(),empty(),capacity(),clear()。这些接口大部分直接访问string类的成员变量就可以得到结果。
typedef char *iterator; typedef const char *const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } iterator begin() { return _str; } iterator end() { return _str + _size; }
因为后面的接口大部分都需要进行空间的调整,所以首先我们将调整空间的接口,reserve和resize实现。
部分函数优化和完善接下来是插入的实现,首先是push_back,这个比较简单,找到尾部进行插入即可。
string &operator+=(const char *str) { append(str); } string &operator+=(char n) { push_back(n); return *this; }
接下来是insert,这个较push_back而言要麻烦一些,因为除了尾插,其他地方去插入数据你都需要挪动后面数据的位置。
void swap(string& str) { std::swap(_str, str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); } string(const string &s) : _str(nullptr), _size(0), _capacity(0) { string tmp(s._str); swap(tmp); } string &operator=(string s) { swap(s); return *this; }
写完了插入,接下来当然就是删除接口:eraser
完整代码
class string { public: typedef char *iterator; typedef const char *const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } iterator begin() { return _str; } iterator end() { return _str + _size; } string(const char *s = "") : _size(strlen(s)), _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, s); } string(const string &s) : _str(nullptr), _size(0), _capacity(0) { string tmp(s._str); swap(tmp); } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } string &operator=(string s) { swap(s); return *this; } char &operator[](size_t pos) { assert(pos < _size); return *(_str + pos); } const char &operator[](size_t pos) const { assert(pos < _size); return *(_str + pos); } const char *c_str() const { return _str; } 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 n) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = n; _str[_size] = ''; } string &operator+=(char n) { push_back(n); return *this; } void append(const char *str) { size_t len = _size + strlen(str); if (len > _capacity) { reserve(len); } strcpy(_str + _size, str); _size = len; } string &operator+=(const char *str) { append(str); } void resize(size_t n, char c = '') { if (n > _capacity) { reserve(n); } for (size_t i = _size; i < n; ++i) { _str[i] = c; } _size = n; _str[_size] = ''; } size_t size() const { return _size; } size_t capacity() const { return _capacity; } bool empty() { return 0 == _size; } bool operator>(const string &s) { return strcmp(_str, s.c_str()); } bool operator==(const string &s) { return strcmp(_str, s.c_str()) == 0; } bool operator!=(const string &s) { return !(*this == s); } bool operator>=(const string &s) { return *this > s || *this == s; } bool operator<(const string &s) { return !(*this >= s); } bool operator<=(const string &s) { return !(*this > s); } string &insert(size_t pos, const char *str) { assert(pos <= _size); size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } size_t end = _size + len; while (end != pos + len - 1) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len); _size += len; return *this; } string &eraser(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = ''; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size = _size - len; } return *this; } void clear() { _size = 0; _str[0] = ''; _capacity = 0; } void swap(string &s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } size_t find(char c, size_t pos = 0) const { while (pos < _size) { if (_str[pos] == c) { return pos; } ++pos; } return npos; } size_t find(char *s, size_t pos = 0) const { const char *p = strstr(_str + pos, s); if (p == nullptr) { return npos; } else { return p - _str; } } private: char *_str; size_t _size; size_t _capacity; const static size_t npos; }; const size_t string::npos = -1;
C++中的迭代器和指针类似。为什么要有迭代器呢?因为C++中有各种各样的容器,每个容器它背后的存储方式不同,访问方式也不同,为了让使用者的使用成本降低,使大部分容器可以以相同的方式去访问,就有了迭代器的产生。
接下来我们来实现string的迭代器,其实string的迭代器就是一个指针。并不用去封装特别的东西。
前面在写运算符重载时,还有部分运算符未重载在此加上
同时增加拷贝构造和operator=的现代写法,之前我们写拷贝构造和operator=时都需要自己去重新开空间,那么这个活可不可以让其他人帮我做呢?
我们来看看下面这一段代码
上述代码同样可以帮我们完成拷贝构造和operator= ,原理如下:
1.首先是拷贝构造,我们在拷贝构造中使用构造函数去创建一个临时对象,这个临时对象在创建时,就帮我们开辟了空间。然后我们将临时对象和此对象的所有成员进行一个交换,这样此对象就可以接管临时对象创建的那块空间,我们的拷贝构造也就成功了
2.在operator=这,我们使用的是传值传参。好处在于由于我们的string类是自定义对象,所以在传参时会去调用拷贝构造,这样传过来的str参数也拥有了自己的空间,此时我们和拷贝构造一样,将str所开辟的那块空间接管,同时由于str是函数参数,当函数结束时,str会去调用析构函数进行一个空间释放。
还没有评论,来说两句吧...