5

我们举个例子,

class base{
public:
virtual void abstract() = 0;
};

class derived:public base{
public:
void abstract(){cout << "Abstract\n";}
};

int main{
derived d;
d.abstract();
return 0;
}

它可以写成其他方式,

class base{
    public:
    void abstract(){cout << "Abstract\n";}
    };

    int main{
    base b;
    b.abstract();
    return 0;
    }

它也提供相同的结果,实际上我不需要派生类。我确实阅读了很多关于抽象类it says we can not instantiate base class的文章,并且pure virtual function强迫用户使用define the function. 但是,如果我们在这两种情况下都看到上面的代码,我会得到相同的结果(or output)。所以在这里我的问题是abstract课程如何帮助我们?

4

7 回答 7

6

正如您所注意到的,在您的示例代码中,有单独的基类和派生类是没有意义的。

一般来说,具有虚函数的类的目的是动态多态性,但是您的代码没有使用它。

假设您有一些使用 a 的代码,base*但不知道它实际指向哪个(几个)派生类。假设他们每个人都有不同的实现abstract()。假设您想强制编写派生类的任何人base实现他们自己的abstract(). 然后有理由制作abstract()一个纯虚函数,因此有理由base成为一个抽象类:

#include <iostream>
#include <cstdlib>
#include <ctime>

struct Base {
    virtual void abstract() = 0;
    virtual ~Base() {};
};

struct Derived1 : Base {
    void abstract() { std::cout << "Derived1\n"; }
};

struct Derived2 : Base {
    void abstract() { std::cout << "Derived2\n"; }
};

Base *random_object() {
    if (std::rand() < RAND_MAX/2) {
        return new Derived1();
    } else {
        return new Derived2();
    }
}

int main() {
    std::srand(std::time(0));
    Base *b = random_object();
    b->abstract();
    delete b;
}

中的代码main只关心Base,它不需要知道任何关于派生类的事情,除了可能有一些。只有其中的代码random_object知道派生类。同时Base不需要知道如何abstract()实现,只有派生类知道(每个只关心自己的实现)。代码不需要了解事物是有好处的——这意味着可以在不触及无关代码的情况下更改这些事物。

使用非抽象类作为公共基类也存在一些高级设计问题。例如,一旦某些函数被覆盖,很容易不假思索地编写无法按预期工作的代码,并且很容易意外地用具体的基类对对象进行切片。所以有一种哲学说你应该始终知道你是在写一个具体的类还是一个基类,而不是试图同时做这两个。当您遵循这一理念时,所有基类都是抽象的,因此您将类抽象化以表明它被设计为派生自。

于 2013-04-17T11:18:45.300 回答
3

抽象类用于为某些功能提供接口,其中确切的行为由调用者的类定义。为确保函数的“用户”实现其功能,您在基类中使用抽象函数。这样,如果“用户”没有提供所需的功能之一,则代码不会编译。

于 2013-04-17T11:17:31.197 回答
2

这是给你的一个例子:假设你的程序需要计算不同形状的面积。

class shape{
public:
    virtual double area() = 0;
};

class triangle:public shape{
public:
    triangle(double base, double height) : b(base), h(height) {}
    double area(){ return 0.5 * b * h; }
private:
    double b;
    double h;
};

class circle:public shape{
public:
    circle(double radius) : r(radius) {}
    double area(){ return M_PI * r * r; }
private:
    double r;
};

int main{
    std::vector<shape*> shapes;

    //add whatever shapes you want here
    shapes.push_back( new triangle(4, 5) );
    shapes.push_back( new circle(3) );

    double dTotal = 0.0;
    std::vector<shape*>::iterator i;
    for (i = shapes.begin(); i != shapes.end(); i++)
    {
        dTotal += (*i)->area();
        delete *i;
    }

    cout << dTotal;
    return 0;
}

