《C++ Primer》筆記,整理關於函數重載與函數匹配的筆記。html
void func(int a); //原函數 void func(double a); //正確:形參類型不一樣 void func(int a, int b); // 正確:形參個數不一樣 int func(int a); //錯誤:只有返回類型不一樣 typedef int int32; void func(int32 a); //與原函數等價:形參類型相同 void func(const int a); //與原函數等價:頂層 const 將被忽略 void func(int); //與原函數等價:只是省略了形參名字
函數重載有以下的規則:git
const
的形參沒法和沒有頂層const
的形參區分。其中返回類型不一樣時編譯時會出錯,而類型別名、項層const
、省略形參名字只是重複聲明而已,只要不定義,編譯就不會出錯,好比:github
//只定義了其中一個 void func(int a); void func(const int a) {}
函數匹配的第一步即是名字查找(name lookup),肯定候選函數。數組
名字查找有兩方面:函數
全部函數調用都會進行常規查找,只有函數的實參包括類類型對象或指向類類型對象的指針/引用的時候,纔會進行實參決定的查找。post
常規查找spa
void func(int a); //1 namespace N { //做用域 void func() {} //2 void func(double a) {} //3 ... void test1() { func(); //候選函數爲函數2和3 } void test2() { using ::func; //將函數1加入當前做用域 func(); //候選函數爲函數1 } ... }
從函數被調用的局部做用域開始,逐漸向上層尋找被調用的名字,一旦找到就中止向上尋找,將找到的全部名字加入候選函數。指針
此外,using語句能夠將其餘做用域的名字引用到當前做用域。code
ADL查找orm
void func() {} //1 //第一個實參所在命名空間 namespace Name1 { class T { friend void func(T&) {} //2 }; void func(T) {} //3 } //第二個實參的間接父類所在命名空間 namespace Name00 { class T00 { friend void func(int) {} //4 }; void func() {} //5 } //第二個實參父類所在命名空間 namespace Name0 { class T0:public Name00::T00 { friend void func(int) {} //6 }; void func() {} //7 } //第二個實參所在命名空間 namespace Name2 { class T:public Name0::T0 { friend void func(T&) {} //8 }; void func(T) {} //9 } void test() { Name1::T t1; Name2::T t2; //9個函數全是候選函數 //第1個函數是normal lookup找到的 //後8個函數全是argument-dependent lookup找到的 func(&t1,t2); }
從第一個類類型參數開始,依次遍歷全部類類型參數。對於每個參數,進入其類型定義所在的做用域(類內友元函數也包括在內),並依次進入其基類、間接基類……定義所在的做用域,查找同名函數,並加入候選函數。
注意:在繼承體系中上升的過程當中,不會由於找到同名函數就中止上升,這不一樣於常規查找。
類中的運算符重載也遵循 ADL 查找,其候選函數集既包括成員函數,也應該包括非成員函數。
namespace N { class A { public: void operator+(int a) {} //1 }; void operator+(A &a, int a) {} //2 }; void operator+(A &a, int a) {} //3 void test() { N::A a; a + 1; //一、二、3都是候選函數 }
第二步即是從候選函數中選出可行函數,選擇的標準以下:
//如下爲候選函數 void func(int a, double b) {} //可行函數 void func(int a, int b) {} //可行函數:實參可轉化成形參類型 int func(int a, double b) {} //可行函數 void func(int a) {} //非可行函數:形參數量不匹配 void func(int a, int b[]) {} //非可行函數:實參不能轉換成形參 void test() { func(1, 0.1); }
從可行函數中選擇最匹配的函數,若是有多個形參,則最佳匹配條件爲:
不然,發生二義性調用錯誤。
//可行函數 void func(int a, float b) {} void func(int a, int b) {} void test() { func(1, 0.1); //二義性錯誤:double 向 int 的轉換與向 float 的轉換同樣好 func(1, 1); //調用 void func(int a, int b) }
爲了肯定最佳匹配,實參類型到形參類型的轉換等級以下:
const
或者從實參中刪除頂層const
。const
轉換實現的匹配。通常不會存在這個階段不會同時存在兩個以上的精確匹配,由於兩個精確的匹配在本質上是等價的,在定義重載函數時,編譯器可能就報出重定義的錯誤了。
挑幾個重點的來詳細說一下。
指針轉換實現的匹配
nullptr
能轉換成任意指針類型。T *
能轉換成 void *
,const void *
轉換成const void*
。類類型轉換實現的匹配
兩個類型提供相同的類型轉換將產生二義性問題。
struct B; struct A { A() = default; A(const B&); //把一個 B 轉換成 A }; struct B { operator A() const; // 也是把一個 B 轉換成 A }; A f(const A&); B b; A a = f(b); //二義性錯誤:f(B::operator A()) 仍是 f(A::A(const B&)) A a1 = f(b.operator A()); //正確:使用 B 的類型轉換運算 A a2 = f(A(b)); //正確:使用 A 的構造函數
類當中定義了多個參數都是算術類型的構造函數或類型轉換運算符,也會產生二義性問題。
struct A { A(int = 0); A(double); operator int() const; operator double() const; }; void f(long double); A a; f(a); //二義性錯誤:f(A::operator int()) 仍是 f(A::operator double())? long l; A a2(l); //二義性錯誤:A::A(int) 仍是 A::A(double)? short s; A a3(s); //正確:使用 A::A(int)
當咱們使用兩個用戶定義的類型轉換時,若是轉換函數以前或以後存在標準類型轉換,則標準類型轉換將決定最佳匹配究竟是哪一個。
部分參考:http://particle128.com/posts/2013/11/name-lookup.html
原文地址:http://simpleyyt.github.io/2016/12/17/function-overload-and-match