彭召意 趙菁菁 劉建國
【摘 要】C++是一門經典的程序設計語言,目前有著廣泛的應用,學習C++語言的難點在于類的構建。為了構建好類,需要根據應用的要求設計合理的構造函數。文章從構造函數的概念出發,總結了其靈活多變的形式和使用方法,對想進一步提高C++編程能力的初學者具有很好的參考價值。
【關鍵詞】C++語言;類;構造函數;教學方法
【中圖分類號】G642 【文獻標識碼】A 【文章編號】1674-0688(2016)10-0050-04
C++面向對象程序設計是一種重要的程序設計語言,在硬件驅動、工業控制、系統軟件等方面具有廣泛的應用。不少初學者在學習C++的過程都出現過不少困難[1-2],其主要原因是他們沒有深刻地理解類的概念。類是面向對象程序設計中最重要的概念,它是構成面向對象程序的基石。在類中,有一個用途廣泛的成員函數,即構造函數。設計一個類時,通常都會設計構造函數。根據應用的不同,構造函數會以靈活多變的形式出現[3-4],構造函數在增強程序功能的同時也加大了初學者的難度。為了給初學者提供方便,本文就C++中的構造函數進行了系統的分析和總結,同時介紹了使用方法。
1 構造函數簡介
構造函數是類中一種特殊的成員函數,它的作用是用于對象的初始化。與其他自定義的成員函數不同,構造函數不需要用戶來調用它,而是在建立對象時自動執行。
構造函數的定義[5]:構造函數的名字必須與類名同名,它不具有任何類型,不返回任何值。
格式如下。
構造函數聲明:<類名> (<參數表>);
構造函數定義如下。
(1)<函數名>(參數表)。
{//構造函數功能體}//類內定義函數體
(2)<類名>::<函數名>(參數表) 。
{//構造函數功能體}//類外定義函數體
例如:定義一個包含構造函數的汽車類Car
class Car //定義類
{
public:
Car( ) //類內定義構造函數
{m_strCarname = “default name”;}
private:
string m_strCarname; //數據成員
};
如果在類外定義函數體,則:
Car::Car( ) //類外定義構造函數
{m_strCarname = “default name”;}
構造函數的功能是由用戶根據對象初始化需要自定義和設計函數體和函數參數。
使用構造函數的注意事項如下:{1}構造函數名稱必須與類名相同;{2}構造函數沒有返回值;{3}構造函數由系統自動調用,不需用戶調用,也不能被用戶調用;{4}在類對象進入其作用域時調用構造函數;{5}其功能是對對象進行初始化,一般由一系列賦值語句構成,但是構造函數中也可以包含其他語句,用于對象初始化時執行的功能;{6}如果用戶自己沒有定義構造函數,則C++系統會自動生成一個空的構造函數。
根據參數的不同,構造函數可以有不同的形式和使用方法。構造函數的不同形式有無參構造函數、有參構造函數、默認參數的構造函數、拷貝構造函數、轉換構造函數等。其中,拷貝構造函數和轉換構造函數屬于有參構造函數。
2 普通形式的構造函數
普通形式的構造函數主要指帶參構造函數(包括默認參數的構造函數)和無參構造函數。構造函數可以帶參數,也可以不帶參數。當需要從外面把參數傳遞給對象時,就需要采用帶參數的構造函數。系統默認的構造函數是不帶參數的,如果想帶參數,必須自定義構造函數。
(1)帶參數構造函數。前面例子中,寫一個帶參數的構造函數如下。
Car::Car(string CName) //類外定義構造函數
{m_strCarname = CName;}
帶有參數的構造函數定義對象的格式如下:
類名 對象名(實參1,實參2,…);
無參數時,定義對象的格式如下:
類名 對象名;
例如:Car c1;//建立對象c1,不帶參數;Car c2(“pzy car.”);//建立對象c2,帶參數。注意:建立無參數的對象時,不能帶括號。Car c1( ); //錯誤!,不要括號,否則該語句是函數聲明語句。
(2)有默認參數構造函數。構造函數的參數也可以像普通函數一樣帶默認參數,帶默認參數的構造函數中,默認參數值的構造函數的一般形式如下。
類名:構造函數名(類型 參數1=默認值,類型 參數2=默認值)
{函數體}
例如,前述例子中:
Car::Car(string CName=“pzy car.”)
{m_strCarname=CName;}
3 拷貝構造函數
拷貝構造函數能夠將參數的屬性值拷貝給新的對象,完成新對象的初始化。它是使用類對象的引用作為參數的構造函數,也稱為復制構造函數。
拷貝構造函數的格式如下:
class 類名
{public:
類名(類名&變量名) //定義一個拷貝構造函數
{ 函數體}
};
例如:定義一個帶有拷貝構造函數的汽車類Car
class Car{
public:
Car(string con_carname, int con_seats)
{
m_strCarname = con_carname;
m_nSeats = con_seats;
}
Car(Car &con_refcar) //拷貝構造函數
{
m_strCarname = con_refcar.m_strCarname;
m_nSeats = con_refcar.m_nSeats;
}
private:
string m_strCarname;
int m_nSeats;
};
下面3種情況會自動調用拷貝構造函數[6]。
(1)用早已存在的對象初始化新對象的時候。例如:Car c2=c1。
(2)將一個對象以值傳遞的方式傳給形參的時候。例如:void findcar(Carc);調用時:findcar(c)。
(3)函數返回一個對象的時候。例如:return Car(c)。
拷貝構造函數中若只完成數據成員本身的賦值,稱為“淺拷貝”。將所有數據都進行復制的拷貝構造函數稱之為“深拷貝”[7]。
在“淺拷貝”過程中,如果在構造函數中有新申請的存儲空間時(比如用new操作符),由于只是完成數據本身的賦值,并沒有新申請空間來賦值,所以在析構函數運行時,會出現錯誤。為了避免這種錯誤,需要采用“深拷貝”。
4 轉換構造函數
轉換構造函數只有一個形參,它的作用是將一個其他類型的數據(可以是基本數據類型,也可以是類對象)轉換成一個類的對象。
自定義數據類型與基本數據類型之間的轉換,除了類型轉換運算符重載,還可以定義轉換構造函數。所謂轉換構造函數就是當一個構造函數只有一個參數,而且該參數又不是本類的const引用時,這種構造函數稱為轉換構造函數。例如:
Complex( double Real ) {real=Real;imag=0;}
其作用是將參數Real轉換成Complex類的對象(Real是double型),該對象的實部是Real,對象的虛部為0。
下面完整的例子說明了轉換構造函數的使用。
#include
using namespace std;
class Complex
{
public:
Complex(double Real,double Imag){real= Real;imag= Imag;}
Complex( ){real=imag=0;}
Complex(double Real) //轉換構造函數
{imag=0;real= Real;}
friend Complex operator+(Complex cp1,Complex cp2);
private:
double imag; double real;
};
Complex operator + (Complex cp1,Complex cp2)
{ Complex c;
c.real=cp1.real+cp2.real;
c.imag=cp1.imag+cp2.imag;
return c;
}
int main()
{
Complex c1(13,24),c2(5,-10),c3;
c3=c1+2.5; //用到轉換構造函數,把2.5轉換成類Complex的對象
return 0;
}
該例子是將一個標準類型的數據轉換成對象,其實參數類型也可以是其他類的對象。比如:可以將一個教師類對象轉換為學生類對象,在Student中定義如下的轉換構造函數:
Student (Teacher & t)
{num=t.num; sex=t.sex;name=t.name; }
注意:對象t中的數據成員(num,name和sex等)都要是公用成員,因為要被類外來訪問。
5 派生類的構造函數
在定義派生類時,派生類的構造函數要考慮派生類新增的數據成員初始化,并且還要考慮基類的數據成員初始化。解決的方法是在執行派生類的構造函數時,同時要調用基類的構造函數。
(1)派生類構造函數一般形式如下。
派生類構造函數名(派生類參數表列):基類構造函數名(基類參數表列)
{自定義派生類中的初始化語句}
(2)如果有多個基類,應該一一把基類列出,比如有2個基類的派生類構造函數形式如下。
派生類構造函數名(派生類參數表列):基類1構造函數(基類1參數表列),基類2構造函數(基類2參數表列)
{自定義派生類中的初始化語句}
(3)派生類構造函數的執行順序為首先調用基類的構造函數,然后執行派生類的構造函數體。如果是多個基類,那么調用多個基類構造函數的順序是按照它們在聲明派生類時基類出現的順序。
6 錯誤使用構造函數分析
構造函數只能由系統自動調用,不能由用戶來調用,否則將出錯。下面用一個實例來進行說明[8]。
#include
public:
MyBox() { m_a = 1; }
MyBox(int b)
{
m_b = b;
MyBox();
}
~MyBox() {}
void Display()
{
std::cout << m_a <<" "<}
private:
int m_a;
int m_b;
};
int main()
{
MyBox myBox(2);
myBox.Display();
return 0;
}
上述程序中,在QT環境中,輸出結果為
6422400 2
在DEV C++環境中,輸出結果為
3 2
顯然,m_a是一個不確定的值,m_b等于2,這是因為m_a沒有被賦初值。在調用MyBox()函數時,實際上是建立了臨時的MyBox類對象,MyBox()中賦值m_a=1也是對對象的賦值,因此在myBox的m_a其實并沒有被賦值。
這個例子說明了不能顯式調用構造函數,也不能給成員變量賦值,否則結果將出現不確定性。
7 結語
本文首先介紹了C++類的構造函數相關概念及其靈活多變的不同形式,并指出不同的應用場合需要建立適合需求的構造函數;然后,介紹了不同形式的構造函數的學習和使用方法,并指出顯式調用構造函數所帶來的后果。這些內容有助于大家深刻理解類的構造函數,對于剛剛接觸C++語言的編程初學者和想進一步提高C++編程能力的人員都有很大的參考價值。
參 考 文 獻
[1]肖菁.高校非計算機專業C/C++教學的探索與實踐[J].現代計算機:專業版,2011(30):21-22.
[2]魯紅英,肖思和,孫淑霞.C/C++語言程序設計課程教學改革與實踐[J].計算機教育,2013(7):95-98.
[3]王帥,馬夢娜.關于C++構造函數的幾點探究[J].電腦編程技巧與維護,2013(10):6-7.
[4]李欣然,靳雁霞.C++程序設計中構造函數的探討[J].計算機時代,2011(12):30-32.
[5]譚浩強.C++面向對象程序設計[M].北京:清華大學出版社,2006.
[6]百度百科.構造函數[EB/OL].http://baike.baidu.com/link?url=uhoOfoj3mULwrmajVpFgRYwfomllKB-1VuO-bbCHnPikWcMMKFOAUqDCWlnTlokl2MH3psipethK-HyAxSfu8qlIv3bPfFOY4gChQ7CGe3KWo4kG1Va-yW-ZEEOQ9GHP1-a,2016-10-03.
[7]傳智播客高教產品研發部.C++程序設計教程[M].北京:人民郵電出版社,2015.
[8]Ticktick.顯式調用構造函數產生的悲劇[EB/OL].http://ticktick.blog.51cto.com/823160/294573,2016-10-03.
[責任編輯:陳澤琦]