C ++中的抽象类是否意味着抽象?

时间:2016-01-26 05:26:12

标签: c++

许多教程将抽象列为C ++中的4个基本原则之一(其余3个作为封装,继承和多态)。我试图理解抽象的概念。很多在线教程都说抽象是隐藏实现细节并仅提供接口的概念。我没有清楚地理解这一点。我不明白我们藏着什么。这是在讨论隐藏函数使用的内部结构吗?如果是这种情况,即使是正常的C函数也会这样做。当我与我的一位同事谈论此事时,他告诉抽象类是抽象的最好例子。但我也不明白这一点。因为当我们有纯虚函数时,我们不能创建类的实例,纯虚函数大多没有定义。所以在这种情况下没有隐藏的概念。任何人都可以用C ++解释一下C ++中的抽象吗?

5 个答案:

答案 0 :(得分:2)

您应该将语言结构区分为抽象类,将通用概念区分为抽象类。

虽然抽象类可能是创建抽象的有用工具,但它不是必要的工具,也不是使用该工具来保证您获得(良好)抽象。

例如,C ++标准中的所有地方都有抽象,所以不应该要求另外一个例子。

以STL为例。有许多不同类型的容器,但是例如,有些序列都符合在它们上定义的一组共同功能,此外,根据您选择的那个,保证了不同操作的复杂性。这里的抽象是这些是可用于存储数据的顺序容器。虽然它们不使用虚函数,但实现因实现而异(或至少可能有所不同),但如果你根据它使用它规范实际的实现对程序员来说无关紧要(而且程序员通常不会深入研究实际的实现)。

规范中的另一个抽象是语言本身,其中指定的执行环境和翻译过程。这些部分没有根据它们的实现方式来指定,而是根据预期的行为来指定。例如,通常一个实现通过将它们放在处理器堆栈上来实现局部变量,但这是C ++规范遗漏的实现细节。该规范提出了许多关于执行行为的假设。并且您使用这些假设构建您的程序,而不是假设实现需要以特定的具体方式完成。

答案 1 :(得分:1)

不,抽象并不意味着你必须隐藏内部结构。

CPP Primer Plus,第507页为您提供解释和示例。

  

生活充满了复杂性,我们应对复杂性的一种方法是简化框架   抽象。你是一个超过octillion原子的集合。一些学生的   大脑会说你的思想是几个半自主代理人的集合。但它是   将自己视为一个单一的实体要简单得多。在计算中,抽象是至关重要的   根据与用户的界面来表示信息的步骤。也就是说,你   摘要问题的基本操作特征并在其中表达解决方案   条款。在垒球统计示例中,界面描述了用户如何初始化,   更新,并显示数据。从抽象来看,它是用户定义类型的一小步,   在C ++中,它是一个实现抽象接口的类设计。

答案 2 :(得分:1)

  

许多教程将抽象列为C ++中的4个基本原则之一(其余3个作为封装,继承和多态)。

该列表似乎用任何语言描述 面向对象 。根据您的观点,C ++有许多“基本原则”,并且没有达成一致的列表。

  

我试图理解抽象的概念。很多在线教程都说抽象是隐藏实现细节并仅提供接口的概念。我没有清楚地理解这一点。我不明白我们藏着什么。这是在讨论隐藏函数使用的内部结构吗?如果是这种情况,即使是正常的C函数也会这样做。

让我们看一个例子。让我们假设一个程序处理一系列数字输入,并且在一个高“抽象”级别,它想收集一些关于这些数字的统计数据。我们可以写:

#include <iostream>

template <typename Stats, typename T>
bool process_input(std::istream& in, Stats& stats)
{
    T v;
    while (in >> std::skipws && !in.eof() && in >> v)
        stats(v);
    return in;  // true if no errors
}

