目录
- 1.默认成员函数
- 1.1 构造函数
- 1.2 拷贝构造
- 1.2.1 传统写法
- 1.2.2 现代写法
- 1.3 赋值构造
- 1.3.1 传统写法
- 1.3.2 现代写法
- 1.4 析构函数
- 2.其他成员函数
- 2.1 迭代器
- 2.2 容量操作
- 2.2.1 size()
- 2.2.2 capacity()
- 2.2.3 reserve()
- 2.2.4 resize()
- 2.2.5 clear()
- 2.3 访问操作(operator[]())
- 2.4 修改操作
- 2.4.1 push_back()
- 2.4.2 append()
- 2.4.3 operator+=()
- 2.4.4 insert()
- 2.4.5 erase
- 2.4.6 find()
- 2.5 c_str()
- 3.非成员函数
- 3.1 operator<<和operator>>
- 3.2 getline()
- 3.3 字符串比较
- 4.整体代码
1.默认成员函数
1.1 构造函数
STL库中实现了多种版本的构造函数,我们这里实现最常用的参数为const char*的一种。还需要提供一个无参的构造函数,这里采用缺省参数来代替。
_str代表用来存放数据的数组
_size代表数组有效字符的个数
_capacity在vs中代表有效字符的容量,在g++中包含了\0。我们采用vs的实现方式,底层开辟空间时多开一个空间存放\0。
string(const char* str = "") //采用缺省参数来代替无参构造: _size(strlen(str)) //_str(str) 不能这样初始化,会导致权限放大
{_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1]; //多开一个空间用来存放\0strcpy(_str, str);
}
1.2 拷贝构造
1.2.1 传统写法
手动开辟空间,并且拷贝数据到新空间里。
string(const string& s): _size(s._size), _capacity(s._capacity)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);
}
1.2.2 现代写法
我们可以利用构造函数来创建一个中间变量tmp,然后再用swap函数将this和tmp交换。
这里swap函数分为两种:一个是std里的swap,一个是string类里的swap。
std中的:
template<class T>
void swap(T& a, T& b)
{T c(a);a = b;b = c;
}
string类中的:
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
上述两种swap在使用中有什么区别吗?
std中的参数是自定义类型,在交换时需要进行连续三次深拷贝,效率很低。
string中的参数则是内置类型,无需深拷贝。
所以我们在这里选用string类中的swap函数。
string(const string& s)
// 初始化,防止交换后tmp中的_str成为野指针,在析构时崩溃: _str(nullptr), _size(0), _capacity(0)
{string tmp(s._str);// 要使用tmp作为中间值,再让this与tmp交换,不能改变s的内容swap(tmp);
}
1.3 赋值构造
1.3.1 传统写法
与拷贝构造类似,需要手动开辟空间拷贝数据。但要考虑到同一对象可能被多次赋值,所以我们要使用delete[]将原有的资源释放。
string& operator=(const string& s)
{// 给出条件this != &s,防止自己给自己赋值if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}
1.3.2 现代写法
// 现代写法
string& operator=(const string& s)
{if (this != &s){string tmp(s._str);swap(tmp);}return *this;
}
// 传值传参的现代写法
string& operator=(string s)
{swap(s);return *this;
}
1.4 析构函数
~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;
}
2.其他成员函数
2.1 迭代器
目前我们无法完全理解迭代器,但我们暂时可以把它理解为指针,现在用typedef把iterator定义为char*来模拟实现迭代器。
begin()返回首元素的地址
end()返回末尾\0对应的地址
typedef char* iterator;iterator begin()
{return _str;
}
iterator end()
{return _str + _size;
}
与上面同理,我们还可以写出const迭代器
typedef const char* const_iterator;
const_iterator begin() const
{return _str;
}
const_iterator end() const
{return _str + _size;
}
范围for本质上就是迭代器,只要实现了begin()和end()就可以使用范围for,不过begin和end的名字是固定的,也就是说不能用其他的功能相同但名字不同的函数替代。
void test4()
{string s("hello world!");string::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;for (auto ch : s){cout << ch << " ";}cout << endl;
}
2.2 容量操作
2.2.1 size()
size_t size() const
{return _size;
}
2.2.2 capacity()
size_t capacity() const
{return _capacity;
}
2.2.3 reserve()
reserve的参数是有效字符所占的字符,不包括\0,所以底层开空间时要开n+1个。
这里的扩容是异地扩容,要将原有的内容拷贝到新的空间。
//reserve不能缩容,如果n<capacity,则什么都不干
void reserve(size_t n)
{if (n > capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}
2.2.4 resize()
resize根据n的大小可分为3种操作:
1.n<_size:删除数据
2.n>_size && n<=_capacity:插入数据
3.n>_size && n>_capacity:扩容并插入数据
void resize(size_t n, char ch = '\0')
{// 删除数据if (n < _size){_size = n;_str[_size] = '\0';}// 插入数据else if (n > _size){// 扩容if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}
}
2.2.5 clear()
void clear()
{_str[0] = '\0';_size = 0;
}
2.3 访问操作(operator)
operator需要实现两个版本分别用来适应const和非const对象。
const char& operator[] (size_t pos) const
{// 越界访问直接报错assert(pos < _size);return _str[pos];
}
char& operator[] (size_t pos)
{assert(pos < _size);return _str[pos];
}
2.4 修改操作
2.4.1 push_back()
void push_back(char ch)
{// 因为在构造函数中我们已经设定好capacity一定不会为0,所以这里不用担心capacity为0的情况if (_size + 1 > _capacity){reserve(_capacity * 2);}_str[_size] = ch;++_size;// 最后别忘了在末尾补上\0_str[_size] = '\0';
}
2.4.2 append()
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}// 将str追加到_str的尾部strcpy(_str + _size, str);_size += len;
}
2.4.3 operator+=()
使用函数重载分别实现尾插字符和尾插字符串即可
string& operator+=(char ch)
{push_back(ch);return *this;
}
string& operator+=(const char* str)
{append(str);return *this;
}
2.4.4 insert()
insert的实现有一些需要注意的地方,先来简单实现一下:
string& insert(size_t pos, char ch)
{assert(pos <= _size);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size;// 挪动数据while (end >= pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size;return *this;
}
上述代码适用于大多数场景,但是当pos=0时,程序会崩溃,通过调试发现,当end=0的时候,–end没有变成-1,而是会变成很大的数,导致无限循环。原因是end的类型是无符号整数,那么如何解决呢?
我们可以用int类型的end,再将pos强制转换为int类型,这样确实可以解决问题,但是代码显得杂乱,所以还有另一种方法,在while循环中加入一个判断:当end=0时跳出循环。
while (end >= pos)
{_str[end + 1] = _str[end];if (end == 0)break;--end;
}
或者,我们将end初始化为_size+1,这样end就不会在等于0时减一了
string& insert(size_t pos, char ch)
{assert(pos <= _size);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;
}
插入字符串和上面也类似,需要注意的点是拷贝时要用strncpy,如果用strcpy会把\0也拷贝进去不符合要求。
string& insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);size_t end = _size + len;if (_size + len > _capacity){reserve(_capacity + len);}while (end-len+1 > pos){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;
}
实现了insert之后,push_back和append也可以通过调用insert来实现了:
void push_back(char ch)
{insert(_size, ch);
}
void append(const char* str)
{insert(_size, str);
}
2.4.5 erase
erase的实现可分为两种情况:
1.len=npos或者pos+len=_size:此时只需要把pos位置的数据改为\0即可
2.pos+len<_size:用strcpy来移动数据
在string中npos是一个静态成员变量,静态成员变量需要在类内声明,类外定义。不过对于const修饰的静态成员变量,但是只有整型可以,其他类型就不可以了。
class string
{
private:char* _str;size_t _size;size_t _capacity;static const size_t npos;// 可以//static const size_t npos = -1;/*static const int N = 10;int a[N];*/// 不可以//static const double dpos = -1;
};const size_t string::npos = -1;
string& erase(size_t pos, size_t len = npos)
{assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size -= pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;
}
2.4.6 find()
寻找字符:遍历一遍,找到相同字符就返回索引,找不到返回npos
寻找字符串:使用strstr来解决,找到返回p-_str,否则返回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)
{assert(pos < _size);char* p = strstr(_str + pos, str);if (p == nullptr){return npos;}return p - _str;
}
2.5 c_str()
const char* c_str()
{return _str;
}
3.非成员函数
3.1 operator<<和operator>>
operator<<实现很容易,一个范围for即可完成
operator>>的实现:
关于流提取,我们知道用cin或者scanf无法拿到空格或者\n,但是我们在循环插入数据时又需要用空格和\n来作为循环结束的标志,所以我们需要用in中的get()函数来读取输入的数据,它能够获取到空格和换行符。
我们知道operator+=在容量满了之后需要扩容,如果我们输入的字符串很大,就会频繁的扩容,效率很低,所以我们用buff数组作为缓冲来解决问题:当数据正常时先都装进buff中,如果buff满了,再将buff中的数据一并转入string里。
ostream& operator<<(ostream& out, string& s)
{for (auto ch : s){out << ch;}return out;
}
istream& operator>>(istream& in, string& s)
{s.clear();char ch = in.get();char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}// 如果buff中还有数据if (i != 0){buff[i] = '\0';s += buff;}return in;
}
3.2 getline()
getline()遇见空格时不会停下,只有遇见\n才会停下。
istream& getline(istream& in, string& s)
{char ch = in.get();while (ch != '\n'){s += ch;ch = in.get();}return in;
}
3.3 字符串比较
都加上const,为的是让const对象也能使用。
bool operator>(const string& s) const
{return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s) const
{return *this > s || s == *this;
}
bool operator<(const string& s) const
{return !(*this >= s);
}
bool operator<= (const string & s) const
{return *this < s || s == *this;
}
bool operator!=(const string& s) const
{return !(s == *this);
}
4.整体代码
如是我们就实现了一个比较完整的string类,整体代码如下:
namespace lgr
{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* str = ""): _size(strlen(str)){_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s): _str(nullptr), _size(0), _capacity(0){string tmp(s._str);swap(tmp);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}const char* c_str(){return _str;}const char& operator[] (size_t pos) const{assert(pos < _size);return _str[pos];}char& operator[] (size_t pos){assert(pos < _size);return _str[pos];}string& operator=(string s){swap(s);return *this;}bool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool operator>=(const string& s) const{return *this > s || s == *this;}bool operator<(const string& s) const{return !(*this >= s);}bool operator<= (const string & s) const{return *this < s || s == *this;}bool operator!=(const string& s) const{return !(s == *this);}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0'){if (n < _size){_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}}void push_back(char ch){insert(_size, ch);}void append(const char* str){insert(_size, str); }string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);size_t end = _size + len;if (_size + len > _capacity){reserve(_capacity + len);}while (end-len+1 > pos){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}string& erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size -= pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}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);char* p = strstr(_str + pos, str);if (p == nullptr){return npos;}return p - _str;}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char ch = in.get();char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in; }istream& getline(istream& in, string& s){char ch = in.get();while (ch != '\n'){s += ch;ch = in.get();}return in;}
}