前言
????????本文將介紹類的6個默認成員函數(shù)中的構(gòu)造函數(shù)、析構(gòu)函數(shù)和拷貝構(gòu)造函數(shù),賦值重載和取地址重載涉及運算符重載的知識,將在下篇講解。所謂默認成員函數(shù),也就是每個類都有的成員函數(shù),我們可以顯式定義這些函數(shù),否則,編譯器會自動生成它們。
目錄
前言
1 構(gòu)造函數(shù)
概念
特性
1 函數(shù)名與類名相同
2 無返回類型
3 可以重載
4 實例化對象時自動調(diào)用
5 默認構(gòu)造函數(shù)
6 合成的默認構(gòu)造函數(shù)
2 析構(gòu)函數(shù)
概念
特性
1 名字為~加類名
2 無參數(shù)和返回類型
3 對象生命周期結(jié)束時自動調(diào)用
4 默認生成的析構(gòu)函數(shù)
3 拷貝構(gòu)造函數(shù)
概念
特性
1 構(gòu)造函數(shù)的一個重載
2 參數(shù)只有一個且為引用類型
3 默認生成的拷貝構(gòu)造函數(shù)
4 拷貝構(gòu)造調(diào)用場景
首先定義一個日期類(Date)和一個棧類(Stack),之后將基于它們演示實例,如下:
// 日期類
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
// 棧類
typedef int DataType;
class Stack
{
public:
void Init(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++;
}
DataType Top()
{
if(_size)
return _array[_size];
}
// 其他方法...
void Destory()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
1 構(gòu)造函數(shù)
概念
??????? 構(gòu)造函數(shù)是一個或幾個(函數(shù)重載)特殊的函數(shù),它的名字與類名相同,用來初始化類的成員變量。只要類的對象被創(chuàng)建,就會執(zhí)行(編譯器自動調(diào)用)構(gòu)造函數(shù)來初始化它,在對象的生命周期內(nèi),構(gòu)造函數(shù)只會被執(zhí)行一次。
注意:構(gòu)造函數(shù)的名稱雖然叫構(gòu)造,但它的任務(wù)并不是創(chuàng)建對象,而是初始化對象。
特性
1 函數(shù)名與類名相同
??????? 日期類已有一個成員函數(shù)Init,它的功能就是初始化成員變量。但是每次創(chuàng)建對象后需要顯式調(diào)用Init函數(shù)來初始化,這樣太過麻煩。另外,一個對象可以多次調(diào)用Init函數(shù)為成員變量重新賦值,這并不符合初始化的含義?,F(xiàn)在,我們將Init函數(shù)改寫成構(gòu)造函數(shù):
class Date
{
public:
/*void Init(int year, int month, int day)*/
// 構(gòu)造函數(shù)
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
棧類的Init函數(shù)改成構(gòu)造函數(shù):
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申請空間失敗!!!");
return;
}
_capacity = capacity;
_size = 0;
}
2 無返回類型
??????? 我們顯式定義了日期類的構(gòu)造函數(shù),沒有寫返回類型,因為構(gòu)造函數(shù)是特殊的成員函數(shù),它沒有返回值,也不需要像其他無返回值函數(shù)那樣定義成void類型。
注:C語言中沒有顯示定義類型的函數(shù)默認是void類型,C++中則是int類型。
3 可以重載
??????? 構(gòu)造函數(shù)和其它函數(shù)一樣可以重載,不同的構(gòu)造函數(shù)的參數(shù)類型或數(shù)量有所區(qū)別。例如,我們可以再定義一個無參的構(gòu)造函數(shù),將對象初始化成1970.1.1:
// 無參構(gòu)造函數(shù)
Date()
{
_year = 1970;
_month = 1;
_day = 1;
}
對于日期類來說,沒有必要定義兩個不同的構(gòu)造函數(shù),它們可以通過設(shè)置缺省參數(shù)進行合并,如下:
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
4 實例化對象時自動調(diào)用
??????? 關(guān)于自動調(diào)用,有兩方面理解:一是實例化一個對象時,編譯器就會自動調(diào)用它的構(gòu)造函數(shù),不需要用戶顯式調(diào)用;二是編譯器會自動調(diào)用合適的構(gòu)造函數(shù),對于有多個重載的構(gòu)造函數(shù),會根據(jù)實參數(shù)量和類型調(diào)用最匹配的一個。例如:
class Date
{
public:
Date()
{
_year = 1970;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 調(diào)用無參構(gòu)造函數(shù)
Date d2(2023, 1, 1);// 調(diào)用帶參構(gòu)造函數(shù)
d1.Print();
d2.Print();
return 0;
}
執(zhí)行結(jié)果:
注意:通過無參構(gòu)造函數(shù)創(chuàng)建對象時,對象名后面不應(yīng)該帶括號,否則成了函數(shù)聲明:
Date d1(); // 聲明函數(shù)d1返回類型是Date
5 默認構(gòu)造函數(shù)
??????? 無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都稱為默認構(gòu)造函數(shù),默認構(gòu)造函數(shù)只能有一個,因為不帶參數(shù)實例化對象,會造成調(diào)用無參構(gòu)造函數(shù)和調(diào)用全缺省構(gòu)造函數(shù)的歧義。另外,下面講的合成的默認構(gòu)造函數(shù)也可以稱為默認構(gòu)造函數(shù),因為只有當類中沒有定義構(gòu)造函數(shù)時,編譯器才會隱式定義構(gòu)造函數(shù),也就是說它和其它構(gòu)造函數(shù)不可能同時存在。
6 合成的默認構(gòu)造函數(shù)
??????? 如果類中沒有顯式定義構(gòu)造函數(shù),那么編譯器會隱式定義一個無參的默認構(gòu)造函數(shù),它會默認初始化成員變量。
? ? ? ? 對于內(nèi)置類型(C++提供的數(shù)據(jù)類型:int,char等)的成員變量,將使用編譯器提供的默認值初始化,例如:
class Date
{
public:
// 編譯器隱式定義的無參構(gòu)造函數(shù)
/*Date()
{
}*/
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int a;
int main()
{
Date d;
d.Print();
cout << a;
return 0;
}
執(zhí)行結(jié)果:
可以看到,對于類中的int成員變量,編譯器合成的默認構(gòu)造函數(shù)將它們初始化成了隨機值,這種方式就如同全局變量a被默認初始化成0一樣。當然,不同編譯器的初始化值可能不同。
? ? ? ? 對于自定義類型成員變量(使用struct/class/union等自定義的類型),編譯器合成的默認構(gòu)造函數(shù)會調(diào)用該類型的默認構(gòu)造函數(shù),例如:
class A
{
public:
A()
{
_a = 1;
}
int _a;
};
class B
{
public:
A _aa;
};
B b1; // 調(diào)用合成的默認構(gòu)造函數(shù)
B類的成員變量_aa是一個A類的對象,實例化一個B類的對象b1,此時編譯器會調(diào)用A類的默認構(gòu)造函數(shù)來初始化b1的成員變量_aa,結(jié)果就是b1._aa._a的值為1。
如果一個類中的自定義類型成員變量沒有默認構(gòu)造函數(shù),那么編譯器將無法初始化該成員,大部分類需要我們自己定義默認構(gòu)造函數(shù)。
? ? ? ? 內(nèi)置類型成員變量使用默認的初始化值,自定義類型成員變量也只有編譯器合成的默認構(gòu)造函數(shù)時,該變量的成員同樣使用默認的初始化值,類合成的默認構(gòu)造函數(shù)似乎并沒有工作?如何很好地解決此問題:
C++11為合成的默認構(gòu)造函數(shù)新增了初始化規(guī)則,即:內(nèi)置類型成員變量在類中聲明時可以給默認值,合成的默認構(gòu)造函數(shù)會使用這些默認值初始化對象的成員變量。例如:
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
// 聲明時給默認值
int _year = 1970;
int _month = 1;
int _day = 1;
};
int main()
{
Date d;
d.Print();
return 0;
}
執(zhí)行結(jié)果:
? ? ? ? 以上講解的都是構(gòu)造函數(shù)通過函數(shù)體對成員變量進行初始化,本質(zhì)上是對它們賦初值。構(gòu)造函數(shù)還有一種初始值列表的用法,實現(xiàn)了真正意義上的初始化工作,該語法將在后續(xù)博客講解。
2 析構(gòu)函數(shù)
概念
???????? 析構(gòu)函數(shù)與構(gòu)造函數(shù)的功能相反,析構(gòu)函數(shù)釋放對象申請的空間,并銷毀對象的成員變量。對于日期類,它的對象在生命周期結(jié)束時,對象的成員變量也就跟著銷毀了,它的析構(gòu)函數(shù)沒有什么作用;對于棧這樣的類,當它的對象生命周期結(jié)束時,其動態(tài)開辟的內(nèi)存空間仍然存在,這時析構(gòu)函數(shù)就非常重要。
特性
1 名字為~加類名
?????? 析構(gòu)函數(shù)的名字由字符~接類名構(gòu)成。在前面的棧類中,已經(jīng)定義了Destory函數(shù)實現(xiàn)清理資源的功能,與Init函數(shù)類似,我們需要顯式調(diào)用它,這個工作通常容易忘記,從而造成資源浪費。現(xiàn)在將Destory函數(shù)改寫成析構(gòu)函數(shù):
typedef int DataType;
class Stack
{
public:
// 構(gòu)造函數(shù)
Stack(size_t capacity = 3)
{
cout << "Stack()" << endl;
_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++;
}
DataType Top()
{
if(_size)
return _array[_size];
}
// 其他方法...
/*void Destory()*/
// 析構(gòu)函數(shù)
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
2 無參數(shù)和返回類型
??????? 析構(gòu)函數(shù)和構(gòu)造函數(shù)一樣沒有返回類型,同時析構(gòu)函數(shù)沒有參數(shù),這意味著析構(gòu)函數(shù)不能重載,一個類只能有一個析構(gòu)函數(shù)。
3 對象生命周期結(jié)束時自動調(diào)用
??????? 無論何時一個對象被銷毀,就會自動調(diào)用它的析構(gòu)函數(shù)。棧類的構(gòu)造函數(shù)和析構(gòu)函數(shù)都增加了一行輸出語句,目的是驗證它們是否被調(diào)用。例如:
int main()
{
Stack st;
st.Push(1);
int top = st.Top();
cout << top << endl;
return 0;
}
執(zhí)行結(jié)果:
4 默認生成的析構(gòu)函數(shù)
??????? 和構(gòu)造函數(shù)一樣,如果類中沒有顯式定義的析構(gòu)函數(shù),那么編譯器會隱式定義一個析構(gòu)函數(shù)。類對象銷毀時,它的成員變量也跟著銷毀,對于內(nèi)置類型的成員變量,不需要額外的處理;對于自定義類型的成員變量,默認生成的析構(gòu)函數(shù)則會調(diào)用該類型的析構(gòu)函數(shù),以清理它申請的資源。例如:
class A
{
public:
A()
{
cout << "A()" << endl;
_a = (int*)malloc(sizeof(int) * 10);
memset(_a, 0, 10);
}
~A()
{
cout << "~A()" << endl;
if (_a)
{
free(_a);
_a = nullptr;
}
}
private:
int* _a;
};
class B
{
public:
// 默認生成的無參構(gòu)造函數(shù)
/*B()
{
}*/
// 默認生成的析構(gòu)函數(shù)
/*~B()
{
}*/
private:
A _aa;
};
int main()
{
B b;
return 0;
}
執(zhí)行結(jié)果:
可以看到,B類有一個A類型的成員_aa,實例化一個B類的對象b時,編譯器生成的構(gòu)造函數(shù)調(diào)用了A類的默認構(gòu)造函數(shù)為_aa初始化,當main函數(shù)棧幀結(jié)束,b被銷毀,編譯器默認生成的析構(gòu)函數(shù)調(diào)用了A類的析構(gòu)函數(shù),清理_aa的資源。
3 拷貝構(gòu)造函數(shù)
概念
??????? 拷貝和賦值是一個意思,當以拷貝的方式初始化一個對象時,就會調(diào)用拷貝構(gòu)造函數(shù)。拷貝構(gòu)造函數(shù)的參數(shù)是自身類類型的引用,一般用const修飾。
特性
1 構(gòu)造函數(shù)的一個重載
??????? 拷貝構(gòu)造函數(shù)也是一個構(gòu)造函數(shù),除了參數(shù)的特殊性,其余形式與構(gòu)造函數(shù)相同。接下來定義日期類的拷貝構(gòu)造函數(shù):
// 日期類的拷貝構(gòu)造函數(shù)
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
調(diào)用拷貝構(gòu)造初始化對象:
int main()
{
Date d1(2023, 9, 1);
Date d2(d1); // 調(diào)用拷貝構(gòu)造
Date d3 = d2;
d1.Print();
d2.Print();
d3.Print();
return 0;
}
執(zhí)行結(jié)果:
實例化對象d2時,參數(shù)并不是3個具體的int類型,而是已經(jīng)存在的Date類的對象d1,那么就會調(diào)用拷貝構(gòu)造函數(shù),函數(shù)體內(nèi)的功能就是用d1的成員變量賦值給d2的成員變量,從而初始化d2。
注意:Date d3 = d2;也會調(diào)用拷貝構(gòu)造函數(shù),因為其本質(zhì)也是用已存在的同類型的對象初始化新的對象。
2 參數(shù)只有一個且為引用類型
??????? 我們知道類的成員函數(shù)都有一個隱含的this指針形參,拷貝構(gòu)造只有一個參數(shù)指的是它只有一個顯式的參數(shù),并且該參數(shù)必須是該類的同類型的引用。
????????對于自定義類型的拷貝,也就是將一個對象的所有成員變量拷貝給另一個對象,那么拷貝構(gòu)造函數(shù)的參數(shù)可以是類類型而不是引用嗎?先將對象拷貝給函數(shù)參數(shù),再用形參這個局部變量拷貝給this對象是否可行:
答案是不可行的,因為傳參會調(diào)用拷貝構(gòu)造,調(diào)用拷貝構(gòu)造又要傳參...結(jié)果就會無窮遞歸,如下圖所示:
3 默認生成的拷貝構(gòu)造函數(shù)
??????? 拷貝構(gòu)造也是默認成員函數(shù),如果類中沒有顯式定義,則編譯器會默認定義一個拷貝構(gòu)造函數(shù)。雖然拷貝構(gòu)造函數(shù)也是構(gòu)造函數(shù),但是只要沒有顯式定義拷貝構(gòu)造函數(shù),即使已經(jīng)定義了構(gòu)造函數(shù),也會生成默認拷貝構(gòu)造函數(shù)??梢哉J為拷貝構(gòu)造函數(shù)是特殊的構(gòu)造函數(shù),它們之間互不影響。
??????? 默認的拷貝構(gòu)造函數(shù)的作用是按內(nèi)存儲存將一個對象拷貝給新的對象,稱之為淺拷貝,對于日期類,我們可以不定義拷貝構(gòu)造函數(shù),默認的拷貝構(gòu)造函數(shù)執(zhí)行的功能是相同的。像棧這樣的類我們就需要自己定義拷貝構(gòu)造函數(shù)。
????????首先分析棧類使用默認的拷貝構(gòu)造函數(shù)的缺陷:
typedef int DataType;
class Stack
{
public:
// 默認生成的拷貝構(gòu)造函數(shù)功能如下:
/*Stack(Stack& st)
{
_array = st._array;
_capacity = st._capacity;
_size = st._size;
}*/
Stack(size_t capacity = 3)
{
cout << "Stack()" << endl;
_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++;
}
DataType Top()
{
if (_size)
return _array[_size - 1];
}
// 其他方法...
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
先定義一個對象st1,再用st1拷貝構(gòu)造對象st2:
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
執(zhí)行結(jié)果:
可以看到析構(gòu)函數(shù)被調(diào)用了兩次,然后程序就崩潰了,原因是st2是st1通過淺拷貝構(gòu)造的,它們的成員_array指向同一塊內(nèi)存空間,其中一個對象調(diào)用析構(gòu)函數(shù)free了_array它的空間歸還給了系統(tǒng),另一個對象的_array就成了野指針,再去調(diào)用析構(gòu)函數(shù)時free野指針引發(fā)程序崩潰。
所以對于棧類,我們必須自己定義拷貝構(gòu)造函數(shù),為對象的_array申請一塊與為其拷貝的對象的_array大小相同的內(nèi)存空間,這樣稱之為深拷貝。
定義棧類的拷貝構(gòu)造函數(shù):
// 拷貝構(gòu)造函數(shù)
Stack(Stack& st)
{
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (NULL == _array)
{
perror("malloc申請空間失敗!!!");
return;
}
_capacity = st._capacity;
_size = st._size;
}
總結(jié):如果類的成員沒有涉及資源申請,拷貝構(gòu)造函數(shù)是否顯式定義都可以;否則,需要我們自己定義拷貝構(gòu)造函數(shù),避免淺拷貝。
4 拷貝構(gòu)造調(diào)用場景
1 使用已存在對象創(chuàng)建新對象時需要調(diào)用拷貝構(gòu)造函數(shù)。
2 函數(shù)參數(shù)為類類型時,需要調(diào)用拷貝構(gòu)造函數(shù)創(chuàng)建臨時對象。
3 函數(shù)返回類型為類類型時,調(diào)用拷貝構(gòu)造為接收的對象賦值。
為了提高程序效率,減少拷貝構(gòu)造函數(shù)的調(diào)用,函數(shù)參數(shù)應(yīng)盡量使用引用類型,函數(shù)返回值根據(jù)實際情況,也盡量選擇引用類型。
如果本文內(nèi)容對你有幫助,可以點贊收藏,感謝支持,期待你的關(guān)注。文章來源:http://www.zghlxwxcb.cn/news/detail-706626.html
下篇預(yù)告:C++ 類和對象(三)運算符重載、const成員函數(shù)、實現(xiàn)日期類文章來源地址http://www.zghlxwxcb.cn/news/detail-706626.html
到了這里,關(guān)于C++ 類和對象(二)構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!