面向对象编程中的抽象数据类型是什么?

67
什么是面向对象编程中的抽象数据类型?我已经查阅了该主题的维基百科,但仍不清楚。能否请有经验的人士进行澄清?

可能是抽象类的特征的重复问题。 - this. __curious_geek
@MarkSeemann 我二十年前就读过了,ADT 不是面向对象的概念。现代大多数语言使用泛型编程来实现它们。 - Pete Kirkham
答案:http://programmers.stackexchange.com/a/288504/114794 - Premraj
1
我找到的最好的简单定义来自Gayle L.M.的《破解编程面试》:“抽象数据类型是由其行为(操作)定义的。底层实现可以有所不同。您可以使用数组或min(或max)堆(或许多其他数据结构)来实现优先队列。”一些常见的抽象数据类型(ADT):列表,集合,图,栈,队列,优先队列等。 - Andrew
ADT是面向对象编程(OOP)抽象原则的核心,应该与多态性和继承相补充。可以说ADT(抽象)是OOP的基础,其余的原则都是从它衍生而来以补充它。 - hyankov
20个回答

63

抽象类 是一个概念的概括。它是一个你发明出来只用作继承基类而不用于实例化对象的类。

抽象数据类型 (ADT) 并不一定是面向对象编程(OOP)的概念。它是一个早期术语,用于描述例如栈(Stack)和队列(Queue)的功能,而不涉及其实现细节。


8
前几个答案只是讨论了Java中的“abstract”关键字,它本身并没有定义数据类型。当我查找“抽象数据类型”时,我得到了http://en.wikipedia.org/wiki/Abstract_data_type。Henk确定了这两个概念。很明显,提问者的问题并不是很明确。 - Ewan Todd

47

抽象数据类型”和“抽象类”是有区别的。

一个抽象类可能没有为其定义的所有方法提供具体的定义。因此,您无法直接实例化一个抽象类。您需要创建一个子类然后再实例化它。

抽象数据类型是某种数据结构的模型,例如一个。一个栈具有入栈(push())和出栈(pop())操作,这些操作都有明确定义的行为。

抽象数据类型(ADT)本身指的是该模型,而不是任何特定编程语言或范式中的任何具体实现。您可以在面向对象的语言中实现一个栈,但也可以在函数式编程语言中实现它。

ADTs允许讨论关于栈、队列等属性的话题,这些属性适用于ADT的所有正确实现。


对于第三点我并没有完全理解;而且这句话“抽象数据类型(ADT)本身指的是这个模型,而不是任何特定编程语言或范式中的具体实现”让我更加困惑。 - JAVA
ADTs也是“抽象测试套件”的基础,遵循Meyer的思想,“一个对象/模块/(子-)系统的定义取决于你可以用它做什么”。 - heltonbiker
我想补充一下,编程语言实现了哪些范式并不重要。在这个术语更为流行的时代,语言从来没有偏向某种范式,所有语言都被认为是多范式的,我们流行的语言也是多范式的。我认为 ADT 和鸭子类型非常相似,如果不是同一件事的话。 - kisai

9
好的,这篇文章主要是关于抽象。在编程中,抽象是非常有用的。其主要优势在于能够隐藏实现细节。您可以将其隐藏在一个模块(所谓的“服务器模块”)中,并为其他模块(所谓的“客户端模块”)提供一些公共接口。现在我们有了三种不同的可能性:

服务器模块可以自行提供抽象数据结构(ADS)

在这种情况下,它本身包含ADS实体。公共接口由一些过程(以及可能的一些常量)组成。

服务器模块的接口(stack_ads.h):

#ifndef STACK_ADS
#define STACK_ADS

const int capacity = 10;

void clear();
int size();
int pop();
void push(int value);

#endif STACK_ADS

实现(stack_ads.cpp):
#include "stack_ads.h"

int items[capacity];
int top = -1;

void clear()
{
  top = -1;
}

int size()
{
  return top + 1;
}

int pop()
{
  top -= 1;
  return items[top + 1];
}

void push(int value)
{
  top += 1;
  items[top] = value;
}

