文章目錄
-
- 1、形參帶預設值的函數
- 2、inline内聯函數
- 3、函數重載
1、形參帶預設值的函數
說到形參帶預設值的函數我們還是引用我們最常寫的函數,并給出語句指令執行方式如下:
#include<iostream>
#include<string.h>
using namespace std;
int sum(int a, int b)
{
int temp = 0;
temp = a + b;
return temp;
}
int main()
{
int a = 10;
int b = 20;
int ret = sum(a, b);
cout << "ret:" << ret << endl;
return 0;
}
那麼形參如何給實參傳參數呢?以下是給出的幾種傳遞方式,以及傳參時應該注意的規則。
規則一:給預設值的時候,從右向左給
如下的方式是正确的
int sum(int a, int b=20)
{
int temp = 0;
temp = a + b;
return temp;
}
但是以下的方式就是錯誤的
int sum1(int a=10, int b )
{
int temp = 0;
temp = a + b;
return temp;
}
函數的調用過程中形參的入棧順序是自右向左的,針對函數的實參和形參的比對順序是自左向右的。
規則二:相比于不傳參數,傳入參數的效率更高,因為少了move指令。是以預設值一般都設定在聲明點
我們來看看,具體的語句指令
int ret = sum(a, b);的指令如下:
mov eax,dword ptr[ebp-8]
push eax
mov ecx,dword ptr[ebp-4]
push ecx
call sum
ret = sum(a);的指令調用如下:
push 14H
mov ecx,dword ptr[ebp-4]
push ecx
call sum
ret = sum2();的指令調用如下:它的效率等同于sum2(20,50)
push 14H
push 0AH
call sum
規則三:定義時可以給形參預設值,聲明也可以給形參預設值。
申明的時候給預設值,函數體放在主函數後面
int sum3(int a = 10, int b = 20);
int main()
{
......
}
int sum3(int a, int b = 20)
{
int temp = 0;
temp = a + b;
return temp;
}
規則四:形參給預設值的時,不管是定義處給還是聲明處給,形參預設值隻能出現一次
如下的傳遞方式則是錯誤的,形參的預設值出現了兩次
int sum3(int a = 10, int b = 20);
int sum3(int a , int b = 20);
但是如下的方式則是正确的,雖然第一個的形參指派不是從右向左的但是第二個等式彌補了錯誤
int sum3(int a = 10, int b );
int sum3(int a, int b = 20);
2、inline内聯函數
還是使用文章最開始的時候我們給出的代碼進行分析。
1、什麼是inline内聯函數?
例如下面一段語句
int ret = sum(a+b);
此處有有函數調用的開銷。包括參數壓棧,函數棧幀的開辟和回退過程。x+y 有兩個指令 mov add mov 如果有10000次的x+y操作,那麼将會有大量的時間用在了函數調用的開銷上。
此時我們就可以使用内聯函數。内聯函數就是在編譯過程中,就沒有函數的調用開銷,在函數的調用點直接把函數的代碼進行展開處理(在符号表中也不用生成函數符号了)
是以
内聯函數的處理過程
就相當于在執行到int ret = sum(a, b);時,直接把sum函數展開,把a,b直接替換成了x,y變成了int ret = a+b;
2、内聯函數和宏的差別
從上面的概念裡面我們可以了解到内聯函數可以看做是C++專門用來替代c的宏函數的解決方案。但是具體他們的差別如下:
-
:内聯函數是在編譯階段将代碼嵌入到調用點,而define是在預編譯階段進行簡單的文本替換。處理時機不同
-
:内聯函數具有類型安全、文法檢查而宏函數沒有此功能安全性
-
:内聯函數可以是成員函數,通路類的成員變量,但是宏函數無法通路類的私有或者保護成員通路權限
-
:宏是沒有辦法進行調試的,内聯函數在debug版本下和普通函數一樣,出了問題很友善進行斷點調試,定位問題。調試
3、内聯函數與static修飾的函數差別
-
:因為内聯函數是直接代碼展開,是以沒有開棧和清棧的開銷。static修飾的函數和普通函數一樣,有開棧和清棧的開銷開銷方面
-
:内聯函數因為不生成符号是以隻在本檔案中可見,static函數是生成local符号也是在目前檔案下可見。相同點——本檔案下可見
4、内聯函數和普通函數的差別
普通函數的調用在彙編上有标準的push壓實參指令,然後call指令調用函數,給函數開辟棧幀,函數運作完成,有函數退出棧幀的過程。
内聯函數在編譯階段,在函數的調用點将函數的代碼展開,省略了函數棧幀開辟回退的調用開銷,效率比較高。
5、内聯函數的限制
-
:當函數執行的開銷小于函數清棧開棧的開銷時,反映出函數體非常小,就建議使用内聯。因為如果以代碼膨脹為代價就會使得目标檔案非常龐大。内聯函數的使用建議
-
:即在debug版本下,内聯函數和普通函數一樣,也有棧幀的開辟和回退内聯函數隻在release版本生效
-
真正是否是内聯函數還是編譯器決定的,是以,遞歸、循環、分支switch是一定不會處理成内聯的inline隻是建議編譯器把這個函數處理成内聯函數
-
,隻有把内聯的整個實作寫在頭檔案中,到時候頭檔案被其他源檔案包含過後,預編譯的第一步處理,直接把頭檔案中的所有東西進行展開過後,目前源檔案下就會出現内聯的定義,由調用點進行調用。一般将内聯函數寫在.h檔案當中
3、函數重載
說到函數重載,我們首先給出下列代碼
#include<iostream>
#include<typeinfo>
using namespace std;
bool compare(int a, int b)
{
cout << "compare_int_int" << endl;
return a > b;
}
bool compare(double a, double b)
{
cout << "compare_double_double" << endl;
return a > b;
}
bool compare(const char* a, const char* b)//compare_char*_char*
{
cout << "compare_char*_char*" << endl;
return strcmp(a ,b)>0;
}
int main()
{
compare(10, 20);
compare(10.0, 20.0);
compare("aaa","bbb");
return 0;
}
代碼執行結果為:
細細觀察上面幾個函數,我們可以發現,他們的函數名相同,隻是參數清單類型不同而已,但是同樣的代碼放在.c檔案中就不可以了,這是為什麼呢?
1、c++為什麼支援函數重載,但是c語言不支援這其實與函數符号的生成有關系。
1、c語言裡面的函數符号生成規則
-
c标準調用約定
C語言源代碼檔案中所有函數經過編譯以後,相對應的符号名前加上下劃線“_”,這其實就隻和函數名相關。
就拿上面的代碼例子來說,最終生成的函數符号都是
是以沒辦法區分開這三個函數,就沒辦法支援函數重載_compare
-
:函數名前加下劃線,函數名後加“@”符号和其參數位元組_stdcall調用約定
-
:函數名前加“@”符号,函數名後加“@”字元和其參數位元組。_fastcall調用約定
2、c++裡面的函數符号生成規則
主要和函數原型有關。函數原型主要包括了函數的傳回值、函數名稱和形參清單。
【舉個栗子】
int Sum(int,int);//[email protected]@[email protected]
double Sum(double,double);//[email protected]@[email protected]
char Sum(char,char);//[email protected]@[email protected]
具體的規則如下:
-
:"?"+函數名+參數表的開始辨別"@@YA"+函數傳回類型代号+參數類型代号+結束辨別"@Z"或"Z”(無參數)_cdecl調用約定
-
:?”+函數名+參數表的開始辨別“@@YG”+函數傳回類型代号+參數類型代号 +結束辨別“@Z”或“Z”(無參數)。_stdcall調用約定
-
:“?”+函數名+參數表的開始辨別 “@@YI”+ 函數傳回類型代号+參數類型代号 +結束辨別“@Z”或“Z”(無參數)。_fastcal調用約定
下表示參數類型代号表
X | D | E | F | H | I | J | K | M | N | _N |
---|---|---|---|---|---|---|---|---|---|---|
void | char | unsigned char | short | int | unsigned int | long | unsigned long | float | double | bool |
由此引出我們來探讨一下哪些元素是可以影響函數重載的。
2、影響函數重載的元素探讨
傳回值——無影響
傳回值其實并沒有什麼實質的影響,因為在調用點隻看得見函數名和形參清單。
函數名——無影響
因為函數重載本來就是主要針對同名的函數,是以函數名不同也不能影響重載
形參清單——有影響
當形參類型不同、形參個數不同還有形參順序不同的時候都會構成函數重載
3、函數重載三要素
-
:一組函數,函數名相同同名
-
:一組函數,參數清單的個數、順序或者類型不同不同參
-
:一組函數要稱得上重載,一定是先處在同一個作用域當中同一個作用域
為了解釋相同作用域是什麼意思,我們舉一個反例,在上面代碼的基礎上,我們把main函數改成這樣:
int main()
{
bool compare(int a, int b);
compare(10, 20);
compare(10.0, 20.0);
compare("aaa","bbb");
return 0;
}
編譯的時候我們會發現,這個程式是錯誤的。
因為當有這個函數聲明的時候,大家都去一股腦的調用參數類型為int的函數了,因為他們在本地的局部作用域已經看到了這個聲明。出現了類型不比對的問題。