您现在可以非常轻松地为矩形、圆形、十二面体等创建形状,并以类似方式对待它们,而无需了解它们如何计算自己的面积的细节。同时,shape定义一个自己的区域是没有意义的。

编辑:添加了另一个派生类并使用抽象方法使用它们。

于 2013-04-17T11:23:59.880 回答
1

Steve Jessop 已经指出了为什么需要抽象类和虚函数。您可以强制使用 vtable 来实现动态调度。

您的抽象基类声明了派生类的共同特征。如果在编译时无法确定对象的类型,则虚函数确保调用正确的函数。

在以下示例中,class other在编译时不知道class shape提供了哪个派生。唯一知道的是:每个shape派生都将为该center方法提供一个实现,因为它是pure virtual。(否则程序不会编译和链接。)这足以提供功能。该程序将shPtr在运行时确定 的类型并调用正确的center().

#include <string>
#include <iostream>

using namespace std;

class v2d
{
public: 
  double x, y;
  v2d (void) : x(0.0), y(0.0) { }
  v2d (double const a, double const b) : x(a), y(b) { }
};


class shape
{
public:
  string name;
  shape (void) : name() { }
  shape (string const n) : name(n) { }
  virtual v2d center (void) const = 0;
};


class circle : public shape 
{
private:
  v2d center_point;
  double radius;
public:
  circle (void) : shape("Circle"), center_point(), radius(0.0) { }
  circle (v2d const cp, double const r) : shape("Circle"), center_point(cp), radius(r) { }
  v2d center (void) const { return center_point; }
};

class square : public shape
{
private:
  v2d lowright;
  double sidelength;
public:
  square (void) : shape("Square"), lowright(), sidelength(0.0) { }
  square (v2d const tl, double const sl) : shape("Square"), lowright(tl), sidelength(sl) { }
  v2d center (void) const
  {
    double const halflen = sidelength/2.0;
    return v2d(lowright.x+halflen, lowright.y+halflen);
  }
};


class other
{
private:
  shape *shPtr;
public:
  other (void) : shPtr(NULL) { }
  other (shape *sh_ptr) : shPtr(sh_ptr) { }
  void doSomething (void)
  {
    cout << "Center of this Shape, which is a " << shPtr->name << " is: "<< shPtr->center().x << ", " << shPtr->center().y << endl;
  }
};



int main (void) 
{

  v2d sq_c(1.0, 2.0), circ_c(4.0, 4.0);
  square square_obj(sq_c, 5.0);
  circle circle_obj(circ_c, 2.0);

  other other1 (&square_obj), other2(&circle_obj);

  cout << fixed << setprecision(2);

  other1.doSomething();
  other2.doSomething();

  return 0;
}

输出是

Center of this Shape, which is a Square is: 3.50, 4.50
Center of this Shape, which is a Circle is: 4.00, 4.00

完全正确(意味着已经调用了正确的中心函数)。

-为清楚起见进行编辑-

SuvP 的动物示例也几乎适用。它的缺点是不需要继承来实现相同的功能。

我稍微调整了一下,试图让纯虚拟的好处变得清晰。首先,用户可以任意添加Animal,并且实现feed_all_animals独立于当前派生Animal的 s。

#include <string>
#include <iostream>
#include <vector>

using namespace std;

class Animal
{
public:
  virtual string food() = 0;
  virtual string name() = 0; 
  void eat()  { cout << "A " << name() << " is eating " << food() << endl; }
};

class Dog:public Animal
{
public:
  string name() { return "Dog"; }
  string food() { return "Meat"; }
};

class Horse:public Animal
{
public:
  string name() { return "Horse"; }
  string food() { return "Gras"; }
};

void feed_all_animals (vector<Animal*> animals)
{
  for (size_t i=0; i<animals.size(); ++i)
  {
    cout << "Feeding animal " << i+1 << " (a " << animals[i]->name() << ") with " << animals[i]->food() << endl;
    animals[i]->eat();
  }
}

