计算机系统概论十九章.docx

上传人:b****5 文档编号:6852916 上传时间:2023-01-11 格式:DOCX 页数:12 大小:26.66KB
下载 相关 举报
计算机系统概论十九章.docx_第1页
第1页 / 共12页
计算机系统概论十九章.docx_第2页
第2页 / 共12页
计算机系统概论十九章.docx_第3页
第3页 / 共12页
计算机系统概论十九章.docx_第4页
第4页 / 共12页
计算机系统概论十九章.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

计算机系统概论十九章.docx

《计算机系统概论十九章.docx》由会员分享,可在线阅读,更多相关《计算机系统概论十九章.docx(12页珍藏版)》请在冰豆网上搜索。

计算机系统概论十九章.docx

计算机系统概论十九章

第十九章数据结构

19.1介绍

C在其核心为数据的三个基本类型:

整数、字符和浮点数值提供了支持。

那就是说,C自然的支持这些类型的变量的分配,以及操作这些类型的运算,诸如加法+,乘法*。

正如我们贯穿于本书的第二部分的主题,我们看到了为了扩展这些基本类型,包括指针和数组的需要。

指针和数组都起源于这三个基本类型。

指针指向这三个基本类型之一;我们可以声明int,char或double数组。

最后,虽然程序员的工作是写程序处理现实世界的对象,诸如一个机翼,或一群人,或一群迁徙的鲸,问题在于只有整数、字符和浮点数值是低层计算系统能够处理的现实。

程序员必须把这些现实世界的对象映射到这些基本类型上,这点有些麻烦。

但是程序设计语言可以有助于建起桥梁。

为描述现实世界的对象提供的支持,以及对其指定的操作是面向对象的基础。

程序操作的是对象而不是基本类型是面向对象程序设计的基本原则。

在本章,我们通过查看一个C程序如何构建一个由多个基本类型组合的类型,向面向对象前进一小步。

在C中,这个集合被称为结构体。

结构体为程序员提供了表示那些最适合通过多个数值表示的对象的一个便利的方法。

例如,在一个公司数据库程序中,一个雇员可以被表示为一个包含姓名(字符串)、职别(字符串)和雇员ID(整数)的结构体。

在设计那样的数据库程序时,我们可以使用C的结构体。

本章的主题是C对数据结构的支持。

首先,我们查看如何用C构建结构体,以及一个操作一个结构体数组的简单程序。

其次,我们查看C中的动态存储分配。

动态分配和结构体的概念没有直接的联系,但是它是我们用于本章的第三项——链表的一个组成元素。

链表是一个类似于数组的基本的(通用的)数据组织,二者都存储数据项的集合,但是在对数据项的组织上不同。

我们将查看对链表内的数据项进行增加、删除和查询的函数。

19.2结构体

有些事物通过基本类型的组合可以被更好的描述。

对于那样的对象,C提供了结构体的概念。

结构体允许程序员去定义一个由基本的数据项,如int,char和double,还有指向这些类型的指针,以及这些类型的数组组合而成的新类型。

结构体变量被声明的方法与声明基本数据类型变量相同。

然而,在任何结构体变量被声明之前,结构体内的数据项的组织和名字必须被定义。

例如,在描述一架飞机时,对于一个飞机模拟器或者一个管理芝加哥的空中交通的一个程序,我们可能想要描绘几个与手头的应用相关的飞行特征。

飞机的航行号用来识别它,既然它典型情况下会是一组数字和字符,我们可以用一个字符串来表示它。

飞行的高度、经纬度和方向也是有用的,我们可以把它们全部存储为整数。

飞行速度是另一个重要的特征,最好用双精度浮点数来表示。

下面是对于描绘一架飞行中的飞机所声明的变量:

charflightNum[7];/*最多6个字符*/

intaltitude;/*以米为单位*/

intlongitude;/*以十分之一度为单位*/

intlatitude;/*以十分之一度为单位*/

intheading;/*以十分之一度为单位*/

doubleairSpeed;/*以千米/小时为单位*/

如果程序要模拟多架飞机,我们需要对每一架声明一个这些变量的副本,这点是乏味的而且导致了过长的代码。

C语言通过struct提供了一个把这些特征组合在一个类型中的方便方法,如下所示:

structflightType{

charflightNum[7];/*最多6个字符*/

intaltitude;/*以米为单位*/

intlongitude;/*以十分之一度为单位*/

intlatitude;/*以十分之一度为单位*/

intheading;/*以十分之一度为单位*/

doubleairSpeed;/*以千米/小时为单位*/

};

