2013-01-07 202 views
13

我剛剛瞭解到OOP類中的多態性,並且很難理解抽象基類如何有用。爲什麼我們需要C++中的抽象類?

抽象類的目的是什麼?定義一個抽象基類提供了什麼,它不是通過在每個實際類中創建每個必需的函數來提供的?

+5

問題是你*在每個類中創建函數。它由編譯器執行。 – chris

回答

14

抽象類的目的是爲一組具體的子類定義一個通用協議。當定義共享代碼,抽象想法等的對象時,這非常有用。

抽象類沒有實例。抽象類必須至少有一個延遲方法(或函數)。在C++實現這一點,一個純虛成員函數被聲明,但在抽象的類沒有定義:

class MyClass { 
    virtual void pureVirtualFunction() = 0; 
} 

嘗試實例化的抽象類將總是導致編譯錯誤。

「通過在每個實際類中創建每個必需的函數,定義抽象基類提供了什麼不提供 ?」

這裏的主要想法是代碼重用和適當的跨類分區。它更有意義,在父類中定義一次函數,而不是在多個子類中再定義一遍又一遍:

class A { 
    void func1(); 
    virtual void func2() = 0; 
} 

class B : public A { 
    // inherits A's func1() 
    virtual void func2(); // Function defined in implementation file 
} 

class C : public A { 
    // inherits A's func1() 
    virtual void func2(); // Function defined in implementation file 
} 
1

我有一隻狗。抽象類的狗用方法樹皮。我特別的狗使一個樹皮。其他狗以不同的方式吠叫。因此,以抽象的方式定義一隻狗是有用的。

+0

當我們想要重寫派生類中的樹皮方法時,我們實際上在每個派生類中創建了函數樹皮。那麼你能否告訴我爲什麼使用它的具體原因? –

+1

一個狗必須執行樹皮功能的特定合同的固有類。其他方面,你不會啓動課程。假設這個子類是由第三方實現的。這個抽象機制確保了第三方實現了樹皮,並且從抽象狗的角度調用樹皮函數時可以安全地使用多態性。 – Hagai

0

當所有具有從AbstractClass派生的類型的對象都需要功能時,需要抽象類AbstractClass作爲基類,但不能在AbstractClass本身上實現。

具有派生類CarMotorcycle一個基類Vehicle的老有點人爲OO例如,...這裏提供了一個很好的例子,假設你想一個方法move() - 你可以實現的方式,一個CarMotorcycle移動,但Vehicle s不會以通用方式移動,因此Vehicle::move()必須是純虛擬的,因此Vehicle是抽象的。

0

爲什麼我們不在每個類中創建每個必需的功能? (C++)

您必須在每個派生類中創建標記爲abstract的每個必需函數。

如果你的問題是,爲什麼要在抽象類中創建抽象函數?

它允許嚴格run time polymorphism

又讀Interface vs Abstract Class (general OO)

+0

不錯,我懂了,如果我使用抽象類,我可以將它們歸類到一個組中。我可以通過使用靜態和動態轉換與基指針來施放它們。 –

6

抽象類允許編譯時的協議執行。這些協議定義了成爲班級家庭的一部分意味着什麼。

另一種想法是,抽象類是你的實現類必須履行的契約。如果他們不履行合同,他們不能成爲班級家庭的一員,他們必須進行修改才能符合合同規定。所提供的合同可能會提供默認功能,但它也會將其留給子類以定義更具體或不同的功能,同時仍在合同範圍內。

對於小型項目來說,這看起來似乎不太有用,但對於大型項目,它提供了符合性和結構,因爲它通過抽象類合同提供了文檔。這使得代碼更具可維護性,並且使得子類具有相同的協議,使得使用和開發新的子類更容易。

0
abstract class dog 
{ 
bark(); 
} 

// function inside another module 

dogbarking(dog obj) 
{ 
    dog.bark(); // function will call depend up on address inside the obj 
} 


// our class 
ourclass: inherit dog 
{ 
    bark() 
    { 
     //body 
    } 
} 


main() 
{ 
    ourclass obj; 
    dogbarking(obj); 
} 

我們可以看到,dogbarking是寫在另一個模塊的功能。它只知道抽象類的狗。即使它可以調用我們課程中的函數樹皮。在main函數中,我們創建了我們的類的對象,並使用抽象類dog的引用對象傳遞到它接收到的函數dogbarking。

0

想象一下,你有兩種方法來顯示字符串:

DisplayDialog(string s); 
PrintToConsole(string s); 

