欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++拷貝構造函數和賦值運算符重載詳解

 更新時間:2024年05月25日 14:33:46   作者:24k純甄  
拷貝構造函數是特殊的構造函數,是用一個已經存在的對象,賦值拷貝給另一個新創(chuàng)建的已經存在的對象,這篇文章主要介紹了C++拷貝構造函數和賦值運算符重載,需要的朋友可以參考下

一,拷貝構造函數

1. 什么是拷貝構造函數

拷貝構造函數是特殊的構造函數。是用一個已經存在的對象,賦值拷貝給另一個新創(chuàng)建的已經存在的對象。

本質:用同類型的對象拷貝初始化。

2. 拷貝構造函數的特性

拷貝構造函數也是特殊的成員函數,其特征如下:

2.1 拷貝構造函數是構造函數的一個重載形式。

2.2 拷貝構造函數的函數名域類名相同,參數只有一個且必須是類類型對象的引用,使用傳值方式編譯器直接報錯,因為在語法上引發(fā)無窮遞歸調用。

注意:

Date d2(d1); 這句代碼也等價于Date d2 = d1;也是拷貝構造的寫法。
#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷貝構造函數
	//參數只有一個,必須是類類型對象的引用。
    //Date d2(d1); 
	Date(Date& d)   //傳引用,正確寫法
	//Date(Date d)  //傳值,錯誤寫法
{
    //用來檢測是否調用該拷貝構造函數
	cout << "Date(Date& d)" << endl;
	//d1是d的別名,隱含的this就是d2,相當于把d1的值拷貝給d2
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
int main()
{
	Date d1(2024,4,21);
	d1.Print();
	//d2對象不在按指定年月日初始化,而是想和d1對象初始化一樣
	//拷貝構造:用同類型的對象拷貝初始化
	Date d2(d1);//這句代碼也等價于Date d2 = d1;也是拷貝構造的寫法
	d2.Print();
	return 0;
}

如果是傳值的方式,如上述代碼中的錯誤寫法,程序會直接報錯。

這是為什么呢?
這是因為自定義類型傳值傳參要調用拷貝構造,而內置類型就是直接拷貝。

通過下面的代碼來側面說明:

定義一個 func 函數,把類對象 d1 傳值過去,運行的結果是調用func之前會先調用拷貝構造函數。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)  
{
    //用來檢測是否調用該拷貝構造函數
	cout << "Date(Date& d)" << endl;
	//d1是d的別名,隱含的this就是d2,相當于把d1的值拷貝給d2
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
void func(Date d)
{
	d.Print();
}
int main()
{
	Date d1(2024, 4, 12);
	//調用func之前會先進入拷貝構造函數
	func(d1);
	return 0;
}

所以在 2.2 的代碼中,如果進行傳值調用,則在語法邏輯上會出現如下的無窮遞歸:

那如何讓它不調用拷貝構造呢?

方式1:傳地址(相當于變?yōu)閮戎妙愋停?/p>

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)  
{
    //用來檢測是否調用該拷貝構造函數
	cout << "Date(Date& d)" << endl;
	//d1是d的別名,隱含的this就是d2,相當于把d1的值拷貝給d2
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
void func(Date* d)
{
	d->Print();
}
int main()
{
	Date d1(2024, 4, 12);
	func(&d1);
	return 0;
}

方式2:傳引用(一般都是傳引用)。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)  
{
    //用來檢測是否調用該拷貝構造函數
	cout << "Date(Date& d)" << endl;
	//d1是d的別名,隱含的this就是d2,相當于把d1的值拷貝給d2
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
void func(Date& d)//d是d1的別名
{
	d.Print();
}
int main()
{
	Date d1(2024, 4, 12);
	//調用func之前會先進入拷貝構造函數
	func(d1);
	return 0;
}

那有些人會想,拷貝構造函數能不能用指針呢?這能不能避免無窮遞歸?

答案:可以的??梢酝瓿煽截悾谴藭r這個函數就不是拷貝構造函數了,而是一個普通的構造函數。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//用指針就不會形成無窮遞歸,但此時它就是一個普通構造了,不是拷貝構造。
	//感覺怪怪的,所以一般用引用
	Date(Date* d)
	{
	    _year = d->_year;
		_month = d->_month;
		_day = d->_day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
int main()
{
	Date d2(2024, 4, 21);
	Date d3(&d2);
	d3.Print();
	return 0;
}

說明:

雖然傳地址可以避免無窮遞歸,并且可以完成拷貝,但是這樣怪怪的。在C++中,一般是傳引用。并且傳引用可以減少拷貝,提高了效率。

在拷貝函數中還有一點就是:在傳引用時一般要加 const 修飾。

在顯式寫拷貝構造函數時,參數寫反了……(這就有點尷尬了)

運行結果是:拷貝一堆隨機值。

加上 const 之后:

2.3.若未顯式定義,編譯器會生成默認的拷貝構造函數。默認的拷貝構造函數對象按內存存儲按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。

注意:

在編譯器生成的默認拷貝構造函數中,內置類型是按照字節(jié)方式直接拷貝的,而自定義類型是調用其拷貝構造函數完成拷貝的。
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::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 基本類型(內置類型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定義類型
	Time _t;
};
int main()
{
	Date d1(2024, 4, 23);
	// 用已經存在的d1拷貝構造d2,此處會調用Date類的拷貝構造函數
	// 但Date類并沒有顯式定義拷貝構造函數,則編譯器會給Date類生成一個默認的拷貝構造函數
		Date d2(d1);
		d2.Print();
	return 0;
}

2.4.編譯器生成的默認拷貝構造函數已經可以完成字節(jié)序的值拷貝了,還需要自己顯式實現嗎?當然像日期類這樣的類是沒必要的。那么下面的類呢?驗證一下試試?

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申請空間失敗!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	//注意:如果沒有顯示寫析構函數,編譯器也會自動生成。
	//自動生成的析構對內置類型不做處理,自定義類型才會去調用它的析構
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(1);
	st1.Push(1);
	Stack st2 = st1;
	return 0;
}

運行結果:完成了拷貝,但程序崩潰!

![](https://img-blog.csdnimg.cn/direct/445beda92a034ed38ddb19f0c614a25c.png

原因:

當棧調用默認生成的拷貝構造函數時,這種函數進行的是淺拷貝(值拷貝) 本質是按字節(jié)進行拷貝的。這可能會導致當兩個對象指向同一塊空間時,如_array,當st1 和st2生命周期結束時,兩個對象會分別析構,就相當于釋放了兩次。 常見的數據結構,棧,隊列,鏈表,樹等都有這個問題。

![](https://img-blog.csdnimg.cn/direct/d9db431da4294841ad110e97d10588ec.png

解決方案:
深拷貝:當有指針指向資源時,會開辟建立一塊和要拷貝的一模一樣的空間,形狀,再進行拷貝。

//實現棧的深拷貝
//Stack st2 = st1;  //st是st1的別名
Stack(const Stack& st)
{
	_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
	if (NULL == _array)
	{
		perror("malloc申請空間失敗!!!");
		return;
	}
	//開辟好空間后再把值拷貝進去
	memcpy(_array, st._array, sizeof(DataType) * st._size);
	_size = st._size;
	_capacity = st._capacity;
}

把上面棧的深拷貝的代碼插入 2.4 的類中,調試結果是:

3. 實踐總結

1.如果沒有管理資源,一般情況下不需要寫拷貝構造,用編譯器默認生成的拷貝構造就可以。如 Date類;

2.如果都是自定義類型成員,內置類型成員沒有指向資源,用編譯器默認生成的拷貝構造就可以。如 MyQueue ;
(小技巧:一般情況下,不需要寫析構的,就不需要寫拷貝構造。)

3.如果內部有指針或者有一些值指向資源,需要顯式寫析構函數釋放,通常就需要顯式寫拷貝構造完成深拷貝。如各種數據類型棧,隊列,鏈表,樹等。

二,賦值運算符重載

2.1 運算符重載

C++為了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數名的函數,也具有其返回值類型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。

函數名字為:關鍵字operator后面接需要重載的運算符符號。

函數原型:返回值類型 operator操作符(參數列表)

注意:

不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@ ;
重載操作符必須有一個類類型參數 ;
用于內置類型的運算符,其含義不能改變,例如:內置的整型+,不能改變其含義 (這條僅供參考);
作為類成員函數重載時,其形參看起來比操作數數目少1,因為成員函數的第一個參數為隱藏的this;
. * (調用成員函數的指針) , :: (域作用限定符) , sizeof (計算變量所占內存的大小) ,?: (三目運算符), . (結構體變量引用符),注意以上5個運算符不能重載。這個經常在筆試選擇題中出現。

簡單介紹一下 . * 運算符的用法:

class OB
{
public:
	void func()
	{
		cout << "void func()" << endl;
	}
};
//重新定義成員函數指針類型
//注意:typedef void(*)()  PtrFunc; 是錯誤寫法
typedef  void (OB::* PtrFunc)();
int main()
{
	//成員函數要加&才能取到函數指針
	PtrFunc fp = &OB::func; //定義成員函數指針fp 指向函數func
	OB tmp;//定義OB類對象tmp
	(tmp.*fp)(); //調用成員函數的指針
	return 0;
}

運算符重載的使用:

class Date
{
public:
	Date(int year , int month , int day )
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//d3.operator== (d4)
	//d3傳給了隱含的this指針,d是d4的別名
	 bool operator== (const Date & d)
	 {
		 return _year == d._year
			 && _month == d._month
			 && _day == d._day;
	 }
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
//但是如果全局重載(沒有private時)和成員函數同時存在,編譯不會報錯,
//調用時會默認調成員函數,相當于全局重載沒有任何意義了。
int main()
{
	Date d3(2024, 4, 12);
	Date d4(2024, 4, 15);
	//顯式調用(一般不這樣寫)
	//cout << d3.operator== (d4) << endl;
	//轉換調用 等價于d3.operator== (d4) 匯編代碼
	//注意:如果是兩個操作數,從左到右的參數是對應的,不能顛倒位置
	cout << (d3 == d4) << endl;
	return 0;
}

2.2 賦值運算符重載

2.2.1 賦值運算符重載格式

  • 參數類型:const 類名 &,傳遞引用可以減少拷貝,提高傳參效率;
  • 返回值類型:類名 &,返回引用可以減少拷貝,提高返回的效率,有返回值目的是為了支持連續(xù)賦值;
  • 檢測是否自己給自己賦值;
  • 返回*this :要復合連續(xù)賦值的含義.
class Date
{
public:
	Date(int year = 2024, int month = 4, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷貝構造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//d1 = d3
	//void operator= (const Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
	//有時候會帶返回值:目的是為了連續(xù)拷貝
	//d1 = d2 = d3; 
	//比如這里,d是d3的別名,從右往左,先d2 = d3,返回值是d2
	Date& operator= (const Date& d)
	{
	    //檢測是否自己給自己賦值
	     if(this != &d)
	  {
			_year = d._year;
			_month = d._month;
			_day = d._day;
	  }
      //這里的d2的地址是this ,*this就是d2。
      //這里的*this才是對象本身,對象在main的作用域
      //里創(chuàng)建的,因此出main作用域才會析構銷毀,
      //出了當前函數不會析構。所以可以用引用返回。
		return *this;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;  // 年
	int _month; // 月
	int _day;   // 日
};
int main()
{
	Date d1(2024, 4, 12);
	//拷貝構造
	//一個已經存在的對象,拷貝給另一個要創(chuàng)建初始化的對象
	Date d2 = d1;
	Date d3(2024, 5, 1);
	//賦值拷貝/賦值重載
	//一個已經存在的對象,拷貝賦值給另一個已經存在的對象
	//d1 = d3;
	//連續(xù)賦值
	d1 = d2 = d3;
	return 0;
}

傳值返回和傳引用返回的區(qū)別:

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 temp(d);
	 return temp;
  }
int main()
  {
	 Date d1(2022,1,13);
	 Test(d1);
	 return 0;
  }

總結一下:

如果返回對象是一個局部對象或者臨時對象,出了當前函數作用域就析構銷毀了,就不能用引用返回。用引用返回是存在風險的,因為引用對象在當前函數棧幀已經被銷毀了。所以雖然引用返回可以減少一次拷貝,但是出了函數作用域,返回對象還在,才能用引用返回。

2.2.2 賦值運算符只能重載成類的成員函數不能重載成全局函數

注意:

重載成全局函數時就沒有this指針了。

2.2.3 和拷貝構造類似,用戶沒有顯式實現時,編譯器會生成一個默認賦值運算符重載,以值的方式逐字節(jié)拷貝。

注意:

內置類型成員變量是直接賦值的,而自定義類型成員變量需要調用對應類的賦值運算符重載完成賦值。

到此這篇關于C++拷貝構造函數和賦值運算符重載詳解的文章就介紹到這了,更多相關C++拷貝構造函數和賦值運算符內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • c語言實現足球比賽積分統(tǒng)計系統(tǒng)

    c語言實現足球比賽積分統(tǒng)計系統(tǒng)

    這篇文章主要為大家詳細介紹了c語言實現足球比賽積分統(tǒng)計系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C++實現雙向循環(huán)鏈表

    C++實現雙向循環(huán)鏈表

    這篇文章主要為大家詳細介紹了C++實現雙向循環(huán)鏈表,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C實現與 uint64_t 相同功能的類

    C實現與 uint64_t 相同功能的類

    本文給大家分享的是筆者實現的仿uint64_t的類,可以用在不支持uint64_t的平臺上,雖然現在功能還不完善,但是還是分享給大家,也算是給大家一個思路吧。
    2015-12-12
  • C語言中數據的存儲詳解

    C語言中數據的存儲詳解

    這篇文章主要為大家介紹了C語言中數據的存儲,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助,希望能夠給你帶來幫助
    2021-11-11
  • 基于C語言編寫一個簡單的抽卡小游戲

    基于C語言編寫一個簡單的抽卡小游戲

    這篇文章主要為大家介紹了如何利用C語言實現原神抽卡的小游戲,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-04-04
  • C/C++多參數函數參數的計算順序與壓棧順序的示例代碼

    C/C++多參數函數參數的計算順序與壓棧順序的示例代碼

    這篇文章主要介紹了C/C++多參數函數參數的計算順序與壓棧順序,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • C語言新手初階教程之三子棋實現

    C語言新手初階教程之三子棋實現

    相信大家在小時候都用紙和筆與小伙伴們玩過一個經典的游戲之井字棋,即三子棋,下面這篇文章主要給大家介紹了關于C語言新手初階教程之三子棋實現的相關資料,需要的朋友可以參考下
    2022-01-01
  • C 語言指針變量詳細介紹

    C 語言指針變量詳細介紹

    本文主要介紹C 語言指針變量,這里詳細介紹了 C語言中指針變量的用法,并附代碼示例及指針變量指向關系圖幫助大家理解指針,有學習C語言指針的朋友可以參考下
    2016-08-08
  • C++ 打開選擇文件夾對話框選擇目錄的操作

    C++ 打開選擇文件夾對話框選擇目錄的操作

    這篇文章主要介紹了C++ 打開選擇文件夾對話框選擇目錄的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • C語言實現輸出鏈表中倒數第k個節(jié)點

    C語言實現輸出鏈表中倒數第k個節(jié)點

    這篇文章主要介紹了C語言實現輸出鏈表中倒數第k個節(jié)點,主要涉及鏈表的遍歷操作,是數據結構中鏈表的常見操作。需要的朋友可以參考下
    2014-09-09

最新評論