String
模拟实现一个类对象,有助于我们更好的理解这个类,同时也会巩固应用之前学习的知识。同时,再即将学习STL的时候,模拟实现容器相关函数,对我们后续学习STL的帮助是很大的。话不多说,手搓开始!!!
String的基本成员变量和常量
我们只是简单实现,所以成员并不像库里那样复杂:
- 基本成员变量_size、_capacity、_str;
- 基本常量npos;
- 为了和库里面string区分开,我们把我们实现的string类放在自己的命名空间内。
namespace Acat{ class string { private: size_t _size; //字符串的长度 size_t _capacity; //字符串容量 char* _str; //开辟空间的字符指针 static size_t npos; //类中声明 }; size_t string::npos = -1; //类外定义 }
默认成员函数
成员函数:string中需要实现的就是constructor(含copy)、destructor、operator=
string构造函数
string(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; memcpy(_str, str, _size+1); }
string(&)拷贝构造
string(const string& s) { _str = new char[s._capacity + 1]; memcpy(_str, s._str, s._size + 1); _size = s._size; _capacity = s._capacity; }
~string析构函数
~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
operator=
以下就是等号运算符重载的现代写法:运用拷贝构造和swap
void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string& operator=(string tmp) //合理运用拷贝构造 { if (this != &s) { swap(tmp); return *this; } }
迭代器
string的迭代器,就是指向一个个字符,所以设置为char*完全可以的。string类暂时不介绍反向迭代器,后续会专门介绍STL的迭代器。
typedef char* iterator; typedef const char* const_iterator;
begin
返回第一个元素的迭代器。
iterator begin() { return _str; } const_iterator begin() const { return _str; }
end
返回最后一个元素的后一个位置的迭代器。
iterator end() { return _str + _size; } const_iterator end() const { return _str + _size; }
容量
size
返回字符串长度。
size_t size() const { return _size; }
capacity
返回容量。
size_t capacity() const { return _capacity; }
reserve
扩容操作:当_size==_capacity就需要扩容了,扩容大小可以两倍扩容。
void reserve(size_t n) { if (n > _capacity) { cout << "reserve()->" << n << endl; char* tmp = new char[n + 1]; memcpy(tmp, _str, _size+1); delete[] _str; _str = tmp; _capacity = n; } }
resize
如果n小于size需要缩小,大于便扩容,再插入指定值。
void resize(size_t n, char ch = '\0') { if (n < _size) { _size = n; _str[_size] = '\0'; } else { reserve(n); for (size_t i = _size; i < n; i++) { _str[i] = ch; } _size = n; _str[_size] = '\0'; } }
clear
清除string,只需要把首元素赋值\0就可以
void clear() { _str[0] = '\0'; _size = 0; }
元素的访问
c_str
返回字符串的首元素地址。
const char* c_str() const { return _str; }
operator[]
要写两个版本,因为不论是const对象还是普通对象,都是可以访问的,所以后面要加const。
const 对象不想被修改,所以返回的引用需要加const。
char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
增删查改
operator+=
这里的加等于就是可以追加字符也可以追加字符串,所以其实只需要复用push_back和append就可以了
string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; }
append
此处也可以复用insert来实现
void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { // 至少扩容到_size + len reserve(_size+len); } memcpy(_str + _size, str, len+1); _size += len; }
push_back
此处也可以复用insert来实现
void push_back(char ch) { if (_size == _capacity) { // 2倍扩容 reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; ++_size; _str[_size] = '\0'; }
insert
在pos位置插入字符,前插入,往后覆盖,从后向前。
void insert(size_t pos, size_t n, char ch) { assert(pos <= _size); if (_size +n > _capacity) { // 至少扩容到_size + len reserve(_size + n); } // 防止end=-1,所以size_t加检查 size_t end = _size; while (end >= pos && end != npos) { _str[end + n] = _str[end]; --end; } for (size_t i = 0; i < n; i++) { _str[pos + i] = ch; } _size += n; }
在pos位置,插入字符串。
void insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) { // 至少扩容到_size + len reserve(_size + len); } size_t end = _size; while (end >= pos && end != npos) { _str[end + len] = _str[end]; --end; } for (size_t i = 0; i < len; i++) { _str[pos + i] = str[i]; } _size += len; }
erase
删除pos位置的字符。删除从前往后移动。
void erase(size_t pos, size_t len = npos) { assert(pos <= _size); if (len == npos || pos + len >= _size) { _size = pos; _str[_size] = '\0'; } else { size_t end = pos + len; while (end <= _size) { _str[pos++] = _str[end++]; } _size -= len; } }
find
分别实现查找字符和字符串
size_t find(char ch, size_t pos = 0) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } size_t find(const char* str , size_t pos = 0) { assert(pos < _size); const char* ptr = strstr(_str + pos, str); if (ptr) { return ptr - _str; } else { return npos; } }
substr
string substr(size_t pos = 0, size_t len = npos) { assert(pos < _size); size_t n = len; if (len == npos || pos + len > _size) { n = _size - pos; } string tmp; tmp.reserve(n); for (size_t i = pos; i < pos + n; i++) { tmp += _str[i]; } return tmp; }
类外函数
operator>>、operator<<的运算符重载可以设置为友元函数。
为什么要设置在类外面,不能写成成员函数?
- 因为我们在使用输入流和输出流的时候,比如:cin<<s1,左操作数是:cin,右操作数:string对象。
- 类成员函数左操作数都是string对象(this指针)。
operator<<
ostream& operator<<(ostream& out, const string& s) { /*for (size_t i = 0; i < s.size(); i++) { out << s[i]; }*/ for (auto ch : s) { out << ch; } return out; }
operator>>
输入流:
- cin遇到空格或者换行就停止读入。
- 自动补齐最后一位的\0。
istream& operator>>(istream& in, string& s) { s.clear(); char ch = in.get(); // 处理前缓冲区前面的空格或者换行 while (ch == ' ' || ch == '\n') { ch = in.get(); } //in >> ch; char buff[128]; int i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 127) { buff[i] = '\0'; s += buff; i = 0; } //in >> ch; ch = in.get(); } if (i != 0) { buff[i] = '\0'; s += buff; } return in; } };
总结
这只是简单的实现string类,需要充分学习到string类中的一些细节,学会应用来协助自己。
- getline的应用,有时候很关键;
- 迭代器遍历,是语法糖;