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、重載函數形參在同位置分別類型爲傳值或者傳引用


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章