在客户端模块(main.cpp)中,我们导入服务器模块并直接使用数据结构。
#include <iostream>
#include "stack_ads.h"

int main (int argc, char* const argv[]) 
{
  push(1);
  push(2);
  push(3);

  std::cout << pop() << std::endl;
  std::cout << pop() << std::endl;
  std::cout << pop() << std::endl;

  return 0;
}

服务器模块可以以结构体/记录的形式提供抽象数据类型(ADT)。

在客户端模块中,我们可以声明变量为该类型。因为模块可以自由地声明多个变量为导出类型,所以它可以有多个数据结构。每个抽象数据结构都是抽象数据类型的变量。

接口(stack_adt.h):

#ifndef STACK_ADT
#define STACK_ADT

const int capacity = 10;

typedef struct
{
  int items[capacity];
  int top;
} StackADT;

void clear(StackADT* stack);
int size(StackADT* stack);
int pop(StackADT* stack);
void push(StackADT* stack, int value);  

#endif STACK_ADT

实现(stack_adt.cpp):
#include "stack_adt.h"

void clear(StackADT* stack)
{
  stack->top = -1;
}

int size(StackADT* stack)
{
  return stack->top + 1;
}

int pop(StackADT* stack)
{
  stack->top -= 1;
  return stack->items[stack->top + 1];
}

void push(StackADT* stack, int value)
{
  stack->top += 1;
  stack->items[stack->top] = value;
}

客户端模块:

#include <iostream>
#include "stack_adt.h"

int main (int argc, char* const argv[]) 
{
  StackADT stack1;
  StackADT stack2;
  stack1.top = -1;
  stack2.top = -1;

  push(&stack1, 1);
  push(&stack1, 2);
  push(&stack1, 3);

  std::cout << pop(&stack1) << std::endl;
  std::cout << pop(&stack1) << std::endl;
  std::cout << pop(&stack1) << std::endl;

  push(&stack2, 10);
  push(&stack2, 20);
  push(&stack2, 30);

  std::cout << pop(&stack2) << std::endl;
  std::cout << pop(&stack2) << std::endl;
  std::cout << pop(&stack2) << std::endl;

  return 0;
}

最后,服务器模块可以提供一个抽象数据类型(ADT),以类的形式呈现。

如果我们的语言支持面向对象编程(OOP),我们可以通过类来描述 ADT。在客户端模块中,我们可以声明变量为该类型。在面向对象编程术语中,这种类型称为“类”,拥有该类型的变量称为“对象”。

服务器模块接口(Stack.h):

#ifndef STACK
#define STACK

const int capacity = 10;

class Stack
{
public:
  Stack();
  void clear();
  int size();
  int pop();
  void push(int value);
private:
  int items[capacity];
  int top;
};

#endif STACK

实现(Stack.cpp):

#include "Stack.h"

Stack::Stack()
{
  this->top = -1;
}

void Stack::clear()
{
  this->top = -1;
}

int Stack::size()
{
  return this->top + 1;
}

int Stack::pop()
{
  this->top -= 1;
  return this->items[this->top + 1];
}

void Stack::push(int value)
{
  this->top += 1;
  this->items[this->top] = value;
}

两种选项的区别如下:
  • 术语上述(类型<->类,变量<->对象)。
  • 在非类ADT中,每个过程的形式参数列表必须包括类型为Stack的变量s。在堆栈类中,数据结构s的规范不包括在其他形式参数后面的过程名称之后,而是单独放在括号中,在过程名称之前。使用Smalltalk术语,在过程名称之前的形式参数称为接收者
  • 过程的位置。在非类ADT中,过程位于Stack struct之外。在类中,过程位于类内部。在面向对象的术语中,具有接收器并因此包含在类类型中的过程称为方法

客户端代码:

#include <iostream>
#include "stack.h"

int main (int argc, char* const argv[]) 
{
  Stack stack1;
  Stack stack2;

  stack1.push(1);
  stack1.push(2);
  stack1.push(3);

  std::cout << stack1.pop() << std::endl;
  std::cout << stack1.pop() << std::endl;
  std::cout << stack1.pop() << std::endl;

  stack2.push(10);
  stack2.push(20);
  stack2.push(30);

  std::cout << stack2.pop() << std::endl;
  std::cout << stack2.pop() << std::endl;
  std::cout << stack2.pop() << std::endl;

  return 0;
}

