1. 程式人生 > 程式設計 >C++語法中的函式過載和預設引數

C++語法中的函式過載和預設引數

C語言中沒有函式過載
C++語言中有函式過載

函式名相同,引數個數不同、引數型別不同、引數順序不同

例如下面就是函式過載

void sum(int a,int b){
 cout << a+b << endl;
}

void sum(int a,double b){
 cout << a+b << endl;
}

返回值型別與函式過載無關

返回值型別與函式過載無關,下面程式碼不構成過載,編譯會報錯

//返回值型別與函式過載無關
int func(){
 return 0;
}

double func(){
 return 0;
}

實參的隱式型別轉換可能會產生二義性

不同編譯器有不同處理
下面程式碼在vs上編譯不過,但是在Xcode中可以編譯通過。

#include "iostream"
using namespace std;

void sum(double a){
 cout << a << endl;
}

void sum(int a){
 cout << a << endl;
}

int main(){
 sum(10);
 
 return 0;
}

函式過載的本質

採用了name mangling或者叫name decoration技術

  • C++編譯器預設會對符號名(比如函式名)進行改編、修飾,有些地方翻譯為“命名傾軋”
  • 過載時會生成多個不同的函式名,不同編譯器(MSVC、g++)有不同的生成規則
  • 通過IDA開啟【VS_Release_禁止優化】可以看到 或者通過hopper檢視

原始碼

下面的程式碼

#include "iostream"
using namespace std;

void sum(double a){
 cout << a << endl;
}

void sum(int a){
 cout << a << endl;
}

int main(){
 return 0;
}

在程式碼中,void sum(double a){} 和void sum(int a){}

是如何過載,呼叫函式的時候是如何能正確找到對應的函式呢?

彙編

我是用xcode的編譯出可執行檔案,放在hopper中檢視

__Z3sumd: // sum(double)
0000000100000ce0 push rbp ; CODE XREF=_main+23
0000000100000ce1 mov rbp,rsp
0000000100000ce4 sub rsp,0x10
0000000100000ce8 mov rdi,qword [__ZNSt3__14coutE_100001000]
0000000100000cef movsd qword [rbp+var_8],xmm0
0000000100000cf4 movsd xmm0,qword [rbp+var_8]
0000000100000cf9 call imp___stubs___ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEd ; std::__1::basic_ostream<char,std::__1::char_traits<char> >::operator<<(double)
0000000100000cfe mov rdi,rax
0000000100000d01 lea rsi,qword [__ZNSt3__1L4endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_]
0000000100000d08 call __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPFRS3_S4_E ; std::__1::basic_ostream<char,std::__1::char_traits<char> >::operator<<(std::__1::basic_ostream<char,std::__1::char_traits<char> >& (*)(std::__1::basic_ostream<char,std::__1::char_traits<char> >&))
0000000100000d0d mov qword [rbp+var_10],rax
0000000100000d11 add rsp,0x10
0000000100000d15 pop rbp
0000000100000d16 ret
; endp
0000000100000d17 nop word [rax+rax]

可知void sum(double a){} 被編譯器修改為函式__Z3sumd

__Z3sumi: // sum(int)
0000000100000da0 push rbp
0000000100000da1 mov rbp,rsp
0000000100000da4 sub rsp,0x10
0000000100000da8 mov rax,qword [__ZNSt3__14coutE_100001000]
0000000100000daf mov dword [rbp+var_4],edi
0000000100000db2 mov esi,dword [rbp+var_4]
0000000100000db5 mov rdi,rax
0000000100000db8 call imp___stubs___ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi ; std::__1::basic_ostream<char,std::__1::char_traits<char> >::operator<<(int)
0000000100000dbd mov rdi,rax ; argument #1 for method __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPFRS3_S4_E
0000000100000dc0 lea rsi,qword [__ZNSt3__1L4endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_]
0000000100000dc7 call __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPFRS3_S4_E ; std::__1::basic_ostream<char,std::__1::char_traits<char> >&))
0000000100000dcc mov qword [rbp+var_10],rax
0000000100000dd0 add rsp,0x10
0000000100000dd4 pop rbp
0000000100000dd5 ret
; endp
0000000100000dd6 nop word [cs:rax+rax]

可知void sum(int a){} 被編譯器修改為函式__Z3sumi

這樣當我們呼叫的時候

int main(){
 sum(10.5);
 return 0;
}

彙編如下,可知:因為 10.5是double型別,呼叫函式的時候是呼叫__Z3sumd

0000000100000de0 push rbp
0000000100000de1 mov rbp,rsp
0000000100000de4 sub rsp,0x10
0000000100000de8 movsd xmm0,qword [0x100000f80]
0000000100000df0 mov dword [rbp+var_4],0x0
0000000100000df7 call __Z3sumd ; sum(double)
0000000100000dfc xor eax,eax
0000000100000dfe add rsp,0x10
0000000100000e02 pop rbp
0000000100000e03 ret
; endp
0000000100000e04 nop word [cs:rax+rax]
0000000100000e0e nop

函式過載結論

由上面的彙編程式碼可知,當引數型別不同的時候,編譯器會生成不同的函式名作為區別,這樣就能實現函式過載。

預設引數

規則

C++允許函式設定預設引數,在呼叫時可以根據情況省略實參。規則如下:

  • 預設引數只能按照右到左的順序
  • 如果函式同時有宣告、實現,預設引數只能放在函式宣告中
  • 預設引數的值可以是常量、全域性符號(全域性變數、函式名)

用法:如果函式的實參經常是同一個值,可以考慮使用預設引數

#include "iostream"
using namespace std;
void test(){
 cout << "test()" << endl;
}
// test2函式
// a沒有預設值
// b 預設值是 10
// 最後一個引數預設值是個函式
void test2(int a,int b = 10,void (*func)() = test){
 cout << "a is " << a << endl;
 cout << "b is " << b << endl;
 func();
}
int main(){
 test2(3);
 return 0;
}

可能有衝突,二義性

函式過載、預設引數可能會產生衝突、二義性(建議優先選擇使用預設引數)
例如下面的程式碼中, 呼叫test(3); 會報錯,因為不知道要執行哪個函式。

#include "iostream"
using namespace std;

void test(int a){
 cout << a << endl;
}

void test(int a,int b = 10){
 cout << a << endl;
}

int main(){
 test(3); // 這裡報錯,因為不知道要執行哪個函式
 test(10,20); //這一句可以正確
 return 0;
}

總結:如果函式的實參經常是同一個值,可以考慮使用預設引數

到此這篇關於C++語法之函式過載和預設引數的文章就介紹到這了,更多相關c++ 函式過載預設引數內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!