一篇通俗易懂的ADT介绍

最近笔者通过哈工大的软件构造课程,学习了抽象数据类型.
我们,将通过打比方的形式,通俗易懂的给大家讲明白ADT是谁,为什么要有这个东西,以及,怎么构建它

ADT:抽象数据类型

定义:抽象数据类型(Abstract Data Type,ADT)是计算机科学中具有类似行为的特定类别的数据结构的数学模型;或者具有类似语义的一种或多种程序设计语言的数据类型。抽象数据类型是间接定义的,通过其上的可执行的操作以及这些操作的效果的数学约束(与可能的代价)。
按照MIT的说法,ADT可以用下面的这幅图来进行描述:


直观的看,ADT就是一道墙,将内在的表示与外部的使用进行隔离这种隔离,通过接口进行的间接访问,降低了代码块与块之间的耦合性.

下面的两个比喻,形象生动的说明了ADT是干什么的,为什么这么重要:

  1. 就像盖房子,我们写的软件代码,java里面一个个的Class,就像很多的***普通的砖头***,我们并不需要他们每一个都那么的独领风骚,我们需要每一块砖头都长得差不多,标准一致,这样可以进行将砖头进行千百次的堆砌.同时,我们需要一块砖头如果觉得它存在质量问题,我们可以马上用另一块相似的砖头进行代替.
    在这里插入图片描述

  2. 就像军舰里面的***水密隔舱***,通过一层一层的将船与外面的大海进行隔离,那么如果一侧因为中弹而进水,将防水门一关,进水将不会影响到军舰的其余部分.
    在这里插入图片描述

为了能够造出更规则统一的砖头(水隔舱),无数计算机领域的巨佬,研究出ADT这样的指导思想,帮助外面更好的,造出统一而又安全的砖头.

可变与不可变数据类型

还是使用砖头来作比方:
如果我们想要自己用砖头搭建起来的房子是可以平稳存在的,那么,我们就首先得要求这个砖头,它是稳定的.
不可变数据类型就像是一块死砖头,它不会因为外界的操作而改变自己的属性,可以说这个砖头,它是死的.我们就可以依靠这个砖头来作房子的地基.从而使房子四平八稳.
在这里插入图片描述
比如说,上图的字符串String,就是不可变的,它如果对其进行操作,会直接返回新的一个对象
那么,如果我们的代码里面用了可变的数据类型,它会带来什么后果呢?

直接给外界你内部的属性的指针,让外界直接对内部属性进行修改.导致一些很阴间的bug.
比如:神不知鬼不觉的,数据结构内部属性的值一下子就变了…

/**
 * @return the first day of spring this year
 */
public static Date startOfSpring() {
    return askGroundhog();
}
********************************************
// somewhere else in the code...
public static void partyPlanning() {
    Date partyDate = startOfSpring();
    // ...
}
/**
 * @return the first day of spring this year
 */
public static Date startOfSpring() {
    if (groundhogAnswer == null) {
        groundhogAnswer = askGroundhog();
    }
    return groundhogAnswer;
}
private static Date groundhogAnswer = null;
**********************************************
// somewhere else in the code...
public static void partyPlanning() {
    // let's have a party one month after spring starts!
    Date partyDate = startOfSpring();
    partyDate.setMonth(partyDate.getMonth() + 1);
    // ... uh-oh. what just happened?
}

所以,正如大厦的地基必须要用混凝土和坚实的砖石来打,程序内部所依靠的数据类型,最好是采用不可变的数据类型

Spec 规约

假如我们是来给别人家房子的施工队,在出发之前,一般都是要和雇主甲方爸爸签一个文书的,这个就是规约Spec

这个spec,它通过规定双方的责任,也就是产品问题,找谁说理去.
细分为两大类:
前置条件: 如果产品坏了,这个责任在用户.
后置条件: 如果产品坏了,这个责任在施工队.

在我们写Java程序时,其实,也有这样的一个spec要去完成
它长这样:

/**
 * Find a value in an array.
 * @param arr array to search, requires that val occurs exactly once
 *            in arr
 * @param val value to search for
 * @return index i such that arr[i] = val
 */
static int find(int[] arr, int val)

不难发现里面的前置与后置可以大致分下:

前置条件
@param arr array to search, requires that val occurs exactly once in arr
@param val value to search for
find(int[] arr, int val)
后置
Find a value in an array.
@return index i such that arr[i] = val

它就像一个防火墙,隔绝开了用户与程序内部的表示.
在这里插入图片描述

换一种视角 数据->操作

如果我们对之前学的数据结构与算法换一种视角来看,我们的数据结构其实可以被一组操作进行刻画.

比如说,以我手里的茶杯为例
在这里插入图片描述
它作为一名普普通通的茶杯,它可以用以下操作来刻画

class TeaCup{
	/**
	* 喝茶
	*/
	public Drink(){};
	/**
	* 倒茶
	*/
	public Fill(Water Tea){};
}

我们作为21世纪的消费者,是不需要了解这个茶杯它是用硅酸盐组成的.然后里面添加了什么样子的化学物质来进行塑性等待的细节.我们使用的,只是它给我们的两个方法:喝茶,倒茶.

ADT也一样,我们给外界的 是一组操作,而非内部的表示.

四大er,四种方法

具体来说,这些操作可以细分为四大方法:
以杯具的一生为例:

构造器 : 凭空直接给你造出一个茶杯
生产器 : 放入一个白色的茶杯,与一些颜料,给你返回一个黑色的茶杯
观察器 : 看下茶杯是否为空的,里面的茶叶是什么品种的.
变值器 : 放入一个空茶杯,返回一个打满了茶水的茶杯.

来一点数学: 抽象函数AbstractFunction

我们上面说了这么多,其实可以用一个简单的数学映射加以描述

AF(内在表示) ==> 抽象值

程序员需要利用号内在的表示值,同时编写程序,来进行映射,向外界提供观测,修改抽象值的接口.

这便是抽象.
通过抽象,隐藏底层复杂的实现细节,将间接,可靠一致的抽象操作提供给外界,从而达到降低系统整体的耦合度的效果.

底层的实现细节就是R空间.而用户关注的空间,或者说,我们给用户呈现的空间,就是A空间,即抽象空间.

下面就是一个例子,通过抽象函数,我们将字符串映射到了集合空间A里面.

在这里插入图片描述
左边的R,是Representation,即数据表示.这个,便如我们施工队的比方,是我们房子下面的地基.
这个地基,必须要稳,否则房子本身就是不稳定的,有害于人民群众的财产安全.

所以,我们作为ADT的编写者,有责任去做点事情,来确保这个房子是稳定的.
体现为:

RI 表示不变量

还是以集合为例,我们用按顺序排列的字母来队这个集合进行表示,所有串里面的字符排列,必须是升序的.
这个升序,就是RI,它对表示空间进行了划分,将我们用到的合法的值,映射为true,其余为false
用函数的角度来说

RI(表示)==>{true,false}

在这里插入图片描述
我们还可以在代码里面对这个进行检查,因为只要我们的表示什么时候被RI给映射到了false,那么就意味着,我们的代码出bug了…

上述内容便是ADT的一些介绍,谢谢大家.

  • 18
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个实现单链表的ADT的程序示例: ``` #include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node *next; } Node; typedef struct List { Node *head; int size; } List; List *createList() { List *list = (List*)malloc(sizeof(List)); list->head = NULL; list->size = 0; return list; } void add(List *list, int data) { Node *newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->next = NULL; if (list->head == NULL) { list->head = newNode; } else { Node *current = list->head; while (current->next != NULL) { current = current->next; } current->next = newNode; } list->size++; } void removeAt(List *list, int index) { if (index < 0 || index >= list->size) { printf("Invalid index\n"); return; } Node *current = list->head; if (index == 0) { list->head = current->next; free(current); } else { Node *prev = NULL; int i; for (i = 0; i < index; i++) { prev = current; current = current->next; } prev->next = current->next; free(current); } list->size--; } int get(List *list, int index) { if (index < 0 || index >= list->size) { printf("Invalid index\n"); return -1; } Node *current = list->head; int i; for (i = 0; i < index; i++) { current = current->next; } return current->data; } int main() { List *list = createList(); add(list, 1); add(list, 2); add(list, 3); printf("%d\n", get(list, 1)); removeAt(list, 1); printf("%d\n", get(list, 1)); return 0; } ``` 这个程序实现了单链表的基本操作,包括创建链表、添加元素、删除元素、获取元素等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值