天天看點

【c++】——形參帶預設值的函數、内聯函數、函數重載

文章目錄

    • 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的宏函數的解決方案。但是具體他們的差別如下:

  1. 處理時機不同

    :内聯函數是在編譯階段将代碼嵌入到調用點,而define是在預編譯階段進行簡單的文本替換。
  2. 安全性

    :内聯函數具有類型安全、文法檢查而宏函數沒有此功能
  3. 通路權限

    :内聯函數可以是成員函數,通路類的成員變量,但是宏函數無法通路類的私有或者保護成員
  4. 調試

    :宏是沒有辦法進行調試的,内聯函數在debug版本下和普通函數一樣,出了問題很友善進行斷點調試,定位問題。

3、内聯函數與static修飾的函數差別

  1. 開銷方面

    :因為内聯函數是直接代碼展開,是以沒有開棧和清棧的開銷。static修飾的函數和普通函數一樣,有開棧和清棧的開銷
  2. 相同點——本檔案下可見

    :内聯函數因為不生成符号是以隻在本檔案中可見,static函數是生成local符号也是在目前檔案下可見。

4、内聯函數和普通函數的差別

普通函數的調用在彙編上有标準的push壓實參指令,然後call指令調用函數,給函數開辟棧幀,函數運作完成,有函數退出棧幀的過程。

内聯函數在編譯階段,在函數的調用點将函數的代碼展開,省略了函數棧幀開辟回退的調用開銷,效率比較高。

5、内聯函數的限制

  • 内聯函數的使用建議

    :當函數執行的開銷小于函數清棧開棧的開銷時,反映出函數體非常小,就建議使用内聯。因為如果以代碼膨脹為代價就會使得目标檔案非常龐大。
  • 内聯函數隻在release版本生效

    :即在debug版本下,内聯函數和普通函數一樣,也有棧幀的開辟和回退
  • inline隻是建議編譯器把這個函數處理成内聯函數

    真正是否是内聯函數還是編譯器決定的,是以,遞歸、循環、分支switch是一定不會處理成内聯的
  • 一般将内聯函數寫在.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++】——形參帶預設值的函數、内聯函數、函數重載

細細觀察上面幾個函數,我們可以發現,他們的函數名相同,隻是參數清單類型不同而已,但是同樣的代碼放在.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]
           

具體的規則如下:

  • _cdecl調用約定

    :"?"+函數名+參數表的開始辨別"@@YA"+函數傳回類型代号+參數類型代号+結束辨別"@Z"或"Z”(無參數)
  • _stdcall調用約定

    :?”+函數名+參數表的開始辨別“@@YG”+函數傳回類型代号+參數類型代号 +結束辨別“@Z”或“Z”(無參數)。
  • _fastcal調用約定

    :“?”+函數名+參數表的開始辨別 “@@YI”+ 函數傳回類型代号+參數類型代号 +結束辨別“@Z”或“Z”(無參數)。

下表示參數類型代号表

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的函數了,因為他們在本地的局部作用域已經看到了這個聲明。出現了類型不比對的問題。

c++

繼續閱讀