[英]abstract methods and overiding function in C++ and Java
在C ++和Java中,或者他們尊重的規則,對覆蓋抽象方法有什么限制。 必須匹配參數或返回類型。 我通常看到只使用返回類型而沒有參數實現的抽象函數,是由派生類來指定其余的。 它是如何工作的?
方法重寫必須 與它覆蓋的父方法具有相同的方法簽名 ,否則不會調用覆蓋。
Java :
public abstract class AbstractTest {
public abstract void test() throws Exception;
}
public class ConcreteTest extends AbstractTest {
@Override
public void test() throws Exception {
}
}
如您所見, ConcreteTest
(擴展AbstractTest
)必須覆蓋test()
。 它們具有相同的方法名稱,返回類型和方法參數。 子類可以省略從基類拋出的異常並拋出自己的異常。 子類還可以添加其他(未)檢查的異常。
正如Peter Lawrey所提到的,java接口方法是隱式抽象方法(參見我在Java抽象接口上的問題 )。
這里至關重要的是在這種情況下方法可見性不會改變(因為它是分層可見性,即private-> protected-> public)。 這是有效的:
public abstract class AbstractTest {
protected abstract void test() throws Exception;
}
public class ConcreteTest extends AbstractTest {
@Override
public void test() throws Exception {
}
}
(父類具有受保護的方法,子類可以覆蓋相同的方法,只有2個可見性選擇:protected或public)。
另外,假設你有
public class B {
}
public class D extends B {
}
public abstract class Base {
public abstract B foo();
}
public class Derived extends Base {
@Override
public D foo() {
// TODO Auto-generated method stub
return new D();
}
}
您將看到Derived
返回D
而不是B
這是為什么? 這是因為派生類遵循相同的簽名父類和派生類的返回類型是subtype
的父類的返回類型。
所以,我可以這樣:
Base pureBase = new Derived();
B b = pureBase.foo(); //which returns class D
if (b instanceof D) {
//sure, it is, do some other logic
}
在C ++中,使用Covariant Return類型可以獲得類似的效果
C ++
class AbstractTest {
public:
virtual void test() = 0;
};
class ConcreteTest : AbstractTest {
public:
void test() {
//Implementation here...
}
};
在C ++中,具有純虛函數的類(以a =0
結尾的虛函數)稱為抽象類。 子類(在C ++中,類擴展由:
分隔:)覆蓋純虛方法(除了它不包含=0
)。 它具有與其父類相同的簽名。
回到我們的Java示例,假設您有:
class B {
};
class D : B {
};
class Base {
public:
virtual B* foo() = 0;
}
class Derived : Base {
public:
D* foo() {
return new D();
}
}
這里完成了相同的推理(如java中所述)。 協變返回類型也適用於受保護和私有繼承。 有關Covariant返回類型的更多信息。
我不了解Java,但在C ++中你必須指定完全相同的參數類型。 返回類型 - 另一方面 - 是協變類型,這意味着如果在原始函數中返回指向類型A的指針或引用,則覆蓋函數可以返回指針或對類型B的引用,只要B是A,或直接或間接來自它。
正如Als所指出的,必須將函數聲明為虛擬以便被覆蓋。 由於OP明確詢問了抽象方法,它們被定義為virtual
和=0
,因此不需要指出這一點。 但是,我想更清楚地表明,覆蓋函數不需要聲明為虛擬。 正如所引用的標准所述,與聲明為virtual的基本成員函數的簽名(使用寬松規則,對於協變類型)匹配的成員函數將是一個覆蓋,無論它是否被指定為虛擬。 也就是說,覆蓋函數不需要聲明為虛擬; 另一方面,抽象成員函數必須是。
兩種語言在覆蓋自然語義差異的要求方面是相似的。 基本上兩者都需要對調用代碼(即參數)完全相同的約束,並在處理時提供相同或更嚴格的保證。 這聽起來有點模糊,但如果你記住這一點很簡單。
什么時候是覆蓋
對於覆蓋基類成員的成員函數(方法),兩種語言都要求函數是多態的(C ++中的virtual
函數,而不是Java中的final
函數)具有相同的名稱和相同的數字作為參數類型。 有些語言允許反變量參數類型,但Java和C ++都沒有。
協變返回類型
此處的協變意味着返回類型的類型更改方式與實現成員函數的類型相同。 也就是說,派生函數返回的類型必須是多態的,並且與基類中聲明的相同類型相同或派生。 Java是一種參考語言,因此除了基本類型之外,所有返回類型都可以表現出多態性。 C ++是一種值語言,只有引用和指針是多態的。 這意味着在Java中,返回的類型必須完全匹配或者是引用類型,並從基類返回的類型派生。 在C ++中,它必須是指向相同或派生類型的引用或指針 。 與介紹中一樣,原因是如果您通過基礎調用成員函數,您將擁有一個與您期望的對應的對象。
例外規格
異常規范在C ++中並不常見,但它們都是Java語言。 在這兩種語言中,盡管覆蓋的方法是相同的:派生類中的重寫方法必須對可以拋出的內容有更嚴格的約束。 這里語言表面的差異,因為Java只驗證已檢查的異常,因此它將允許未被基類拋出的派生類型中的未經檢查的異常 。 另一方面,派生函數不能添加基類中不存在的新檢查異常,協方差也會發揮作用,派生函數可以拋出協變異常。 在C ++中,異常規范具有完全不同的含義,但是以相同的方式,派生類型中的規范必須比基礎中的規范更具約束性,並且它還允許協變異常規范。
基本原理是相同的,如果你通過引用基類型來編寫一個try {} catch() {}
塊,並且它捕獲了在base中聲明的所有異常,對override的調用將會將所有異常捕獲在相同的塊中 - 除了 Java中可能未經檢查的異常 。
訪問修飾符
在Java中,派生方法的訪問規范必須至少與基類的訪問規范一樣,即,如果基函數聲明指定protected
,那么派生函數不能是public
,但另一方面可以是private
,有趣的是Java不允許您覆蓋基類中的private
函數。
在C ++中,訪問說明符不會用於覆蓋,您可以根據需要修改訪問說明符,使其在派生類中具有或多或少的限制性。 順便提一下,您可以覆蓋基類中的private
成員(聲明為virtual
),這通常用於實現NVI模式(非虛擬接口),必須通過Java中的protected
方法實現。
停止壓倒一切
Java允許您通過將成員函數標記為final
或者將其設置為private
來打破任何級別的覆蓋鏈。 在C ++(當前標准)中,您無法在任何時候打破覆蓋鏈,即使在最終覆蓋者無法訪問其覆蓋的成員函數的情況下也是如此,這會產生一種奇怪的效果:
struct base {
virtual void f() {}
};
struct derived : private base {
void g() {
f();
}
};
struct most_derived : derived {
void f() { // overrides base::f!!!
//base::f(); // even if it does not have accesss to it
}
};
在該示例中,因為繼承在derived
級別是私有的,所以most_derived
無法訪問base
子對象,從它的角度來看,它不是從base
派生的(為什么base::f()
無法在most_derived::f()
編譯的most_derived::f()
)但另一方面,通過使用簽名void ()
實現一個函數,它為base::f
提供了一個覆蓋。 對most_derived
對象的g()
調用將被調度到most_derived::f()
,而derived
對象將被調度到base::f()
。
Java的:
abstract class MyAbstract {
abstract String sayHelloTo(String name);
}
final class SayEnglish extends MyAbstract {
@Override
public String sayHelloTo(String name) {
return "Hello, " + name + "!";
}
}
final class SayLatin extends MyAbstract {
@Override
public String sayHelloTo(String name) {
return "Lorem, " + name + "!";
}
}
考慮到語法差異,C ++也是如此,即重寫抽象方法的簽名相同。
您在java中的覆蓋方法應該與您要覆蓋的抽象方法具有相同的簽名。 此外,您不能限制訪問多於父類。 請參閱http://download.oracle.com/javase/tutorial/java/IandI/override.html
我假設你的意思是C ++。 與java相同,重寫方法簽名應與重寫匹配。 請參閱http://www.learncpp.com/cpp-tutorial/126-pure-virtual-functions-abstract-base-classes-and-interface-classes/
Wiki也有一個頁面en.wikipedia.org/wiki/Method_overriding。 抽象方法可以有參數。 沒有限制。 在許多情況下,傳遞參數可能沒有意義。 希望這可以幫助 :)
方法的簽名(返回類型,參數的類型和數量)應該在派生類中與基類完全匹配。 否則派生類也將變得抽象。
例:
struct foo{
virtual void foobar( int myNum) = 0;
};
struct bar: foo{
int foobar(int myNum ){}
};
int main(){
foo *obj = new bar();
return 0;
}
test.cc:6:錯誤:為'virtual int bar :: foobar(int)'指定的沖突返回類型
test.cc:2:錯誤:覆蓋'virtual void foo :: foobar(int)'
正如@Als所提到的, Covariant Return Type是一個例外,其中返回類型可以不同。 不同的是,我的意思是不同的類型應該與每種類型兼容。 C ++中派生類類型的指針/引用與基類型的指針/引用類型兼容。
鏈接示例:
#include <iostream>
// Just create a class, and a subclass
class Foo {};
class Bar : public Foo {};
class Baz
{
public:
virtual Foo * create()
{
return new Foo();
}
};
class Quux : public Baz
{
public:
// Different return type, but it's allowed by the standard since Bar
// is derived from Foo
virtual Bar * create()
{
return new Bar();
}
};
int main()
{
Quux *tmp = new Quux();
Bar *bar = tmp->create();
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.