int main (void)
{
  vector<Animal*> animals;
  // We have a zoo with three dogs and two horses
  Dog dog1, dog2, dog3;
  Horse horse1, horse2;
  animals.push_back((Animal*)&dog1);
  animals.push_back((Animal*)&dog2);
  animals.push_back((Animal*)&dog3);
  animals.push_back((Animal*)&horse1);
  animals.push_back((Animal*)&horse2);
  // now we let the user add another animal
  int ani(0);
  cout << "Do you want to add a Horse [1] or a Dog [0]: ";
  cin >> ani;
  switch (ani)
  {
    case 1: animals.push_back((Animal*)new Horse); break;
    default: animals.push_back((Animal*)new Dog); break;
  }
  // so they don't starve
  feed_all_animals(animals);

  delete animals[animals.size()-1];
  animals.clear();
  return 0;
}
于 2013-04-17T12:11:04.860 回答
1

用“抽象”的话来说,抽象类(完全抽象时也称为接口)从一组相似类的实现中抽象出来。在这里,相似意味着它们具有相同的功能集,但可能以不同的方式实现它们。

抽象类本身并没有定义实现。在程序中传递(指针或引用)抽象类只会传递一个“封闭的包”,您可以与之交互(例如摇动)。但是你隐藏了包里面的东西,包括接口是如何实现的(例如当你摇动它时会发生什么)。

也就是说,抽象类只是将抽象提高了一级。类描述对象具有的属性和行为,但抽象类仅描述您可以对对象执行的操作。只有实际的实现才能定义行为。

话虽如此,一个重要的事实是您不能创建抽象类的实例。

在设计一个软件时,抽象类/接口是一种非常重要的抽象方法。每当您的软件的某些部分想要与它不知道的东西进行交互时,您可以编写一个接口来描述此类对象必须提供的功能,而无需指定实际类型。这使得可以轻松地传递不同类型的对象。获取指向此类接口/抽象类的指针/引用的函数可以在不知道其实际类型的情况下与对象交互。

于 2013-04-17T11:21:52.987 回答
1

an 的目的是为子类abstract (base-)class定义一个。interface通过这种方式,您可以确定您的类必须具备什么能力(它的方法),但不能确定它是如何做事的(实现)。
当您从一个abstract类派生时,您承诺在您的子类中提供某种行为。使用它,函数可以使用您的对象并执行它们的操作,而无需详细了解它们的具体行为。
到处都有类层次结构,比如在.NET-framework 中表示概念或 UI 控件之类的东西。
其次,有时拥有一个类的实例是没有意义的。考虑一个类Animal。如何Animal定义?实例化 a 没有意义Animal,而是实例化 aDog或 a Cat,派生自Animal.

于 2013-04-17T11:20:39.607 回答
1

在您不希望为类创建对象的情况下,抽象类也很有帮助。

考虑到这一点,

/* Abstract Class Animal */
class Animal
{
public:
    virtual void eat() = 0;
    virtual void walk() = 0;
};

class Dog:public Animal
{
public:
    void walk() { std::cout<<"\nWalk with Paws"; }   
    void eat()  { std::cout<<"\nEat meat";       }
};

class Horse:public Animal
{
public:
        void walk() {  std::cout<<"\nWalk with Hooves"; }   
        void eat()  {  std::cout<<"\nEat Grass";        }
};

int main()
{
    Dog d;
    Horse h;

    d.eat();
    d.walk();
    h.eat();
    h.walk();
return 0;
}

在这里,您不希望Animal创建类型的对象。sleep eat该对象对等没有任何特定行为。

您可以利用这个abstract类来创建子类,例如DogCatpure virtual functions必须由派生类实现,否则编译器会出错。

这保证了您的所有子类都将具有所需的方法,并且您最终不会创建一个undefined对象,例如Animal

于 2013-04-17T11:26:24.440 回答