在前面的声明中,我们构建了一个包含六个成员元素的新类型。

我们还没有声明任何存储,我们只是向编译器说明这个新类型的组成。

我们已经给了这个结构体一个标签flightType,这对于在代码的其他部分引用这个结构体是很有必要的。

我们可以用下面的语句来声明这个新类型的一个变量:

structflightTypeplane;

这就声明了一个叫做plane的变量,由在结构体声明中定义的6个字段组成,但是对它的处理就像任何其他的类型那样。

我们可以使用下面的语法访问这个结构体变量的个别成员:

structflightTypeplane;

plane.airspeed=800.00;

plane.altitude=10000;

每一个成员都可以使用变量名作为基名,在它后面跟一个点.,后面跟上成员的名字来访问。

声明的变量plane如果是局部变量,就在栈中分配一段空间,而且占据一段足够的可以容纳所有成员元素的连续的存储区域。

在这种情况下,如果每个基本类型占据一个LC-3存储单元,那么变量plane将占用12个单元。

结构体的分配是简单的。

结构体和基本数据类型变量被分配空间的方式一样:

局部(缺省的)变量被分配在运行时栈中,全局变量被分配于全局数据区。

图19.1显示了当一个包含如下声明的函数被调用时,运行时栈的部分情况:

一般的,一个结构体声明的语法如下所示:

structtag{

type1member1;

type2member2;

......

typeNmemberN;

}identifiers;

tag为后面的代码中引用这个结构体提供了一个句柄,正如后面的该结构体格式的变量声明的情况。

成员列表定义了一个结构体的组织形式,语法上是一列声名。

成员可以是任意类型,包括另一个结构体类型。

最后,我们可以有选择的在一个结构体的声明中包含标识符,以声明该结构体类型的变量。

这些出现在结构体声明中闭括号之后,分号之前。

19.2.1typedef

C语言结构体可以让程序员定义自己的类型。

在C语言中typedef可以允许程序员为他们自己的类型命名。

它有一个一般的形式:

typedeftypename;

这个语句使得标识符name和类型type是同义的,type可以是任意基本类型或组合类型(如一个结构体)。

例如,

typedefintColor;

允许我们定义Color类型的变量,Color类型现在和整数类型同义。

使用这个定义,我们可以声明(例如,对于一个位图):

Colorpixels[500];

typedef声明在处理结构体时尤其有用。

例如,我们可以为我们先前定义的结构体构建一个名字:

structflightType{

charflightNum[7];/*最多6个字符*/

intaltitude;/*以米为单位*/

intlongitude;/*以十分之一度为单位*/

intlatitude;/*以十分之一度为单位*/

intheading;/*以十分之一度为单位*/

doubleairSpeed;/*以千米/小时为单位*/

};

typedefstructflightTypeFlight;

现在,我们可以通过使用类型名称Flight来声明变量,例如

Flightplane;

现在它和我们在之前使用过的声明structflightTypeplane;是等价的。

typedef声明并没有提供额外的功能。

但它使代码更清晰明了,尤其对那些频繁使用程序员自定义的类型的代码而言。

恰当选择的类型名称意味着所声明的变量的特征,甚至可以超出变量本身的名字所表达的含义。

19.2.2 在C语言中实现结构体

既然我们已经了解了结构体类型变量的声明和空间分配(以及赋予它们新的类型名称),我们将关注如何访问其成员字段,并对它们执行运算。

例如,在以下的代码中,Flight类型的结构体变量的成员altitude被访问。

intx;

Flightplane;

inty;

plane.altitude=0;

这里,变量plane是Flight类型的,意味着它包含我们先前定义的6个成员字段。

标记为altitude的成员字段被使用后面跟一个点,点后跟成员字段的标记来访问。

了解该结构体的形式的编译器使用适当的偏移量生成访问结构体成员字段的代码。

图19.1显示了这个函数的活动记录的部分布局。

编译器在其符号表中记录每个变量的相对于指针R5的位置,如果变量是一个组合数据类型,它也记录变量中每个元素的位置。

注意,对于特定的引用plane.altitude=0;,编译器必须生成访问栈中第二个变量和访问该变量的第二个成员元素的代码。

如下代码是对于赋值语句plane.altitude=0;,被LC-3的C编译器生成的:

19.3结构体数组

如果我们在写一段判断芝加哥上空的飞机是否有撞机的危险的程序。

对于这个程序,我们将使用我们先前定义的Flight类型。

如果可以同时存在于空中的飞机数最多是100架的话,那么如下声明是适当的:

Flightplanes[100];