而且要編寫一些代碼,可以在這兩個方法之間進行切換:

void foo(bool useDialogs) { 
    if (useDialogs) { 
     DisplayDialog("Hello, World!"); 
    } else { 
     PrintToConsole("Hello, World!"); 
    } 

    if (useDialogs) { 
     DisplayDialog("The result of 2 * 3 is "); 
    } else { 
     PrintToConsole("The result of 2 * 3 is "); 
    } 

    int i = 2 * 3; 
    string s = to_string(i); 

    if (useDialogs) { 
     DisplayDialog(s); 
    } else { 
     PrintToConsole(s); 
    }   
} 

此代碼是緊耦合用於顯示字符串的具體方法。添加一個額外的方法,改變方法的選擇方式等都會影響到使用它的每一段代碼。此代碼是與我們用來顯示字符串的一組方法緊密耦合的

抽象基類是解耦的一種方式使用實現該功能的代碼中的某些功能的代碼。它通過爲所有執行任務的各種方式定義一個通用界面來實現這一點。

class AbstractStringDisplayer { 
public: 
    virtual display(string s) = 0; 

    virtual ~AbstractStringDisplayer(); 
}; 

void foo(AbstractStringDisplayer *asd) { 
    asd->display("Hello, World!"); 
    asd->display("The result of 2 * 3 is "); 

    int i = 2 * 3; 
    string s = to_string(i); 

    asd->display(s); 
} 

int main() { 
    AbstractStringDisplayer *asd = getStringDisplayerBasedOnUserPreferencesOrWhatever(); 

    foo(asd); 
} 

使用,使用的抽象接口將不需要改變由AbstractStringDisplayer定義的接口,我們可以創建和,因爲我們希望用顯示的字符串儘可能多的新途徑,和代碼。

13

有一個抽象類,像「狗」包含「樹皮」的虛擬方法允許所有類即使比格犬的樹皮的實施方式與科利犬的方式不同,它們也繼承了狗的同樣的叫法。

如果沒有共同的抽象父(或至少有樹皮虛方法共同的父),這將會是很難做到以下幾點:

有型的矢量狗包含牧羊​​犬,比格犬,德國牧羊犬並讓他們每個人都吠叫。使用包含Collies,Beagles,德國牧羊犬的狗矢量,你需要做的就是讓所有的樹皮都在for循環中循環,並在每個樹皮上稱呼它們。否則,你必須有一個獨立的大象矢量,矢量的小獵犬等。

如果問題是「爲什麼讓Dog抽象爲具體的時候,有一個虛擬樹皮定義了一個可以被重寫的默認實現?」,答案是有時候這可能是可以接受的 - 但是,從從設計的角度來看,真的沒有任何一種狗不是一隻牧羊犬,一隻小獵犬或者其他一些品種或混合物,儘管它們都是狗,但實際上它們中沒有一隻是狗,但是,不是其他派生類。此外,由於狗吠聲從一個品種到另一個品種是如此不同,所以不可能有任何真正可接受的默認樹皮實施方式,這對任何體面的狗羣都是可以接受的。

我希望這可以幫助你理解目的:是的,無論如何你必須在每個子類中實現樹皮,但是通用抽象祖先可以讓你將任何子類視爲基類的成員並調用行爲可能在概念上與樹皮類似,但實際上具有非常不同的實現。

+1

我喜歡這個答案 –

+0

感謝您使用狗的例子。它使這個概念非常清楚地理解 – TKerr

2

抽象類的目的是提供一個合適的基類,其他類可以從中繼承。抽象類不能用於實例化對象,只能用作接口。嘗試實例化抽象類的對象會導致編譯錯誤。 (因爲vtable條目沒有填充我們在抽象類中提到的虛函數的內存位置)

因此,如果需要實例化ABC的子類,則必須實現每個虛函數,這意味着它支持ABC聲明的接口。未能在派生類中重寫純虛函數,然後試圖實例化該類的對象,是一個編譯錯誤。

例子:

class mobileinternet 
{ 
public: 
virtual enableinternet()=0;//defines as virtual so that each class can overwrite 
}; 


class 2gplan : public mobileinternet 

{ 
    private: 

     int providelowspeedinternet(); //logic to give less speed. 

    public: 

     void enableinternet(int) { 
            // implement logic 
           } 

}; 

//similarly 

class 3gplan : public enableinternet 
{ 
    private: high speed logic (different then both of the above) 

    public: 
      /* */ 
} 

這裏在這個例子中,你能理解。

相關問題