记录

數據結構

电脑科学中,记录(英语:record)也称为结构体struct)或复合资料compound data)是基本的数据结构,记录是一些相关字段的聚集,它们可由不同的数据类型组成,通常是固定的数量和序列。记录中的每个字段或称为元素,但可能与集合的元素概念混淆不清。在面向对象编程中,记录的字段也另外被称为成员;依照惯例和具体的编程语言,多元组有可能会被认为是一个记录,反之亦然。

譬如将日期存储为一个记录,则其中包含了数字的年份,以字符串表示的月份和数字的日期等字段。而人事记录可包含姓名,薪水和职级等字段。一个圆形的记录可包含圆中心点和它的半径-在这种情况下,圆中心点本身可能表示为x和y座标的点记录。

记录与数组的区别在于,它们的字段数通常是固定的,每个字段都有一个名称,而且每个字段可能有不同的类型。

一个记录类型是描述其中字段所具有值和变量的数据类型。大多数现代电脑语言允许开发人员自由定义新的记录类型。记录类型的定义将会指定每个字段的数据类型和存取它的标识符(名称或标签)。

记录可以存在于任何存储介质中,包括主存储器和大容量存储装置,如磁带或硬盘。记录是大多数数据结构的基本组成部分,特别是链接的数据结构。

许多电脑文件是以逻辑记录的数组组成的,通常被分组成更大的实体记录或区块以提高存取效率。

函数或程序的参数通常当作是一个记录变量其中的字段;而在调用该函数时,传递给它的参数可被视为将字段的值指派给该记录变量。此外,通常用于实现程序调用的调用堆栈中,每个登录即是一条启动记录或调用框页,包含了程序参数和局部变量,返回地址和其它内部字段。

面向对象语言中的物件本质上是一个记录,有如何处理该记录的专用程序;而物件类型是对记录类型的详细描述。实际上在大多数面向对象语言中,记录只是物件的特殊情况,并且被称为普通旧数据结构(plain old data structures, PODS),与使用OO特征的物件形成对比。

电脑的记录可模拟为数学的元组。相同地,记录类型可看作是两个或多个数学集合的笛卡尔积,或是以特定语言实现的抽象乘积类型。

在许多电脑语言中,都对结构有所定义:

关系键

一条记录可能有零个或多个关系键。关系键是以记录其中一个或多个字段的集合,来作为标识符。唯一的键通常被称为主键,或简称为记录键。例如,员工文件可能包含了员工编号,姓名,部门代码和薪资,那么员工编号在组织中应该是唯一的,并且为主键。依据存储介质和文件结构,员工编号可被编入索引档,另外个别地存储能使查询过程加速。部门代码或许不是唯一的,它也可能被编成索引;这样子它会被当作是次要键或备用键。如果没有索引,则必须依序扫描整个员工文件,以产生特定部门的所属员工列表。薪资领域通常不被视为可用的关键。索引是设计资料档时需考量的一个因素。

历史

 
Journal sheet from 1880 United States Census, showing tabular data with rows of data, each a record corresponding to a single person.

记录的概念可从久远的会计使用中回溯到各种类型的表格和分类帐。现代电脑科学中的记录形式,具有明确的类型和大小的字段,已经隐含在19世纪机械的计算器,如巴贝奇分析机中。

 
Hollerith punched card (1895)

最初用于处理资料(与控制相反)的机器可读介质,是在1890年美国人口普查中用于记录的打孔卡:每张打孔卡是单个记录。若将1895年的打孔卡与1880年的日记账两相比较,记录在20世纪上半叶成熟,大多数资料处理是用打孔卡片完成的。通常资料档的每条记录会记录在一张打孔卡中,特定栏分配给特定字段。一般地,记录是可从外部存储装置(例如读卡器,磁带或磁碟)读取的最小单元。

大多数机器语言实现和早期汇编语言没有记录的特殊语法,但借由使用索引寄存器,间接寻址和自修改代码,这个概念是可用的(并广泛被使用)。一些早期的电脑如IBM 1620,具备了对分隔记录和字段的硬件支持,以及复制这些记录的特殊指令。

一些早期的文件排序和制表工具如IBM的报告程序生成器(Report Program Generator, RPG)中,记录和字段是其核心概念。

COBOL是第一个支持记录类型并广泛使用的编程语言,其记录定义在当时已相当复杂。它允许以任意大小和精度的英数字、整数和小数字段,来定义嵌套记录,并自动格式化将值指定给纪录的字段(例如,插入货币符号,小数点和数字组分隔符)。每个文件与读取或写入的资料一并关系到一个记录变量。 COBOL 还提供了一个MOVECORRESPONDING陈述语句,根据名称来指派两个记录的相对应字段。

早期开发用于数值计算的电脑语言,如FORTRAN(到FORTRAN IV)和Algol 60,并不支持记录类型;但这些语言的更新版本,如Fortran 77和Algol 68增加了记录类型。最初的Lisp编程语言也缺乏记录(除了内建的cons单元),但它的S表达式能满足对纪录的需求。将记录类型与其它基类型完全集成逻辑上一致性的类型系统,则是Pascal编程语言。

