拷贝构造,赋值运算符重载(六千字长文详解!)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了拷贝构造,赋值运算符重载(六千字长文详解!)相关的知识,希望对你有一定的参考价值。

c++之类和对象详解 拷贝构造,赋值运算符重载

拷贝构造

拷贝构造特征

那能不呢使用指针呢?答案是可以的!==因为指针的类型是date 产生的临时变量不会引发拷贝构造,但是这时候叫做构造函数,不是拷贝构造函数!==,==因为拷贝构造的定义就是参数类型要是类的引用!==但是那样子很不方便还要解引用等操作,不如使用引用!*

拷贝构造的注意

为了防止反向拷贝的情况发生我们一般都会加上const缩小权限!

Date(Date& d)

    _year = d._day;
    _month = d._month;
    d._day = _day;
//没有加上const会导致反向拷贝使原先数据丢失!
Date(const Date& d)

    _year = d._day;
    _month = d._month;
    d._day = _day;
//这样的话一旦发生反向拷贝就会直接报错!
  • 若未显式定义,编译器会生成==默认的拷贝构造函数==。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做==浅拷贝==,或者值拷贝。

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

  • 赋值运算符重载

    class Date
    
    public:
    	Date(Date& d)
    	
    	    _year = d._day;
    	    _month = d._month;
    	    _day = d._day;
    	
    	Date(int year, int month = 1, int day = 1)
    	
    		_year = year;
    		_month = month;
    		_day = day;
    	
    private:
    	int _year = 0;
    	int _month = 0;
    	int _day = 0;
    ;
    int main()
    
    	Date d1(2000, 1, 1);
    	Date d2(2000, 2, 28);
        d1 > d2;
        d1 == d2;
        d1 + 100;
        d1 - d2;
    	return 0;
    
    

    运算符重载

    函数名字为:关键字operator后面接需要重载的运算符符号。 函数原型:返回值类型 operator操作符(参数列表)

    class Date
    
    public:
    	Date(Date& d)
    	
    	    _year = d._day;
    	    _month = d._month;
    	    _day = d._day;
    	
    	Date(int year, int month = 1, int day = 1)
    	
    		_year = year;
    		_month = month;
    		_day = day;
    	
    	bool operator==(const Date& d2)
    	
    		return _year == d2._year
    			&& _day == d2._day
    			&& _month == d2._month;
    	
    private:
    	int _year = 0;
    	int _month = 0;
    	int _day = 0;
    ;
    int main()
    
    	Date d1(2000, 1, 1);
    	Date d2(2000, 2, 28);
        d1 == d2;//此时编译器会转换成调用d1.operator==(d2);
        d1.operator==(d2);//直接这样调用也行!
        
        //编译器很聪明如果是放在全局的那么会优先调用全局,如果全局没有就回去类里面找!
       //如果是全局就会被转换成operator==(d1,d2)
    	cout << (d1 == d2) << endl;//要加括号因为运算符优先级问题,因为我们并没有重载<< 所以<< 没哟办法输出自定义类型!
    	return 0;
    
    
    
    

    赋值重载

    	Date& operator=(const Date& d)
    	
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    		return *this;
    	;//这就是赋值重载
    

    赋值运算符的写法注意

        void operator=(const Date& d)
    	
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	
    

    赋值重载的默认性

    所以对于默认拷贝构造的问题也会出现在默认赋值重载上面

    像是对于要动态开辟的类型 例如stack/queue/list.....

    class stack
    
    public:
    	stack(int newcapcacity = 4)
    	
    		int* temp = (int*)malloc(sizeof(int) * newcapcacity);
    		if (temp == nullptr)
    		
    			perror("malloc fail");
    			exit(-1);
    		
    		_a = temp;
    		_top = 0;
    		_capacity = newcapcacity;
    	
    	~stack()//这就是栈的析构函数!
    	
    		free(_a);
    		_a = nullptr;
    		_top = 0;
    		_capacity = 0;
    	
    	stack(stack& st)
    	
    		_a = (int*)malloc(sizeof(int) * st._capacity);
    		if (_a == nullptr)
    		
    			perror("malloc fail");
    			exit(-1);
    		
    		memcpy(_a, st._a, st._top * sizeof(int));
    		_capacity = st._capacity;
    		_top = st._top;
    	
    	void Push(int x)
    	
    		if (_top == _capacity)
    		
    			int newcapacity = 2 * _capacity;
    			int* temp = (int*)realloc(_a, sizeof(int) * newcapacity);
    			if (temp == nullptr)
    			
    				perror("realloc fail");
    				exit(-1);
    			
    			_a = temp;
    			_capacity = newcapacity;
    		
    		_a[_top++] = x;
    	
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    ;
    int main()
    
    	stack st1;
    	st1.Push(1);
    	st1.Push(2);
    	stack st2;
    	st2.Push(3);
    	st2.Push(4);
    	st1 = st2;
    	return 0;
    
    

    ==然后这个程序会发生崩溃!==

    因为两个指针指向了同一块的内存地址!

    st2的析构函数释放了这一块内存空间

    然后st1的析构函数又一次的释放了这一块内存空间!

    释放野指针导致了崩溃!

    而且还会导致==内存泄漏!==

    因为st的指针指向了st2的空间,但是却没有释放自己原有的空间!

    所以要自己去写赋值重载!

    	stack& operator=(const stack& st)
    	
    		free(_a);
    		_a = (int*)malloc(sizeof(int) * st._capacity);
    		if (_a == nullptr)
    		
    			perror("malloc fail");
    			exit;
    		
    		memcpy(_a, st._a, sizeof(int) * _top);
    		_top = st._top;
    		_capacity = st._capacity;
            return *this;
    	
    

    这样子就可以完成对于栈这种类型的赋值

    但是这样写还是有缺陷!比如遇到自己赋值给自己的时候!

    我们会发现原本的值竟然变成的随机值!

    因为我们一开始就free掉了原来的空间!所以导致了一旦自己赋值给自己的时候,一开始的free就会导致数据的丢失!

    所以我们要进一步的修改

    	stack& operator=(stack& st)
    	
    		if (this != &st)
    		
    			_a = (int*)malloc(sizeof(int) * st._capacity);
    			if (_a == nullptr)
    			
    				perror("malloc fail");
    				exit(-1);
    			
    			memcpy(_a, st._a, st._top * sizeof(int));
    			_capacity = st._capacity;
    			_top = st._top;
    		
    		return *this;
    	
    

    为什么不使用realloc去改变原来的数组大小,反倒是使用先free再malloc的形式呢?

    答:==因为要考虑的情况太多了,数组比原来大,数组比原来小,数组和原来相同==

    数组比原来的大我们可以正常使用realloc。

    数组比原来小,我们不使用realloc(realloc一般不用于缩小数组!)

    但是如果不对数组进行缩小而是正常的进行拷贝,万一出现原先数组有100000个 赋值数组只有10个这种情况的话就会导致极大的空间浪费!

    所以基于以上的几种情况我们选择采用先free在malloc的方式!

    我们从上面的特征可以看出来拷贝构造和赋值重载具有很多的相似之处!

    我们也可以得出一个相似的结论:

    ==需要写析构函数的就要写显性赋值重载,否则就不需要!==

    像是类似myquene的情况因为不用谢析构所以自然也不用谢赋值重载都是显性的都够用了!

    因为myqueue的默认赋值重载会自动的去调用stack类型的赋值重载来使用!

    class stack
    
        //....
    
    class MyQuene
    
    public:
    	void Push(int x)
    	
    		_PushST.Push(x);
    	
    private:
    	stack _PopST;
    	stack _PushST;
    ;
    int main()
    
    	MyQuene q1;
    	q1.Push(1);
    	q1.Push(2);
    	MyQuene q2;
    	q2.Push(3);
    	q2.Push(4);
    
    	q1 = q2;
    	return 0;
    
    

    赋值重载和拷贝赋值的区别在哪里?

    class Date
    
    public:
    	Date(int year = 10,int month = 10,int day = 10)
    	
    		_year = year;
    		_month = month;
    		_day = day;
    	
    	Date& operator=(const Date& d)
    	
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    		return *this;
    	
    private:
    	int _year;
    	int _month;
    	int _day;
    ;
    
    int main()
    
    	Date d1(100,10,100);
    	Date d2(d1);//拷贝构造 是初始化另一个要马上创建的对象!
    	d2 = d1;//赋值重载(赋值拷贝!)已经存在的两个对象之间的拷贝!
        
        Date d3 = d1;//这看上去好像是赋值重载!
        //但是其实拷贝构造!因为从定义上看它更符合 初始化一个要创建的对象!
    	return 0;
    
    

    以上是关于拷贝构造,赋值运算符重载(六千字长文详解!)的主要内容,如果未能解决你的问题,请参考以下文章

    C++初阶:类和对象(中篇)构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载

    const成员,流插入,流提取重载,初始化列表! 流插入,流提取的重载(6千字长文详解!)

    运算符重载详解

    c++之构造函数,析构函数(五千字长文详解!)

    拷贝构造函数——重载赋值运算符

    c++中拷贝构造函数和赋值运算符重载本质上一样么