C++基础——拷贝构造函数

        前言

        在上篇文章中,我简单介绍了一下类的两大特殊函数——构造函数和析构函数,构造函数主要用来进行对象的成员变量初始化操作,而析构函数主要用来对战斗后的战场做清理工作。当我们不写这些函数时,编译器会自动生成默认的构造与析构函数,帮助我们合理的运行程序,但在一些情况下,编译器生成的并不能满足我们对代码的需求,这就需要我们自己去写了,所以要根据情况的不同而去选择性的写。

        接下来我将继续介绍类的另外一大特殊成员函数——拷贝构造函数

目录

目录

        前言

一.拷贝构造函数

1.定义:

2.特征:

为什么使用传值会引发无穷递归调用。 

3.默认拷贝构造函数

4.浅深拷贝

深拷贝:

规律:

5. 拷贝构造函数典型调用场景:



一.拷贝构造函数

1.定义:

        拷贝构造函数是构造函数的一种重载形式,它可以用来创建一个与已存在的对象一模一样的新对象。对于拷贝构造,它只有单个形参,且该形参必须是对本类类型对象的引用,因为要引用,所以要加const修饰。

2.特征:

        1.拷贝构造函数的参数若使用传值方式编译器直接报错, 因为会引发无穷递归调用。

        2.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。  

        3.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了。 

     

代码展示: 

例:

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 // Date(const Date d)   // 错误写法
    Date(const Date& d)   // 正确写法
 _year = d._year;
 _month = d._month;
 _day = d._day;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date d1;
 Date d2(d1);
 return 0;
}

测试: 

针对第一点特征,我来说说:

为什么使用传值会引发无穷递归调用。 

 

 画图的方式模拟编译器采用值传递运行情况:

        如上图所示,执行date d2(d1); d1传参给拷贝构造的形参d,即需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参,那么现在的对象只有d1了,所以会出现 date d(d1),而拷贝的过程中又会调用自身的拷贝构造函数,传值方式会继续传进一个A的对象作为实参,会无休止的递归下去。

3.默认拷贝构造函数

        当我们没有在类中写拷贝构造函数时,编译器会自动生成一个默认的拷贝构造。

        系统生成的拷贝构造也会针对成员变量的内置类型和自定义类型做一个区分。对于内置类型的成员变量,编译器会按照被拷贝对象的内存存储字节序完成拷贝,就好比被拷贝的对象有3个int类型成员变量,占12字节内存,编译器会根据该对象的内存和成员初始值拷贝给新对象。

例:

class Time
{
public:
 Time()
 {
 _hour = 1;
 _minute = 1;
 _second = 1;
}
 Time(const Time& t)
 {
 _hour = t._hour;
 _minute = t._minute;
 _second = t._second;
 cout << ""Time(const Time&)拷贝构造"" << endl;
 }
private:
 int _hour;
 int _minute;
 int _second;
};

class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};

int main()
{
 Date d1;
 Date d2(d1);    //调用该类的拷贝构造
 return 0;
}

        解析:创建d2时,调用该类的默认拷贝构造函数,编译器对于内置类型的成员:_year,_month,_day全都拷贝出相同的d1数据值,对于自定义类型成员_t,则会跳转到Time类中调用Time类的拷贝构造。

4.浅深拷贝

对于上例日期类代码,完全可以不用写拷贝构造函数,使用默认的即可,但在特殊情况下,又会有有不同的结果:

typedef int DataType;
class Stack{
public:
    Stack(size_t capacity = 10){
        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array){
            perror("malloc申请空间失败");
            return;
        }
        _size = 0;
        _capacity = capacity;
    }
    void Push(const DataType& data){
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }
    ~Stack(){
        if (_array)
        {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }
private:
    DataType* _array;
    size_t _size;
    size_t _capacity;
};
int main(){
    Stack s1;
     s1.Push(1);
    s1.Push(2);
    s1.Push(3);
    s1.Push(4);
    Stack s2(s1);
    return 0;
}