这个声明与简单声明intd[100]类似,不同的是后者声明了100个整数值,而它声明了一段包含100个结构体的连续存储空间,每一个都由声明structFlightType所指明的6个成员组成。

例如,引用planes[12]指的是在存储器里100个那样的对象区域中的第13个对象。

每一个对象都为它的6个组成成员元素包含足够的存储空间。

这个数组的每个元素都是Flight类型,可以使用标准的数组符号访问。

例如,可以使用标识符plane[0]访问第一架飞机的飞行特征。

访问一个成员字段可以通过访问数组的元素,再说明该字段来实现:

plane[0].heading。

如下代码片断提供了一个示例。

它找出被程序监视的空中所有飞机的平均飞行速度。

我们也可以创建结构体指针。

如下声明创建了一个包含Flight类型变量的地址的指针变量。

Flight*planePtr;

我们可以像为任意指针变量赋值那样为该变量赋值。

planePtr=&plane[34];

如果我们想访问通过该指针变量所指的任意成员字段,我们可以使用一个如下的表达式:

(*planePtr).longitude

使用这个麻烦的表达式,我们可以对变量planePtr解除引用。

它指向某个Flight类型。

因此当planePtr被解除引用时,我们就访问了一个Flight类型的对象。

我们可以通过使用点运算符(.)访问其成员字段之一。

正如我们即将看到的,使用一个指针引用一个结构体是一种普遍的运算,既然这个表达式对于理解不是非常直接,因此为其定义了一个特殊的运算符。

先前的表达式等价于:

planePtr->longitude

那就是说,表达式->就像解除引用运算符*一样,只是它是用于解除一个结构体类型的成员元素的引用。

现在,我们准备通过展示一个对一个结构体数组进行操作的函数的例子来讨论结构体的使用。

这个例子检查空中的100架飞机,来判断其中的任意两架是否可能有相撞的危险。

为了实现这一点,我们需要检查每架飞机的位置、高度和方向以判断是否存在相撞的可能。

在图19.2中,函数PotentialCollisions对于每两架调用Collide函数,来判断它们的飞行路线是否有相交的危险(该函数只实现了一部分,剩下的部分是一个练习题,要求你写出更精确的判断两架飞机路线是否相交的代码)。

注意PotentialCollisions传给Collide的是两个指针,而不是结构体本身。

当可能传递结构体时,传递指针将更加高效,因为它向运行时栈压入更少的数据,那就是说,在这种情况下,压入的是两个指针,而不是为两个Flight类型的对象压入占用24个单元的数据。

19.4动态存储分配

在C程序中,存储对象(即变量)被分配于存储器的三个地点之一:

运行时栈,全局数据区,或堆。

缺省情况下,声明为局部的变量在执行期间被分配到运行时栈中。

全局变量被分配到全局数据区,而且可以被程序的所有部分访问。

动态的分配数据对象——在运行期间创建的对象——被分配到堆中。

在先前的例子中,我们声明了一个包含100个对象的数组,每个对象都是一架飞行的飞机。

但是,如果我们想构建一个灵活的程序,它能够处理与任意时刻的飞机数目相同的飞机,不管它是2架还是20,000架呢?

一个可能的解决方案就是声明一个假设程序可能遇到的飞机数目的上限的数组。

这样做可能会导致许多浪费的存储空间,或更糟的是,我们可能低估了飞机的数目,这可能会撞机。

一个更好的解决方案是根据空中飞机的数目调整数组的大小。

为了实现这一方案,我们要依赖于动态存储分配的概念。

简要的说,动态存储分配工作如下:

一段被称为存储器分配器的代码管理被称为堆的存储区域。

图19.3是图12.7的一个副本,它显示了各种存储区域之间的关系,包括堆。

在执行期间,一个程序能够请求存储器分配器分配一段特定大小的连续存储空间。

存储器分配器预留这段存储空间,并且返回一个指向这个新预留的存储空间的指针给这个程序。

例如,如果我们想在我们的空中交通控制程序中存储1,000个飞行数据,我们可以请求分配器分配空间。

如果在堆中存在足够的空间,分配器将会返回一个指向它的指针。

注意,堆与栈都向对方增长。

栈的大小是基于当前函数调用的深度,然而堆的大小是基于存储器分配器已经为它收到的请求预留了多少存储单元。

被分配到堆中的一块存储器保持着被分配的状态,直到程序员通过调用存储器释放器显式地释放它。

释放器把这块存储器加入堆中,供以后再分配。

19.4.1动态大小的数组

在C语言中,动态分配被C语言标准库函数处理。

特别地,存储器分配器通过函数malloc被调用。

