C++中的构造函数
文章目錄
- 1 構造函數的基本概念
- 1.1 構造函數的作用
- 1.2 構造函數的特點
- 1.3 構造函數的種類
- 2 默認構造函數
- 2.1 合成的默認構造函數
- 2.2 手動定義的默認構造函數
- 3 自定義的重載構造函數
- 3.1 自定義的重載構造函數
- 3.2 創建對象的幾種方式
- 3.3 手工調用構造函數
- 4 拷貝構造函數
- 4.1 合成的拷貝構造函數
- 4.2 手動定義的拷貝構造函數
- 4.3 拷貝構造函數的調用時機
- 4.4 繼承關系中的拷貝構造函數
- 5 賦值構造函數
- 5.1 合成的賦值構造函數
- 5.2 自定義的賦值構造函數
- 6 在已經申請的空間上調用構造函數
1 構造函數的基本概念
1.1 構造函數的作用
構造函數的作用:
- 在創建一個新的對象時,自動調用的函數,用來進行“初始化”工作。
- 對這個對象內部的數據成員進行初始化。
1.2 構造函數的特點
構造函數的特點:
- 自動調用(在創建新對象時,自動調用)。
- 構造函數的函數名,和類名相同。
- 構造函數沒有返回類型。
- 可以有多個構造函數(即函數重載形式)。
1.3 構造函數的種類
構造函數的種類:
- 默認構造函數
- 自定義的構造函數
- 拷貝構造函數
- 賦值構造函數
2 默認構造函數
沒有參數的構造函數,稱為默認構造函數。
2.1 合成的默認構造函數
當類中沒有定義任何構造函數時,編譯器默認提供一個無參構造函數,并且其函數體為空:
- 如果數據成員使用了“類內初始值”,就使用這個值來初始化數據成員。【C++11】
- 否則,就使用默認初始化(實際上,不做任何初始化)
注意:
只要手動定義了任何一個構造函數(包括拷貝構造函數),編譯器就不會生成“合成的默認構造函數”。一般情況下,都應該定義自己的構造函數,不要使用“合成的默認構造函數”,僅當數據成員全部使用了“類內初始值”,才宜使用“合成的默認構造函數”。
2.2 手動定義的默認構造函數
手動定義的默認構造函數,常稱為“默認構造函數”。
Human::Human() {name = "無名氏";age = 18;salary = 30000; }int main(void) {Human h1; // 使用自定義的默認構造函數cout << "姓名:" << h1.getName() << endl;cout << "年齡: " << h1.getAge() << endl; cout << "薪資:" << h1.getSalary() << endl; system("pause");return 0; }說明: 如果某數據成員使用類內初始值,同時又在構造函數中進行了初始化,那么以構造函數中的初始化為準。相當于構造函數中的初始化,會覆蓋對應的類內初始值。
3 自定義的重載構造函數
3.1 自定義的重載構造函數
自定義的重載構造函數比較簡單,示例代碼如下:
Human::Human() {name = "無名氏";age = 18;salary = 30000; }Human::Human(int age, int salary) {cout << "調用自定義的構造函數" << endl; this->age = age; //this是一個特殊的指針,指向這個對象本身this->salary = salary;name = "無名"; }int main(void) {Human h1(25, 35000); // 使用自定義的默認構造函數cout << "姓名:" << h1.getName() << endl;cout << "年齡: " << h1.getAge() << endl; cout << "薪資:" << h1.getSalary() << endl; system("pause");return 0; }3.2 創建對象的幾種方式
#include <stdio.h>class Test { public:Test() { printf("Test()\n");}Test(int v) { printf("Test(int v), v = %d\n", v);} };int main() {Test t; // 第一種:調用 Test()Test t1(1); // 第二種:調用 Test(int v)Test t2 = 2; // 第三種:調用 Test(int v)Test t3 = Test(); // 第4種:調用Test()int i(100);printf("i = %d\n", i);return 0; }3.3 手工調用構造函數
一般情況下,構造函數在對象定義時被自動調用;但是在一些特殊情況下,我們需要手工調用構造函數。
創建對象數組就需要手工調用構造函數:
#include <stdio.h>class Test { private:int m_value; public:Test() { printf("Test()\n");m_value = 0;}Test(int v) { printf("Test(int v), v = %d\n", v);m_value = v;}int getValue(){return m_value;} };int main() {Test ta[3] = {Test(), Test(1), Test(2)}; for(int i=0; i<3; i++){printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());}Test t = Test(100);printf("t.getValue() = %d\n", t.getValue());return 0; }4 拷貝構造函數
4.1 合成的拷貝構造函數
當類中沒有定義拷貝構造函數時,編譯器默認提供也給拷貝構造函數,簡單的進行成員變量的復制。編譯器默認提供的拷貝構造函數也叫合成的拷貝構造函數,合成的拷貝構造函數使用“淺拷貝”。
拷貝構造函數的意義:
- 兼容C語言的初始化方式。
- 初始化行為能夠符合預期的邏輯。
拷貝構造函數分為淺拷貝和深拷貝:
- 淺拷貝:拷貝后對象的物理狀態相同。
- 深拷貝:拷貝后對象的邏輯狀態相同。
當對象中有成員指代了系統的資源,我么需要進行深拷貝:
- 成員指向了動態內存空間。
- 成員打開了外存中的文件。
- 成員使用了系統中的網絡端口。
- …
編譯器提供的拷貝構造函數只進行淺拷貝!
4.2 手動定義的拷貝構造函數
一般性原則:自定義拷貝構造函數,必然要實現深拷貝!
4.3 拷貝構造函數的調用時機
什么時候調用拷貝構造函數?
4.4 繼承關系中的拷貝構造函數
假設B繼承自A,如下代碼:
B b1; B b2(b1); // 先調用A類的構造函數,再調用B類的拷貝構造函數5 賦值構造函數
5.1 合成的賦值構造函數
如果沒有定義賦值構造函數,編譯器會自動定義“合成的賦值構造函數”,與其他合成的構造函數相同,是“淺拷貝”(又稱為“位拷貝”)。
5.2 自定義的賦值構造函數
簡要的賦值構造函數代碼如下:
Human& Human::operator=(const Human &man) {cout << "調用" << __FUNCTION__ << endl;if (this == &man) {return *this; //檢測是不是對自己賦值:比如 h1 = h1;}// 如果有必要,需要先釋放自己的資源(動態內存)//delete[] addr;//addr = new char[ADDR_LEN];// 深拷貝strcpy_s(addr, ADDR_LEN, other.addr);// 處理其他數據成員name = man.name;age = man.age;salary = man.salary;// 返回該對象本身的引用, 以便做鏈式連續處理,比如 a = b = c;return *this; }6 在已經申請的空間上調用構造函數
這真是一個騷操作,很多情況下我們需要為對象預先分配空間,但是如果對象沒有默認的構造函數就會很尷尬。我們可以通過malloc分配空間,然后在需要的時間再在這片空間上調用指定的構造函數。示例代碼如下:
#include <iostream> #include <vector> #include <cstdlib>using namespace std;class Test39 { private:int age; public:Test39(){cout << "Test39()" << endl;}~Test39(){cout << "~Test39()" << endl;} };void test39() {Test39* p = (Test39*)malloc(sizeof(Test39));new (p) Test39();p->~Test39(); }不得不說C++真的是騷,真的足夠強大,真的讓我學不會!越學不會我越喜歡,不信搞不定C++。
參考資料:
總結
- 上一篇: C#中类的属性(Property)
- 下一篇: C++中的析构函数