Overloaded Functions 前言 處理多載函式宣告的規則 處理多載函式呼叫的規則 多載函式與 scope 函式呼叫的議決
前言 C 語言規定:函式的名稱不可相同。這樣的規定使得我們必須為功能相近但參數型態相異的函式取不同的名稱,譬如: int imax (int, int); // max function for integer double dmax (double , double ); // max function for double 這種命名方式顯然無益於程式的可讀性。C++ 因而解除這個限制,讓函式的名稱可以重複使用,譬如: int max (int, int); // max function for integers double max (double , double ); // max function for integers 這種同名的函式稱之為多載函式(overloaded function)。
程式原始檔中的同名函式可能造成以下四種狀況: 成為合法的多載函式。 成為函式的再次宣告。 產生編譯上的錯誤。 內層的函式宣告隱蓋了外層的同名函式宣告。
處理多載函式宣告的規則 在同一個 scope 中,C++ 編譯器會採用以下的規則來處理同名函式的宣告: 若參數的型態或個數不同的話,則這些同名函式被視為多載函式。譬如: // overloaded functions void print (); void print (int); void print (double);
若傳回值型態以及參數型態和個數都相同的話,則宣告在後的函式被視為再次宣告(redeclaration)。譬如: void print (const string &str); void print (const string &); 若參數型態和個數都相符但傳回值型態不同的話,則宣告在後的函式會產生編譯錯誤。譬如: void print (const string &str); int print (const string &); // compile error
若只是引數預設值不相同的話,則宣告在後的函式被視為再次的宣告。譬如: int max (int *ia, int sz); int max (int *ia, int sz = 10); // redeclaration 由於 typedef 只是提供某種資料型態的別名,而非真正引介新的資料型態,所以資料型態的別名和所指涉的資料型態會被視為相同的型態。譬如:底下的第二個宣告會造成編譯錯誤: typedef double real; real calc (real); int calc (double); // compile error
若修飾字 const 不影響參數的唯讀性,則不被用來區別參數的型態。譬如以下兩行被視為宣告相同的函式: void foo (int); void foo (const int); // redeclaration 若修飾字 const 用於指標或參照,則會被用來區別參數的型態,而形成多載函式。譬如: // 宣告兩個不同的函式 void foo (int *); void foo (const int*); void foo (int &); void foo (const int&);
多載函式與 scope 在相同 scope 的同名函式才會成為多載函式。內層的函式宣告只是隱蓋外層的同名函式宣告,而不是後者的多載函式。譬如: void print (const char *); void print (double); // overloads print() void fooBar (int iVal) { // hide global print() extern void print (int); print("Value: "); // error: print(const char*) is hidden print(iVal); // ok }
開放名稱空間時,同名函式必須符合前述的多載化規定才會形成多載函式,否則會造成編譯的錯誤。我們用以下的幾個例子來說明之。假定名稱空間 IBM 的宣告如下: namespace IBM { void print (int); void print (double); } 以下兩行是正確的: void print (const char *); // 多載函式 using IBM::print; 但以下兩行會造成編譯錯誤: void print (int); using IBM::print; // error: ::print(int) 和 IBM::print(int) 造成混淆。
因為 IBM::print(int) 和 MS::print(int) 造成混淆。 假定名稱空間 IBM 和 MS 的宣告如下: namespace IBM { void print (int); void print (double); } namespace MS { void print (const char*); 則以下兩行會造成編譯錯誤: using namespace IBM; using namespace MS; 因為 IBM::print(int) 和 MS::print(int) 造成混淆。
函式呼叫的議決 由於 C++ 允許多載函式的存在,因此 C++ 編譯器處理函式呼叫時,必須用一套規則來判斷呼叫那一個函式,這個過程稱之為「函式呼叫的議決(resolution)」。舉例來說,假定底下兩個多載函式: void foo (int, int); void foo (double, double); 下列的函式呼叫是呼叫那一個 foo 呢? foo (1, 2); foo (1.0, 2.0); foo (1, 2.0); foo (1, "Hello"); foo ('a', 'b');
C++ 編譯器選擇呼叫的函式時,會根據引數列的型態來找尋與其最相符(best match)的函式。引數列和參數列型態相符比對的優先順序如下: 引數與參數的型態完全一致或經過簡單轉換而一致(如陣列名稱轉成指標、函式名稱轉成函式指標、或型態 T 轉成 const T)。 引數經過型態提昇而與參數相符,如 char → int, float → double 等等。 引數經過標準型態轉換而與參數相符,如 double → int, int → double , int → unsigned int 等等。 引數經過使用者自定的型態轉換而與參數相符。 引數與不定參數列(…)相符。
經過引數列和參數列的型態相符比對後,下列兩種函式呼叫的狀況會產生編譯錯誤: 函式呼叫時的引數無法用前述五項的方式轉換成參數。 範例: void foo (int, int); void foo (double, double); foo (1, 2); // ok: 呼叫 foo(int, int) foo (1.0, 2.0); // ok: 呼叫 foo(double, double) foo (1, 2.0); // error: 無法判斷呼叫那一個 foo (1, “Hello”); // error: 無法轉換 foo ('a', 'b'); // ok: 呼叫 foo(int, int) 函式呼叫時的引數有兩種同等級轉換成參數的方式。
範例:完全相符 void print (int); void print (char); void print (const char*); void print (long); void print (double); char ca[100]; print (3); // match print(int) print (3L); // match print(long) print (3.0); // match print(double) print ('A'); // match print(char) print ("Hello"); // match print(const char*) print (ca); // match print(const char*)
引數提昇轉換的規則 整數的提昇規則 浮點數的提昇規則 引數型態 bool, char, unsigned char, short 提昇成 int。 若 int 的容量大於 short 的容量,則引數型態 unsigned short 提昇成 int,否則提昇成 unsigned int。 列舉型態依其最大值的範圍而提昇成 int, unsigned int, long, 或 unsigned long。 浮點數的提昇規則 引數型態 float 提昇成 double。
範例:經過提昇而相符 void print (int); void print (long); void print (double); enum color {BLACK, WHITE}; print (true); // match print(int), bool → int print (3.0f); // match print(double), float → double print ('A'); // match print(int), char → int print (BLACK); // match print(int), color → int
引數標準轉換的規則 除了前述的整數提昇以外,其他把整數(或列舉)轉換成另一種整數型態,如 int → char。 除了前述的浮點數提昇以外,其他把浮點數轉換成另一種浮點數型態,如 double → float。 整數與浮點數之間的轉換,如 int → double 或 double → int。 指標轉換:如把整數 0 轉成指標;把任何型態的指標轉成 void * 型態的指標。 把任何整數、浮點數、列舉值、指標轉成 bool 型態。
範例:經過標準轉換而相符 void print (char); void print (void *); int *cp; print (3.0); // match print(char), double → char print (3); // match print(char), int → char print (cp); // match print(void *), int * → void *
標準轉換規則是一視同仁而沒有優先次序,比如說: int → double 和 char → double 兩者地位相同。 範例: void print (long); void print (float); void print (void *); void print (bool); int *cp; print (3.14); // error: double → float or double → long? print ('A'); // error: char → float or char → long? print (cp); // error: int * → void * or int * → bool?
多引數的呼叫 呼叫多引數的函式時,前述的型態比對不受引數的順序影響。譬如: 則底下的函式呼叫會造成編譯錯誤: void foo (int, int); void foo (double, double); 則底下的函式呼叫會造成編譯錯誤: foo (1, 2.0); // error: 無法判斷呼叫那一個 因為它可以是 foo (1, int(2.0)) 外,也可以是 foo (double(1), 2.0) ,因此造成混淆。
使用強制的型態轉換 當不明確或會造成混淆時,我們可以用強制的型態轉換(type casting)來呼叫指定的函式。譬如: void print (long); void print (float); void print (void *); void print (bool); int *cp; print (static_cast<float> 3.14); // match print(float) print (static_cast<long>'A'); // match print(long) print (static_cast<void *>cp); // match print(void *)