18

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

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

4

10 回答 10

21

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

抽象类没有实例。一个抽象类必须至少有一个延迟方法(或函数)。为了在 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
}
于 2013-01-07T20:17:39.137 回答
16

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

如果没有一个共同的抽象父(或至少一个具有树皮虚拟方法的共同父),就很难做到以下几点:

有一个 Dog 类型的向量,其中包含 Collies、Beagles、German Shepherds 等,并让它们每个都吠叫。使用包含 Collies、Beagles、German Shepherds 的 Dogs 向量,要使它们全部吠叫,您要做的就是在 for 循环中迭代并在每个循环中调用 bark。否则你必须有一个单独的牧羊犬向量、比格犬向量等。

如果问题是“为什么在 Dog 可以是具体的情况下将其抽象化,定义一个可以被覆盖的默认实现的虚拟树皮?”,答案是有时这可能是可以接受的——但是,从设计的角度来看,真的没有什么狗不是牧羊犬、比格犬或其他品种或混合的狗,所以尽管它们都是狗,但实际上没有一个是狗,但不是其他衍生品也上课。此外,由于狗的叫声因一个品种而异,因此不太可能有任何真正可接受的默认树皮实现,这对于任何体面的狗群都是可以接受的。

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

于 2014-01-13T17:22:05.977 回答
9

抽象类允许执行编译时协议。这些协议定义了成为类族的一部分意味着什么。

另一种思考方式是抽象类是您的实现类必须履行的契约。如果他们不履行这个合同,他们就不能成为班级家庭的一部分,他们必须被修改以符合合同。提供的合约可以提供默认功能,但它也留给子类来定义更具体或不同的功能,同时仍保留在合约的范围内。

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

于 2013-01-07T03:32:56.827 回答
2

我有一条狗。具有方法吠叫的抽象类狗。我那只特别的狗叫一声。其他狗以不同的方式吠叫。所以以抽象的方式定义狗是有用的。

于 2013-01-07T03:27:57.557 回答
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: 
          /*    */
}

在这个例子中,你可以理解。

于 2015-01-24T06:00:53.670 回答
0

抽象类用于定义要实现的接口。查看一些参考资料:

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

于 2013-01-07T03:27:10.920 回答
0

当所有具有派生自 的类型的对象都需要某种功能时,需要将抽象类AbstractClass作为基类AbstractClass,但不能在其AbstractClass自身上合理地实现。

Vehicle具有派生类Car的基类的旧且有些人为的 OO 示例Motorcycle在这里提供了一个很好的示例,假设您想要一个方法move()- 您可以实现 aCar或 aMotorcycle移动但Vehicles 不移动的方式一种通用的方式,因此Vehicle::move()必须是纯虚拟的,Vehicle因此是抽象的。

于 2013-01-07T03:31:03.500 回答
0

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

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

如果您的问题是,为什么要在抽象类中创建抽象函数?

它允许严格的运行时多态性

另请阅读接口与抽象类(通用 OO)

于 2013-01-07T03:31:08.037 回答
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。在主函数中,我们创建类的对象并传递给函数 dogbarking,它使用抽象类 dog 的引用对象接收它。

于 2013-01-07T05:37:26.987 回答
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 定义的接口,我们可以创建和使用尽可能多的显示字符串的新方法,并且不需要更改使用抽象接口的代码。

于 2013-01-07T20:06:50.950 回答