string模拟实现
- 构造函数和析构函数
- begin和end
- reserve和resize
- push_back和append
- c_str
- empty,size,capacity,clear
- 拷贝构造和赋值
- +=和比较大小
- []重载
- insert和erase
- find查找
前面我们已经对string进行了简单的介绍,只要会用各个函数即可,下面是string的模拟实现。
在这里我们可以定义一个自己的命名空间,防止与库里的string造成冲突。
namespace haifan
{class string{public:.....private:char* _str;size_t _size;size_t _capacity;};
}
构造函数和析构函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JhE4Trrp-1690193767195)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230724170813742.png)]
这是库里的几种构造函数,在这里,我只实现了一种。
如果在传参的时候我们是以haifan::string a
的形式或者是以 haifan::string b("123")
我们可以考虑用 string(const char* str = "")
这样的形式来写构造函数,当没有参数的时候,就用str的缺省值,有参数就用传递的参数。
string(const char* str = "")
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];memcpy(_str, str, _size + 1);
}
析构函数很简单,把_str释放掉,在置为空指针即可
~string()
{delete[] _str;_str = nullptr;_capacity = _size = 0;
}
begin和end
begin和end分别由两种写法,一种是带const的,一种是不带的。
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
reserve是为字符串预留空间的一个函数,并且只有一个参数,表示要为字符串预留多少空间,如果n要比capacity小,可以不做处理,反之,要为字符串预留n + 1的空间,因为要放一个\0
void reserve(size_t n)
{if (_capacity < n){//注意还有一个\0char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}
}
resize是用字符c来填充多出的元素空间。在改变元素个数时,如果是增多,可能会改变底层容量的大小,如果减少,底层空间总大小不变。
void resize(size_t n, char c = '\0')
{if (_capacity > n){_str[n] = '\0';_size = n;}else{reserve(n);for (int i = _size; i < n; i++){_str[i] = c;}_str[n] = '\0';_size = n;}
}
push_back和append
push_back是在字符串的尾部,添加一个字符—尾插,但在进行插入的时候,要注意空间是否足够,对此进行一个检查即可。
在插入字符后,别忘了在将\0进行一次尾插
void push_back(char ch)
{if (_size + 1 > _capacity){if (_capacity == 0){reserve(4);}elsereserve(2 * _capacity);}_str[_size++] = ch;_str[_size] = '\0';
}
append是将一个字符串进行尾插,同样,要先进行空间检查。如果空间不足,将空间开辟到跟尾插后的字符串一样的大小即可。
void append(const char* str)
{size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}memcpy(_str + _size, str, len + 1);_size += len;}
c_str
c_str是将string类以C字符串的形式返回。
const char* c_str() const
{return _str;
}
empty,size,capacity,clear
empty用于判断string是否为空。
bool empty() const
{return _capacity == 0;
}
size用于判断string的长度
size_t size() const
{return _size;
}
capacity用于判断string当前的容量
size_t capacity() const
{return _capacity;
}
clear用于清空string,但是capacity并不会被清0
void clear()
{_size = 0;_str[_size] = '\0';
}
拷贝构造和赋值
拷贝构造设计到了空间的开辟,要用深拷贝,浅拷贝可能会造成两个string共用同一块地址。
string(const string& str)
{_str = new char[str._capacity + 1];_size = str._size;_capacity = str._capacity; memcpy(_str, str._str, str._capacity + 1);
}
赋值在这里,只实现了两种情况,一种是 string a = b
另一种是 string a = "abcd"
,前者利用拷贝构造先创建一个临时变量,然后用swap将*this和tmp进行交换,从而实现=的目的。后者是开辟了一块空间,将str里的内容,拷贝到tmp里面,然后将_str=tmp。
void swap(string& str){std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}string& operator=(const string& s){if (this != &s){string tmp(s);swap(tmp);return *this;}}string& operator= (const char* str){char* tmp = new char[strlen(str) + 1];memcpy(tmp, str, strlen(str) + 1);_size = strlen(str);_capacity = strlen(str) + 1;delete[] _str;_str = tmp;return *this;}
+=和比较大小
加等于一个字符,其实就是尾插
string& operator+= (char c){push_back(c);return *this;}
加等于一个字符串其实就是append
string& operator+= (const char* str){append(str);return *this;}
用于比较的代码很好写,只要写出来一个,其他的都可以复用。
比如小于
两种情况 两个string的长度相同或者不相同,如果相同直接memcpy进行字符串的比较即可,如果不相同,看哪个string短,先比短的部分,如果短的部分都相同,则string长的大,如果短的部分不相同,在比较的时候做出判断即可。
bool operator<(const string& s){if (s._size == _size){return memcpy(_str, s._str, _size);}else{if (_size > s._size){for (int i = 0; i < s._size; i++){if (_str[i] > s._str[i]){return false;}}return false;}else{for (int i = 0; i < _size; i++){if (_str[i] > s._str[i]){return false;}}return true;}}//int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);//return ret == 0 ? _size < s._size : ret < 0;}一个好理解的写法,一个两行的写法
写出来一个,其他的都可以以<为基础来写
bool operator<=(const string& s){if (*this < s || *this == s){return true;}return false;}bool operator>(const string& s){return !(*this <= s);}bool operator>=(const string& s){return !(*this < s);}bool operator==(const string& s){return memcpy(_str, s._str, _size) == 0 && _size == s._size;}bool operator!=(const string& s){return !(*this == s);}
[]重载
string类可以像字符串一样,用下表来访问,这是因为把[]进行了重载
char& operator[] (size_t pos) {assert(pos < _size);return _str[pos];}const char& operator[] (size_t pos) const{assert(pos < _size);return _str[pos];}
insert和erase
在第pos位置上插入一个字符,只需要把pos和pos后面的数据向后移动一位即可。
void insert(size_t pos,char c){assert(pos < _size);if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t tail = _size;size_t head = pos;while (tail >= pos && tail != npos){_str[tail] = _str[tail - 1];tail--;}_str[pos - 1] = c;++_size;_str[_size] = '\0';}
在pos位置上插入一个字符串,将pos和pos之后的数据,向后移动字符串的长度即可。
void insert(size_t pos, const char* str){assert(pos < _size);const size_t len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}size_t tail = _size;while (tail >= pos && tail != npos){_str[tail + len] = _str[tail];tail--;}for (int i = 0; i < len; i++){_str[i + pos] = str[i];}_size += len;_str[_size] = '\0';}
erase(size_t pos = 0, size_t len = npos)
pos是要删除的起始位置,len是要删除多少个,不写默认把pos之后的元素全部删除。
npos是 size_t npos = -1,因为size_t是无符号数,-1是一个正数且是一个及其大的值
void erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len + pos > _size){_str[pos] = '\0';_size = pos;}else{size_t tail = pos + len;while (tail < _size){_str[pos++] = _str[tail++];}_size -= len;_str[_size] = '\0';}}
find查找
size_t find(char ch, size_t pos = 0)
从pos位置开始查找某个字符,如果找到了,返回下标,没找到,返回npos
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)
查找子串,如果找到了,返回第一次找到子串的位置,反之,返回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;}}