close

重載函式的問題

某一天我正在看《設計模式之禪》,看到P18,書上範例是 JAVA,我改成 C++之後寫出類似這樣的東西。繼承類別中的多載。

#include <iostream>
using namespace std;
class CFather
{
public:
	void Test(int p){cout<<"CFather"<<endl;} 
};
class CChild : public CFather
{
public:
	void Test(){cout<<"CChild"<<endl;}
};
int main()
{
	CChild *pC = new CChild;
	CFather *pF = new CFather;
	pC->Test(10);		//compiler 不給過
	delete pC;
	delete pF;
	system("pause");
	return 0;
}

錯誤訊息是 error C2660: 'CChild::Test' : function does not take 1 arguments

書上還說「不在一個類別就不能是多載?繼承是什麼意思,子類別擁有父類別所有的屬性和方法。方法名稱相同,輸入參數型別又不相同,當然是多載了。」理論上好像這樣可以,那為什麼 C++ 不能這樣寫?問了幾個朋友和 Google 也得不到答案,只好翻書去了。

以下來自《C++ Primer 4/e》

重載函式(P265)
如果兩個函式擁有相同名稱、出現於同一作用域、參數列不同,這兩個函式就形成所謂的「重載」(overload)。

函式內宣告的 local 名稱會把 global 作用域內宣告的相同名稱遮蓋掉。不論函式名稱或變數名稱,情形都一樣。

int GetInt()
{
	return 3;
}
int main()
{
	int GetInt = 5;
	cout<<GetInt()<<endl;	//錯誤,被int GetInt遮蔽
	system("pause");
	return 0;
}


標準的作用域規則(scoping rules)亦適用於重載函式命名。如果我們區域性地(locally)宣告一個函式,該函式會遮掩掉外層作用域所宣告的同名函式,不會形成重載。因此重載函式的每一個版本的宣告都必須出現在同一作用域內。

void print(const string &s){};
void print(double d){};
int main()
{
	int p = 3;
	void print(int n);	//新作用域內,遮蓋了先前的print
	print("pp");		//錯誤print(const string)被遮蔽
	print(p);			//呼叫print(int n)
	print(3.15);		//呼叫print(int n)
	system("pause");
	return 0;
}

main 函式裡的 print(int) 宣告遮蓋了其他 print 宣告,於是只有一個 print 函式可用。

當呼叫 print 時,編譯器首先搜尋該名稱,他找到接收一個 int 的 print local 函式,一旦找到,編譯器就不再檢查外層作用域是否有同名函式存在。

C++的名稱搜尋(name lookup)發生於型別檢驗(type checking)之前。

重載決議(Overload Resolution)三步驟(P270)
1.候選函式(Candidate Function)
確認呼叫動作所考慮的重載函式集,此集合內的所有函式稱為候選函式。候選函式與被呼叫的函式名稱相同,其宣告式必須在呼叫點可見。
2.定出可行函式(Viable Functions)
從候選函式挑出可被「呼叫動作所指明的引數」喚起的函式,被選出的函式稱為可行函式。挑選條件有二:第一,參數個數要與被呼叫函式相同。第二,每個引數型別必須吻合,或是能夠轉換至對應參數的型別。
3.找出最契合函式(Best Match)
判斷哪個可行函式與呼叫動作所指明的實際引數最契合。這將檢查呼叫動作的每個引數,挑出與對應參數最契合的一或多個可行函式。所謂最契合,概念上是「引數和參數的型別關係越接近越契合」。

繼承情況下的 class 作用域(P592)
如果 base 和 derived class 擁有相同名稱的成員函式,其運作方式如同成員變數:derived 作用域內的成員會遮蔽 base class 的成員。即使函式原型不同,base 成員仍會受到遮蔽。

class Base
{
public:
	void memfcn(){};
};

class Derived : public Base
{
public:
	void memfcn(int){};
};

int main()
{
	Base b;
	Derived d;
	b.memfcn();		    //呼叫Base::memfcn()
	d.memfcn(10);		//呼叫Derived::memfcn(int)
	d.memfcn();		    //錯誤:無引數的memfcn已被遮蔽
	d.Base::memfcn();	//呼叫Base::memfcn()
	system("pause");
	return 0;
}

d.memfcn(); 為了決議此一呼叫,編譯器搜尋 memfcn 名稱,並在 Derived class 發現。一旦找到名稱,編譯器就不再繼續尋找。而這個呼叫與 Derived 內的 memfcn() 定義不契合,因此導致錯誤。

回憶一下,宣告於 local 作用域的函式,並不會與定義於 global 作用域內的函式形成重載。同樣道理,定義於 derived class 的函式不會與 base 定義的函式形成重載。只有當 derived class 完全未定義該函式時,編譯器才會考慮 base class 的同名函式。

題外話
C# 和 java 可以過。

//測試C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class Base
{
    public void memfcn(int n)
    {
        System.Console.WriteLine("Base::memfcn");
    }
};

public class Derived : Base
{
    public void memfcn()
    {
        System.Console.WriteLine("Derived::memfcn");
    }
};

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Base b = new Base();
            Derived d = new Derived();

            b.memfcn(20);       //Base::memfcn           
            b.memfcn();         //錯誤
            d.memfcn(10);       //Base::memfcn 
            d.memfcn();         //Derived::memfcn

            System.Console.Read();
        }
    }
}

//測試java
public class A
{
	public void print()
	{
		System.out.println("A::Print");
	}
}
public class B extends A
{
	public void print(int n)
	{
		System.out.println("B::Print");
	}
}
public class Test {
	public static void main( String[] args ) {
        A a = new A();
		B b = new B();
        a.print();		//A::Print
        b.print();		//A::Print		
        b.print(10);	//B::Print
     }
}

如果想要讓第一個程式可以過的話,可以這樣寫。但是 C++ 似乎完全不建議你做這種事。

#include <iostream>
using namespace std;
class CFather
{
public:
	void Test(int p){cout<<"CFather"<<endl;} 
};
class CChild : public CFather
{
public:
	using CFather::Test;	//主要是這裡
	void Test(){cout<<"CChild"<<endl;}
};
int main()
{
	CChild *pC = new CChild;
	CFather *pF = new CFather;
	pC->Test(10);
	delete pC;
	delete pF;
	system("pause");
	return 0;
}

 

arrow
arrow
    文章標籤
    C++ 重載 多載
    全站熱搜

    kamory 發表在 痞客邦 留言(0) 人氣()