目录
string - 构造函数
简介:
string构造函数重载的形式比较多样,最常用的是1. 默认构造函数 2. 拷贝构造函数 4. 以C字符串-const char* 为参数的构造函数。
其他的用的并不多。比如,string从pos位置开始的len个,C字符串的前n个,n个char,还有迭代器范围版本。
void test1()
{
string s0("12345abc"); // string(const char* s);
string s1; // string();
string s2(s0); // 拷贝构造,string(const string& s);
string s3 = s0; // 拷贝构造,C方式的拷贝构造。
string s4 = "aaaaa";
}
分析:
s2 为典型的拷贝构造,s3为C语言风格的拷贝构造。 值得注意的是s4,这里因为string(const char*) 版本构造函数不是explicit的,所以"aaaaa"隐式类型转换为(构造)string对象,再拷贝构造s4,编译器对此情况优化为直接进行构造。
验证:
当explicit string(const char* s); 时
解除explicit后,如果在string(const char* s)构造函数和拷贝构造中加上输出语句,运行这个构造代码后,发现并没有拷贝构造的输出语句,即编译器对此优化为了直接构造。
string - 析构函数,重载赋值运算符
分析:
比较简单,operator=重载了多个版本,注意区分赋值和构造的情况。析构函数在对象销毁时自动调用,用于回收清理对象申请的空间。
模拟实现string构造,拷贝构造,重载赋值运算符,析构
数据成员只有这几个。
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos = -1;
构造:
(都是class string的成员函数)
// string() : _size(0),_capacity(0),_str(new char[1])
// {
// _str[0] = '\0';
// }
string(const char* str = "")
{
_size = strlen(str); // strlen不包含\0,计算的是有效字符个数。
_capacity = _size;
_str = new char[_capacity+1];
strcpy(_str, str); // strcpy会拷贝'\0'
}
string(size_t n, char c)
: _size(n),_capacity(n),_str(new char[n+1])
{
for(size_t i = 0; i < n; ++i)
_str[i] = c;
_str[n] = '\0';
}
没有全部实现,实现了几个常用的,明确size和capacity的意义,利用C语言库函数和new即可简单实现。
拷贝构造+重载赋值运算符
传统写法:
string(const string& s)
: _size(s._size),_capacity(s._capacity),_str(new char[s._capacity+1])
{
strcpy(_str,s._str);
}
// s1 = s3;
string& operator=(const string& s)
{
if(this != &s)
{
delete[] this->_str;
_str = new char[s._capacity+1];
_size = s._size;
_capacity = s._capacity;
strcpy(_str,s._str);
}
return *this;
}
// 略微改进了一点,考虑到如果new开空间失败,不破坏原对象。
string& operator=(const string& s)
{
if(this != &s)
{
char* tmp = new char[s._capacity+1];
strcpy(tmp,s._str);
delete[] this->_str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法:
void swap(string& tmp)
{
std::swap(_str,tmp._str);
std::swap(_size,tmp._size);
std::swap(_capacity,tmp._capacity);
}
// 现代写法: 老板思维
string(const string& s) // string s1(s2)
:_size(0),_capacity(0),_str(nullptr) // 如果不这样,this的_str是随机值,tmp析构时,delete[]随机值非法。
{
string tmp(s._str); // 调用string(const char*) 构造函数
this->swap(tmp);
}
string& operator=(const string& s)
{
if (this != &s) {
// string tmp(s);
string tmp(s._str);
swap(tmp);
}
return *this;
}
// 最简版
string& operator=(string s)
{
swap(s);
return *this;
}
string& operator=(const char* s) {
string tmp(s);
this->swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
1. 拷贝构造的现代写法:利用string(const char* s)构造出临时对象tmp,其实就是用实参string拷贝一份,然后交换*this 和 tmp,因为这里的*this刚刚初始化完,数据成员都是随机值,如果和tmpswap之后,tmp析构时,数据成员是随机值会报错,所以利用初始化列表将*this数据成员简单初始化以下,再与tmp交换。
2. 对于swap函数,这里利用的是string的成员函数swap,上方有实现。原因有2:a、标准库中的std::swap()对于交换的两个对象,是先拷贝构造一份,再赋值两次,对于深拷贝的自定义类型来说,效率较低。 b、std::swap()会调用交换对象的类型的拷贝构造和operator=,我们在实现的拷贝构造中使用std::swap就会造成无限递归。 所以必须使用strin