6

定义:

抽象数据类型(ADT)大致而言,是一种查看数据结构的方式:关注它所做的事情,忽略它如何执行其工作。

抽象数据类型主要由其接口来定义:可以对它们执行的可允许操作。用于实现它们的底层机制通常对其用户不可见。


示例:

StackQueuePriorityQueue是ADT的一些示例,它们比数组、链表和许多其他数据存储结构更抽象。

例如,堆栈的底层机制可以是一个Array或者是一个LinkedListPriorityQueue的底层机制可以是一个Array或者称为Heap的特殊树。


代码:

这里是使用Heap实现的Java抽象数据类型PriorityQueue的示例:

class Heap {
  private Node heapArray[];
  public void insert(Node node) {...}
  public Node remove() {...}
}

class PriorityQueue {
  private Heap heap;
  public void insert(Node node) { 
    heap.insert(node);
  }
  public Node remove() {
    return heap.remove();
  }
}

在这里,您可以看到PriorityQueue类的方法只是包装在底层Heap类的方法周围。同样,您可以使用Array代替Heap来实现相同的功能,即使在Array的情况下,您需要更多的代码来处理插入和删除等操作。这个例子应该让人们从概念上清楚,PriorityQueue是一种ADT,可以使用堆、数组等多种方式进行实现。
尽管ADT在面向对象编程(OOP)语言中更有意义,但它们不仅限于只能在OOP语言中创建,也可以使用非OOP语言创建。

6

抽象数据类型(ADT)是一种数据类型的数学模型。它描述了可在数据上执行的操作以及使用方程式定义这些操作的数学定义。

例如,您可以使用pop(),push(),top()等操作以及表示空堆栈的常量符号,完全抽象地对数字堆栈的行为进行建模。

以下是一些可能构成数字堆栈定义的方程式:

pop(empty) = empty  // silently ignores popping an empty stack
pop(push(N,S)) = S  // i.e. pop removes the top element of push(N,S)
top(push(N,S)) = N  // return topmost element of the stack without changing the stack

一个抽象数据类型并不是一个面向对象模型中的类,尽管它们有一些相似之处。
以下是重要概念的名称:初始代数语义、同构、商、同余。
抽象数据类型的目的是通过方程和一些高级数学来理解整个等价类型表示类的行为,以证明每个实现是“同构”的,即就可观察行为而言,两个实现完全等价。
维基百科上关于这个主题的条目非常好:http://en.wikipedia.org/wiki/Abstract_data_type 以下是一些很好(但非常理论化)的课程笔记,阐述了ADT是什么:http://www-compsci.swan.ac.uk/~csulrich/ftp/adt/adt.pdf 虽然表面上与某些面向对象编程语言中的“类”概念相似,但“类”不是ADT,但可以使用类来实现特定的ADT。
总的来说,ADT概念可能比面向对象编程更适用于函数式编程,因为并非所有面向对象编程语言都有类,并且ADT风格的思考会导致效果较差的OO设计。
以下是一篇论文,展示了在OO语言中以ADT思考的问题:http://portal.acm.org/citation.cfm?id=74885 基本上,这篇论文显示,用于实现ADT的“类”最终被覆盖了许多微小的方法(看起来像ADT方程的基础),而不是具有少量强大、高抽象方法。

