string类是一个存放字符串的容器,既然是容器,就有容器的容量以及实际存放的量
所以我们可以构造出一些基本的成员变量
class MyString
{
private:
char* _str; //存放字符串
int _size; //实际存放的有效字符个数
int _capacity; //最多可以存放的有效字符个数
};
目录
(1) 不另外开辟空间,s3和s2共享一块空间 —— 浅拷贝
(2) 另外开辟空间,s2、s3各自拥有一块空间 —— 深拷贝
一、默认构造函数
像下面这种情况会调用默认构造函数
MyString s1;
(1) 开辟一个元素的空间存放 '\0'——》占位用的
(2) 实际有效字符个数为 0
(3) 容量暂时设置为 1
//写法一:
MyString()
:_str(new char[1]),
_size(0),
_capacity(0)
{
*_str = '\0';
}
//写法二:
MyString()
{
_str = new char[1]; //开辟一个空间存放 '\0'
*_str = '\0';
_size = 0; //有效字符个数为 0
_capacity = 0; //最多存放的有效字符个数为0
}
二、参数为字符串常量的构造函数
但有些时候,也会输入字符串常量
MyString s2("hello,world");
基本思路依然围绕三个成员变量展开
//写法一:
MyString(const char* str = "")
:_str(new char[strlen(str)+1]),
_size(strlen(str)),
_capacity(_size)
{
strcpy(_str,str);
}
//写法二:
MyString(const char* str = "")
{
_size = strlen(str); //有效字符个数为 strlen(str)
_capacity = _size; //后面可能不会新增,一次写的过多会导致空间的浪费
_str = new char[_capacity + 1]; //分配_capacity+1个空间,多加一个空间是留给'\0'的
strcpy(_str,str); //按字节把str指向的内容拷贝到_str
}
三、拷贝构造函数
使用字符串常量是一种初始化方式,那如果是使用对象初始化呢??
MyString s2("hello,world");
MyString s3(s2); //使用对象s2,来初始化s3
1、对象初始化
s2是已经存在的对象,s3希望自己的空间大小和内容和s2完全一样
把s2成员变量的值逐一赋给s3 的成员变量,这就是对象初始化
2、浅拷贝和深拷贝
既然是希望和 s2 一样,那就有两种方式
(1) 不另外开辟空间,s3和s2共享一块空间 —— 浅拷贝
既然是指向同一块空间,那就只需要把 s2的_str 地址赋值给 s3 的_str即可
MyString(MyString& s):_str(s._str)
{
_size = s._size;
_capacity = s._capacity;
}
注意:
a. 当 s3 被销毁的时候,会调用析构函数,指向的空间会被销毁
b. s3被释放时,调用一次析构函数,这个空间就被释放了,如果接下来s2调用了析构函数,那么这个空间会被释放两次,程序会崩溃
这显然不是 s2 愿意看到的,因此这里并不适合使用浅拷贝
(2) 另外开辟空间,s2、s3各自拥有一块空间 —— 深拷贝
既然不能共享,那就另外开辟一块空间
然后把 s2成员变量的值逐一赋值给 s3 的成员变量
//这里可以使用 参数列表初始化,这样写只是为了了解步骤
MyString(MyString& s)
{
//_str = new char[strlen(s._str) + 1]; //先开辟空间
//strcpy(_str, s._str); //再拷贝内容
//_size = s._size;
//_capacity = s._capacity;
//上面的写法在遇到 s3 = s3,也就是自己给自己赋值时会崩溃
if(this != &s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1]; //先开辟空间
strcpy(_str, s._str); //再拷贝内容
}
return *this;
}
我们发现s2 和 s3的地址不一样,s3被释放的时候,也不会影响到s2
注意:
形参必须要加引用 & ,否则会引发 拷贝构造函数的无限调用
3、现代写法
上面写的这种方式是传统的写法,下面将介绍现代写法
MyStirng s1("hello,world");
MyString s2(s1);
(1) 构造一个临时对象tmp,用s1._str 去初始化这个对象,那么这个对象就和 s1 一模一样
这样就不用手动开辟空间,而是借用构造函数开辟空间
(2) 定义一个swap函数,把 s2 的_str 、_size 、_capacity 和 tmp中的交换
(3) 调用这个swap函数交换 成员变量的 值
void swap(MyString& s1, MyString& s2)
{
std::swap(s1._str, s2._str); //交换 _str
std::swap(s1._size, s2._size); //交换 _size
std::swap(s1._capacity, s2._capacity); //交换 _capacity
}
MyString::MyString(MyString& s)
{
MyString tmp(s._str); //创建一个对象tmp,使用对象s的字符串初始化
swap(*this, tmp); //“交换” 两个对象
}
代码测试:
int main() {
MyString s1("hello,world");
MyString s2(s1);
return 0;
}
交换前:
交换后: