c++之拷贝构造和赋值函数,以及explicit的详解

目录

拷贝构造和赋值函数

拷贝构造:

赋值函数:

浅拷贝:

深拷贝:

 浅拷贝(数据不独立):

深拷贝(数据独立):

区分赋值函数和拷贝构造:

总结

 

 explicit的应用:防止隐式转换。

#include <iostream>
using namespace std;
class A
{
public:
    //阻止构造函数的隐式转换
    explicit A(int x=10):val(x){}
    void set_val(int x){
        this->val = x;
    }
    void print_val(void){
        cout<<"val:"<<val<<endl;
    }
    //转换函数 实现 A->int
    explicit operator int(){
        return val;
    }
    //explicit 用于防止隐式转换
private:
    int val;
};
int main(void)
{
    A a1(3);//普通构造
    a1.print_val();
    //强制转换,将 A 类型的 a1 转换成 int 类型
    cout<<int(a1)<<endl;
    //一般情况下,左右类型不一致,将自动发生隐式转换
    //报错:不存在从 "A" 到 "int" 的适当转换函数,这里便是由于explicit的存在
    int i = a1;//赋值函数
    //报错:没有与这些操作数匹配的 "+" 运算符C/C++(349) 
    //conversion_func.cpp(31, 15): 操作数类型为: A + int
    //因为explicit的存在,阻止了 A 类型的a1转换成 int 类型,所以会抱没有这里对象相+的重载错误
    A a2 = a1 + 54;

    A a2(a1);//拷贝构造
    //报错:不存在从 "int" 转换到 "A" 的适当构造函数
    // explicit阻止了 10 转换成 类型A,从而进行拷贝构造的操作
    A a2 = 10;
   
    return 0;
}

拷贝构造和赋值函数

拷贝构造

实质是一种特殊的构造函数,区别于普通构造,其中普通构造是将类中的私有数据进行赋值的操作,而拷贝构造是将定义一个与被拷贝类型相同的对象,如A a1(a2),即定义一个a2的对象,其类型与A类型的a1对象相同

赋值函数

分为对象赋值,数据赋值,其实质是赋值操作运算符重载,operator=()

//对象赋值
A a1,a2;
a2 = a1;
//数据赋值
int i = 10;

谈及拷贝构造便会引发深拷贝和浅拷贝问题

浅拷贝

不开辟空间,直接进行指针复制

#include <iostream>
using namespace std;
class A{
public:
    A(int x=10):val(x){
        cout<<"构造函数"<<endl;
        val = x;
        ptr = new int;
        *ptr = x;
    }
    ~A(){
        cout<<"析构函数"<<endl;
        delete [] ptr;
    }
    A(const A &obj){
        cout<<"浅拷贝"<<endl;
        val = obj.val;
        //两个指针指向同一片 空间,这里会造成double free()问题,也就是指针悬挂现象
        ptr = obj.ptr;
    }
    void print_data(void){
        cout<<"val:"<<val<<endl;
        cout<<"ptr:"<<ptr<<endl;
    }
private:
    int val;
    int *ptr;
};
int main(void){
    A a1(23);
    A a2(a1);
    a1.print_data();
    a2.print_data();
}
//运行结果
/*
构造函数
浅拷贝
val:23
ptr:0x5581a90b22c0
val:23
ptr:0x5581a90b22c0
析构函数
析构函数
free(): double free detected in tcache 2
已放弃 (核心已转储)
*/

深拷贝

开辟空间,并不进行指针的直接复制

#include <iostream>
using namespace std;
class A{
public:
    A(int x=10):val(x){
        cout<<"构造函数"<<endl;
        val = x;
        ptr = new int;
        *ptr = x;
    }
    ~A(){
        cout<<"析构函数"<<endl;
        delete [] ptr;
    }
    A(const A &obj){
        cout<<"深拷贝"<<endl;
        //由于成员数据中有指针,因此需要实现深拷贝
        //如果成员数据里面没有指针,则不需要实现深拷贝
        //浅拷贝实质是两个指针指向同一片空间,释放空间时会导致同一片空间释放两次
        //深拷贝实质就是重新开辟一个空间,释放空间时各种释放自己指向的空间
        val = obj.val;
        ptr = new int;
        *ptr = *obj.ptr;
    }
    void print_data(void){
        cout<<"val:"<<val<<endl;
        cout<<"ptr:"<<ptr<<endl;
    }
private:
    int val;
    int *ptr;
};
int main(void){
    A a1(23);
    A a2(a1);
    a1.print_data();
    a2.print_data();
}
/*
构造函数
深拷贝
val:23
ptr:0x561f764b62c0
val:23
ptr:0x561f764b62e0
析构函数
析构函数
*/

深拷贝、浅拷贝拓展:实现智能指针中的共享指针;单例问题,如windows的任务管理器

讲完拷贝函数,我们再聊聊赋值函数,赋值函数需要根据实际情况决定是否进行深拷贝

赋值函数:本质是赋值运算符的重载,即operator=()

 浅拷贝(数据不独立)

#include <iostream>
using namespace std;
class A{
    public:     
    A(int x=10):val(x){
        cout<<"构造函数"<<endl;
        val = x;
        ptr = new int;
        *ptr = x;
    }
    ~A(){
        cout<<"析构函数"<<endl;
        delete [] ptr;
    }
    A(const A &obj){
        cout<<"深拷贝"<<endl;
        //由于成员数据中有指针,因此需要实现深拷贝
        val = obj.val;
        ptr = new int;
        *ptr = *obj.ptr;
    }
    void operator=(const A &obj){
        cout<<"赋值函数"<<endl;
        //虽然成员数据中有指针,但是是对一个 int 型的指针进行赋值操作,空间也就是4字节,所以不需要进行深拷贝,也就是说不需要考虑越界(非法访问)问题
        if(this == &obj){
            //自己给自己赋值,不需要进行赋值操作,退出
            cout<<"this=&obj"<<endl;
            return ;
        }
        this->val = obj.val;
        //指针指向的4字节内容被覆盖
        *ptr = *obj.ptr;
    }
    void print_data(void){
        cout<<"val:"<<val<<endl;
        cout<<"ptr:"<<ptr<<endl;
    }
private:
    int val;
    int *ptr;
};
int main(void){
    A a1(10);
    A a2(20);
    a2 = a1;
    a1.print_data();
    a2.print_data();
}
/*
构造函数
构造函数
赋值函数
val:10
ptr:0x55ff490d82c0
val:10
ptr:0x55ff490d82e0
析构函数
析构函数
*/

那么可能就有人会发出疑问呢?为啥子a1和a2的地址不一样,因为本文代码中a2和a1都是在构造时开辟了空间,两个所占空间不一样,本来a2是20的,但是进行了赋值操作,所以就变成了10,但是两者的所占空间并不一样,注意,我们只是进行的赋值操作,赋值!!!

那么我们升华一点,如果一个占20个字节的空间,该空间中本来有内容,但是进行了赋值操作,注意:这里会分两种情况①是否需要保留原有数据,如果没必要则不用进行深拷贝(重新调用拷贝构造开辟空间),浅拷贝即可②原有数据后续有用,那么必须进行深拷贝。

深拷贝(数据独立)

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

class Array
{
public:
    Array(int n=10){
        cout<<"构造函数"<<endl;
        data = new int [n];
        size = n;
        pos = 0;
    }
    ~Array(){
        cout<<"析构函数"<<endl;
        delete [] data;
    }
	void operator=(const Array &obj){
	    cout<<"赋值函数深拷贝"<<endl;	
		if(this==&obj){
            cout<<"自我赋值"<<endl;
			return ;
		}
		if(size != obj.size){
			//大小不一致 重新开辟空间
			delete [] data;
			data = new int [obj.size];
		}
        //内存拷贝函数
        size = obj.size;
		memcpy(data,obj.data,size*sizeof(int));
		pos = obj.pos;
	}
    //从尾巴插人数据
    int append(int val){
        if(pos>size-1){
            cout<<"full"<<endl;
            return -1;
        }
        data[pos++] = val;
        return 0;
    }
    //修改数组数据
    int update(int index,int val){
        data[index] = val;
        return 0;
    }
    //show数组
    void show_array(){    
        int i;
        for(i=0;i<pos;i++){
            cout<<data[i]<<" ";
        }
        cout<<endl;
    }
    //查看数组首地址
    void print_data(){
        cout<<data<<endl;
    }
private:
    int *data;   
    int  size;
    int  pos;
};

int main(void)
{
    Array a(3);
    a.append(13);
    a.append(14);
    a.append(520);
    Array b;
    b = a;
    cout<<"数组a :";
    a.show_array();
    cout<<endl;
    cout<<"数组b :";
    b.show_array();
    cout<<endl;
    //修改数组b下标为2的内容
    //如果是浅拷贝,则代表着公用一片空间,数组a内容也会被修改
    //如果数组 a 内容没有被修改则代表着深拷贝
    b.update(2,250);
    cout<<"数组a :";
    a.show_array();
    cout<<endl;
    cout<<"数组b :";
    b.show_array();
    cout<<endl;
    cout<<"================"<<endl;
    a.print_data();
    b.print_data();
    return 0;
}
/*
构造函数
构造函数
赋值函数深拷贝
数组a :13 14 520 

数组b :13 14 520 

数组a :13 14 520 

数组b :13 14 250 

================
0x5614e47fc2c0
0x5614e47fc310
析构函数
析构函数
*/

拓展:赋值函数的深浅拷贝还需要看等号左右的空间是否一致,如果等号右边的空间大于等号左边的空间,那么就需要进行深拷贝,不然会造成数组越界,非法访问内存。

那么你们是否感觉有点怪怪的,对,就目的而言,赋值函数本来就需要被覆盖,被覆盖的数据都没用,对,赋值函数根本就没有数据独立这一说,所以,赋值函数大多数也都是空间不够,所以需要重新开辟空间,才进行的深拷贝

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

class Array
{
public:
    Array(int n=10){
        cout<<"构造函数"<<endl;
        data = new int [n];
        size = n;
        pos = 0;
    }
    ~Array(){
        cout<<"析构函数"<<endl;
        delete [] data;
    }
	void operator=(const Array &obj){
	    cout<<"赋值函数浅拷贝"<<endl;	
		if(this==&obj){
            cout<<"自我赋值"<<endl;
			return ;
		}
        *data = *obj.data;
		size = obj.size;
		pos = obj.pos;
	}
    //从尾巴插人数据
    int append(int val){
        if(pos>size-1){
            cout<<"full"<<endl;
            return -1;
        }
        data[pos++] = val;
        return 0;
    }
    //修改数组数据
    int update(int index,int val){
        data[index] = val;
        return 0;
    }
    //show数组
    void show_array(){    
        int i;
        for(i=0;i<pos;i++){
            cout<<data[i]<<" ";
        }
        cout<<endl;
    }
    void print_data(){
        cout<<data<<endl;
    }
private:
    int *data;   
    int  size;
    int  pos;
};

int main(void)
{
    Array a(3);
    a.append(13);
    a.append(14);
    a.append(520);
    Array b = a;    //拷贝构造浅拷贝,第一次出现
    cout<<"数组a :";
    a.show_array();
    cout<<endl;
    cout<<"数组b :";
    b.show_array();
    cout<<endl;
    //修改数组b下标为2的内容
    //如果是浅拷贝,则代表着公用一片空间,数组a内容也会被修改
    //如果数组 a 内容没有被修改则代表着深拷贝
    b.update(2,250);
    cout<<"数组a :";
    a.show_array();
    cout<<endl;
    cout<<"数组b :";
    b.show_array();
    cout<<endl;
    cout<<"================"<<endl;
    a.print_data();
    b.print_data();
    return 0;
}
/*
构造函数
数组a :13 14 520 

数组b :13 14 520 

数组a :13 14 250 

数组b :13 14 250 

================
0x55a2ef01a2c0
0x55a2ef01a2c0
析构函数
析构函数
free(): double free detected in tcache 2
已放弃 (核心已转储)
*/

区分赋值函数和拷贝构造

#include <iostream>
using namespace std;
class A{
    public:     
    A(int x=10):val(x){
        cout<<"构造函数"<<endl;
        val = x;
        ptr = new int;
        *ptr = x;
    }
    ~A(){
        cout<<"析构函数"<<endl;
        delete [] ptr;
    }
    A(const A &obj){
        cout<<"深拷贝"<<endl;
        //由于成员数据中有指针,因此需要实现深拷贝
        val = obj.val;
        ptr = new int;
        *ptr = *obj.ptr;
    }
    void operator=(const A &obj){
        cout<<"赋值函数"<<endl;
        //虽然成员数据中有指针,但是是对一个 int 型的指针进行赋值操作,空间也就是4字节,所以不需要进行深拷贝
        if(this == &obj){
            //自己给自己赋值,不需要进行赋值操作,退出
            cout<<"this=&obj"<<endl;
            return ;
        }
        this->val = obj.val;
        //指针指向的4字节内容被覆盖
        *ptr = *obj.ptr;
    }
    void print_data(void){
        cout<<"val:"<<val<<endl;
        cout<<"ptr:"<<ptr<<endl;
    }
private:
    int val;
    int *ptr;
};
int main(void){
    A a1 = 10;    //a1 第一次出现
    a1.print_data();
    cout<<"==========="<<endl;
    A a2(a1);    //a2 第一次出现
    a2.print_data();
    cout<<"==========="<<endl;
    A a3(20);    //a3 第一次出现
    a3.print_data();
    cout<<"==========="<<endl;
    a2 = a1;    //a2 第二次出现
    cout<<"a1:";
    a1.print_data();
    cout<<endl;
    cout<<"a2:";
    a2.print_data();
    cout<<endl;
    return 0;
}
/*
构造函数
val:10
ptr:0x562ecc03e2c0
===========
深拷贝
val:10
ptr:0x562ecc03e2e0
===========
构造函数
val:20
ptr:0x562ecc03e300
===========
赋值函数
a1:val:10
ptr:0x562ecc03e2c0

a2:val:10
ptr:0x562ecc03e2e0

析构函数
析构函数
析构函数
*/

注意:系统自带的全是拷贝构造,赋值函数全是浅拷贝

=====================================================================

总结

有指针的情况下:

①拷贝构造进行浅拷贝时是    A a1(23);         A a2(a1);  这里a1开辟了空间,a2并没有开辟空间,还是指向之前开辟的空间,析构时会出现double free问题(上文);

②拷贝构造进行深拷贝时是    A a1(23);         A a2(a1); 这里两者都开辟了空间(上文);

③普通构造和拷贝构造是对象第一次出现,会调用拷贝构造或者普通构造;

④赋值函数是在对象已经被定义,只是进行赋值操作时才会被调用;

注意:类型不匹配,也会先调用拷贝函数,在调用赋值函数,如:

A a1;        a1 = 10;                A有两种构造函数,一个无参构造,一个有参构造A(int a){};

⑤调用赋值函数时如果原有数据不重要,则不需要进行深拷贝,浅拷贝即可;

⑥调用赋值函数时如果原有数据很重要,则需要进行深拷贝;

⑦调用赋值函数时如果空间不足,则必须在赋值函数中开辟新空间,进行深拷贝;

⑧调用赋值函数时,由于对象已经被定义,所以地址并不相同;

⑨调用赋值函数浅拷贝时,会间接调用拷贝构造的浅拷贝,所以地址才会显示一样。

无指针深浅拷贝都可

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在引用的内容中,并没有提到explicit关键字可以直接修饰拷贝构造函数explicit关键字通常用于修饰类的构造函数,以防止隐式类型转换。在C++中,拷贝构造函数是一个特殊的构造函数,用于创建对象的副本。通常情况下,拷贝构造函数不会被explicit关键字修饰。所以,如果要强制禁止隐式调用拷贝构造函数,可以使用其他的方法,例如将拷贝构造函数声明为私有或删除该函数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [explicit作用,拷贝构造函数,隐式类型](https://blog.csdn.net/NBE999/article/details/77881518)[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: 33.333333333333336%"] - *2* [【C++学习】explicit修饰构造函数](https://blog.csdn.net/TwT520Ly/article/details/80974757)[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: 33.333333333333336%"] - *3* [c/c++拷贝构造函数和关键字explicit详解](https://download.csdn.net/download/weixin_38548589/13994418)[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: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值