我们会发现s1和s2的成员变量_array都指向同一块空间,也就说编译器调用系统生成的默认构造时,把s1._array指向的地址也拷贝给了s2._array,这看起来没什么,等到程序结束完成时,调用析构函数会出现大问题!

        程序即将结束时,调用析构函数,会先析构s2,第一次析构完成后,s2._array指向的空间会被释放,其他置为0,也就是等价于s1._array指向的空间也被释放(同一块空间),此时再执行s1的析构函数时,原本释放的空间会被再一次free,就会报错!!! 所以通过这个例子告诫我们,在成员变量是指针,或有文件的情况下,我们不能再用默认的拷贝构造函数了,需要自己生成一个拷贝构造。

        之前的日期类全是普通的内置类型成员变量,所以不需要特意去写,编译器能够自主解决完成拷贝,这种拷贝称为浅拷贝。

深拷贝:

//深拷贝
    Stack(const Stack& st) {
        _array = (DataType*)malloc(sizeof(DataType) * st._capacity);
        if (_array == nullptr) {
            perror("malloc fail");
            return;
        }
        //只需要自己创建一块与st1相同大小的堆空间,其他的还是拷贝st1的数据
        memcpy(_array, st._array, _capacity = st._capacity);
        _size = st._size;
        _capacity = st._capacity;
    }

而深拷贝就是单靠编译器生成的默认构造不能满足需求,需要自主去写一个,称为深拷贝。 

规律:

需要写析构函数的类,都需要写拷贝构造函数(Stack类)

不需要写析构函数的类,默认生成的拷贝构造即可。(Date类)

5. 拷贝构造函数典型调用场景:

使用已存在对象创建新对象

函数参数类型为类类型对象

函数返回值类型为类类型对象

class Date
{
public:
 Date(int year, int minute, int day)
 {
 cout << "Date(int,int,int):" << this << endl;
 }
 Date(const Date& d)
 {
 cout << "Date(const Date& d):" << this << endl;
 }
 ~Date()
 {
 cout << "~Date():" << this << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};

Date Test(Date d)    //返回值类型是Date,需要调用拷贝构造,函数参数是Date型,也需要调用拷贝构                
                     //造
{
 Date temp(d);
 return temp;
}
int main()
{
 Date d1(2022,1,13);
 Test(d1);
 return 0;
}

        代码解析:执行Test(d1);时,编译器进入Test函数中,因为形参是Date类型,所以调用一次拷贝构造函数,输出拷贝语句;之后执行Date temp(d);——创建对象Temp,又要调用一次拷贝构造函数;之后执行return temp返回类对象时,又要调用一次拷贝构造函数,总共调用三次拷贝构造,充分验证了上面调用拷贝构造的三种场景。

  • 36
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++中,拷贝构造函数是一种特殊的构造函数,它由编译器在特定情况下自动调用,用于创建一个新的对象并将其初始化为已有对象的副本。拷贝构造函数的参数通常采用引用的方式传递,以避免无限递归的问题。 对于类C中的拷贝构造函数,可以通过以下方式进行定义: ```cpp class C { public: // 拷贝构造函数 C(const C& other) { // 在此处进行对象的成员变量的拷贝 // 可以使用已有对象的成员变量值来初始化新对象的成员变量 } }; ``` 拷贝构造函数的作用是创建一个新对象,并将已有对象的成员变量值复制给新对象的对应成员变量。需要注意的是,函数成员是共用的,只有一份拷贝,所以拷贝构造函数只需要复制数据成员即可。 拷贝构造函数可以用来进行对象的初始化,例如通过已有对象来初始化新对象的存储空间。这种情况下,编译器会自动调用拷贝构造函数来完成初始化过程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++——拷贝构造函数](https://blog.csdn.net/weixin_59179454/article/details/124853916)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙予清的zzz~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值