2. C++函數

2.1 內聯函數

C++內聯函數其實是對C中的宏的優化(或者說新的實現方法)。使用內聯函數代替宏能避免某些錯誤的風險。
至少在函數聲明與函數定義之中的一處使用關鍵字inline,可以使函數成爲內聯函數。在類聲明中定義的函數會被自動轉換爲內聯函數。
內聯函數的調用語句會被編譯器自動替換爲函數的代碼。從而省去了調用函數所需的時間。

2.2 函數重載

通過創建擁有不同參數列表的同名函數,可以實現函數重載。重載函數的返回值可以不同,但不能作爲區分不同重載函數的標準,也就是說不能用來進行函數重載。

2.3 函數模版

第三代具體化(IOS/ANSI C++標準)

  • 對於給定的函數名,可以有非模版函數、模板函數、和顯式具體化模板函數及它們的重載版本。
  • 顯式具體化的原型與定義應以template<>打頭,並來指出類型。
  • 具體化模板優先於常規模板,而非模板函數優先於模板函數。

隱式實例化顯式實例化顯式具體化統稱爲具體化

模版函數定義(typename可以用 class代替):

template<typename AnyType>
bool isgreater(AnyType a, AnyType b)
{
    if(a > b)
        return true;
    return false;
}

2.3.1 decltype 關鍵字(C++11)

decltype的作用是檢測表達式的類型(type),並將其用來指出類型。常常在模板函數中使用。

decltype(expression) var;

decltype還可以搭配auto實現的後置類型聲明處理特殊的函數返回值。常常用於模板函數。

後置類型聲明

auto h(int x, float y) -> double;

搭配decltype,用於模板函數

template<typename T1, typename T1>
auto h(T1 x, T2 y) -> decltype(x + y)
{
    // ...
    return x + y;
}

2.3.2 顯式具體化

假設定義了一個類Data, Data中有成員number

// 原型
template<> isgreater<Data>(Data, Data);    // <Data>是可選的
// 定義
template<> isgreater<Data>(Data a, Data a)    // <Data>是可選的
{
    if(a.number > b.number)
        return true;
    return false;
}

2.3.3 實例化

  • 隱式實例化:模板函數本身並不會創建函數, 在使用模板函數時會創建相應地函數, 這種實例化稱爲隱式具體化
  • 顯式實例化:具體做法是聲明所需的種類, 用<>指出類型, 並在聲明開頭添加關鍵字template
template isgreater<int>(int, int);

!注意: 在同一文件(或轉換單元)中若已有顯式具體化, 編譯器不會隱式創建實例, 使用同一類型的顯式具體化和顯式實例化將會導致錯誤

2.4 重載解析

2.4.1 重載解析過程

  1. 創建候選函數列表。其中包括與被調用函數名稱相同的函數與模板函數。
  2. 使用候選函數列表創建可行函數列表。其中包括參數數目正確且能匹配的函數。
  3. 確定是否有最佳函數。如果有,則調用它,否則函數調用出錯。

2.4.2 匹配"等級"

可行函數從最佳到最差的順序:

  1. 完全匹配(常規函數在同類中優先於模板函數)
  2. 提升轉換(例如, charshort自動轉換爲int, float自動轉換爲double, double自動轉換爲Long double)
  3. 常規轉換(例如, int轉換爲char, long轉換爲double)
  4. 用戶定義的轉換(例如類中定義的轉換函數)

識別完全匹配時, C++允許進行"無關緊要的匹配", 如下(Type指其他類型而非變量名):

從實參 到形參
Type Type &
Type& Type
Type [] Type* 數組到指針
Type(argument-list) Type (*)(argument-list) 函數名到函數指針
Type const Type
Type volatile Type
Type* const Type*
Type* volatile Type*

2.4.3 完全匹配與最佳匹配

  • 僅對非模板函數, 指向非const數據的指針或引用優先於指向const數據的指針或引用
  • 非模版函數優先於模版函數(如上面所提到的)
  • 顯式具體化優先於隱式具體化()

對於第一條, 需注意模板函數非模板函數的不同, 參考以下代碼:

1. 對非模板函數, 見上文

#include <iostream>
using namespace std;

void func(int &){
    cout << "int &" << endl;
}