无意义的话,就实际目的而言 ADT 即是鸭子类型,你通过它的操作来定义一个类型,而不是像名义类型那样使用标签,或者通过数据结构来定义。关键是“实现并不重要”。在类型化的面向对象语言中,你会使用接口。在动态类型语言中,就是鸭子类型。 - kisai
那里究竟有什么无意义之处?“实际上”不是一个定义。这是否意味着“我不能被打扰来分析你所说的话”? - Dafydd Rees
@kisai - 你有没有在大学里学过“抽象数据类型”这门课程?我想你没有,所以请不要随意发表价值判断。 - Dafydd Rees
说实话,那个评论是一年前的了,我甚至不记得当时为什么这样写了,但是没错,我在大学和以后都学过 ADTs。对于实际用途而言,这意味着当你使用特定编程语言进行代码实现时,它应该对你来说是显而易见的。 - kisai
如果它走起来像鸭子,叫起来像鸭子,那么它一定是鸭子。"走路"和"叫声"是操作,"像鸭子"总结了它必须满足的属性,而"为实际目的"意味着在解释上不要过于严格。不要因此感到冒犯,这也是你回复的原因,小脆皮。 - kisai
@kisai 好的,明白了,没有使用足够的“鸭子”。 - Dafydd Rees

3
在学校里,他们教我抽象数据类型(ADT)只是一个包含一组数据和可以对这些数据进行操作的一组操作的集合。它只是一个概念,并且与任何语言、实现或范例都没有关系。
更新:
因此,根据我的定义,在面向对象编程中,抽象数据类型应该是一个类抽象,无论是否继承,因为它包含数据(属性、字段等)和操作(方法)。
问候

3

抽象是编程和现实生活中最基本和最广泛的概念。

面向对象编程中的抽象数据类型是什么?

ADT是一个容器,它具有规范的不同类型对象。数据的逻辑表示(即接口或协议)以及操作以操纵数据的组成元素。

ADT的例子:列表、映射、集合、堆栈、队列、树、图。

数据结构可以实现一个或多个特定的抽象数据类型(ADT)。例如,在Java中,ArrayList、LinkedList、Stack和Vector是List的数据结构实现(类)。

现实生活中的堆栈示例:

  1. 当一个人戴手镯时,最后一个戴上的手镯是最先摘下的,第一个戴的手镯会最后摘下。这遵循了堆栈的后进先出(LIFO)原则。
  2. 在一叠盘子中,可以从顶部取出盘子或将盘子放在顶部。放置的第一个盘子将是最后一个拿出的盘子。这遵循了堆栈的后进先出(LIFO)原则。
  3. 手电筒中的电池:除非您取出最后一个电池,否则无法取出第二个电池。因此,放入的第一个电池将是最后一个拿出的电池。这遵循了堆栈的后进先出(LIFO)原则。
  4. 行李箱中的衣服

现实生活中的队列示例:

  1. 售票窗口的人员队列:第一个到达的人先得到门票。最后一个到达的人最后得到门票。因此,它遵循了队列的先进先出(FIFO)策略。
  2. 通过收费桥的车辆:第一个到达收费站的车辆最先离开收费站。最后到达的车辆最后离开。因此,它遵循了队列的先进先出(FIFO)策略。
  3. 行李检查机器:行李检查机器首先检查先到的行李。因此,它遵循了队列的FIFO原则。
  4. 在医生诊所外等待的病人:第一个到达的患者先看医生,最后到达的患者最后看医生。因此,它遵循了队列的先进先出(FIFO)策略。

上述示例来自Source1Source2


2

从代码中退一步:

什么是抽象?抽象

它的要点是“不是真实的,但捕捉到真实事物的属性”

在面向对象编程中需要了解这个概念,因为你将设计对象宇宙,这需要你思考这些对象之间的关系。

抽象允许你将其中一些对象分组,从而组织:

1)你的思考过程 2)你的代码


1

我也一直有这个问题,但是上周解决了。

抽象类是一种通用或概括性的东西。您可以使用该类来模塑并以任何您喜欢的方式扩展它。

我可以在这里给您一个实际的例子

拿一个叫做动物的类。它包含像吃、声音、移动这样的函数,这些都是所有动物都会做的事情。您可以扩展该类来获取特定的猫、狗等。

例如。

abstract class animal {

    abstract protected function eat();
    abstract protected function sound();        

}

class dogs extends animal
{
   protected function eat() {
       return "meat";
   }

   public function sound() {
       return "bow wow";
   }
}

希望我的回答对你有意义


1
  1. 类使用数据抽象的概念,即抽象数据类型。

  2. 抽象数据类型是一个较旧的术语,用于描述堆栈和队列的功能而不描述它们的实现方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接