在C++中,有一種特殊的成員函數,它的名字和類名相同,沒有返回值,不需要用戶顯式調用(用戶也不能調用),而是在創建對象時自動執行。這種特殊的成員函數就是構造函數(Constructor)。
在《C++類成員的訪問權限》一節中,我們通過成員函數 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)
,它的作用是給三個 private 屬性的成員變量賦值。要想調用該構造函數,就得在創建對象的同時傳遞實參,並且實參由
( )
包圍,和普通的函數調用非常類似。
在棧上創建對象時,實參位於對象名後面,例如
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()。
以前我們就是這樣做的,創建對象時都沒有寫括號,其實是調用了默認的構造函數。