文章目錄
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 重載解析過程
- 創建候選函數列表。其中包括與被調用函數名稱相同的函數與模板函數。
- 使用候選函數列表創建可行函數列表。其中包括參數數目正確且能匹配的函數。
- 確定是否有最佳函數。如果有,則調用它,否則函數調用出錯。
2.4.2 匹配"等級"
可行函數從最佳到最差的順序:
- 完全匹配(常規函數在同類中優先於模板函數)
- 提升轉換(例如,
char
和short
自動轉換爲int
,float
自動轉換爲double
,double
自動轉換爲Long double
) - 常規轉換(例如,
int
轉換爲char
,long
轉換爲double
) - 用戶定義的轉換(例如類中定義的轉換函數)
識別完全匹配時, 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):
- 考慮兩個函數模板:T1 和 T2。
- 將 T1 中的參數替換爲假想的唯一類型 X。
- 不進行任何隱式轉換,查看 T2 是否爲此時 T1 中參數列表的有效模板。
- 對 T1 和 T2 執行相反的過程。
- 如果一個模板參數列表是另一個模板的有效模板參數列表, 則認爲該模板不是專用於其他模板, 但反之不成立。 如果通過使用上一步, 兩個模板都同時構成彼此的有效自變量, 則它們被視爲同等專用化, 當你嘗試使用它們時, 將會產生不明確的調用結果。
- 使用以下規則:
- 針對特定類型的模板的專用化程度高於採用泛型類型參數的模板。
- 僅
T*
採用一個模板的專用化比只T
採用一個模板更爲專用, 因爲X*
假設類型是T
模板參數的有效參數, 但X
它不是有效的參數T*
模板參數。 const T
比T
更專業化, 因爲const X
是模板參數T
的有效參數, 但const T
不是模板參數X
的有效參數。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++中一般禁止創建臨時變量。
因爲使用普通引用一般是爲了修改實參的值, 若允許產生臨時變量, 可能會產生不易察覺的錯誤。