C++的函數(shù)
標(biāo)簽(空格分隔): Cpp
函數(shù)是C\C++中重要的功能模塊。這一部分主要總結(jié)C\C++中函數(shù)的主要規(guī)則和用法。
基本知識(shí)
使用函數(shù)必須提供:
- 提供函數(shù)定義
- 提供函數(shù)原型
- 調(diào)用函數(shù)
定義函數(shù)
函數(shù)分為兩類:有返回值和無(wú)返回值
無(wú)返回值定義:
void functionName(parameterList){
statements(s)
return;
}
有返回值定義:
typeName functionName(parameterList){
statements
return value;
}
通用的函數(shù)定義格式:
返回類型 函數(shù)名(參數(shù)列表){
語(yǔ)句
return 返回值;
}
函數(shù)定義注意事項(xiàng):
- 返回值可以是除數(shù)組以外任何類型(但是可以把數(shù)組放到結(jié)構(gòu)體或類中返回)
- 當(dāng)返回值與返回類型不一致時(shí),會(huì)發(fā)生強(qiáng)制類型轉(zhuǎn)換
- 參數(shù)列表為空和參數(shù)列表為void等效
- 不指定參數(shù)列表時(shí),使用(...)
返回值機(jī)制:
函數(shù)通過(guò)將返回值放到指定的內(nèi)存位置,然后調(diào)用函數(shù)從指定的內(nèi)存位置中獲取返回值,在這個(gè)過(guò)程中,調(diào)用函數(shù)和被調(diào)函數(shù)必須就返回值的類型達(dá)成一致,這樣才可以正確的獲得返回值。如何達(dá)成一致?通過(guò)被調(diào)函數(shù)的原型,原型告訴了編譯器這個(gè)函數(shù)的返回值是什么類型,要求分配指定類型的內(nèi)存空間,調(diào)用函數(shù)也需按照此類型獲取返回值。如果不一致,則可能發(fā)生強(qiáng)制類型轉(zhuǎn)換,或者報(bào)錯(cuò)。
函數(shù)原型和函數(shù)調(diào)用
1、為什么需要原型?
- 原型描述了函數(shù)到編譯器的接口
告知編譯器,函數(shù)的返回類型,函數(shù)名稱,參數(shù)列表等信息。此時(shí),編譯器也指定了函數(shù)的返回值要存放的位置。
2、原型的語(yǔ)法是什么?
- 函數(shù)原型是一條語(yǔ)句,必須以分號(hào)結(jié)束。
最簡(jiǎn)單的聲明方式就是復(fù)制函數(shù)頭,以分號(hào)結(jié)尾就可以了。原型中參數(shù)列表不必須有變量名,變量名就相當(dāng)于是一個(gè)占位符,所以不必須與函數(shù)定義中的變量名相同。
3、原型的功能有哪些?
- 降低程序出錯(cuò)的記錄
- 編譯器正確處理函數(shù)返回值
- 編譯器檢查參數(shù)數(shù)目是否正確
- 編譯器可以檢查參數(shù)類型是否正確,可以幫助轉(zhuǎn)換為正確的類型
這一階段的函數(shù)原型檢查屬于靜態(tài)類型檢查**
函數(shù)參數(shù)和按值傳遞
被調(diào)函數(shù)用來(lái)接收參數(shù)的變量叫做形參,調(diào)用函數(shù)用來(lái)傳遞給被調(diào)函數(shù)的參數(shù)稱為實(shí)參。
在函數(shù)聲明中的變量(包括參數(shù))是該函數(shù)私有的。也就是說(shuō),這些變量的作用范圍在函數(shù)中,出了函數(shù)的范圍,內(nèi)存就會(huì)被釋放。所以說(shuō),這些變量都是局部變量。
函數(shù)與數(shù)組
int sum_arr(int arr[],int n)
在上一行代碼中,arr不是數(shù)組,而是指針!但在函數(shù)中可以當(dāng)做是數(shù)組使用。
只有(也就是說(shuō)當(dāng)且僅當(dāng))在函數(shù)頭或函數(shù)原型中int arr[]和int arr是等效的,都表示arr是一個(gè)指針。*
函數(shù)如何使用指針來(lái)處理數(shù)組?
一般來(lái)說(shuō),數(shù)組名就是數(shù)組中第一個(gè)元素的地址。但是數(shù)組名又和數(shù)組的第一個(gè)元素的地址有一些不同之處,包括:
- 數(shù)組聲明使用數(shù)組名來(lái)標(biāo)記存儲(chǔ)位置
- 對(duì)數(shù)組名使用
運(yùn)算符得到的是整個(gè)數(shù)組的長(zhǎng)度(以字節(jié)為單位)
- 對(duì)數(shù)組名使用地址云算符&時(shí),返回整個(gè)數(shù)組的地址
也就是說(shuō),數(shù)組名不同于普通地址的地方在于,數(shù)組名包含了數(shù)組整體的特性。
int sum_arr(const int * begin, const int * end);
上一行代碼介紹了另一種傳遞數(shù)組的方法,傳遞數(shù)組的開(kāi)始指針和結(jié)束指針,遍歷數(shù)組是可以用
for(int i = 0;(begin + i) != end; i++){}
或者
const int* pt;
for(pt = begin;pt != end; pt ++){}
這種用法常在STL中,叫做“超尾”。
指針和const
用const修飾指針有兩種方式:
- 讓指針指向一個(gè)常量對(duì)象。
例如:int const * pt = const int* pt = a。這樣就禁止了利用指針pt來(lái)修改a的值,但是可以修改pt本身。- 將指針變量聲明為常量。
例如:int * const pt = a 。這樣就是禁止更改pt指向的地址,但是可以通過(guò)pt修改a的值。
C++禁止將const的地址賦給非指向const類型的指針,但是可以通過(guò)const_cast進(jìn)行強(qiáng)制類型轉(zhuǎn)換
也就說(shuō),一定要保證,如果一個(gè)內(nèi)存單元是const類型的,那么不管是通過(guò)變量名還是通過(guò)指針都不能更改這個(gè)內(nèi)存單元的值。
要盡量使用const,好處有兩點(diǎn):1、可以避免無(wú)意中修改了原本不希望更改的值;2、接受const參數(shù)的函數(shù),除了可以接受const的實(shí)參,還可以接受非const的實(shí)參。
函數(shù)和二維數(shù)組
難點(diǎn):如何真確地聲明指針?
例子:
int data[3][4] = {{1,2,3,4},{9,8,7,6},{2,4,6,8}};
int total = sum(data,3);
/*如何編寫(xiě)sum函數(shù)的原型?*/
int sum(int(*p)[4],int size);
int sum(int p[][4],int size);
解析:
int(*p)[4]和int p[4]的區(qū)別,前者是說(shuō),p是一個(gè)指針,指向有4個(gè)元素的數(shù)組,每個(gè)元素為int;后者是說(shuō),p是一個(gè)數(shù)組有4個(gè)元素,每個(gè)元素是一個(gè)指針,每個(gè)指針是int。
int(p)[4] == int p[][4];兩者等價(jià),sizeof(p)返回的是1個(gè)指針的內(nèi)存大小,對(duì)p+1,會(huì)跳過(guò)4個(gè)int的內(nèi)存地址。對(duì)于,int *p[4],sizeof(p)返回4個(gè)int *的內(nèi)存大小,對(duì)p+1,會(huì)跳過(guò)1個(gè)int *的內(nèi)存大小。
函數(shù)和C-風(fēng)格字符串
將C-風(fēng)格字符串作為參數(shù)的函數(shù)
有三種形式:
- char數(shù)組
- 字符串字面值
- char* 指針
C-風(fēng)格的字符串和char數(shù)組之間的一個(gè)重要區(qū)別是,字符串結(jié)尾有內(nèi)置的結(jié)束字符。
函數(shù)與結(jié)構(gòu)
結(jié)構(gòu)體賦值,屬于深拷貝;
結(jié)構(gòu)體作為參數(shù)傳遞時(shí),函數(shù)會(huì)生成一個(gè)原來(lái)結(jié)構(gòu)體的副本,和普通類型的形實(shí)結(jié)合是一樣的。
遞歸
包含一個(gè)遞歸調(diào)用的遞歸
遞歸調(diào)用的一般形式:
void recurs(argumentlist){
statements1
if(test)
recurs(arguments)
statements2
}
遞歸的運(yùn)行過(guò)程,當(dāng)進(jìn)入recurs函數(shù)后,如果test為真,則再次調(diào)用recurs函數(shù),一直執(zhí)行到test為假,此時(shí)再執(zhí)行statement2,此時(shí),對(duì)于之前調(diào)用的recurs函數(shù)都依次執(zhí)行statement2,然后結(jié)束函數(shù)。流程如下:
st=>start: start
op1_1=>operation: Into recurs No.1
op1_2=>operation: statements1 No.1
cond1=>condition: test No.1
op1_3=>operation: statements2 No.1
op2_1=>operation: Into recurs No.2
op2_2=>operation: statements1 No.2
cond2=>condition: test No.2
op2_3=>operation: statements2 No.2
op3_1=>operation: Into recurs No.3
op3_2=>operation: statements1 No.3
cond3=>condition: test No.3
op3_3=>operation: statements2 No.3
op4_1=>operation: Into recurs No.4
op4_2=>operation: statements1 No.4
cond4=>condition: test No.4
op4_3=>operation: statements2 No.4
op5_1=>operation: Into recurs No.5 ...
e=>end
st->op1_1->op1_2->cond1
cond1(yes)->op2_1->op2_2->cond2
cond1(no)->op1_3->e
cond2(yes)->op3_1->op3_2->cond3
cond2(no)->op2_3->op1_3->e
cond3(yes)->op4_1->op4_2->cond4
cond3(no)->op3_3->op2_3->op1_3->e
cond4(yes)->op5_1
cond4(no)->op4_3->op3_3->op2_3->op1_3->e
函數(shù)指針
函數(shù)指針基礎(chǔ)知識(shí)
要使用函數(shù)指針,需要:
- 獲取函數(shù)的地址
- 聲明一個(gè)函數(shù)指針
- 使用函數(shù)指針來(lái)調(diào)用函數(shù)
- 獲取函數(shù)地址
要獲取函數(shù)的地址,只要使用函數(shù)名就可以了,函數(shù)名就是一個(gè)函數(shù)的首地址。 - 聲明函數(shù)指針
一般聲明函數(shù)指針的方法:
把函數(shù)原型中的函數(shù)名稱更改為(pf),pf為指針名稱,可以自定義*。聲明一個(gè)函數(shù)指針,必須制定函數(shù)指針指向的函數(shù)類型。例如:
double pam(int);
double (*pf)(int) = pam;//聲明pf指向函數(shù)pam
- 使用指針調(diào)用函數(shù)
如果一個(gè)函數(shù)指針指向了一個(gè)函數(shù),那么這個(gè)指針就相當(dāng)于是這個(gè)函數(shù)的一個(gè)別名,可以像使用原來(lái)函數(shù)那樣,使用這個(gè)指針來(lái)調(diào)用函數(shù),比如:
double pam(int);
double (*pf)(int);
pf = pam;
double x = pam(4); //相當(dāng)于
double y = pf(5); //也相當(dāng)于
double m = (*pf)(8);
在C++中,一個(gè)函數(shù)指針pf,可以使用pf調(diào)用函數(shù),也可以使用(*pf)調(diào)用函數(shù)。
函數(shù)指針舉例
// arfupt.cpp -- an array of function pointers
#include <iostream>
// various notations, same signatures
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(const double *, int); //三個(gè)函數(shù)是一樣的特征標(biāo),一樣的返回類型
int main()
{
using namespace std;
double av[3] = {1112.3, 1542.6, 2227.9};
// pointer to a function
/*
p1是一個(gè)指針,指向函數(shù),這個(gè)函數(shù)的特征標(biāo)是(const double *, int),返回類型是const double*
*/
const double *(*p1)(const double *, int) = f1;
auto p2 = f2; // C++0x automatic type deduction
// pre-C++0x can use the following code instead
/*
p2是一個(gè)指針,指向函數(shù),函數(shù)的特征標(biāo)是(const double *, int),返回類型是const double*
*/
// const double *(*p2)(const double *, int) = f2;
cout << "Using pointers to functions:\n";
cout << " Address Value\n";
/*
(*p1)(av,3)相當(dāng)于p1(av,3)相當(dāng)于f1(av,3)
*(*p1)(av,3)相當(dāng)于*(p1(av,3))相當(dāng)于*(f1(av,3)),意思是:函數(shù)的返回值是一個(gè)const double*,取這個(gè)指針指向的地址的值。
*/
cout << (*p1)(av,3) << ": " << *(*p1)(av,3) << endl;
cout << p2(av,3) << ": " << *p2(av,3) << endl;
// pa an array of pointers
// auto doesn't work with list initialization
/*
pa是一個(gè)有三個(gè)元素的數(shù)組,數(shù)組的元素是指針,指針的類型是指向函數(shù)的指針,指向的那個(gè)函數(shù)的特征標(biāo)是(const double *, int),返回類型是const double *
*/
const double *(*pa[3])(const double *, int) = {f1,f2,f3};
// but it does work for initializing to a single value
// pb a pointer to first element of pa
auto pb = pa;
// pre-C++0x can use the following code instead
/*
pb是一個(gè)指針,指向的是一個(gè)指針,被指向的指針指向一個(gè)函數(shù),這個(gè)函數(shù)的特征標(biāo)是(const double *, int),返回類型是const double *。
*/
// const double *(**pb)(const double *, int) = pa;
cout << "\nUsing an array of pointers to functions:\n";
cout << " Address Value\n";
for (int i = 0; i < 3; i++)
cout << pa[i](av,3) << ": " << *pa[i](av,3) << endl;
cout << "\nUsing a pointer to a pointer to a function:\n";
cout << " Address Value\n";
for (int i = 0; i < 3; i++)
cout << pb[i](av,3) << ": " << *pb[i](av,3) << endl;
// what about a pointer to an array of function pointers
cout << "\nUsing pointers to an array of pointers:\n";
cout << " Address Value\n";
// easy way to declare pc
auto pc = &pa;
// pre-C++0x can use the following code instead
/*
pc是一個(gè)指針,指向一個(gè)有3個(gè)元素的數(shù)組,這個(gè)數(shù)組的元素是指針,指向的是函數(shù),函數(shù)的特征標(biāo)是(const double *, int),返回類型是const double *
pc是指向3個(gè)元素的數(shù)組,因此*pc就是那個(gè)有3個(gè)元素的數(shù)組,那么(*pc)[0]就是n
*/
// const double *(*(*pc)[3])(const double *, int) = &pa;
cout << (*pc)[0](av,3) << ": " << *(*pc)[0](av,3) << endl;
// hard way to declare pd
const double *(*(*pd)[3])(const double *, int) = &pa;
// store return value in pdb
const double * pdb = (*pd)[1](av,3);
cout << pdb << ": " << *pdb << endl;
// alternative notation
cout << (*(*pd)[2])(av,3) << ": " << *(*(*pd)[2])(av,3) << endl;
// cin.get();
return 0;
}
// some rather dull functions
const double * f1(const double * ar, int n)
{
return ar;
}
const double * f2(const double ar[], int n)
{
return ar+1;
}
const double * f3(const double ar[], int n)
{
return ar+2;
}
感謝auto
在C++11后,C++中增加了auto關(guān)鍵字,可以自動(dòng)進(jìn)行類型推斷。但是auto只能用于單值初始化,而不能用于初始化列表,例如:
const double *( *pa[3])(const double *,int) = {f1,f2,f3};
//不能使用auto pa[3] = {f1,f2,f3};
這個(gè)時(shí)候就不可以用auto進(jìn)行類型推斷。
題外話
在C++中,當(dāng)知道了一個(gè)內(nèi)存地址保存的是什么類型時(shí),那么所有關(guān)于這個(gè)內(nèi)存地址的操作,都需要遵循這個(gè)內(nèi)存地址的類型所規(guī)定的操作。
C++內(nèi)聯(lián)函數(shù)
使用方法(一下二選一):
- 在函數(shù)聲明前加上關(guān)鍵字inline
- 在函數(shù)定義前加上關(guān)鍵字inline
注意事項(xiàng):
- 內(nèi)聯(lián)函數(shù)不能遞歸
- 聲明為內(nèi)聯(lián)函數(shù),但是編譯器不一定實(shí)現(xiàn)成內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)比宏更好用!
引用變量
引用變量相當(dāng)于一個(gè)變量的別名,但是這個(gè)別名有什么用呢?
引用變量主要用途是用作函數(shù)的形參,通過(guò)使用引用變量作為形參,那么函數(shù)就是使用的傳入的實(shí)參,而不是實(shí)參的副本。
引用變量必須在聲明時(shí)進(jìn)行初始化!!
引用變量和指針:引用變量更像是指針常量,指向某一個(gè)變量后,便不能再被更改。
int a = 10;
int & b = a; //b是a的別名
將引用用作函數(shù)的參數(shù)
當(dāng)把引用當(dāng)做是函數(shù)的參數(shù)時(shí),這種傳參數(shù)方式叫做按引用傳遞。
按引用傳遞時(shí),函數(shù)處理的是實(shí)參本身,而不是副本。
引用的屬性和特別之處
按引用傳遞參數(shù)時(shí),函數(shù)對(duì)于形參的改變,也會(huì)導(dǎo)致實(shí)參的改變。如果不想造成實(shí)參的改變,那么在按引用傳遞時(shí),應(yīng)使用常量引用。
一般來(lái)說(shuō),如果參數(shù)類型是基本數(shù)據(jù)類型,最好使用按值傳遞;如果數(shù)據(jù)比較大(如結(jié)構(gòu)或類的對(duì)象時(shí)),最好使用按引用傳遞。
按引用傳遞時(shí),對(duì)函數(shù)傳入的參數(shù)必須是左值,不能是右值。
臨時(shí)變量、引用參數(shù)和const
一般來(lái)說(shuō),普通變量不能和引用變量進(jìn)行類型轉(zhuǎn)換。但是在向函數(shù)傳參數(shù)時(shí),部分情況會(huì)發(fā)生類似的類型轉(zhuǎn)換,就是在傳入的實(shí)參滿足下邊條件時(shí),會(huì)生成一個(gè)臨時(shí)變量,傳入函數(shù)。這些臨時(shí)變量只在函數(shù)調(diào)用期間存在,此后編譯器可以隨意將其刪除。
在引用參數(shù)帶有const關(guān)鍵字時(shí):
- 實(shí)參的類型正確,但不是左值
- 實(shí)參的類型不正確,但可以轉(zhuǎn)換成正確的類型。
左值是可以被引用的數(shù)據(jù)對(duì)象。非左值包括字面常量和包含多項(xiàng)的表達(dá)式?,F(xiàn)在,常規(guī)變量和const變量都是左值,因?yàn)榭梢酝ㄟ^(guò)地址訪問(wèn)他們。而const變量屬于不可修改的左值。
函數(shù)的引用參數(shù)使用const的好處:- 使用const可以避免無(wú)意中修改數(shù)據(jù)
- 使用const可以使函數(shù)既可以處理const的實(shí)參,還可以處理非const的實(shí)參
- 使用const引用使函數(shù)能夠正確生成并使用臨時(shí)變量
右值引用 &&
返回引用時(shí),要注意,不能返回那種在函數(shù)返回后不存在的內(nèi)存單元的引用。要避免這樣的問(wèn)題,兩招:
- 返回一個(gè)作為參數(shù)傳遞給函數(shù)的引用
- 返回用new新建的存儲(chǔ)空間。(別忘了在函數(shù)外delete)
accumulate(dup,five) = four;
這條語(yǔ)句成立的前提是,accumulate函數(shù)返回的位置是一個(gè)可以被修改的內(nèi)存單元,也就是說(shuō),返回值是一個(gè)左值,那么這條語(yǔ)句就成立。但是常規(guī)(非引用)返回類型是右值---不能通過(guò)地址訪問(wèn)的值,因?yàn)檫@種返回值位于臨時(shí)內(nèi)存單元中。
何時(shí)使用引用參數(shù)
使用引用參數(shù)的原因:
- 程序員能夠修改調(diào)用函數(shù)中的對(duì)象
- 通過(guò)傳遞引用而不是整個(gè)數(shù)據(jù)對(duì)象,可以提高程序的運(yùn)行速度。
什么時(shí)候使用按引用傳參,什么時(shí)候使用按指針傳參,什么時(shí)候使用按值傳參?
對(duì)于使用傳遞的值,而不作修改的函數(shù):
- 如果數(shù)據(jù)對(duì)象很小,則按值傳遞
- 如果數(shù)據(jù)對(duì)象是數(shù)組,則使用指針,因?yàn)檫@是唯一的選擇,并將指針聲明為指向const的指針
- 如果數(shù)據(jù)對(duì)象是較大的結(jié)構(gòu),則使用const指針或者const引用,以提高程序效率。
- 如果數(shù)據(jù)對(duì)象是類對(duì)象,則使用const引用
對(duì)于修改調(diào)用函數(shù)中數(shù)據(jù)的函數(shù):
- 如果數(shù)據(jù)對(duì)象是內(nèi)置數(shù)據(jù)類型,則使用指針。
- 若數(shù)據(jù)對(duì)象是數(shù)組,則只能使用指針
- 如果數(shù)據(jù)對(duì)象是結(jié)構(gòu),則使用引用或指針
- 如果數(shù)據(jù)對(duì)象是類對(duì)象,則使用引用
默認(rèn)參數(shù)
如何設(shè)置默認(rèn)值?必須通過(guò)函數(shù)原型!添加默認(rèn)參數(shù)時(shí),必須是從右到左添加,順序不能變。
只有原型指定了默認(rèn)值,函數(shù)定義與沒(méi)有默認(rèn)參數(shù)時(shí)一樣。
函數(shù)重載
函數(shù)重載的關(guān)鍵是函數(shù)的參數(shù)列表,參數(shù)列表又稱為函數(shù)的特征標(biāo),包括三部分,參數(shù)的數(shù)目、類型、排列順序。只有這三者同時(shí)相同時(shí),才可以說(shuō)兩個(gè)函數(shù)的特征標(biāo)相同。(特征標(biāo)不包含函數(shù)的返回類型喲?。?br>
C++中的函數(shù)重載,可以允許函數(shù)名稱相同,但是特征標(biāo)必須不同。C++把類型的引用和類型本身視為同一個(gè)特征標(biāo)!
在函數(shù)重載時(shí),如果傳入?yún)?shù)與所有函數(shù)的特征標(biāo)都不完全相同時(shí),會(huì)對(duì)實(shí)參進(jìn)行類型轉(zhuǎn)換,前提是只有一個(gè)函數(shù)可以用來(lái)作為類型轉(zhuǎn)換的目標(biāo),如果有多個(gè)時(shí),就會(huì)發(fā)生錯(cuò)誤。例如:
void fun(string,double);
void fun(string,long);
int a = 10;
fun("chongzai",a);//發(fā)生錯(cuò)誤,因?yàn)橛袃蓚€(gè)可以接受的函數(shù),發(fā)生了二義性。
const是可以用來(lái)區(qū)分特征標(biāo)的。函數(shù)可以重載,是因?yàn)镃++編譯器執(zhí)行了名稱修飾。
函數(shù)模板
函數(shù)模板特性也被稱為參數(shù)化類型。模板定義:
template <typename AnyType>
void 函數(shù)名(AnyType a,AnyType b){
}
- template關(guān)鍵字和typename(或者class)是必需的。
- <>
- 模板不創(chuàng)建任何函數(shù),只是告訴編譯器如何定義函數(shù)
- 函數(shù)模板不能縮短可執(zhí)行程序
重載的模板
可以像常規(guī)函數(shù)重載那樣,定義重載的模板。
模板的局限性
template <typename T>
void f(T a,T b){
statements
}
局限性主要體現(xiàn)在,模板所假設(shè)的類型T可能不能滿足模板中statements所用到的部分操作,這是就會(huì)出錯(cuò)。比如,如果T為structe類型,statements中有 a + b等等。
解決的方法有兩個(gè):
1、重載操作符
2、為特定對(duì)象提供具體化的模板定義
這一章主要講解方法二。
顯式具體化
具體化函數(shù)定義---顯示具體化。當(dāng)編譯器找到與函數(shù)調(diào)用匹配的具體化定義時(shí),就不再尋找模板了。
具體化的方法和具體化的特點(diǎn):
- 對(duì)于給定的函數(shù)名,可以有非模板函數(shù)、模板函數(shù)和顯式具體化模板函數(shù)以及他們的重載版本
- 顯式具體化的原型和定義應(yīng)以template<>開(kāi)頭,并通過(guò)名稱來(lái)指出類型
- 對(duì)于編譯器,選擇重載的優(yōu)先級(jí)是:非模板函數(shù)>具體化模板函數(shù)>模板函數(shù)
void Swap(job& job&) //非模板
template <typename T>
void Swap(T&,T&) //模板
template <> void Swap<job>(job& job&); //具體化模板
實(shí)例化和具體化
實(shí)例化:編譯器使用模板為特定類型生成函數(shù)定義時(shí),得到的是模板實(shí)例
實(shí)例化可以隱式實(shí)例化,也可以顯示實(shí)例化。隱式實(shí)例化是編譯器通過(guò)函數(shù)的參數(shù),自動(dòng)推斷函數(shù)實(shí)例化的定義的,顯示實(shí)例化是通過(guò)主動(dòng)說(shuō)明函數(shù)的定義。
顯示具體化,是告訴編譯器,在生成特定函數(shù)定義的時(shí)候,需要使用函數(shù)具體化的函數(shù)模板,而不是普通函數(shù)模板。
隱式實(shí)例化,顯式實(shí)例化和顯式具體化統(tǒng)稱為具體化。相同之處是,他們表示的都是使用具體類型的函數(shù)定義,而不是通用描述。
template---顯式實(shí)例化
template <> ----顯示具體化
通用模板、顯示具體化、顯示實(shí)例化、隱式實(shí)例化的區(qū)別:
通用模板:就是一個(gè)模板函數(shù),可以用來(lái)實(shí)現(xiàn)范式?!盎A(chǔ)款模板” 是模板
顯示具體化:在通用模板的基礎(chǔ)上,針對(duì)某一種類型專門實(shí)現(xiàn)一種模板,當(dāng)模板函數(shù)需要實(shí)例化為這個(gè)類型的函數(shù)時(shí),要求編譯器使用顯示具體化模板,而不是通用模板。因此,顯示具體化也是一種模板。是模板
顯示實(shí)例化:在函數(shù)聲明中,進(jìn)行顯示實(shí)例化,那么編譯器遇到這個(gè)聲明時(shí),根據(jù)模板實(shí)現(xiàn)函數(shù)的定義。這是顯示要求編譯器定義一種指定類型的函數(shù)。是定義
隱式實(shí)例化:當(dāng)編譯器遇到一個(gè)和函數(shù)模板名稱相同的函數(shù)語(yǔ)句時(shí),編譯器根據(jù)這個(gè)語(yǔ)句中參數(shù)的類型,自動(dòng)根據(jù)模板生成一個(gè)定義,無(wú)需指定類型,編譯器根據(jù)參數(shù),自動(dòng)推斷類型。是定義
編譯器選擇使用哪個(gè)函數(shù)版本
函數(shù)重載不緊可以在不同的函數(shù)間,也可以在不同的函數(shù)模板間,也可以在函數(shù)模板和函數(shù)之間進(jìn)行重載。
因此,產(chǎn)生一個(gè)問(wèn)題,編譯器應(yīng)該選擇使用哪一個(gè)函數(shù)版本呢?
重載解析過(guò)程:
- 第一步:創(chuàng)建候選函數(shù)列表。只要函數(shù)名稱相同就可以
- 第二步:使用候選函數(shù)列表創(chuàng)建可行函數(shù)列表。要求函數(shù)的特征標(biāo)相同,會(huì)發(fā)生隱式類型轉(zhuǎn)換,也就是說(shuō)這一步要求所有的函數(shù)放到程序的那個(gè)位置都可以執(zhí)行
- 第三部:確定最佳的可行函數(shù)。在所有的可行函數(shù)中,找到一個(gè)編譯器進(jìn)行最少操作就可以執(zhí)行的最佳,就是說(shuō),這個(gè)函數(shù)越不需要編譯器幫助就越好
確定最佳函數(shù)的順序
大等級(jí):
完全匹配 > 提升轉(zhuǎn)換 > 標(biāo)準(zhǔn)轉(zhuǎn)換 > 用戶定義的轉(zhuǎn)換同等級(jí)時(shí):
常規(guī)函數(shù) > 具體化模板 > 通用模板
注:如果在同等級(jí)時(shí),還有兩個(gè)相同優(yōu)先級(jí)別的函數(shù),那么重載會(huì)報(bào)錯(cuò)!!錯(cuò)誤為二義性三種轉(zhuǎn)換
- 無(wú)關(guān)緊要要的轉(zhuǎn)換,也就是說(shuō),這些形實(shí)參的轉(zhuǎn)換可以忽略不計(jì),相當(dāng)于是完全匹配
| 從實(shí)參 | 到形參 | 備注 |
| ----------- | ------ | ---- |
| Type | Type& | 引用 |
| Type& | Type |
| Type[] | Type* | 數(shù)組 |
| Type(argument-list)| Type(*)(argumente-list)| 函數(shù) |
| Type | const Type| 常量 |
| Type | volatile Type| |
| Type* | const Type | 指針 |
| Type* | volatile Type| |
多個(gè)參數(shù)的函數(shù)進(jìn)行匹配的原則:
一個(gè)函數(shù)要比其他函數(shù)都合適,其所有參數(shù)的匹配程度都必須不比其他函數(shù)差,同時(shí)至少有一個(gè)參數(shù)的匹配程度比其他函數(shù)都高。
C++11特性
decltype關(guān)鍵字:
decltype可以通過(guò)語(yǔ)句推斷類型。例如:
int x;
decltype(x) y; //讓y的類型為x的類型
decltype(x+y) xpy; //讓xpy的類型為語(yǔ)句x+y的結(jié)果的類型
后置返回類型
auto h(int x, float y) --> double;
//auto是一個(gè)占位符,->double稱為后置返回類型。
//結(jié)合decltype就可以解決使用模板時(shí)不知道返回值什么類型的問(wèn)題了。例如:
template<class T1,class T2>
auto gt(T1 x, T2 y) -> decltype(x + y){
...
return x + y;
}
在此輸入正文