C++建構函式詳解
在C++中,有一種特殊的成員函式,它的名字和類名相同,沒有返回值,不需要使用者顯式呼叫(使用者也不能呼叫),而是在建立物件時自動執行。這種特殊的成員函式就是建構函式(Constructor)。
我們通過成員函式 setname()、setage()、setscore() 分別為成員變數 name、age、score 賦值,這樣做雖然有效,但顯得有點麻煩。有了建構函式,我們就可以簡化這項工作,在建立物件的同時為成員變數賦值,請看下面的程式碼(示例1):
- #include <iostream>
- using namespace std;
- class Student{
- private:
- char *m_name;
- int m_age;
- float m_score;
- public:
- //宣告建構函式
- Student(char *name, int age, float score);
- //宣告普通成員函式
- void show();
- };
- //定義建構函式
- Student::Student(char *name, int age, float score){
- m_name = name;
- m_age = age;
- m_score = score;
- }
- //定義普通成員函式
- void Student::show(){
- cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
- }
- int main(){
- //建立物件時向建構函式傳參
- Student stu("小明", 15, 92.5f);
- stu.show();
- //建立物件時向建構函式傳參
- Student *pstu = new Student("李華", 16, 96);
- pstu -> show();
- return 0;
- }
執行結果:
小明的年齡是15,成績是92.5
李華的年齡是16,成績是96
該例在 Student 類中定義了一個建構函式Student(char *, int, float)
( )
包圍,和普通的函式呼叫非常類似。在棧上建立物件時,實參位於物件名後面,例如
Student stu("小明", 15, 92.5f)
;在堆上建立物件時,實參位於類名後面,例如new Student("李華", 16, 96)
。建構函式必須是 public 屬性的,否則建立物件時無法呼叫。當然,設定為 private、protected 屬性也不會報錯,但是沒有意義。
建構函式沒有返回值,因為沒有變數來接收返回值,即使有也毫無用處,這意味著:
- 不管是宣告還是定義,函式名前面都不能出現返回值型別,即使是 void 也不允許;
- 函式體中不能有 return 語句。
建構函式的過載
和普通成員函式一樣,建構函式是允許過載的。一個類可以有多個過載的建構函式,建立物件時根據傳遞的實參來判斷呼叫哪一個建構函式。
建構函式的呼叫是強制性的,一旦在類中定義了建構函式,那麼建立物件時就一定要呼叫,不呼叫是錯誤的。如果有多個過載的建構函式,那麼建立物件時提供的實參必須和其中的一個建構函式匹配;反過來說,建立物件時只有一個建構函式會被呼叫。
對示例1中的程式碼,如果寫作Student stu
或者new Student
就是錯誤的,因為類中包含了建構函式,而建立物件時卻沒有呼叫。
更改示例1的程式碼,再新增一個建構函式(示例2):
- #include <iostream>
- using namespace std;
- class Student{
- private:
- char *m_name;
- int m_age;
- float m_score;
- public:
- Student();
- Student(char *name, int age, float score);
- void setname(char *name);
- void setage(int age);
- void setscore(float score);
- void show();
- };
- Student::Student(){
- m_name = NULL;
- m_age = 0;
- m_score = 0.0;
- }
- Student::Student(char *name, int age, float score){
- m_name = name;
- m_age = age;
- m_score = score;
- }
- void Student::setname(char *name){
- m_name = name;
- }
- void Student::setage(int age){
- m_age = age;
- }
- void Student::setscore(float score){
- m_score = score;
- }
- void Student::show(){
- if(m_name == NULL || m_age <= 0){
- cout<<"成員變數還未初始化"<<endl;
- }else{
- cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
- }
- }
- int main(){
- //呼叫建構函式 Student(char *, int, float)
- Student stu("小明", 15, 92.5f);
- stu.show();
- //呼叫建構函式 Student()
- Student *pstu = new Student();
- pstu -> show();
- pstu -> setname("李華");
- pstu -> setage(16);
- pstu -> setscore(96);
- pstu -> show();
- return 0;
- }
執行結果:
小明的年齡是15,成績是92.5
成員變數還未初始化
李華的年齡是16,成績是96
建構函式Student(char *, int, float)
為各個成員變數賦值,建構函式Student()
將各個成員變數的值設定為空,它們是過載關係。根據Student()
建立物件時不會賦予成員變數有效值,所以還要呼叫成員函式 setname()、setage()、setscore() 來給它們重新賦值。
建構函式在實際開發中會大量使用,它往往用來做一些初始化工作,例如對成員變數賦值、預先開啟檔案等。
預設建構函式
如果使用者自己沒有定義建構函式,那麼編譯器會自動生成一個預設的建構函式,只是這個建構函式的函式體是空的,也沒有形參,也不執行任何操作。比如上面的 Student 類,預設生成的建構函式如下:
Student(){}
一個類必須有建構函式,要麼使用者自己定義,要麼編譯器自動生成。一旦使用者自己定義了建構函式,不管有幾個,也不管形參如何,編譯器都不再自動生成。在示例1中,Student 類已經有了一個建構函式Student(char *, int, float)
,也就是我們自己定義的,編譯器不會再額外新增建構函式Student()
,在示例2中我們才手動添加了該建構函式。
實際上編譯器只有在必要的時候才會生成預設建構函式,而且它的函式體一般不為空。預設建構函式的目的是幫助編譯器做初始化工作,而不是幫助程式設計師。這是C++的內部實現機制,這裡不再深究,初學者可以按照上面說的“一定有一個空函式體的預設建構函式”來理解。
最後需要注意的一點是,呼叫沒有引數的建構函式也可以省略括號。對於示例2的程式碼,在棧上建立物件可以寫作Student stu()
或Student stu
,在堆上建立物件可以寫作Student *pstu = new Student()
或Student *pstu = new Student
,它們都會呼叫建構函式 Student()。
以前我們就是這樣做的,建立物件時都沒有寫括號,其實是呼叫了預設的建構函式。