void func(const int &){
    cout << "const int &" << endl;
}

int main()
{
    int i;
    int& ri = i;
    func(ri);
    return 0;
}

輸出:

int &

2. 對模板函數, 見下文"部分排序規則"

#include <iostream>
using namespace std;

template<typename T>
void func(T &){
    cout << "T &" << endl;
}

template<typename T>
void func(const T &){
    cout << "const T &" << endl;
}

int main()
{
    int i;
    int& ri = i;
    func(ri);
    return 0;
}

輸出:

const T &

2.4.3.1 部分排序規則

有多個與函數調用的自變量列表匹配的函數模板可用。 C++ 定義了函數模板的部分排序以指定應調用的函數。 由於有一些模板可能會視爲專用化程度相同,因此排序只是部分的。

編譯器從可能的匹配項中選擇可用的專用化程度最高的模板函數。 例如, 如果一個函數模板採用類型T , 而另一個所採用T*的函數模板可用, 則T*認爲該版本更爲專用化。 只要參數是指針類型, 就優先使用T*泛型版本, 即使兩者都是允許的匹配項。

通過以下過程可確定一個函數模板候選項是否更加專用化(more specialized):

  1. 考慮兩個函數模板:T1 和 T2。
  2. 將 T1 中的參數替換爲假想的唯一類型 X。
  3. 不進行任何隱式轉換,查看 T2 是否爲此時 T1 中參數列表的有效模板。
  4. 對 T1 和 T2 執行相反的過程。
  5. 如果一個模板參數列表是另一個模板的有效模板參數列表, 則認爲該模板不是專用於其他模板, 但反之不成立。 如果通過使用上一步, 兩個模板都同時構成彼此的有效自變量, 則它們被視爲同等專用化, 當你嘗試使用它們時, 將會產生不明確的調用結果。
  6. 使用以下規則:
    1. 針對特定類型的模板的專用化程度高於採用泛型類型參數的模板。
    2. T*採用一個模板的專用化比只T採用一個模板更爲專用, 因爲X*假設類型是T模板參數的有效參數, 但X它不是有效的參數T*模板參數。
    3. const TT更專業化, 因爲const X是模板參數T的有效參數, 但const T不是模板參數X的有效參數。
    4. const T*T*更專業化, 因爲const X*是模板參數T*的有效參數, 但const T*不是模板參數X*的有效參數。

簡單地說, 哪一個模板指出的細節更多就使用哪個

示例

// partial_ordering_of_function_templates.cpp
// compile with: /EHsc
#include <iostream>

template <class T> void f(T) {
   printf_s("Less specialized function called\n");
}

template <class T> void f(T*) {
   printf_s("More specialized function called\n");
}

template <class T> void f(const T*) {
   printf_s("Even more specialized function for const T*\n");
}

int main() {
   int i =0;
   const int j = 0;
   int *pi = &i;
   const int *cpi = &j;

   f(i);   // Calls less specialized function.
   f(pi);  // Calls more specialized function.
   f(cpi); // Calls even more specialized function.
   // Without partial ordering, these calls would be ambiguous.
}

輸出

Less specialized function called
More specialized function called
Even more specialized function for const T*

2.4 小細節

2.4.1 默認參數

函數聲明中可以指定參數的默認值。
第一個默認參數後只有默認參數。

2.4.2 指針與"數組"

在函數頭與函數聲明中, int arr[]int* arr等效

2.4.3 臨時變量與引用

僅當引用形參爲const時, 編譯器纔會在實參與形參不匹配時生成臨時變量。

  • 實參的類型正確, 但不是左值
  • 實參的類型不正確, 但可以轉換爲正確的類型

C中一開始將等式左邊的實體稱爲左值, 但在引入const關鍵字後情況發生了變化。現在的情況是將可以被引用的數據對象稱爲引用, 是否爲const對是否爲左值並沒有影響。

  • 左值: 變量、數組元素、結構成員、引用、解除引用的指針(如*p, p爲指針)
  • 非左值: 字面常量(C-style 字符串除外, 其值類似指針常量)、包含多項的表達式

若形參爲普通引用, C++中一般禁止創建臨時變量。
因爲使用普通引用一般是爲了修改實參的值, 若允許產生臨時變量, 可能會產生不易察覺的錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章