前言
類的6個(gè)默認(rèn)成員函數(shù):如果一個(gè)類中什么成員都沒(méi)有,簡(jiǎn)稱為空類。
空類中真的什么都沒(méi)有嗎?并不是,任何類在什么都不寫時(shí),編譯器會(huì)自動(dòng)生成以下6個(gè)默認(rèn)成員函數(shù)。
默認(rèn)成員函數(shù):用戶沒(méi)有顯式實(shí)現(xiàn),編譯器會(huì)生成的成員函數(shù)稱為默認(rèn)成員函數(shù)。
class Date {};
一、拷貝構(gòu)造函數(shù)概念
理解
在現(xiàn)實(shí)生活中,可能存在一個(gè)與你一樣的自己,我們稱其為雙胞胎。
那在創(chuàng)建對(duì)象時(shí),可否創(chuàng)建一個(gè)與已存在對(duì)象一某一樣的新對(duì)象呢?
定義
拷貝構(gòu)造函數(shù):只有單個(gè)形參,該形參是對(duì)本類類型對(duì)象的引用(一般常用const
修飾),在用已存在的類類型對(duì)象創(chuàng)建新對(duì)象時(shí)由編譯器自動(dòng)調(diào)用。
C++拷貝構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),用于創(chuàng)建對(duì)象時(shí),使用一個(gè)已有對(duì)象的內(nèi)容來(lái)初始化新的對(duì)象。它接受一個(gè)同類對(duì)象作為參數(shù),并按照該對(duì)象的數(shù)據(jù)成員的值來(lái)創(chuàng)建新的對(duì)象。
拷貝構(gòu)造函數(shù)通常用于以下情況:
- 在創(chuàng)建對(duì)象時(shí),使用同類已有對(duì)象的值來(lái)初始化新對(duì)象。
- 以值傳遞方式將對(duì)象傳遞給函數(shù)。
- 以值返回方式從函數(shù)返回對(duì)象。
拷貝構(gòu)造函數(shù)的定義形式為:
類名(const 類名&obj)
{
// 構(gòu)造函數(shù)的實(shí)現(xiàn)
}
其中,類名是要?jiǎng)?chuàng)建的對(duì)象的類名,obj
是要拷貝的對(duì)象。
拷貝構(gòu)造函數(shù)的工作原理是將obj
的數(shù)據(jù)成員的值復(fù)制給新創(chuàng)建的對(duì)象。這意味著新對(duì)象的數(shù)據(jù)成員會(huì)與原對(duì)象具有相同的值,但是它們是獨(dú)立的,改變其中一個(gè)對(duì)象的數(shù)據(jù)成員的值不會(huì)影響另一個(gè)對(duì)象的數(shù)據(jù)成員。
如果沒(méi)有顯式定義拷貝構(gòu)造函數(shù),編譯器會(huì)提供一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)。默認(rèn)的拷貝構(gòu)造函數(shù)執(zhí)行的是淺拷貝,即簡(jiǎn)單地將原對(duì)象的值復(fù)制給新對(duì)象的數(shù)據(jù)成員。如果類中包含指針類型的數(shù)據(jù)成員,需要自己定義拷貝構(gòu)造函數(shù),進(jìn)行深拷貝,確保指針指向的對(duì)象也被復(fù)制。
注意,拷貝構(gòu)造函數(shù)是類成員函數(shù),通常定義在類的公有部分。拷貝構(gòu)造函數(shù)是通過(guò)對(duì)象名來(lái)調(diào)用的,而不是通過(guò)函數(shù)名來(lái)調(diào)用。
二、拷貝構(gòu)造函數(shù)的特征
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征如下:
- 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類類型對(duì)象的引用,使用傳值方式編譯器直接報(bào)錯(cuò),因?yàn)闀?huì)引發(fā)無(wú)窮遞歸調(diào)用。
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) // 錯(cuò)誤寫法:編譯報(bào)錯(cuò),會(huì)引發(fā)無(wú)窮遞歸
{
_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;
}
3. 若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
和構(gòu)造函數(shù)不一樣,構(gòu)造函數(shù)內(nèi)置類型不會(huì)初始化,拷貝構(gòu)造函數(shù)會(huì)初始化
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
{
private:
// 基本類型(內(nèi)置類型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定義類型
Time _t;
};
int main()
{
Date d1;
// 用已經(jīng)存在的d1拷貝構(gòu)造d2,此處會(huì)調(diào)用Date類的拷貝構(gòu)造函數(shù)
// 但Date類并沒(méi)有顯式定義拷貝構(gòu)造函數(shù),則編譯器會(huì)給Date類生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)
Date d2(d1);
return 0;
}
注意:在編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)中,內(nèi)置類型是按照字節(jié)方式直接拷貝的,而自定義類型是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝的。
- 編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己顯式實(shí)現(xiàn)嗎?
當(dāng)然像日期類這樣的類是沒(méi)必要的。那么下面的類呢?驗(yàn)證一下試試?
// 這里會(huì)發(fā)現(xiàn)下面的程序會(huì)崩潰掉?這里就需要深拷貝去解決。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
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;
}
注意:類中如果沒(méi)有涉及資源申請(qǐng)時(shí),拷貝構(gòu)造函數(shù)是否寫都可以;一旦涉及到資源申請(qǐng)時(shí),則拷貝構(gòu)造函數(shù)是一定要寫的,否則就是淺拷貝。
- 拷貝構(gòu)造函數(shù)典型調(diào)用場(chǎng)景:
- 使用已存在對(duì)象創(chuàng)建新對(duì)象
- 函數(shù)參數(shù)類型為類類型對(duì)象
- 函數(shù)返回值類型為類類型對(duì)象
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;
}
為了提高程序效率,一般對(duì)象傳參時(shí),盡量使用引用類型,返回時(shí)根據(jù)實(shí)際場(chǎng)景,能用引用盡量使用引用。
三、注意要點(diǎn)
寫法
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) // 錯(cuò)誤寫法:編譯報(bào)錯(cuò),會(huì)引發(fā)無(wú)窮遞歸
{
_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;
}
除了下面這種寫法外
Data d2(d1);
我們還可以寫成,這種寫法也是拷貝構(gòu)造
Data d2 = d1;
實(shí)踐
- 如果沒(méi)有管理資源,一般情況下不需要寫拷貝構(gòu)造函數(shù),默認(rèn)生成的拷貝構(gòu)造函數(shù)就可以。如
Date
(日期類) - 如果都是自定義類型成員,內(nèi)置類型成員沒(méi)有指向資源,也類似默認(rèn)生成的拷貝構(gòu)造函數(shù)就可以。如
MyQueue
- 一般情況下,不需要顯示寫析構(gòu)函數(shù),就不需要寫拷貝構(gòu)造函數(shù)
- 如果內(nèi)部有指針或者一些值指向資源,需要顯示寫析構(gòu)釋放,通常就需要顯示寫構(gòu)造函數(shù)完成深拷貝。如:
Stack
Queue
List
等
傳址返回與引用返回的區(qū)別
關(guān)于下面代碼的展示,VS2022編譯器可能顯示不出來(lái),因?yàn)榫幾g器等級(jí)比較高,像下面的情況,編譯器會(huì)自行優(yōu)化,使代碼運(yùn)行效率更高,致使本來(lái)的結(jié)果顯示不出來(lái)。
傳址返回
傳址返回會(huì)生成Date d
的臨時(shí)拷貝進(jìn)行返回
引用返回
引用返回會(huì)直接返回Date d
的地址,而不會(huì)進(jìn)入臨時(shí)拷貝
~Date()
{
cout << "~Date()" << endl;
_year = -1;
_month = -1;
_day = -1;
}
出了函數(shù)作用域之后會(huì)調(diào)用析構(gòu)函數(shù),致使結(jié)果為-1,此時(shí)的ref
類似野指針
依次類推,我們?cè)俣x一個(gè)fx()
函數(shù),在fx()
函數(shù)的空間里存放一些變量,ret空間里的內(nèi)容會(huì)被fx()
函數(shù)里的內(nèi)容給覆蓋
當(dāng)出了作用域,返回對(duì)象還在沒(méi)有析構(gòu),那就可以用引用返回,減少拷貝,比如用static
修飾
傳值返回和傳址返回的對(duì)比
Date operator=
運(yùn)用的下篇文章賦值運(yùn)算符重載,可以看到傳值和傳址在遇到不同問(wèn)題時(shí)有不同的表現(xiàn),如下,在運(yùn)算符重載的問(wèn)題下,傳址調(diào)用比傳值調(diào)用的效率更高
總結(jié)
返回對(duì)象是一個(gè)局部對(duì)象或臨時(shí)對(duì)象,出了當(dāng)前func
函數(shù)作用域,就析構(gòu)銷毀了,那么不能用引用返回,用引用返回時(shí)存在風(fēng)險(xiǎn)的,因?yàn)橐脤?duì)象在func
函數(shù)棧幀已經(jīng)銷毀了
雖然引用返回可以減少一次拷貝,但是出了函數(shù)作用域,返回對(duì)象還在,才能用引用返回文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-858890.html
即下述情況文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-858890.html
- 返回對(duì)象生命周期到了,會(huì)析構(gòu),傳值返回
- 返回對(duì)象生命周期沒(méi)到,不會(huì)析構(gòu),傳引用返回
測(cè)試
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
cout<< "Date(int,int,int):" << this << endl;
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)"<<this << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
cout << "~Date()" <<this<< endl;
}
private:
int _year;
int _month;
int _day;
};
Date func(Date d)
{
Date temp(d);
return temp;
}
//Date& fun()
//{
// Date d(2024,4,14);
// return d;
//}
int main()
{
Date d;
func(d);
//const Date& ret = func();
return 0;
}
//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;
//}
到了這里,關(guān)于C++從入門到精通——類的6個(gè)默認(rèn)成員函數(shù)之拷貝構(gòu)造函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!