? ?c++構造函數的知識在各種c++教材上已有介紹,不過初學者往往不太注意觀察和總結其中各種構造函數的特點和用法,故在此我根據自己的c++編程經驗總結了一下c++中各種構造函數的特點,并附上例子,希望對初學者有所幫助。
c++類的構造函數詳解????????????????????????
一、 構造函數是干什么的
class?Counter
{
public:
?????????// 類Counter的構造函數
?????????// 特點:以類名作為函數名,無返回類型
???????? Counter()
???????? {
??????????????? m_value = 0;
???????? }
?????????
private:
??????
?????????// 數據成員
?????????int?m_value;
}
?????? 該類對象被創建時,編譯系統對象分配內存空間,并自動調用該構造函數->由構造函數完成成員的初始化工作
eg:??? Counter c1;
??????? 編譯系統為對象c1的每個數據成員(m_value)分配內存空間,并調用構造函數Counter( )自動地初始化對象c1的m_value值設置為0
故:
??????? 構造函數的作用:初始化對象的數據成員。
二、 構造函數的種類
class?Complex?
{?????????
private?:
????????double??? m_real;
????????double??? m_imag;
public:
????????//??? 無參數構造函數
????????// 如果創建一個類你沒有寫任何構造函數,則系統會自動生成默認的無參構造函數,函數為空,什么都不做
????????// 只要你寫了一個下面的某一種構造函數,系統就不會再自動生成這樣一個默認的構造函數,如果希望有一個這樣的無參構造函數,則需要自己顯示地寫出來
??????? Complex(void)
??????? {
???????????? m_real = 0.0;
???????????? m_imag = 0.0;
??????? }?
????????
????????//??? 一般構造函數(也稱重載構造函數)
????????// 一般構造函數可以有各種參數形式,一個類可以有多個一般構造函數,前提是參數的個數或者類型不同(基于c++的重載函數原理)
????????// 例如:你還可以寫一個 Complex( int num)的構造函數出來
????????// 創建對象時根據傳入的參數不同調用不同的構造函數
??????? Complex(double?real,?double?imag)
??????? {
???????????? m_real = real;
???????????? m_imag = imag;?????????
???????? }
????????
????????//??? 復制構造函數(也稱為拷貝構造函數)
????????//??? 復制構造函數參數為類對象本身的引用,用于根據一個已存在的對象復制出一個新的該類的對象,一般在函數中會將已存在對象的數據成員的值復制一份到新創建的對象中
????????//??? 若沒有顯示的寫復制構造函數,則系統會默認創建一個復制構造函數,但當類中有指針成員時,由系統默認創建該復制構造函數會存在風險,具體原因請查詢 有關 “淺拷貝” 、“深拷貝”的文章論述
??????? Complex(const?Complex & c)
??????? {
????????????????// 將對象c中的數據成員值復制過來
??????????????? m_real = c.m_real;
??????????????? m_img??? = c.m_img;
??????? }????????????
????
????????// 類型轉換構造函數,根據一個指定的類型的對象創建一個本類的對象
????????// 例如:下面將根據一個double類型的對象創建了一個Complex對象
??????? Complex::Complex(double?r)
??????? {
??????????????? m_real = r;
??????????????? m_imag = 0.0;
??????? }
????????// 等號運算符重載
????????// 注意,這個類似復制構造函數,將=右邊的本類對象的值復制給等號左邊的對象,它不屬于構造函數,等號左右兩邊的對象必須已經被創建
????????// 若沒有顯示的寫=運算符重載,則系統也會創建一個默認的=運算符重載,只做一些基本的拷貝工作
??????? Complex &operator=(?const?Complex &rhs )
??????? {
????????????????// 首先檢測等號右邊的是否就是左邊的對象本,若是本對象本身,則直接返回
????????????????if?(?this?== &rhs )?
??????????????? {
????????????????????????return?*this;
??????????????? }
????????????????
????????????????// 復制等號右邊的成員到左邊的對象中
????????????????this->m_real = rhs.m_real;
????????????????this->m_imag = rhs.m_imag;
????????????????
???????????????// 把等號左邊的對象再次傳出
???????????????// 目的是為了支持連等 eg:??? a=b=c 系統首先運行 b=c
???????????????// 然后運行 a= ( b=c的返回值,這里應該是復制c值后的b對象)????
????????????????return?*this;
??????? }
};
下面使用上面定義的類對象來說明各個構造函數的用法:
void?main()
{
????????// 調用了無參構造函數,數據成員初值被賦為0.0
??????? Complex c1,c2;
????????// 調用一般構造函數,數據成員初值被賦為指定值
??????? Complex c3(1.0,2.5);
????????// 也可以使用下面的形式
??????? Complex c3 = Complex(1.0,2.5);
????????
????????//??? 把c3的數據成員的值賦值給c1
????????//??? 由于c1已經事先被創建,故此處不會調用任何構造函數
????????//??? 只會調用 = 號運算符重載函數
??????? c1 = c3;
????????
????????//??? 調用類型轉換構造函數
????????//??? 系統首先調用類型轉換構造函數,將5.2創建為一個本類的臨時對象,然后調用等號運算符重載,將該臨時對象賦值給c1
??????? c2 = 5.2;
????????
???????// 調用拷貝構造函數( 有下面兩種調用方式)?
??????? Complex c5(c2);
??????? Complex c4 = c2;??// 注意和 = 運算符重載區分,這里等號左邊的對象不是事先已經創建,故需要調用拷貝構造函數,參數為c2
????????
????????
}
三、思考與測驗
1. 仔細觀察復制構造函數
??????? Complex(const?Complex & c)
??????? {
????????????????// 將對象c中的數據成員值復制過來
??????????????? m_real = c.m_real;
??????????????? m_img = c.m_img;
??????? }????
????????
為什么函數中可以直接訪問對象c的私有成員?
2. 挑戰題,了解引用與傳值的區別
? Complex test1(const?Complex& c)
? {
??????????return?c;
? }
??
? Complex test2(const?Complex c)
? {
?????????return?c;
?? }
???
?? Complex test3()
?? {
??????????static?Complex c(1.0,5.0);
??????????return?c;
?? }
??
? Complex& test4()
? {
?????????static?Complex c(1.0,5.0);
?????????return?c;
? }
??
??void?main()
? {
??????? Complex a,b;
????
????????// 下面函數執行過程中各會調用幾次構造函數,調用的是什么構造函數?
????
?????? test1(a);
?????? test2(a);
?????
?????? b = test3();
?????? b = test4();
?????
?????? test2(1.2);
???????// 下面這條語句會出錯嗎?
?????? test1(1.2);?????//test1( Complex(1.2 )) 呢?
? }
四、附錄(淺拷貝與深拷貝)
?????? 上面提到,如果沒有自定義復制構造函數,則系統會創建默認的復制構造函數,但系統創建的默認復制構造函數只會執行“淺拷貝”,即將被拷貝對象的數據成員的值一一賦值給新創建的對象,若該類的數據成員中有指針成員,則會使得新的對象的指針所指向的地址與被拷貝對象的指針所指向的地址相同,delete該指針時則會導致兩次重復delete而出錯。下面是示例:
【淺拷貝與深拷貝】
#include <iostream.h>
#include <string.h>
class?Person?
{
public?:
????????
????????// 構造函數
??????? Person(char?* pN)
??????? {
????????????? cout <<?"一般構造函數被調用 !\n";
????????????? m_pName =?new?char[strlen(pN) + 1];
??????????????//在堆中開辟一個內存塊存放pN所指的字符串
??????????????if(m_pName != NULL)?
????????????? {
?????????????????//如果m_pName不是空指針,則把形參指針pN所指的字符串復制給它
?????????????????? strcpy(m_pName ,pN);
????????????? }
??????? }????????
????????
????????// 系統創建的默認復制構造函數,只做位模式拷貝
??????? Person(Person & p)????
??????? {?
??????????????????//使兩個字符串指針指向同一地址位置?????????
???????????????? m_pName = p.m_pName;?????????
??????? }
???
??????? ~Person( )
??????? {
??????????????? delete m_pName;
??????? }
????????
private?:
????????char?* m_pName;
};
void?main( )
{?
??????? Person man("lujun");
??????? Person woman(man);?
????????
????????// 結果導致?? man 和??? woman 的指針都指向了同一個地址
????????
????????// 函數結束析構時
????????// 同一個地址被delete兩次
}
// 下面自己設計復制構造函數,實現“深拷貝”,即不讓指針指向同一地址,而是重新申請一塊內存給新的對象的指針數據成員
Person(Person & chs);
{
?????????// 用運算符new為新對象的指針數據成員分配空間
???????? m_pName=new?char[strlen(p.m_pName)+ 1];
?????????if(m_pName)?????????
???????? {
?????????????????// 復制內容
??????????????? strcpy(m_pName ,chs.m_pName);
???????? }
??????
????????// 則新創建的對象的m_pName與原對象chs的m_pName不再指向同一地址了
}
一、 構造函數是干什么的
class?Counter
{
public:
?????????// 類Counter的構造函數
?????????// 特點:以類名作為函數名,無返回類型
???????? Counter()
???????? {
??????????????? m_value = 0;
???????? }
?????????
private:
??????
?????????// 數據成員
?????????int?m_value;
}
?????? 該類對象被創建時,編譯系統對象分配內存空間,并自動調用該構造函數->由構造函數完成成員的初始化工作
eg:??? Counter c1;
??????? 編譯系統為對象c1的每個數據成員(m_value)分配內存空間,并調用構造函數Counter( )自動地初始化對象c1的m_value值設置為0
故:
??????? 構造函數的作用:初始化對象的數據成員。
二、 構造函數的種類
class?Complex?
{?????????
private?:
????????double??? m_real;
????????double??? m_imag;
public:
????????//??? 無參數構造函數
????????// 如果創建一個類你沒有寫任何構造函數,則系統會自動生成默認的無參構造函數,函數為空,什么都不做
????????// 只要你寫了一個下面的某一種構造函數,系統就不會再自動生成這樣一個默認的構造函數,如果希望有一個這樣的無參構造函數,則需要自己顯示地寫出來
??????? Complex(void)
??????? {
???????????? m_real = 0.0;
???????????? m_imag = 0.0;
??????? }?
????????
????????//??? 一般構造函數(也稱重載構造函數)
????????// 一般構造函數可以有各種參數形式,一個類可以有多個一般構造函數,前提是參數的個數或者類型不同(基于c++的重載函數原理)
????????// 例如:你還可以寫一個 Complex( int num)的構造函數出來
????????// 創建對象時根據傳入的參數不同調用不同的構造函數
??????? Complex(double?real,?double?imag)
??????? {
???????????? m_real = real;
???????????? m_imag = imag;?????????
???????? }
????????
????????//??? 復制構造函數(也稱為拷貝構造函數)
????????//??? 復制構造函數參數為類對象本身的引用,用于根據一個已存在的對象復制出一個新的該類的對象,一般在函數中會將已存在對象的數據成員的值復制一份到新創建的對象中
????????//??? 若沒有顯示的寫復制構造函數,則系統會默認創建一個復制構造函數,但當類中有指針成員時,由系統默認創建該復制構造函數會存在風險,具體原因請查詢 有關 “淺拷貝” 、“深拷貝”的文章論述
??????? Complex(const?Complex & c)
??????? {
????????????????// 將對象c中的數據成員值復制過來
??????????????? m_real = c.m_real;
??????????????? m_img??? = c.m_img;
??????? }????????????
????
????????// 類型轉換構造函數,根據一個指定的類型的對象創建一個本類的對象
????????// 例如:下面將根據一個double類型的對象創建了一個Complex對象
??????? Complex::Complex(double?r)
??????? {
??????????????? m_real = r;
??????????????? m_imag = 0.0;
??????? }
????????// 等號運算符重載
????????// 注意,這個類似復制構造函數,將=右邊的本類對象的值復制給等號左邊的對象,它不屬于構造函數,等號左右兩邊的對象必須已經被創建
????????// 若沒有顯示的寫=運算符重載,則系統也會創建一個默認的=運算符重載,只做一些基本的拷貝工作
??????? Complex &operator=(?const?Complex &rhs )
??????? {
????????????????// 首先檢測等號右邊的是否就是左邊的對象本,若是本對象本身,則直接返回
????????????????if?(?this?== &rhs )?
??????????????? {
????????????????????????return?*this;
??????????????? }
????????????????
????????????????// 復制等號右邊的成員到左邊的對象中
????????????????this->m_real = rhs.m_real;
????????????????this->m_imag = rhs.m_imag;
????????????????
???????????????// 把等號左邊的對象再次傳出
???????????????// 目的是為了支持連等 eg:??? a=b=c 系統首先運行 b=c
???????????????// 然后運行 a= ( b=c的返回值,這里應該是復制c值后的b對象)????
????????????????return?*this;
??????? }
};
下面使用上面定義的類對象來說明各個構造函數的用法:
void?main()
{
????????// 調用了無參構造函數,數據成員初值被賦為0.0
??????? Complex c1,c2;
????????// 調用一般構造函數,數據成員初值被賦為指定值
??????? Complex c3(1.0,2.5);
????????// 也可以使用下面的形式
??????? Complex c3 = Complex(1.0,2.5);
????????
????????//??? 把c3的數據成員的值賦值給c1
????????//??? 由于c1已經事先被創建,故此處不會調用任何構造函數
????????//??? 只會調用 = 號運算符重載函數
??????? c1 = c3;
????????
????????//??? 調用類型轉換構造函數
????????//??? 系統首先調用類型轉換構造函數,將5.2創建為一個本類的臨時對象,然后調用等號運算符重載,將該臨時對象賦值給c1
??????? c2 = 5.2;
????????
???????// 調用拷貝構造函數( 有下面兩種調用方式)?
??????? Complex c5(c2);
??????? Complex c4 = c2;??// 注意和 = 運算符重載區分,這里等號左邊的對象不是事先已經創建,故需要調用拷貝構造函數,參數為c2
????????
????????
}
三、思考與測驗
1. 仔細觀察復制構造函數
??????? Complex(const?Complex & c)
??????? {
????????????????// 將對象c中的數據成員值復制過來
??????????????? m_real = c.m_real;
??????????????? m_img = c.m_img;
??????? }????
????????
為什么函數中可以直接訪問對象c的私有成員?
2. 挑戰題,了解引用與傳值的區別
? Complex test1(const?Complex& c)
? {
??????????return?c;
? }
??
? Complex test2(const?Complex c)
? {
?????????return?c;
?? }
???
?? Complex test3()
?? {
??????????static?Complex c(1.0,5.0);
??????????return?c;
?? }
??
? Complex& test4()
? {
?????????static?Complex c(1.0,5.0);
?????????return?c;
? }
??
??void?main()
? {
??????? Complex a,b;
????
????????// 下面函數執行過程中各會調用幾次構造函數,調用的是什么構造函數?
????
?????? test1(a);
?????? test2(a);
?????
?????? b = test3();
?????? b = test4();
?????
?????? test2(1.2);
???????// 下面這條語句會出錯嗎?
?????? test1(1.2);?????//test1( Complex(1.2 )) 呢?
? }
四、附錄(淺拷貝與深拷貝)
?????? 上面提到,如果沒有自定義復制構造函數,則系統會創建默認的復制構造函數,但系統創建的默認復制構造函數只會執行“淺拷貝”,即將被拷貝對象的數據成員的值一一賦值給新創建的對象,若該類的數據成員中有指針成員,則會使得新的對象的指針所指向的地址與被拷貝對象的指針所指向的地址相同,delete該指針時則會導致兩次重復delete而出錯。下面是示例:
【淺拷貝與深拷貝】
#include <iostream.h>
#include <string.h>
class?Person?
{
public?:
????????
????????// 構造函數
??????? Person(char?* pN)
??????? {
????????????? cout <<?"一般構造函數被調用 !\n";
????????????? m_pName =?new?char[strlen(pN) + 1];
??????????????//在堆中開辟一個內存塊存放pN所指的字符串
??????????????if(m_pName != NULL)?
????????????? {
?????????????????//如果m_pName不是空指針,則把形參指針pN所指的字符串復制給它
?????????????????? strcpy(m_pName ,pN);
????????????? }
??????? }????????
????????
????????// 系統創建的默認復制構造函數,只做位模式拷貝
??????? Person(Person & p)????
??????? {?
??????????????????//使兩個字符串指針指向同一地址位置?????????
???????????????? m_pName = p.m_pName;?????????
??????? }
???
??????? ~Person( )
??????? {
??????????????? delete m_pName;
??????? }
????????
private?:
????????char?* m_pName;
};
void?main( )
{?
??????? Person man("lujun");
??????? Person woman(man);?
????????
????????// 結果導致?? man 和??? woman 的指針都指向了同一個地址
????????
????????// 函數結束析構時
????????// 同一個地址被delete兩次
}
// 下面自己設計復制構造函數,實現“深拷貝”,即不讓指針指向同一地址,而是重新申請一塊內存給新的對象的指針數據成員
Person(Person & chs);
{
?????????// 用運算符new為新對象的指針數據成員分配空間
???????? m_pName=new?char[strlen(p.m_pName)+ 1];
?????????if(m_pName)?????????
???????? {
?????????????????// 復制內容
??????????????? strcpy(m_pName ,chs.m_pName);
???????? }
??????
????????// 則新創建的對象的m_pName與原對象chs的m_pName不再指向同一地址了
}