1. 浅拷贝
string类简单的来说就是一个字符数组+一个’\0’,那么在模拟实现string类的默认成员函数就要小心一点。因为如果你不显式实现默认成员函数,那么将会产生浅拷贝的问题。如下图:
两个字符指针指向同一块空间,当s1被析构的时候,空间也会被释放,但是s2却是不知道的,此时s2析构的时候,就没有空间析构,或者说对同一块空间析构了两次,造成了崩溃。
所以在模拟实现string类的时候,我们要自己实现拷贝构造函数和赋值重载函数。
2. 默认成员函数的实现
string类的成员变量
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos = -1;
其中的npos也是模拟官方库的npos。
2.1 构造函数
string(const char* str = "") {
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
2.2 拷贝构造函数
string(const string& s) {
_str = new char[s._capacity + 1];
_size = s._size;
_capacity = s._capacity;
strcpy(_str, s._str);
}
2.3 赋值重载函数
//s2 = s1
string& operator=(const string& s) {
if (this != &s) {
string tmp(s);
swap(tmp);
}
return *this;
}
这里利用拷贝构造函数的机制,用临时变量tmp拷贝了s的全部信息,然后再和s2交换。这里要自己实现一个swap函数。
void swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
2.4 析构函数
~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
3. sting类的增
关于string类其实最主要的就是增删查改,那么这里讲解一下增。
void push_back(char c) {
if (_size >= _capacity) {
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
string& operator+=(char c) {
push_back(c);
return *this;
}
void append(const char* str) {
int len = strlen(str);
if (_size + len >= _capacity) {
size_t newcapacity = _size + len;
reserve(newcapacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
有两种方式增,一种是增加一个字符,一种是增加一个字符串。对于字符个数的增加,涉及到capacit和size的增加。
size_t size()const {
return _size;
}
size_t capacity()const {
return _capacity;
}
bool empty()const {
return _size == 0;
}
void resize(size_t n, char c = '\0') {
if (n > _size) {
reserve(n);
for (size_t i = _size; i < n; i++) {
_str[i] = c;
}
}
_size = n;
_str[_size] = '\0';
}
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
string& insert(size_t pos, char c) {
assert(pos < _size);
if (_size >= _capacity) {
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
size_t end = _size;
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
_str[_size] = '\0';
return *this;
}
string& insert(size_t pos, const char* str) {
assert(pos < _size);
int len = strlen(str);
if (_size + len >= _capacity) {
size_t newcapacity = _capacity == 0 ? len : _size + len;
reserve(newcapacity);
}
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;
}
最主要的是用到reserve接口,resize接口一般不常用。
4. string类的删
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len) {
assert(pos < _size);
if (len == -1 || pos + len >= _size) {
_str[pos] = '\0';
_size = pos;
}
else {
strcpy(_str + pos, _str + pos + len);
_size = len;
}
return *this;
}
该接口就是在pos位置,删除len个字符。
5. string类的查
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (_str[i] == c) {
return i;
}
}
return npos;
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const {
assert(pos < _size);
char* tmp = strstr(_str + pos, s);
if (tmp == nullptr) {
return npos;
}
else {
return tmp - _str;
}
}
简单实现两个查找接口,一个是查找字符,一个是查找字符串。
6. string类的改
string类的本质是一个字符数组,可以通过下标的方式来改变。
char& operator[](size_t index) {
assert(index < _size);
return _str[index];
}
7. string类的比较函数
//s1 < s2
bool operator<(const string& s) {
size_t i = 0;
size_t j = 0;
while (i < _size && j < s._size) {
if (_str[i] > s._str[j]) {
return false;
}
else if (_str[i] < s._str[j]) {
return true;
}
i++;
j++;
}
return i < _size ? true : false;
}
bool operator<=(const string& s) {
return *this < s || *this == s;
}
//s1 > s2
// abcd abcd
// abcd abcde
// abcde abcd
bool operator>(const string& s) {
return !(*this == s || *this < s);
}
bool operator>=(const string& s) {
return *this > s || *this == s;
}
bool operator==(const string& s) {
size_t i = 0;
size_t j = 0;
while (i < _size && j < s._size) {
if (_str[i] != s._str[j]) {
return false;
}
i++;
j++;
}
return true;
}
bool operator!=(const string& s) {
return !(*this == s);
}
以上函数可以复用,实现了 > == 就基本可以拿来复用改写其他函数了。
8. string类的流提取和流插入
ostream& operator<<(ostream& _cout, const my_string::string& s) {
for (size_t i = 0; i < s.size(); i++) {
_cout << s[i];
}
return _cout;
}
istream& operator>>(istream& _cin, string& s) {
s.clear();
char buffer[128] = { '\0' };
char c = _cin.get();
size_t i = 0;
while (c != ' ' && c != '\n') {
if (i == 127) {
s += buffer;
i = 0;
}
buffer[i++] = c;
c = _cin.get();
}
if (i > 0) {
buffer[i] = '\0';
s += buffer;
}
return _cin;
}
我这里是把这两个函数写成了友元函数,所以参数是少了一个的。其中要注意的是,流提取函数是不在乎’\0’的,就算有也会正常打印,不会停止,和字符串是不同的。
流插入函数利用了buffer字符数组,避免了s的多次扩容。