建構函式是一種隨著物件建立而自動被呼叫的函式,它的主要用途是為物件作初始化。那麼,建構函式到底是什麼樣子的呢?

建構函式的宣告與定義

在C++中,規定與類同名的成員函式就是建構函式。需要注意的是,建構函式應該是一個公有的成員函式,並且建構函式沒有返回值型別。以下是我們為連結串列結點類編寫的一個建構函式:(其他成員函式定義見14.3節)
//node.h
#include <iostream>//如果不包含iostream標頭檔案,這個檔案裡就不能用cout
using namespace std;
class Node//定義一個連結串列結點類
{
   public:
   ……
   Node();//建構函式的宣告,建構函式是公有的成員函式,沒有返回值型別
   ……
   private:
   int idata;//儲存資料保密
   char cdata;//儲存資料保密
   Node *prior;//前驅結點的儲存位置保密
   Node *next;//後繼結點的儲存位置保密
};

Node::Node()//建構函式的定義
{
   cout <<"Node constructor is running..." <<endl;//提示建構函式執行
   idata=0;//初始化idata
   cdata='0';//初始化cdata
   prior=NULL;//初始化前驅結點指標
   next=NULL;//初始化後續結點指標
}

這時,我們建立一個連結串列結點物件,建構函式隨著物件建立而自動被呼叫,所以這個物件建立之後idata的值為0,cdata的值為'0',prior和next的值都是NULL:(程式15.2.1)
//main.cpp
#include <iostream>
#include "node.h"
using namespace std;
int main()
{
   Node a;//建立一個連結串列結點物件a,呼叫建構函式
   cout <<a.readi() <<endl;
   cout <<a.readc() <<endl;
   return 0;
}

執行結果:
Node constructor is running...
0
0
可是,這樣的建構函式還是不太理想。如果每次初始化的值都是固定的,那麼有沒有建構函式都是一樣的。建構函式變成了一種擺設!我們該怎麼辦?

帶引數的建構函式

函式的特徵之一就是能夠在呼叫時帶上引數。既然建構函式也是函式,那麼我們就能給建構函式帶上引數,使用過載或預設引數等方法,從而實現更自由地對物件進行初始化操作。以下便是對連結串列結點類的進一步修改:(程式15.2.2)
//node.h
#include <iostream>
using namespace std;
class Node//定義一個連結串列結點類
{
   public:
   Node();//建構函式0
   Node(int i,char c='0');//建構函式過載1,引數c預設為'0'
   Node(int i,char c,Node *p,Node *n);//建構函式過載2
   int readi() const;//讀取idata
   char readc() const;//讀取cdata
   Node * readp() const;//讀取上一個結點的位置
   Node * readn() const;//讀取下一個結點的位置
   bool set(int i);//過載,通過該函式修改idata
   bool set(char c);//過載,通過該函式修改cdata
   bool setp(Node *p);//通過該函式設定前驅結點
   bool setn(Node *n);//通過該函式設定後繼結點
   private:
   int idata;//儲存資料保密
   char cdata;//儲存資料保密
   Node *prior;//前驅結點的儲存位置保密
   Node *next;//後繼結點的儲存位置保密
};
int Node::readi() const//成員函式readi的定義
{
   return idata;
}

char Node::readc() const
{
   return cdata;
}
Node * Node::readp() const
{
   return prior;
}
Node * Node::readn() const
{
   return next;
}
bool Node::set(int i)//過載成員函式定義
{
   idata=i;
   return true;
}
bool Node::set(char c)
{
   cdata=c;
   return true;
}
bool Node::setp(Node *p)
{
   prior=p;
   return true;
}
bool Node::setn(Node *n)
{
   next=n;
   return true;
}
Node::Node()//建構函式0的定義
{
   cout <<"Node constructor is running..." <<endl;//提示建構函式執行
   idata=0;//初始化idata
   cdata='0';//初始化cdata
   prior=NULL;//初始化前驅結點指標
   next=NULL;//初始化後續結點指標
}
Node::Node(int i,char c)//建構函式過載1,預設引數只需要在函式原型中出現
{
   cout <<"Node constructor is running..." <<endl;
   idata=i;
   cdata=c;
   prior=NULL;
   next=NULL;
}
Node::Node(int i,char c,Node *p,Node *n)//建構函式過載2
{
   cout <<"Node constructor is running..." <<endl;
   idata=i;
   cdata=c;
   prior=p;
   next=n;
}
//main.cpp
#include <iostream>
#include "node.h"
using namespace std;
int main()
{
   Node a;//建立一個連結串列結點物件a,呼叫建構函式0
   Node b(8);//建立一個連結串列結點物件b,呼叫建構函式過載1,引數c預設為'0'
   Node c(8,'F',NULL,NULL);//建立一個連結串列結點物件c,呼叫建構函式過載2
   cout <<a.readi() <<' ' <<a.readc() <<endl;
   cout <<b.readi() <<' ' <<b.readc() <<endl;
   cout <<c.readi() <<' ' <<c.readc() <<endl;
   return 0;
}

執行結果:
Node constructor is running...
Node constructor is running...
Node constructor is running...
0 0
8 0
8 F

我們看到,在引數和過載的幫助下,我們可以設計出適合各種場合的建構函式。初始化各個物件的成員資料對我們來說已經是小菜一碟了。但是,這時你是否會回想起當初沒有編寫建構函式時的情形?如果沒有編寫建構函式,物件的建立是一個怎樣的過程呢?

在C++中,每個類都有且必須有建構函式。如果使用者沒有自行編寫建構函式,則C++自動提供一個無引數的建構函式,稱為預設建構函式。這個預設建構函式不做任何初始化工作。一旦使用者編寫了建構函式,則這個無引數的預設建構函式就消失了。如果使用者還希望能有一個無引數的建構函式,必須自行編寫。