Cplusplus 简明教程

Data Abstraction in C++

数据抽象是指只向外界提供基本信息并隐藏其背景细节,即在程序中表示必要的信息而不显示细节。

数据抽象是一种编程(和设计)技术,它依赖于接口和实现的分离。

让我们举一个电视机的真实例子,您可以打开和关闭它,改变频道,调节音量,并添加外部组件,例如扬声器、录像机和 DVD 播放器,但是您不知道它的内部细节,即,您不知道它是如何通过空中或电缆接收信号,如何转换信号以及最后如何将它们显示在屏幕上。

因此,我们可以说电视机清楚地将它的内部实现与其外部接口分开,您可以在不了解其内部知识的情况下使用其接口,如电源按钮、频道转换器和音量控制。

在 C++ 中,类提供了很高的 data abstraction 级别。它们向外界提供了足够多的公共方法,以使用对象的函数并操作对象数据(即状态),而无需实际了解该类在内部是如何实现的。

例如,您的程序可以调用 sort() 函数,而不知道函数实际使用什么算法来对给定值进行排序。事实上,排序功能的基础实现可以在库的发布之间更改,并且只要接口保持不变,您的函数调用仍然有效。

在 C++ 中,我们使用 classes 来定义我们自己的抽象数据类型 (ADT)。你可以使用类 ostreamcout 对象以这种方式将数据流式传输到标准输出:

#include <iostream>
using namespace std;

int main() {
   cout << "Hello C++" <<endl;
   return 0;
}

在这里,无需了解 cout 如何在用户的屏幕上显示文本。你只需要了解公用接口,并且 ‘cout’ 的底层实现是免费更改的。

Access Labels Enforce Abstraction

在 C++ 中,我们使用访问标签来定义类的抽象接口。一个类可能含有零个或多个访问标签:

  1. 使用公用标签定义的成员在程序的所有部分都可以访问。类型的抽象数据视图是由其公用成员定义的。

  2. 使用私有标签定义的成员对于使用该类的代码是不可访问的。私有部分将实现隐藏在使用该类型代码中。

访问标签可以出现多次,对此没有限制。每个访问标签指定了后续成员定义的访问级别。指定访问级别有效,直到遇到下一个访问标签或看到类体的闭合右大括号。

Benefits of Data Abstraction

数据抽象提供了两个重要优势:

  1. 类内部不受无意的用户级错误的影响,而这些错误可能会损坏对象的状况。

  2. 随着变化需求或错误报告的出现,类实现会随着时间的推移发展,而不需要更改用户级代码。

通过仅在类的私有部分中定义数据成员,类作者可以自由地更改数据。如果实现发生变化,只须检查类代码即可查看更改可能会产生什么影响。如果数据是公开的,则任何直接访问旧表示形式的数据成员的函数都可能损坏。

Data Abstraction Example

任何实现具有公有和私有成员的类的 C++ 程序都是数据抽象的一个例子。考虑以下示例:

#include <iostream>
using namespace std;

class Adder {
   public:
      // constructor
      Adder(int i = 0) {
         total = i;
      }

      // interface to outside world
      void addNum(int number) {
         total += number;
      }

      // interface to outside world
      int getTotal() {
         return total;
      };

   private:
      // hidden data from outside world
      int total;
};

int main() {
   Adder a;

   a.addNum(10);
   a.addNum(20);
   a.addNum(30);

   cout << "Total " << a.getTotal() <<endl;
   return 0;
}

编译并执行上述代码后,将产生以下结果 −

Total 60

上述类将数字相加,并返回总和。公有成员 addNumgetTotal 是与外界的接口,用户需要了解它们才能使用该类。私有成员 total 是用户不需要了解的,但类需要它才能正常运行。

Designing Strategy

抽象将代码划分为接口和实现。因此,在设计组件时,你必须保持接口独立于实现,以便在更改底层实现时,接口保持不变。

在这种情况下,无论哪些程序使用这些接口,都不会受到影响,只需使用最新的实现重新编译即可。