文章目录- 一、string类的构造、拷贝构造、赋值重载以及析构
- 1.构造函数
- 2.拷贝构造
- 3.swap问题
- 4.赋值重载
- 5.析构函数
- 二、常用接口
- 1.c_str
- 2.[]
- 3.迭代器和范围for
- 4.size和capacity
- 三、插入
- 1.reserve和resize
- 2.push_back
- 3.append
- 4. =
- 5.insert
- 四、删除
- 1.erase
- 2.clear
- 五、查找
- 1.find
- 六、运算符重载
- 流插入<<和流提取>>
- 七、总体代码
- 1.构造函数
- 2.拷贝构造
- 3.swap问题
- 4.赋值重载
- 5.析构函数
- 1.c_str
- 2.[]
- 3.迭代器和范围for
- 4.size和capacity
- 1.reserve和resize
- 2.push_back
- 3.append
- 4. =
- 5.insert
- 1.erase
- 2.clear
- 1.find
- 流插入<<和流提取>>
一、string类的构造、拷贝构造、赋值重载以及析构
1.构造函数
分为无参和带参这两种构造函数。无参构造函数默认构造空字符串"",所以我们只需要给一个缺省值即可。
代码语言:javascript复制string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity 1];
strcpy(_str, str);
}
对于这里的capacity问题,这里是字符的个数,不包括 ,所以要给 预留位置。
2.拷贝构造
对于拷贝构造和赋值是默认成员函数,不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用拷贝构造
对于string类型来说,如果不写拷贝构造会导致浅拷贝问题(只完成值拷贝)
所以我们需要进行深拷贝:
- 传统写法
string(const string& s)
{
_str = new char[s._capacity 1];
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
- 现代写法
传统写法比较循规蹈矩,现代写法更加灵活,拷贝构造的现代写法可以通过构造出tmp,然后把tmp和s2进行交换(swap)
注意:我们需要把s2的_str置为nullptr,如果不置为空,tmp会变成随机值,tmp是局部变量出作用域时会调用析构函数
代码语言:javascript复制void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);//this->swap(tmp)
}
3.swap问题
对于上面现代写法swap的问题:标准库有一个swap,string也有一个swap,有什么区别?
代码语言:javascript复制s1.swap(s2);
swap(s1,s2);
第二个swap交换代价比较大,需要三次深拷贝(拷贝 赋值 赋值),造成空间损耗,所以我们可以提供一个成员函数swap交换string,直接交换,swap中的swap要指定作用域std::,否则需要从局部找,再去全局找,发现参数不匹配
4.赋值重载
默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。同时,对于赋值重载,我们不要直接去进行销毁,有可能自己给自己赋值,导致自身进行销毁。同时,为了安全起见,我们最好利用tmp来进行赋值
- 传统写法
string& operator =(const string& 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;
}
- 现代写法
string& operator = (const string& s)
{
if (this != &s)
{
//string tmp(s._str);
string tmp(s);
swap(tmp);
}
return *this;
}
但是此方法仍然可以简化,不需要临时tmp,直接进行传值传参,更加简洁
代码语言:javascript复制 //直接传值传参
string& operator = (string s)
{
swap(s);
return *this;
}
5.析构函数
析构函数比较简单,直接delete[]释放空间即可
代码语言:javascript复制~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
二、常用接口
下面几个常用的接口实现比较简单,我们先一起来看一看:
1.c_str
代码语言:javascript复制const char* c_str() const
{
return _str;
}
2.[]
代码语言:javascript复制//普通对象:可读可写
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//const对象:可读不可写
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
3.迭代器和范围for
- 迭代器
迭代器有普通迭代器以及const修饰的迭代器,所以我们可以实现两种不同的迭代器,其中,const迭代器可读不可写
代码语言:javascript复制typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str _size;
}
- 范围for
实现完迭代器之后,对于范围for我们自然可以直接使用:
4.size和capacity
直接返回值即可,比较简单
代码语言:javascript复制size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
三、插入
1.reserve和resize
- reserve
在已知开多少空间是调用,避免频繁扩容,具体实现要开辟新的空间,在进行拷贝,对旧空间进行释放
代码语言:javascript复制void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n 1];
strcpy(tmp,_str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
- resize
resize需要分情况:
1.元素个数大于容量,需要扩容,多出来的用’ ’(默认情况下)来进行填充
2.元素个数小于原有的,需要删除
代码语言:javascript复制void resize(size_t n, char ch = '