让我们看一个使用函数malloc的例子:

intairbornePlanes;

Flight*planes;

printf("Howmanyplanesareintheair?

");

scanf("%d",&airbornePlanes);

planes=malloc(24*airbornePlanes);

函数malloc在堆上分配一片连续存储空间,这个空间的大小就是由参数标明的字节数。

如果这个堆拥有足够没有被利用的存储单元,并且函数调用是成功的,malloc函数就返回一个指向分配空间的指针。

这里我们分配一大块由24*airbornePlanes字节组成的存储空间,这里的airbornePlanes是由用户指定的空中的飞机数目。

那“24”是什么呢?

回顾一下,Flight类型是由6个数组成——7个字符的数组,4个整数和1个双精度数,在LC-3中,每一个占据一个2个字节的存储单元。

每一个结构体需要24字节的存储单元。

对程序员来说,作为必要的便利性,C语言支持一个被称为sizeof函数的编译时运算符。

这个运算符返回存储对象或者作为一个变元传给它的类型的以字节为单位的大小。

例如,sizeof(Flight)将返回被Flight类型变量占据的字节数,或24。

程序员不需要计算各种数据对象的大小;编译器被指示去执行这个计算。

如果堆中的所有内存已经被分配掉,并且当前的分配不能被实现,malloc返回值NULL。

回顾一下,NULL符号是一个预处理宏记号,取决于不同的计算机系统,被定义为某个特定的值,表示一个空指针。

检查malloc的返回值来指示存储分配是否成功是一个好的编程实践。

malloc函数返回一个指针。

但是这个指针的类型是什么?

在前面的例子中,我们把被malloc返回的指针作为指向Flight类型的某个变量的指针对待。

以后,我们将使用malloc分配整数数组,意味着返回值将被作为int*来对待。

为了实现这一点,malloc返回一个通用数据指针,或void*,这就需要在返回时类型强制转换为适当的形式。

那就是说,无论何时我们调用存储器分配器,我们都需要让编译器把返回值作为一个不同于被声明的类型。

在前面的例子中,我们需要把被malloc返回的指针类型强制转换为我们要赋值的变量的类型。

因为我们把指针赋值给planes,这是Flight*类型的,因此我们把该指针强制转换为Flight*类型。

否则,代码在不同的计算机系统间难以移植;大多数编译器由于我们把一个类型的指针值赋值给另一个类型的指针变量,而产生一个警告信息。

类型强制转换使编译器把一个类型值作为另一个类型来处理。

为了把一个值从一个类型强制转换为一个newType,我们使用如下的语法。

变量var应该是newType。

对于类型强制转换的更多信息,参考D5.11节。

var=(newType)expression;

已知类型强制转换,sizeof运算符和对malloc返回值的错误检验,对前面的例子编写代码的正确方法为:

intairbornePlanes;

Flight*planes;

printf("Howmanyplanesareintheair?

");

scanf("%d",&airbornePlanes);

planes=(Flight*)malloc(sizeof(Flight)*airbornePlanes);

if(planes==NULL){

printf("Errorinallocatingtheplanesarray\n");

……

}

plane[0].altitude=……

由于通过malloc分配的空间是在存储器中连续的一段,我们就能够在指针符号和数组符号之间转换。

现在,我们可以使用表达式planes[29]来访问第30个飞机的特征(当然,假设airbornePlanes比30大)。

注意,我们平滑地从指针符号转换到了数组符号,这个灵活性有助于使C语言成为一个非常流行的语言。

其他一些派生的语言,特别是C++保留了这种在指向连续存储空间的指针和数组之间的双重性。

函数malloc仅仅是标准库中的几个存储器分配函数中的一个。

函数calloc分配存储空间并且把它初始化为0。

函数realloc试图去增大或收缩先前分配的存储空间。

为了使用C语言标准库中的存储分配函数,我们需要包括stdlib.h头文件。

你能使用realloc去构建一个适应数据大小的数组吗?

——例如,写一个在planes当前的数目太小的情况下增加一架飞机的函数AddPlane()。

同样的,在数组大小比需求的大时,写一个函数DeletePlane()。

一个对于存储分配函数非常重要的对应部分是用来解除存储分配并把它返回堆中的函数。

这个函数叫做free。

它把一个指向先前由malloc(或calloc或realloc)分配的区域的指针作为参数,解除这个区域的分配。

在一个区域被free后,它就可以被再次分配了。

为什么解除分配是必需的?

正如我们将要看到的,在程序执行的时候,有一类动态的增长或收缩的数据结构。

为了收缩操作,我们把分配的存储空间放回堆中,这样我们就能够在后来的分配中再一次使用它。

19.5链表

已经讨论了结构体的概念和动态存储分配的概念,我们现在准备来介绍一种在计算中很普遍的基本数据结构。

链表和数组类似,它们都可以用来存储最好被表示为一列元素的数据。

在数组中,每个元素(除了最后一个)都有下一个元素在存储器中顺序的跟在它后面。

同样的,在链表中,每一个元素有下一个元素,但是下一个元素不需要在存储器中与之顺序相邻。

相反的,每一个元素都包含一个指向下一个元素的指针。

链表是一个节点的集合,每一个节点都是一个数据单元,正如前一节中的一架飞行的特征。

在链表中,我们使用指针把这些节点连接在一起。

每一个节点都包含一个指向链表中的下一个节点的指针元素。

给定一个起始节点,我们就可以通过跟踪每一个节点的指针,从一个节点到达另外一个节点。

为了构建这些节点,我们依赖于C的结构体。

对于定义链表节点的结构体的一个关键元素是一个指向像它自己一样的节点的成员元素。

如下代码演示了这是如何实现的。

我们使用在前面的几节中定义的Flight类型。

注意,我们已经在结构体定义中增加了一个新的成员元素。

它是指向与一个相同类型的节点的指针。

typedefstructfligheTypeFlight;

structflightType{

charflightNum[7];/*最多6个字符*/

intaltitude;/*以米为单位*/

intlongitude;/*以十分之一度为单位*/

intlatitude;/*以十分之一度为单位*/

intheading;/*以十分之一度为单位*/

doubleairSpeed;/*以千米/小时为单位*/

Flight*next;

};

就像数组一样,链表有一个起点和一个终点。

它的起点,或头,是使用一个被称为头指针的指针访问的。

在链表中的最后的节点,或尾,指向NULL值。

图19.4显示了一个链表数据结构的两种表示:

一个节点被表示为块、指针被表示为箭头的抽象描述,和一个显示数据结构在存储器中看上去像什么的更自然的表示。

尽管数组和链表有相似性,它们存在根本的区别。

数组可以按照随机的顺序被访问。

例如,我们可以访问数目为4的元素,然后是元素911,然后是45。

一个简单的链表必须从头开始顺序的遍历。

如果我们想访问节点29,那么我们必须从节点0(头节点)开始,然后到节点1,然后到节点2等等。

但是链表在本质上是动态的,增加另外的节点或删除节点不需要移动其他节点。

虽然它对于动态大小的数组是简单的(见19.4.1节,使用malloc),但是移走数组中一个元素却要花费更大的代价,特别是如果该元素位于中间的话。

例如,你将如何从19.3节的空中交通控制程序中移走一架刚刚着陆的飞机的信息?

使用链表,我们可以动态的增加节点,为更多的数据腾出空间;我们可以删除不再需要的节点。

19.5.1一个示例

假设我们想写一个管理二手车货物清单的程序。

在货物中,一直有汽车进和出,数据库不断更新——当一辆汽车被加入到货物中,就创建一条新纪录,当卖掉一辆车时,就删除一条记录。

此外,记录以汽车标识号的顺序被保存,以便更快速的处理二手车销售人员的查询。

我们需要保存的每辆车的信息如下:

intvehicleID;/*汽车的唯一标识*/

charmake[20];/*制造商*/

charmodel[20];/*型号*/

intyear;/*制造年份*/

intmileage;/*以英里为单位*/

doublecost;/*以美元为单位*/

Car*next;/*指向一个汽车节点*/

在现实中,一辆汽车的ID是一串字符和数字,不能被作为一个int来存储,但是我们把它存储为整数却可以让这个例子更简单。

我们想频繁执行的操作——对纪录的增加、删除和查询——使用链表数据结构就可以被简单快速的执行。

链表中的每个节点都包含与每辆货物汽车相关的所有信息,如上所示。

我们现在可以定义节点结构体,然后使用typedef命名为Car:

typedefstructcarTypeCar;

structcarType{

intvehicleID;/*汽车的唯一标识*/

charmake[20];/*制造商*/

charmodel[20];/*型号*/

intyear;/*制造年份*/

intmileage;/*以英里为单位*/

doublecost;/*以美元为单位*/

Car*next;/*指向一个car_node*/

};

注意这个结构体包含一个指向某个与之相同的类型,或Car类型的指针元素。

我们将使用这个成员元素指向链表中的下一个节点。

如果next字段等于NULL,那么该节

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 法律文书 > 调解书

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1