1. 程式人生 > >C++為什麼可以進行函式過載以及引起的二義性問題

C++為什麼可以進行函式過載以及引起的二義性問題

        關於C++中函式過載是在C語言基礎上的一大特色,不過有好也有壞,雖然C++的函式過載大大方便了程式設計人員,但是卻有時候使用不當會引起問題,最典型的就是函式過載的二義性問題。首先我們知道C++函式過載的條件,以及C++中為什麼可以函式過載,這樣才可以避免C++函式過載中的二義性問題。

C++函式過載的條件有三個:

(1)函式必須位於同一作用域之中。(過載顧名思義是地位相同的兩個函式,可以說兩個函式是平等的,所以憑什麼有的函式作用域大呢,那自然就是同一作用域)

(2)函式名必須相同。

(3)最重要的就是引數列表不同。(引數列表不同又可以分為1--引數個數不同 2--引數型別不同 3--引數順序不同,滿足以上三條任意一條就可以了。)

還有一點要說的是函式的返回值可以相同,也可以不同。

下面就來說說為什麼C++中可以進行函式的過載,我們都知道,如果在C語言中定義一個或者多個同名的函式,哪怕是引數的型別、個數、順序都不同可不可以進行過載。

如:以下程式碼用C++編譯沒有任何問題

int My_add(int a, int b)
{
	return (a + b);
}

int My_add(char a, char b)
{
	return (a + b);
}

int main()
{
	int m, n, ret = 0;
	m = 10;
	n = 20;
	ret = My_add(m, n);
	return 0;
}
但是我們用C編譯器編譯會報錯:
extern "C"
{
	int My_add(int a, int b)
	{
		return (a + b);
	}

	int My_add(char a, char b)
	{
		return (a + b);
	}

	int main()
	{
		int m, n, ret = 0;
		m = 10;
		n = 20;
		ret = My_add(m, n);
		return 0;
	}
}

1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(52): error C2733: “My_add”: 不允許過載函式的第二個 C 連結

1>          d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(47) : 參見“My_add”的宣告

順便提一下用extern “C” (C是大寫)表面該範圍內採用C風格編譯。C語言內引起了重定義但是C++卻沒有這又是為什麼呢?

主要就是C與C++編譯器在修飾函式名的規則有不同之處,修飾名由函式名、類名、呼叫約定、返回型別、引數等共同決定。

呼叫約定C++中比C語言中多一個__thiscall,其餘均相同。

(關於呼叫約定可以參考http://blog.csdn.net/loving_forever_/article/details/51472040)

回到我們的問題,那麼C語言中的函式被修飾成什麼了呢?還是剛剛的程式碼,讓我們轉到反彙編看一看:

01051ABB  call        _My_add (010511E0h)  
01051AC0  add         esp,8  
在C語言中My_add()被修飾成了_My_add(),這通常是C語言中的預設呼叫約定(__cdecl)產生的。

但是C++就不是那麼簡單了,C++的函式名修飾規則有些複雜,但是資訊更充分,通過分析修飾名不僅能夠知道函式的呼叫方式,返回值型別,引數個數甚至引數型別。不管__cdecl,__fastcall還是__stdcall呼叫方式,函式修飾都是以一個“?”開始,後面緊跟函式的名字,再後面是引數表的開始標識和按照引數型別代號拼出的引數表。對於__stdcall方式,引數表的開始標識是“@@YG”,對於__cdecl方式則是“@@YA”,對於__fastcall方式則是“@@YI”

怎麼看被修飾成什麼了呢?我們不給函式的主體,只給定義,看看下面的報錯就知道了。

1>Main.obj : error LNK2019: 無法解析的外部符號 "int __cdecl My_add(int,int)" (?My_add@@YAHHH@Z),該符號在函式 _main 中被引用
HHH分別是返回值,引數型別。引數表的拼寫代號如下所示:
X--void   
D--char   
E--unsigned char   
F--short   
H--int   
I--unsigned int   
J--long   
K--unsigned long(DWORD)
M--float   
N--double   
_N--bool
U--struct
也正是因為C++與C語言修飾函式風格的不同,C++更加複雜,所以C++編譯器可以識別函式名相同但引數列表不同的原因。說到這裡又有一個新問題產生了,既然函式的返回值也修飾在其中那為什麼不能通過返回值的不同來過載函式呢?因為如果返回值也被當作可以過載的條件的話,那麼函式過載的二義性就太多了?(什麼是二義性下面會提到)來看一個例子:
假設你有兩個返回值不同的函式,比如
int getvalue(viod) {return value1;}
float getvalue(viod) {return value;}
那麼當你去呼叫他們的時候,由於你呼叫的時候
寫的是
getvalue();
於是你的編譯器就無法知道 你呼叫的是上面哪個 函式(因為兩個函式都不用傳引數,編譯器無法區分它們,產生二義性), 所以就會報錯。

又有人可能會說既然函式修飾過程返回值也在其中,那麼編譯器應該可以區分啊,那麼我告訴你的是這是微軟的編譯器,只能說我們用的VS是這樣編譯的,但是不代表其他C++編譯器也是這樣編譯的(比如Linux中的gcc編譯器),所以C++標準就這樣規定了。

下面還剩下最後一個問題就是函式過載的二義性。

先來看看什麼是二義性:二義性說簡單點就是編譯器不知道你需要呼叫哪個函式。

如下面兩個函式:

        int My_add(int a, int b)
	{
		return (a + b);
	}

	char My_add(int a, int b)
	{
		return (a + b);
	}
編譯器報錯如下:

1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(54): error C2556: “char My_add(int,int)”: 過載函式與“int My_add(int,int)”只是在返回型別上不同
1>          d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(48) : 參見“My_add”的宣告

再如:

        int fun()
	{
		return 0;
	}
	int fun(int a = 5)
	{
		return a;
	}
        int main()
    {
        int ret = 0;
        ret = fun();
        return 0;
    }
首先編譯期間就會指出錯誤:

1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(72): error C2668: “fun”: 對過載函式的呼叫不明確

這也是一種產生二義性的例子。

再來看一種:

        int fun(int a)
	{
		return 0;
	}
	int fun(int &b)
	{
		b = 20;
		return b;
	}
	int main()
	{
		int m, ret = 0;
		m = 10;
		ret = fun(m);
		return 0;
	}
同樣也會產生二義性,報錯如下:
1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(73): error C2668: “fun”: 對過載函式的呼叫不明確
1>          d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(62): 可能是“int fun(int &)”
1>          d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(58): 或       “int fun(int)”

總結一下就是以下三個原因會產生二義性:

1、形參個數一致,僅僅是形參名或者返回值不同
2、過載函式有一個形參有預設引數時
3、過載函式形參在同位置分別型別為傳值或者傳引用