string类的模拟实现(一)—— 构造函数(无参、拷贝等)

string类是一个存放字符串的容器,既然是容器,就有容器的容量以及实际存放的量

所以我们可以构造出一些基本的成员变量

class MyString
{
private:
	char* _str;        //存放字符串
	int _size;         //实际存放的有效字符个数
	int _capacity;     //最多可以存放的有效字符个数

};

目录

一、默认构造函数

二、参数为字符串常量的构造函数

三、拷贝构造函数

1、对象初始化

 2、浅拷贝和深拷贝

(1)  不另外开辟空间,s3和s2共享一块空间    —— 浅拷贝

(2)  另外开辟空间,s2、s3各自拥有一块空间 —— 深拷贝

3、现代写法


一、默认构造函数

像下面这种情况会调用默认构造函数

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

注意:

形参必须要加引用 & ,否则会引发 拷贝构造函数的无限调用

调用拷贝构造函数的几种情况_abs(ln(1+NaN))的博客-CSDN博客当我们使用一个已经存在的对象去初始化其他对象的时候,会调用拷贝构造函数除了这种情况之外,还有哪些情况会调用拷贝构造函数??https://blog.csdn.net/challenglistic/article/details/123708019?spm=1001.2014.3001.5501

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;
}

交换前:

 交换后:

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值