string类的接口
namespace zjw { class string {public: typedef char* iterator; typedef const char* const_iterator; private: char* _str; int _size; int _capacity; };
这里的迭代器直接使用原生指针来封装。
_str为指向string数组的首地址的指针。
_size为string数组的大小。
_capacity为string数组的容量
基本函数
实现 函数返回string的首地址;
实现 函数返回string的尾地址;
实现 函数返回string的大小;
实现 函数返回string的容量;
iterator begin() { return _str; } iterator end() { return _str+_size; } size_t size() { return _size; } size_t capacity() { return _capacity; }
如果一个string类不能被修改的话,就无法调用非const的成员函数
const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; }
默认构造以及析构函数
string(const char* str = "") :_size(strlen(str)) { _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } ~string() { delete[]_str; _str = nullptr; _size = _capacity = 0; }
这里有一个问题,就是默认构造传的缺省参数可以是nullptr 吗??
答案是不行的,因为strlen要将str解引用找’\0’,如果传nullptr的话,对空指针解引用就会出错。
这里也不可以传单字符‘\0’,因为这里的str是const char* 类型,而’\0’是char 类型
运算符重载下标访问函数
char& operator[](size_t pos) { assert(pos<_size); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
下标为_size的地方存的是’\0’,所以不用访问,pos<_size,为了让const迭代器不修改对于下标的值,修改上面得到下面,对上面进行函数重载
reserve函数
void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[]_str; _str = tmp; _capacity = n; } }
注意:这里多开一个是为了存’\0’;
push_back()函数
void push_back(char ch) { if (_capacity == _size) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; _size++; _str[_size] = '\0'; }
原来下标是_size的地方是‘\0’,然后之间在_size的地方放入ch,_size++,染后在补一个’\0’;要插入数据的一半都要扩容
append函数
void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size += len; //_str[_size] = '\0'; }
尾插一个字符串,如果string的大小+要插入的字符串大小>_capacity,就扩容到_size + len,刚好,strcpy会从str开始直到遇到‘\0’一个字节一个字节拷贝到_str + _size(原字符串’\0’的地方),这里会把str字符串后面的‘\0’也拷贝过来。
运算符重载+=字符以及+=字符串
string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; }
在这里传引用返回是因为_str指向的空间是在堆上开的,出函数不会被销毁,所以可以传引用返回
insert函数(插入字符)
void insert(size_t pos, char ch) { assert(pos<=_size); if (_capacity == _size) { reserve(_capacity == 0 ? 4 : _capacity * 2); } size_t end = _size; while (end >=pos) { _str[end+1] = _str[end]; end--; } _str[pos] = ch; _size++; }
上面这个写法可以吗??
是不可以的
怎么修改呢??
void insert(size_t pos, char ch) { assert(pos<=_size); if (_capacity == _size) { reserve(_capacity == 0 ? 4 : _capacity * 2); } size_t end = _size+1; while (end >pos) { _str[end] = _str[end-1]; end--; } _str[pos] = ch; _size++; }
这样就可以了,我们从’\0’的下一个位置开始end,此时end结束的条件是=0,所以不会陷入循环,这里和顺序表的insert很像,可以参考顺序表那里。
earse函数
void earse(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos > _size-len) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } }
删除从pos位置开始的len个字符长度的字符,如果len大于剩下的字符,就全删了,这里给的缺省值为npos=-1,可以认为是很大的数,绝对超过了pos后面的字符数,我们要在string里面声明一个npos,在类外面定义初始化。
如果len==npos或者len+pos>_size,就始要将pos开始后面的字符串全删除,我们直接在pos位置放’\0’即可,
_size=pos;
如果没有超过字符串
insert(插入字符串)
void insert(size_t pos, const char* str) { size_t len = strlen(str); 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, str, len); _size += len; }
void test6() { string str; str.push_back('a'); str.push_back('a'); str.push_back('b'); str.insert(2, "hello"); cout << str.c_str(); }
swap
void swap(string& str) { std::swap(_str, str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); }
void test12() { string str1; string str2; str1 += "hello"; str2+="nihao"; cout << "str1:"<<str1 << endl; cout << "str2:"<<str2 << endl; str1.swap(str2); cout << "str1:" << str1 << endl; cout << "str2:" << str2 << endl; }
这里我们自己实现的swap是类成员函数,但是#include< algorithm >这个头文件库里面也有一个swap,swap全局也有一个非成员函数swap
我们可以发现算法库中的swap需要完成三次的拷贝,以及一次析构(临时变量c的析构),而string里面定义一个全局的swap,就是为了防止调用算法库里面的swap,因为会先在全局找,然后会在展开的库里找,而全局的swap实现只需要调用类里面的swap即可
void swap(string& x, string& y) { x.swap(y); cout << "没使用库里的" << endl; }
void test13() { string str1; string str2; str1 += "hello"; str2 += "nihao"; cout << "str1:" << str1 << endl; cout << "str2:" << str2 << endl; swap(str1, str2);//检测是调用库里的,还是全局的 cout << "str1:" << str1 << endl; cout << "str2:" << str2 << endl; }
赋值以及拷贝构造
string(const string& s) { string tmp(s._str); swap(tmp); } string& operator=(string tmp) { swap(tmp); return *this; }
根据调试发现
老版本赋值
string& operator=(const string& s) { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; return *this; }
find函数(查找字符)
size_t find(char ch, size_t pos = 0) { for (int i = pos; i < _size; i++) { if (_str[i] == ch) return i; } return npos; }
从pos位置开始查找,如果pos使用缺省,则从第一个位置开始查找,从pos位置开始遍历,如果找到返回下标,如果找不到返回npos
void test7() { string str; str.push_back('a'); str.push_back('a'); str.push_back('b'); str.insert(2, "hello"); cout << endl; int ret = str.find('b', 0); cout << ret; }
find函数(查找子串)
size_t find(const char* sub, size_t pos = 0) const { assert(pos < _size);//下标为_size的是‘\0’ const char* ptr = strstr(_str + pos, sub); if (ptr != nullptr)//找到了,但是strstr返回的是找到子串的起始地址-字符串起始地址就是子串相对起始位置的长度 { return ptr - _str; } else { return npos; } }
void test8() { string str; str += "beijing huanyingni zhangjiawang"; int ret = str.find("zhangjiawang"); cout << ret; }
substr函数
string substr(size_t pos = 0, size_t len = npos) const { string substr; if (pos > _size - len) { for (int i = pos; i <= _size; i++) { substr += _str[i]; } } else { for (int i = pos; i < pos + len; i++) { substr += _str[i]; } substr += '\0'; } return substr; }
从pos位置开始取,取len个长度,分两种情况,如果从pos开始还没取到len长,就结束,就取到结尾,遍历pos到_size,string substr 保存遍历的值.
第二种,遍历pos到pos+len,string substr 保存遍历的值.最后记得加‘\0’;