为什么我们需要C ++中的抽象类?

时间:2013-01-07 03:23:52

标签: c++ oop inheritance abstract-class

我刚刚了解了我的OOP类中的多态性,并且我很难理解抽象基类是如何有用的。

抽象类的目的是什么?定义一个抽象基类提供了什么,而不是通过在每个实际类中创建每个必要的函数来提供?

10 个答案:

答案 0 :(得分:17)

抽象类的目的是为一组具体子类定义公共协议。这在定义共享代码,抽象概念等的对象时很有用。

抽象类没有实例。抽象类必须至少有一个延迟方法(或函数)。要在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 :(得分:15)

像“Dog”这样的抽象类使用像“bark”这样的虚拟方法,允许所有从Dog继承的类以相同的方式调用它们的树皮代码,即使Beagle的树皮的实现方式与Collie不同。

如果没有共同的抽象父级(或者至少是一个带有bark虚方法的公共父级),则很难执行以下操作:

有一个类型狗的矢量包含牧羊犬,小猎犬,德国牧羊犬等,并使它们每个都吠叫。使用包含牧羊犬,小猎犬,德国牧羊犬的狗的矢量,你只需要在一个for循环中迭代并在每个上面调用树皮。否则你必须有一个单独的Collies矢量,Beagles矢量等。

如果问题是“为什么让Dog抽象的时候它可能是具体的,有一个虚拟的树皮定义了一个可以覆盖的默认实现?”,答案是这有时可以接受 - 但是,从从设计的角度来看,实际上没有任何像狗不是牧羊犬或小猎犬或其他品种或混合物的东西,所以虽然它们都是狗,但实际上没有一只是狗,但没有其他一些派生类也是如此。此外,由于狗的吠叫从一个品种到另一个品种是如此多样化,因此不可能有任何真正可接受的默认实施树皮,这对于任何体面的狗群都是可以接受的。

我希望这可以帮助您理解目的:是的,无论如何,您将不得不在每个子类中实现bark,但是通用的抽象祖先允许您将任何子类视为基类的成员并调用可能的行为。在概念上类似于树皮,但实际上有不同的实现。

答案 2 :(得分:7)

抽象类允许编译时协议实施。这些协议定义了成为一个阶级家庭的一部分。

另一种思考方式是抽象类是您的实现类必须满足的契约。如果他们不履行本合同,则他们不能成为集体家庭的一部分,必须对其进行修改以符合合同。提供的合同可以提供默认功能,但它也可以由子类来定义更具体或不同的功能,同时仍然属于合同范围。

对于小型项目,这似乎没有用,但对于大型项目,它提供了符合性和结构,因为它通过抽象类合同提供文档。这使得代码更易于维护,并使每个子类具有相同的协议,从而更容易使用和开发新的子类。

答案 3 :(得分:2)

我有一只狗。与方法吠声的抽象类狗。我特别的狗做了一个树皮。其他狗以不同的方式吠叫。因此,以抽象的方式定义一条狗是有用的。

答案 4 :(得分:2)

抽象类的目的是提供一个适当的基类,其他类可以从中继承。抽象类不能用于实例化对象,仅用作接口。尝试实例化抽象类的对象会导致编译错误。 (因为vtable条目没有填充我们在Abstract Class中提到的虚函数的内存位置)

因此,如果需要实例化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: 
          /*    */
}
在这个例子中,你可以理解。

答案 5 :(得分:0)

抽象类用于定义要实现的接口。参见一些参考文献:

http://en.wikibooks.org/wiki/C%2B%2B_Programming/Classes/Abstract_Classes

答案 6 :(得分:0)

当所有具有源自AbstractClass的类型的对象需要的功能,但是在{{1}上无法合理地实现时,需要抽象类AbstractClass作为基类。本身。

有一个带有派生类AbstractClassVehicle的基类Car的旧的,有点人为的OO示例......在这里提供了一个很好的例子,比如你想要一个方法{ {1}} - 您可以实现Motorcyclemove()移动的方式,但Car不会以通用方式移动,因此Motorcycle会有是纯虚拟的,Vehicle因此是抽象的。

答案 7 :(得分:0)

  

为什么我们不在每个班级创建每个必要的功能? (C ++)

您必须在每个派生类中创建标记为abstract的每个必要函数。

如果您有疑问,为什么要在抽象类中创建抽象函数?

允许严格run time polymorphism

另请阅读Interface vs Abstract Class (general OO)

答案 8 :(得分: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是一个用另一个模块编写的函数。它只知道抽象类狗。即使它可以在我们的类中调用函数bark。在main函数中,我们创建了我们类的对象,并传递给函数dogbarking,它使用抽象类dog的引用对象接收它。

答案 9 :(得分: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定义的接口,我们可以创建和使用尽可能多的新方法来显示字符串,并且不需要更改使用抽象接口的代码。