C++ 函數重載與函數匹配

《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),肯定候選函數數組

名字查找有兩方面:函數

  • 常規查找(normal lookup)
  • 實參決定的查找(argument-dependent lookup,ADL)

全部函數調用都會進行常規查找,只有函數的實參包括類類型對象或指向類類型對象的指針/引用的時候,纔會進行實參決定的查找。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)
}

爲了肯定最佳匹配,實參類型到形參類型的轉換等級以下:

  1. 精確匹配:
    • 實參類型和形參類型相同。
    • 實參從數組類型或函數類型轉換成對應的指針類型。
    • 向實參添加頂層const或者從實參中刪除頂層const
  2. 經過const轉換實現的匹配。
  3. 經過類型提高實現的匹配。
  4. 經過算術類型轉換或指針轉換實現的匹配。
  5. 經過類類型轉換實現的匹配。

通常不會存在這個階段不會同時存在兩個以上的精確匹配,由於兩個精確的匹配在本質上是等價的,在定義重載函數時,編譯器可能就報出重定義的錯誤了。

挑幾個重點的來詳細說一下。

指針轉換實現的匹配

  • 0 或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

相關文章
相關標籤/搜索