在上面的代码中,我们使用从输入中读取的每个值stats“调用”v。但是,我们不知道stats对值的作用是什么:它是否全部保存,计算最小值,最大值,总计,stdddev,第三个百分点?其他人可以关心,因为我们已经将上面的输入逻辑写到了 abstract 那些问题:调用者可以提供一个合适的stats对象来做任何必要的事情(即使没有,只要使用T表示法使用类型stats(v)的值“调用”它是有效的。同样,我们没有决定输入包含哪些类型的数据:T可能是double,或std::string,或int或某些尚未-be-written类,但我们的算法适用于其中任何一个,因为它抽象了输入逻辑。

  

假设我们想要一个Stats对象,它可以找到一组值的最小值和最大值。在C ++中,我可以写:

template <typename T>
class Stats
{
  public:
    Stats() : num_samples_(0) { }

    void operator()(T t)
    {
        if (++num_samples_ == 1)
            minimum_ = maximum_ = t;
        else if (t < minimum_)
            minimum_ = t;
        else if (t > maximum_)
            maximum_ = t;
    }
    T minimum() const { return minimum_; }
    T maximum() const { return maximum_; }
    size_t num_samples() const { return num_samples_; }

    friend std::ostream& operator<<(std::ostream& os, const Stats& s)
    {
        os << "{ #" << s.num_samples_;
        if (s.num_samples_)
            os << ", min " << minimum_ << ", max " << maximum_;
        return os << " }";
    }
  private:
    size_t num_samples_;
    T minimum_, maximum_;
};

这只是可以传递给上面process_input的对象的一种可能实现。 void operator()(T t)函数满足process_input的接口期望。处理一系列值的任何其他函数都可以将它们传递给Stat对象,它们甚至可以流出收集的统计数据......

std::cout << stats << '\n';

...没有理解计算/收集了哪些统计数据。再次,这是抽象:你可以说 要在非常高的水平上完成,而不知道更低级别的细节,更不用说 如何 它将完成。

  

当我与我的一位同事谈论此事时,他告诉抽象类是抽象的最好例子。但我也不明白这一点。因为当我们有纯虚函数时,我们不能创建类的实例,纯虚函数大多没有定义。所以在这种情况下没有隐藏的概念。任何人都可以用C ++解释一下C ++中的抽象吗?

你在抽象中隐藏的是事情是如何完成的 - 这是在定义中表达的,所以抽象类至少具有少量的抽象。尽管如此,让我们对上面那个具有合理抽象级别的例子进行对比,尽管使用了一个抽象类,但它仍然缺乏抽象的代码:

class Abstract_Stats
{
  public:
    virtual double get_minimum() const = 0;
    virtual void set_minimum(double m) = 0;
    virtual double get_maximum() const = 0;
    virtual void set_maximum(double m) = 0;
  private:
    double minimum_, maximum_;
};

使用这样一个愚蠢的抽象类,我们的process_input函数需要重写:

bool process_input(std::istream& in, Abstract_Stats& stats)
{
    int v;
    size_t n = 0;
    while (in >> std::skipws && !in.eof() && in >> v)
        if (++n == 1) { stats.set_minimum(v); stats.set_maximum(v); }
        else if (v < stats.get_minimum()) stats.set_minimum(v);
        else if (v > stats.get_maximum()) stats.set_maximum(v);
    return in;  // true if no errors
}

突然之间,我们的Abstract_Stats类的抽象界面不那么抽象,迫使我们将统计信息收集功能的特定内容混合到输入逻辑中。

因此,抽象不是关于函数是否是纯虚函数,而是更多关于工作分工以使事物可以在不同的组合中重复使用,每个都是可以独立测试和理解的。

答案 3 :(得分:1)

抽象是日常生活中非常自然的东西,谈论某些事情而不涉及事物的许多细节是很常见的。您可以在不考虑/了解力学,流体力学,化学,工程学等的情况下使用您的汽车。计算机工程中的抽象是完全相同的(通常)。

是的,一个简单的函数提供了一个抽象。但是函数只是软件的一小部分,它们有时是通过对代码进行分解来构建的(一个好主意,但并不总能导致良好的抽象)。抽象应该具有明确的语义含义并不棘手。

OOP是女巫的范例,你可以建立新的类型,让你忘记它们的细节。正如在一个关于算法的课程中,人们可以告诉你quicksort如何工作,但从不谈论他们正在排序的元素的真实性质(它在排序中肯定不是一个有趣的点)。对象(与您的汽车一样)的有趣之处在于可以操纵对象而不是如何实现行为。我想通过向左旋转转向左转,我不想知道当我这样做时真的发生在幕后。当我把车开给维修人员时,我让他在我的车上做任何他想做的事情,只要它像往常一样工作(他可以改变他想要的任何东西)。作为用户,我只想关注手册而不是内部。因此,您需要在理想对象(手册)的接口和具体对象的实现(内部模式)之间做出改变。这就是每种OOP语言都能让你写的东西(当然,你可以通过不同的方式实现所有这些)。

所以你想谈谈代码中某处飞机上的点?让我们来谈谈手册(为了简单起见,这是一本简短的手册)。点是一个物体,你可以从中得到它的笛卡尔坐标或它的极坐标,对吧?然后是它的摘要,无论在软件中获得/实现什么,你都希望能够用它做到这一点。所以这是一个抽象:

class Point {
  public:
    virtual double getX() = 0;
    virtual double getY() = 0;
    virtual double getAngle() = 0;
    virtual double getLength() = 0;
}

这是一本手册,你可以使用一个点(如果你有一个),那么你可以写一个有效的可编译代码:

void f(Point *p) {
    cout << p->getX() << "," << p->getY() << endl;
}

在这里你需要小心,要么传递指针或引用。你传递一个对象作为抽象,然后应该发生一些事情来检索实现,在C ++中这需要引用或指针。请注意,此函数不接收Point(Point是抽象的东西,不存在),但可以接收Point的任何类型的实现(这会产生很大的不同)。注意:这段代码是可编译的,并且在您通过抽象实现调用它时仍然有效(这可能在很长一段时间内有效!代码可重用性,您知道吗?)

好的,现在你可以实现抽象:

class PolarPoint : public Point {
  private:
    double angle, length;
  public:
    PolarPoint(double a,double l) : angle(a), length(l) {}
    virtual double getX() { return length*cos(angle); }
    virtual double getY() { return length*sin(angle); }
    virtual double getLength() { return length; }
    virtual double getAngle() { return angle; }
}

在某处你实例化它(创建这个具体模型的对象然后使用它(然后忘记它的所有特性):

...
Point *p = new PolarPoint(3.14/4,10.0);
f( p );
....

提醒即使很久以前f已被编译,但现在可以使用这个新的实现!抽象是一种契约。

您还可以通过其他方式实现:

class CartesianPoint : public Point {
  private:
    double x, y;
  public:
    CartesianPoint(double x,double y) : x(x), y(y) {}
    virtual double getX() { return x; }
    virtual double getY() { return y; }
    virtual double getLength() { return /* the calculus from x/y*/; }
    virtual double getAngle() { return /* the calculus from x/y */; }
}

...
Point *p2 = new CartesianPoint(3.14/6,20.56);
f( p );
...

在这个例子中,我还使用了信息隐藏,与抽象相关的概念(至少对抽象有用)。私人/公共与信息隐藏有关,这可以让你强制隐藏,这意味着一个班级的用户无法访问(至少太容易)细节,不仅不鼓励他看他们,而且他可以& #39; t操纵它们。再次,对于您的汽车,更换活塞并不容易,不仅因为它是发动机的内部部件,而且因为构造器提供了许多方法来隐藏这一点:没有使用说明书,特殊工具很难您可能知道您的汽车有化油器,但您可能无法接触它。

请注意,抽象并不意味着隐藏,但如果你不想要(并且一般你不想),就让你忘记细节。抽象是获得软件组件低耦合的好方法。

答案 4 :(得分:0)

抽象和抽象类不一样。

抽象只是创建一个概念或事物的模型。然而,编程中的抽象通常意味着模型比你抽象的更简单。这适用于大多数编程语言:大多数都有构造或方法来模拟你想要的东西,以便以某种方式提供一个好处。

抽象交通流模拟,例如,作为一堆无关的变量是混乱的。但是,如果您将每个车辆建模为 对象 ,则每个对象都可以处理其自身的内部状态,并且处理“车辆”对象的概念变得更加简单。一堆彼此无关的变量。

抽象类更像是Java的接口。它们在程序的不同内部部分中用作统一编程“接口”。通过限制对象如何与其他对象进行交互,您可以通过限制程序的编程方式将确定性带入程序。它通常利用语言类型系统来减少程序各部分中发生的不可预测的行为或不需要的行为,方法是强制它符合类型约束。

抽象的一些例子:lambda演算,对象,结构,构造函数和析构函数,多态等等。