PL/I编程语言提供了COBOL格式的记录。C编程语言最初将记录类型(struct)当作是置于存储区块之上的模板概念,而并不是真实的记录数据类型。后来C语言提供了(以typedef宣告),但这两个概念在语言上仍然是不同的。在Pascal之后设计的大多数语言(如 Ada,Modula 和 Java)也都支持了记录。

记录的操作

  • 新记录类型的宣告,包括每个字段的位置,类型和(可能)名称;
  • 将变量和值声明为已定义的记录类型;
  • 从给定的字段值和(有时)字段名称来建构一条记录;
  • 以明确名称来选取一条记录的字段;
  • 将值指定给记录变量;
  • 比较两条记录的等同性;
  • 计算记录的标准散列值

从记录中选取一个字段会产生一个值。

有些语言提供枚举记录所有字段的功能,或至少是引用的字段。该功能需要实现某些服务,像是调试器垃圾收集器序列化。它需要具备一定程度的多态

在具有记录子类型的系统中,记录类型的值操作还可以包括:

  • 增添新字段到记录,设置新字段的值。
  • 从记录中删除一个字段。

在这样的设置中,特定的记录类型表示目前有特定的一组字段,但是该类型的值可能包含其它字段。因此,具有xyz字段的记录将属于具有xy字段的记录类型,以及具有xyr字段的记录。理由是将(x,y,z)记录传递给某个函数,若该函数默认使用(x,y)记录作为参数,因为该函数将在记录中找到所需的全部字段。实际上在编程语言中实现记录的许多方法,若允许这样的类型转换会遇到麻烦,然而在理论中这个事实是记录类型的核心特征。

赋值与比较

大多数语言允许在完全相同类型的记录之间进行指派(包括相同的字段类型和名称,以相同的顺序)。
然而,依照语言的不同,两个单独定义的记录类型,即使它们具有完全相同的字段,也可能被视为不同类型。

在记录之间,有些语言也允许对其中不同名称的字段进行指派,按照字段在其记录中的位置,将每个字段值与相应的字段变量进行对应;所以譬如有一复数的纪录,其中有realimag两个字段,则可将两字段值指派给有XY字段,表示平面上一点的记录变量。在这种方案中,两个操作数仍旧要求相同的字段类型序列。

某些语言可能也要求相对应的类型,应该具有相同的大小和编码,以便整个记录能被指派成不解译的位元字符串。而其它语言可能在这方面更有弹性,仅要求每个值字段可合法地指派给相应的变量字段;所以譬如可将短整数字段分配给长整数字段,反之亦然。

另外的语言(例如COBOL)可借由名称来匹配字段与值,而不用位置。

以上所述的功能性质同理应用在比较两个记录值的等同性。某些语言也允许使用根据单一字段的字典序来进行次序的比较('<'和'>')。

PL/I允许以上两种类型的赋值,也允许结构表达式,如a = a + 1;其中“a”是一条记录,或是在PL/I术语中所指的结构。

ALGOL 68的字段选取

在Algol 68中,如果Pts是一组记录,每个都有整数字段XY,可以写入Pts.Y来获得一个整数数组, 由Pts的所有元素的Y字段组成。结果,语句Pts[3].Y:=7Pts.Y[3]:= 7将具有相同的效果。

PASCAL中的WITH陈述句

在Pascal编程语言中,使用with R do S的陈述将执行指令序列S,就像记录R的所有字段都被声明 为变量一样。 所以不必全部写出Pt.X := 5; Pt.Y := Pt.X +3,而可以缩写为 with Pt do begin X := 5; Y := X +3 end

在存储器中的表现

记录在存储器中的表现取决于编程语言。通常,这些字段会依照在记录类型中宣告的相同顺序,连续地存储于存储器中。这可能导致两个或多个字段存储在相同的存储器字组中;实际上,此功能经常用于系统编程以存取字组的特定位元。另一方面,大多数编译器会添加开发人员看不见的填补字段,以符合硬件要求的对齐限制 -比如浮点字段必须补满整个字组。

有些语言将记录实现为地址指向字段(可能的名称和/或类型)的数组。面向对象语言中的物件通常以相对复杂的方式实现,特别是在允许多重类别继承的语言中。

自定义记录

自定义记录是一种类型,其中包含辨别记录类型和记录内定位的相关资讯。它可能包含了元素的偏移量;因此,元素可用任何次序存储或者可省略掉次序。另外在这样类型的记录中,具有标识符的各种元素可简单地依任何次序,跟随彼此。

示例

以下显示记录定义的示例:

  • PL/I:
      declare 1 date,
                2 year  picture '9999',
                2 month picture '99',
                2 day   picture '99';
    
  • C:
    struct date {
       int year;
       int month;
       int day;
    };
    
  • Rust:
    struct Date {
       year: u32,
       month: u32,
       day: u32,
    }
    

外部链接

参考