Ada程序设计-第2章 基本数据类型和表达式(Basic Types and Expressions)

104 阅读29分钟

第2章 基本数据类型和表达式(Basic Types and Expressions)

2.1 概述(Overview)

    数据类型是一门计算机语言最基本的特性,表示一个对象的类型,是数字、字符或其它类型。由于 Ada 在数据类型上提供的强大处理能力,我们又不能很简单地认为数据类型仅是定义一个对象的类型。在 Ada 里,数据类型可以自己创建,它的相关运算符也可以自己定义,同时又有数据类型属性这项特性,具有相当大的灵活性。学过其它的语言,特别是有 C 和 Pascal 背景的朋友初次接触时可能会感到有点新鲜。

    创建新类型,是用户自己定义数据类型,包括该类型的名称、取值范围及相关操作;其中又包括了派生类型和创建子类型,即以现有数据类型为母类型创建新类型,继承母类型的一部份属性。

    数据类型属性,如同我们玩RPG游戏时的人物属性:体力值、魔法值一样,是说明该类型固有的属性,包括最小取值范围、最大取值范围等等。

    本章将会先介绍词法元素以及创建数据类型的基础性知识,然后是整型(integer)、实型(real)、字符型(character)、布尔型(boolean)、枚举类型(enumuration)这几种标量类型,最后则是相关的数据类型属性、类型限制和类型转换、表达式和运算符。

2.2 词法元素(Lexical Element)

    Ada 里的词法元素与其它语言的定义还是有不小差别,下面按照 RM95 关于词法元素的分类来逐项介绍,包括标识符、保留字、字符、数值文字等及它们的一些规则。

2.2.1 基本字符集(Character Set)

    Ada 95 规定的标准字符集是 Latin-1,支持8位(基于ISO-8869)和16位(基于ISO-10646)字符,在非标准模式下也可用本地字符集,具体情况取决于当前的系统。(一般来说,涉及字符时默认是指 Latin-1,程序几乎都是用 Latin-1 字符集写的)

    字符在RM 95 中是分成了三类:图形字符(graphic character)、格式控制符(format effector)、其它控制符(other control function),它们所表示的范围为:

    图形字符包括字母(letter)、数字(digit)、空格符(space)、特殊字符(special character)(例如" # & ' ( ) \* + , - . / : ; < = > \_ | { } \[ \]);

    格式控制符包括水平制表符(HT)、垂直制表符(VT)、回车(CR)、换行(LF)、换页(FF);

    其它控制符则是除了格式控制符以外的控制符。

    更详细的内容参见RM 95、ISO 8869,ISO 10646。

    Ada 是大小写忽略的(除了字符和字符串中的实际内容,如字符'z'和'Z'不相同,但标识符 z 和 Z 相同),但为了可读性,一般来说变量名或函数名首字母都会大写,其余小写,缩近格式也需要引起注意。根据实际情况尽量保证可读性。

    为了兼容性,Ada95 要求编译器最少支持一行字符串和一个词法元素的长度为 200个字符(不包括行尾符)。

    Ada 在字符集上并没有很严格要求编译器一定要怎样,但应该支持标准字符集。

2.2.2 标识符(Identifier)

    Ada 中不管是变量、函数还是其它对象都需要一个名称,这就叫做标识符。如 X、Count 、me就是简单的标识符。

    Ada 中的标识符有以下一些注意点:

  1. 标识符一定要是字母开头,接下去可以是数字和下划线,结尾不能为下划线。如Fig_、_Lik、1me是不合法的。

  2. 两个连续的下划线不能在一起,因为有些打印机可能会将两个下划线当作一个处理。

  3. 虽然单个字符可作为标识符,但一般情况下不应该滥用这项特性(我不知道这除了少敲几个字外,还有什么意义,想想数字 0 和字母 O、数字 1 和字母l吧,绝对害死人的做法)。

  4. 不能将保留字作为标识符。在 Ada 83 中,标识符包括了保留字,但在Ada 95 中保留字从标识符中分离了出来。

  5. .如上节所提及的,标识符是不分大小写的,但为了可读性,请注意你对标识符的命名。

2.2.3 保留字(Reserved Word)

    保留字在程序语法中有特殊的含义,不属于标识符范围,这与C 和 Pascal 等语言的定义有所不同。Ada 95 中的保留字如下:

abort abs abstract accept access aliased all and array at begin body case constant declare delay delta digits do else elsif end entry exception exit for function generic goto if in is limited loop mod new not null of or others out package pragma private procedure protected raise range record rem renames requeue return reverse select separate subtype tagged task terminate then type until use when while with xor

    在以后的内容中,我们会逐个解释它们的含义及作用。Ada95 的保留字比原先添加了6个:abstract, aliased, protected, requeue, taggeduntil,虽然这可能会牵连到兼容性问题,但通常也无须计较这点。

2.2.4 分隔符(Separator and Delimiter)

    Ada 程序中,各元素之间需要空格符、格式控制符或 EOF 隔开。RM 95里将它们作为separator,而delimiter 则是指下列特殊字符(中文有点说不清楚):& ' ( ) \* + , -. / : " < = > | 或是复合型的: => .. \*\* := /= >= <= << >> <>。当分隔符作为注释、字符串、字符、数值的一部分时,就不再是分隔符了。

2.2.5 数值文字(Numeric Literal)

    数值文字就是指数字。Ada 和 Pascal 相同,将数字分为实数型(real literal)和整数型(integer literal)两大类。实数型是有小数点的数字,整数型则无小数点。如 1.2787,0.871,7.0是实数型,而-882,5441,1是整数型。Ada 在数字表示上有一个很好的特性就是可以明确指定使用何种基数(2进制到16进制)表示,下面是数字的表示:

十进制数(Decimal Literal)

    不管是实型还是整型数,都可以在其间加上下划线,使长数字更加易读。如56886515645125615,可写为56_886_515_645_125_615或5_6886_5156_4512_5615,下划线并不改变数字的值。但两个下划线不能是连续的,下划线也不可以在数字首部和尾部,如676__66和67_E4都是非法的。

    字母 E 作为数字的指数,同时适用于实型和整型。如 123_98E4、5.087E-5、 4.8E7都是合法的,但负指数不能用于整型,指数也一定要是整数。E 大小写皆可以。

基型数字(Based Literal)

    在大部分语言中,都使用 10 进制数字表示;Ada 里整数可以不用10进制的表示方法书写,而是直接使用 2至16进制的表示法,格式为:Base # Number #, Base 表示所采用的进制,Number 为该进制下所表示的数字。

2#1001_1001#,表示2进制数 1001 1001,中间的下划线可取消,其10进值为153;

10#153#,表示10进制数153,等价与153;

16#90#,表示16进制数90,其10进值为144;

2.2.6 字符文字(Character Literal)

    字符文字的表示是单个图形字符在单引号‘ ’中,如'a'表示小写字母a,'K'表示大写字母K,'''表示一个单引号,' '表示一个空格。

2.2.7 字符串文字(String Literal)

    字符串是双引号(" ")之间的有序图形字符。如"What I said."就是一个字符串。表示空字符串时直接用" "。如果字符串中有双引号,一个双引号要用两个"来表示。如"He said,"" I am hungry."" ",而"He said," "I am hungry.""He said," I am hungry.""是不合法的。至于其它字符,如$ %之类可以直接出现在两个双引号间。与 C 语言不同,Ada 里没有与之相同的转义字符,并且EOL不会算到字符串中。

2.2.8 注释(Comment)

    注释由两个连字号(hyphen)(--)开始,直到行尾。可以出现在程序的任一个地方,不影响程序本身。例如:

-- the comment;
end;-- processing of Line is complete.

2.2.9 Pragmas

    Pragma 是编译指示(compile directive),给编译器指令如优化程序,列表控制等。它的作用往往不只影响一个编译单元,而是整个程序。

   Pragma 是些预先定义的指令,如 pragma Page,pragma List(OFF) ,编译器也可扩展 RM 95 中 pragma。我们先接触 List,Page,Optimize 这3个 pragma。更多内容我们会在以后接触。

  • pragma List(identifier);
  • pragma Page;
  • pragma Optimize(identifier);

pragma List 将 identifier On 或 Off 作为它的参数。它指定编译列表(listing of compilation) 是继续还是停止,直到在同一个编译单元内,一个pragma List 使用了相反的参数。

pragma Page 指定在 pragma 后的程序正文在新页开始(如果编译器正在列表)

pragma Optimize 有 Time,Space 或 Off 3个参数,它的影响区域直到 pragama 所在编译单元的底部。Time 和 Space 指示优化时间还是优化空间,Off则关闭优化。

  下面是简单的例子:

pragma List(Off); --关闭列表生成
pragma Optimize(Off); --关闭可选的优化

  不过,上述3个 pragma 是影响编译过程用的,基本上用户也用不着,以后涉及的 inline,Pure 等 pragma 使用频率倒挺高

2.3 创建数据类型和子类型(Creating Types and Subtypes)

  使用变量时,除了以某标识符作为变量的名称外,还要指定该变量的数据类型。一个数据类型定义了变量可接受的值以及所能执行的操作。比如说,一个数据类型为 Age 的变量 Bill,Age 的取值范围为 1..100,并只有 + - 这两种操作,在这里,对象(object)为名为 Bill 的变量,它的取值在 1..100 之间(包括 1,100),值的变化只能通过+ -这些基本运算符(primitive operation)来实现,而无法通过* /等其它运算符。Ada 中的数据类型,包括预定义类型,都是按照一定的格式在程序包中创建的。下面就介绍创建数据类型的一些基本内容,更多相关内容会在以后见到。

2.3.1 创建新的数据类型

    创建一个新类型,需要使用保留字 type,is,range。格式如下:

type type_name is range range_specifcation;

    type_name 为新类型的名称,是一个合法标识符;range_specifcation 表示该类型的取值范围,表示方式为 First .. Last,如 1..100 , -9 ..10 。

    例如创建上面提及的一个新类型 Age

type Age is range 1 .. 100;

    这样就有了一个数据类型 Age, 取值范围 1 .. 100。

    有一点要注意:range_specfication 中 First 要大于 Last。 如 type months is range 12 .. 0, 实际上 months 是一个空集(null),而不是所期望的 0..12。

    不同数据类型之间是不能进行混合运算的,即使取值范围和运算符一样,看以下的程序例子:

000 -- filename: putwage.adb  
001 with Ada.Text_IO; use Ada.Text_IO;  
002 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; 003 procedure putwage is  
004    type Age is range 1 .. 100;  
005    type Wage is range 1 .. 100;  
006    Bill_Age : Age := 56;  
007    Bill_Wage: Wage := 56;  
008 begin 
009    Put ("Total wage is");  
010    Put (Bill_Wage * Bill_Age);  
011    New_Line;  
012 end putwage;

[001]-[002]: 使用软件包 Ada.Text_IO,Ada.Integer_Text_IO;两个软件包分别处理字符类输出和整数输出。

[003] [008] [012] 定义一个过程 putwage。

[004]-[005]: 定义新的数据类型Age,Wage,它们取值范围都为 1..100。

[006]-[007]: 声明两个变量 Bill_Age,Bill_Wage,类型分别为 Age 和Wage, 并赋予相同初始值56。

[009]-[011]:依次输出字符串"Total wage is",整数 Bill_Wage和Bill_Age的乘积,和一个新行符(EOL)。

    以上程序看上去毫无问题,但根本无法编译通过。首先,没有定义类型Age和wage的 * 操作,因此Bill_Age和Bill_Wage无法相乘;第二,两者数据类型不同,即使定义了*操作,还是无法相乘。 当然也可使用后面提到的类型转换 ,如果将[010]改为Put (Integer(Bill_wage) * Integer(Bill_Age)),将会输出所要的 3136;但如果改成Put (Integer(Bill_wage * 56)),看上去也行的通,但实际结果却不是3136。不同数据之间不能进行运算,要牢牢记住。(Integer 是预先定义的一个整型,Integer(Bill_Wage)是将Bill_Wage强制转换为整型)。

2.3.2 派生类型

    大家可能会发现,如果像上面一样创建一个截然不同的新类型,还需要定义它的运算符,使用很不方便。因此,往往是派生现有的类型,其格式为:

type type_name is new old_type {range range_specification};

    type_name 为新类型的名称,是一个合法标识符;range range_specification 表示该类型的取值范围,是可选的,没有的话表示新类型 type_name 的取值范围和 old_type 一样。如将上例改为:

000 -- filename:putwage.adb  
001 with Ada.Text_IO; use Ada.Text_IO;  
002 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; 003 procedure putwage is  
004    type Age is new Integer range 1 .. 100;  
005    type wage is new Integer;  
006    Bill_Age : Age := 56;  
007    Bill_Wage: Wage := 56;  
008 begin  
009    Put ("Total wage is");  
010    Put (Bill_Wage * Bill_Age);  
011    New_Line;  
012 end putwage;

    上例还是不能编译通过,因为派生类型只继承母类型的属性,如运算符,不同的派生类型即使母类型相同也还是属于不相同的类型。但将[10]改为Put (Integer(Bill_wage * 56))则能输出正确的结果。但是派生类型使用还是麻烦了一点,不同类型之间即使都是数字类型也无法混合使用,只是自己不用创建运算符省力了点。

2.3.3 创建子类型

    创建新类型和派生类型的麻烦从上文就可以感受的到,特别是在科学计算这些有很多种小类型的软件当中,上述两种方法实在过于繁杂。这时子类型(subtype)就相当有用,子类型的定义格式为:

subtype type_name is old_type {range range_specification};

    type_name 为新类型的名称,是一个合法标识符;range range_specification 表示该类型的取值范围,是可选的,没有的话表示新类型 type_name 的取值范围和 old_type 一样。再将先前的例子改一下:

000 -- putwage.adb  
001 with Ada.Text_IO; use Ada.Text_IO;  
002 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; 003 procedure putwage is 
004    subtype Age is Integer range 1 .. 100;  
005    subtype Wage is Integer;  
006    Bill_Age : Age := 56;  
007    Bill_Wage: Wage := 56;  
008 begin  
009    Put ("Total wage is");  
010    Put (Bill_Wage * Bill_Age);  
011    New_Line;  
012 end putwage;

    编译通过,输出值为3136。子类型不仅继承母类型的属性,而且和母类型、其它同母类型的子类型可混合使用。

    在前面的例子中的,我们都提到了取值范围,这也是 Ada 的一项“特色”:Ada 不同于 C 和 Pascal--- 赋给一个变量超过其取值范围的值或进行不合法运算,会输出错误的值而不报错,与此相反,Ada 程序在编译时会提示错误,或在运行 Ada 程序时产生Constraint_Error异常(异常和 C 中的信号Signal差不多,详见**第9章 异常(Exception)),挂起程序,来减少程序的调试时间。

2.4 标量类型(Scalar Type)

    大部份语言,基本的数据类型如果按照该类型所表示的数据类型来分,一般来说可分为整型(integer),实型(real),布尔型(boolean),字符型(character)这四类,并以它们为基础构成了数组,记录等其它更复杂的数据类型。在程序包 Standard 中预定义了一些简单数据类型,例如Integer,Long_Integer,Float,Long_Float,Boolean,Character,Wide_Character,以及这些数据类型的运算符。下面我们除了学习上述的4种标量类型(Scalar Type)外,还要学习一下枚举类型(Enumration)。由于 Ada 中布尔型和字符型都是由枚举类型实现的,因此也可将这两种类型认为是枚举类型。

2.4.1 整型(Integer)

Integer 整型

    一个整型数据能存放一个整数。预定义的整型有Integer,Short_Integer,Short_Short_Integer,Long_Integer,Long_Long_Integer还有Integer的子类型 Positive ,Natural。RM95 没有规定 Integer及其它整型的具体取值范围及其位数,由编译器决定。只规定了没多大意思的最小取值范围,如要求一个Integer 至少要为16位数,最小取值范围为-32767..32767(-2 ** 15+1 .. 2**15-1)。因此还有Integer_8,Integer_16,Integer_32,Integer_64这些指定了位数的整型,以方便用户。在RM95里,也就是编译器实现里,以上类型声明格式为:

type Integer is range implementation_defined(Long_Integer它们也一样)

subtype Positive is Integer range 1..Integer'Last;

subtype Natural is Integer range 0..Integer'Last; (Integer'Last 表示Integer的最后一个值,即上限,见 2.5 数据类型属性)。

    程序 System 里定义了整数的取值范围:

Min_Int : constant := Long_Long_Integer'First;

Max_Int : constant := Long_Long_Integer'Last;

Modular 整型

    还有一类整型是 Modular,异于上面的整型。如果将 Integer 整型与 C 中的 signed int 相类比,它们的取值范围可包括负数;那么 Modular 类型就是unsigned int,不能包含负数。其声明格式为:

type tyep_name is mod range_specification;

    其中的 range_specification 应为一个正数; type_name 的取值范围为(0..range_specification - 1)。

    如下面类型 Byte:

type Byte is mod 256;

    这里 Byte 的取值范围为 0 .. 255。

    Modular 类型在程序包 System 也有常量限制,range_specification 如是2的幂则不能大于 Max_Binary_Modulus ,如不是幂的形式则不能大于Max_Nonbinary_Modulus。 这两个常量的声明一般如下:

Max_Binary_Modulus : constant := 2 ** Long_Long_Integer'Size;

Max_Nonbinary_Modulus : constant := Integer'Last;

    细心的读者可能会发现上面两个常量的值实际上是不一样的,也就是说 Modular 类型实际上有两个不同的限制。RM95 关于这点的解释是,2进制兼容机上,Max_Nonbinary_Modulus 的值大于 Max_int 很难实现。

2.4.2 实型(Real)

    相对于整型表示整数,实型则表示浮点数。实型分为两大类: 浮点类型(floating point) 和定点类型 (fixed point)。它们之间的区别在于浮点类型有一个相对误差;定点类型则有一个界定误差,该误差的绝对值称为 delta。下面就分类介绍这两类数据类型。

浮点类型(Floating Type)

    浮点类型预定义的有Float,Short_Float,Short_Short_Float,Long_Float,Long_Long_Float等,它们的声明格式入下:

type type_name is digits number [range range_specification] ;

    digits number 表示这个浮点类型精度,即取 number 位有效数字,因此 number 要大于0;range range_specification 是可选的,表示该类型的取值范围。下面是几个例子:

type Real is digits 8;

type Mass is digits 7 range 0.0 .. 1.0E35;

subtype Probability is Real range 0.0 .. 1.0;

     Real 表示精度为8位的符点数类型,它的取值范围由于没给定,实际上由编译器来决定;RM 95里关于这种情况是给出了安全范围(safe range), 取值范围是-10.0**(4*D) .. +10.0**(4*D), D 表示精度,此例中为8,所以 Real 的安全取值范围一般来说应为 -10.0E32 .. +10.0E32。

    Mass 是表示精度为7位的符点型,取值范围为 00.. 1.0E35;

    Probability 是Real的子类型,精度也是8位,取值范围 0.0..1.0;

    程序包 System 定义了精度的两个上限:Max_Base_Digits 和 Max_Digits ,一般来说应为

Max_Base_Digits : constant := Long_Long_Float'digits;(即Long_Long_Float的精度)

Max_Digits : constant := Long_Long_Float'digits;

    当range_specification指定时,所定义类型的精度不能大于 Max_Base_Digits;当range_specification没有指定时,所定义类型的精度不能大于 Max_Digits。

定点类型

    定点类型主要是多了一个delta,它表示该浮点类型的绝对误差。比方说美元精确到 0.01 元(美分),则表示美元的数据类型 Dollar 的 delta 为 0.01,不像浮点型是近似到 0.01。

    定点型的声明格式有两种:

普通定点型:type type_name is delta delta_number_range range_specification

十进制定点型:type type_name is delta delta_number digits digit_number [range range_specification]

    除 delta delta_number 外,各部份意义与浮点型相同。

    定点型中有一个 small 的概念。定点数由一个数字的整数倍组成,这个数字就称为该定点数类型的 small。如果是普通定点型,则 small 的值可以被用户指定(见下节 数据类型属性),但不能大于该类型的 delat;如果没有指定,small 值由具体实现决定,但不能大于 delta。如果是十进制定点型,则 small 值为 delta,delta 应为 10 的幂,如果指定了该定点型的取值范围,则范围应在 -(10**digits-1)*delta..+(10**digits-1)*delta 之间。看一下下例:

type Volt is delta 0.125 range 0.0..255.0;
type Fraction is delta System.Fine_Delta range -1.0..1.0;
type Money is delta 0.01 digits 15;
subtype Salary is Money digits 10;

2.4.3 布尔型(Boolean)

    逻辑运算通常需要表示"是"和"非"这两个值,这时就需要使用布尔型。Ada 中的布尔型与 Pascal 中的类似,是 True 和 False 两个值。布尔型属于枚举数据类型,它在程序包 Standard 中定义如下:

type Boolean is (True, False);

    习惯于 C 语言的朋友在这里需要注意一下,Boolean 的两个值 True,False 和整型没有什么关系,而不是 C 语言中往往将True 定义为值1,False 为2。

2.4.4 字符型(Character)

    Ada83 最初只支持 7 位字符. 这条限制在 Ada95 制订前已经放松了,但一些老编译器如 Meridian Ada 还是强制执行. 这导致在一台PC上显示图形字符时出现问题;因此,在一般情况下,是使用整型来显示 Ascii 127以后的字符,并使用编译器厂商提供的特殊函数。

    在 Ada95 里,基本字符集已由原来的ISO 646 标准的7位字符变为ISO 8859标准的8位字符,基于 Latin-1并且提供了 256 个字符位置。 Ada95 同样也支持宽字符 ISO 10646,有2**16个的字符位置。因此现代编译器能很好地处理 8 位字符和 16 位字符。

    7 位字符在已经废弃的程序包 Standard.Ascii 内定义。在程序包 Standard 内预定义的字符型 Character 和 Wide_Character 分别表示Latin-1 字符集和宽字符集,类型 Wide_Character 已经包含了类型 Character 并以它作为前 256 个字符。程序包 Ada.Characters.Latin_1和Ada.Characters.Wide_Latin_1 提供了 Latin-1 字符集的可用名称,Ada.Characters.Handling 则提供一些基本的字符处理函数。具体内容见 第14章 字符和字符串处理。

    从下例可以了解一下字符型:

000 -- filename: puta.adb  
001 with Ada.Text_IO; use Ada.Text_IO; 002 procedure puta is  
003    subtype Small_Character is {'a' ,'b','c', 'd'};  
004    Level : Small_Character := 'a';  
005 begin 
006    Put ("You level is");  
007    Put (Level);  
008    New_Line;  
009 end puta;

[003] 创建了一个字符类型 Small_Character,包含 a,b,c,d四个字母;如 C 语言一样,使用字符时需加' '。

[004]声明变量 Level,类型为Small_Character,值为字母 a 。

上面这个例子主要还是说明一下字符类是怎样定义的,但 Character和Wide_Chracter 实际实现却不是这么简单。

2.4.5 枚举类型(Enumeration)

    有时候我们需要一个变量能表示一组特定值中的一个。如 today 这个变量,我们希望它的值是Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday其中的一个,这时枚举类型就相当有用,上述情况中就可以创建新类型 Day,如下:

type Day is ( Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);

    然后声明变量 today 的数据类型为 Day:

today : Day ;

    这样today 就能接受Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday这几个值中的任意一个。

    前面提及的类型 Character ,Wide_Character,Boolean 都是枚举类型,也按照下面给出的格式声明:

type type_name is (elememt_list);

    element_list 需列举出该类型所有的可能值。

    Ada 能自动检测上下文,因此大部份情况下能分辨不同枚举数据类型下的枚举元素,如再声明一个类型 Weekend:

type Weekend is ( Saturday, Sunday);

    或

subtype Weekend is range Saturday .. Sunday;

    赋给上例中的变量 Today 值为 Sunday时,不会产生歧义;但在有些情况下,Ada 无法分辨枚举元素时则会产生问题,这时就要使用类型限制,详见 2.6 类型限制和类型转换。

   Ada 中的基本数据类型就讲到这里,实际上本节是基于上一节内容的扩展,说穿了还是创建数据类型。Ada 在数据类型处理上提供的强大功能在接下的章节里我们将会接触的更多,在这方面 Ada 的确比其它大部份语言做的好多了,熟悉 C ,Pascal的朋友大概会感到相当有意思。

2.5 数据类型属性 (Attributes)

    数据类型属性,表示某个数据类型的具体特征---取值范围,最小值,最大值,某数在该类型中的位置 …… 应该说是相当有用的-----起码不像 C 语言,还要翻翻系统手册才能知道某个数据类型的具体定义。这些属性的用法和调用函数一样,也可以认为它们就是预定义的函数----虽然不怎么准确,关于函数,详见 第6章 子程序;有些返回值为通用类型(universal type)和字符串型。

    数据类型的属性既可以使用预先定义的操作,也可以自己定义数据类型的属性,如S'First 是返回 S 类型的下限,如果用户不满意默认的属性,也可自己指定S'First(虽然没必要),如:

for S'First use My_Version_First;

  My_Version_First 是用户自己的函数,以后当使用 S'First 时,实际上调用 My_Version_First;有些数据属性也可以直接用数字标示,如:

for S'First use 0;

  这样,S'First 的值就成了0。在很多地方,如内存管理中,这种用法还是比较普遍的。下面简单地列出标量类型的属性,S 表示某个标量类型:

2.5.1 通用标量类型属性

S'First  返回 S 类型的下限,返回值为 S 型。

S'Last   返回 S 类型的上限,返回值为 S 型。

S'Range  返回 S 类型的取值范围,即 S'First .. S'Last。

S'Base   表示 S 类型的一个子类型,但没有范围限制(单纯从取值范围角度讲,“儿子”反而比“父母”大),称之为基类型(base type)。

S'Min    函数定义为: function (Left,Right:S'Base) return S'Base 。比较 LeftRight 的大小,返回较小值。如:Integer'Min (1 , 2) = 1 。

S'Max    函数定义为: function (Left,Right:S'Base) return S'Base 。比较 LeftRight 的大小,返回较大值。如:Integer'Max (1 , 2) =2 。

S'Succ   函数定义为:function S'Succ (Arg :S'Base) return S'Base 。返回 Arg 的后趋。

S'Pred   函数定义为: function S'Pred (Arg :S'Base) return S'Base 。返回 Arg的前趋。

S'Wide_Image 函数定义为:function S'Wide_Image (Arg : S'Base) return Wide_String 。返回 Arg 的“像”,即可显示的字符串,这里返回宽字符串型 Wide_String。如:Float'Wide_Image (9.00) = “9.00" 。详见 第三章 数组

S'Image      与 S'Wide_Image 一样,但返回字符串型 String 。

S'Wide_Width 表示 S'Wide_Image 返回的字符串的最大长度,返回值为 universal_integer

S'Width      表示 S'Image 返回的字符串的最大长度,返回值为 universal_integer

S'Wide_Value 函数定义为: function S'Wide_Value ( Arg : Wide_String) return S'Base。是 S'Wide_Image 的逆过程,返回与“像”Arg 相对应的 S 类型的值。如: Float'Wide_Value ("9.00") = 9.00 。

S'Value      与 S'Value 一样,但参数 Arg 是 String 类型。

2.5.2 通用离散类型属性

    离散类型包括整型和枚举型,除了上述的属性外,还有:

S'Pos   函数定义为: function S'Pos (Arg : S'Base) return universal_integer。返回 Arg 在 S 类型中的位置。

S'Val   函数定义为: function S'Pos (Arg : S'Base) return S'Base。返回 在 S 类型中位置为 Arg 的值。

例如:

type Color is (red, white, blue);

Color'Pos (White) = 2

Color'Val (1) = red

2.5.3 浮点类型属性

S'Digits  返回 S 类型的精度,为 universal_integer 类型。

2.5.4 定点类型属性

S'Small  返回 S 类型的 small 值,返回值为实型。

S'Delta 返回 S 类型的 delata,返回值为实型。

S'Fore  返回 S 类型所表示数的小数点前面的最小字符数量,返回值类型 universal_integer。

S'Alt    返回 S 类型所表示数的小数点后面的最小字符数量,返回值类型 universal_integer。

十进制定点型属性

S'Digits 返回 S 类型的精度。

S'Scale  返回 S 类型的标度 N,使 S'Delta=10.0**(-N)。

S'Round  函数定义为 function S'Round(X:universal_real) return S'Base;返回 X 的舍入值。

2.6 类型限制和类型转换(Type Qualification and Type Conversion)

    先让我们看一下下面的例子:

type primary is (red, green, blue);
type rainbow is (red, yellow, green, blue, violet);

...

for i in red..blue loop

...

   这里明显有歧义,编译器也就无法自动判定 red,blue 到底是 primary 还是 rainbow 中的元素。因此我们就需要使用类型限制,精确指明元素: 

for i in rainbow'(red)..rainbow'(blue) loop

for i in rainbow'(red)..blue loop -- 只需要一个限制

for i in primary'(red)..blue loop

   类型限制并不改变一个值的类型,只是告诉编译器程序员所期望的数据类型。由于 Ada 中提供了重载等特性,变量、子程序等重名的现象相当普遍,以后我们会碰到,解决方法和现在所讲的类型限制差不多,都是指明资源的准确位置。

    类型转换我们也常常在使用,比方说 modular 类型的数的输入输出需要特定的程序包,初学者便可以将 modular 数转换成 Integer 数来处理(虽然不怎么好)。下面是一个 Integer 和 Float 类型数互相转换的例子:

X : Integer:= 4;

Y : Float;

Y := float(X);

...

X := Integer(Y);

    这导致编译器插入合适的代码来做类型转换,实际效果就取决于编译器,而且一些类型之间的转换是禁止的;像上述的强行转换一般也不推荐,除非意义很明确,建议是使用上节所讲的数据类型属性、以及以后会碰到的相关子程序等来进行类型转换。

    还有一种不进行检查的类型转换,我们会在以后遇到。

2.7 表达式和运算符(Expressions and Operators)

   我们在先前的简单例子中对于一些表达式和运算符已有所认识,现在具体的讲解一下。

2.7.1 变量、常量声明

变量声明

    变量声名的格式很简单:

variable_name : type;

    variable_name 是变量名称,只要是合法的标识符即可;type 为该变量的数据类型。声明时可以初始化变量值,也就只需在上述语句后添加 := value,:= 赋值运算符。如:

Var_1: Integer;

Var_2: Integer := 1;

    也可以在声明某一变量时指定它的取值范围。如:

Var_1 : Integer range 1..10;

Var_2 : Integer range 1..20 := 2;

常量声明

    常量声明格式如下:

variable_name :constant type := value;

    这里需要注意一下,考虑下列两种情况:

Cons1 :constant Integer := 8;

Cons2 :constant := 8;

    虽然 Cons1 和 Cons2 都是常量 8,但数据类型是不一样的。Cons1 为 Integer 型的常量;Cons2 是 universal_integer 的常量。

2.7.2 运算符

Ada 下的运算符有以下几类:

逻辑运算符: and、or、xor;

关系运算符: =、/=、<、>、<=、>=

加减运算符:+,-,&

乘除运算符:*,/,rem,mod 

最高优先级运算符:**,not,abs

逻辑运算符

    逻辑运算符适用于布尔型(即布尔型作为元素的数组)和Modular型的数。对于布尔型,and,or,xor 分别执行"与"、"或"、"异或"操作。对于 Mudular 类型的数,进行2进制逐位运算,该数的二进制位中 0 表示  False,1表示 True。具体参见下面的表格:

关系运算符

    运算符=、/=、<、>、<=、>=的使用和大部份语言以一样,只要看一下例子即可,下面的有些未讲到的内容如字符串、访问类型,读者可翻看后面的章节:

X /= Y
"" < "A" and "A" < "Aa"   --True
"Aa" < "B" and "A" <"A  " -- True
My_Car = null;           --True,如果 My_Car 被设置为 null
My_Car = Your_Car        --True,如果 My_Car 和 You_Car 所指的是同一个对象
My_Car.all = Your_Car.all  --True,如果 My_Car 和 You_Car 所指的对象相同
N not in 1..10 --范围测试,如果 N 在 1..10中,为 True
Today in Mon..Fri
Today in Weekday

加减运算符

    +,-对于任何数值 T 都进行加法运算;&则是用于数组及字符串,如 "A"&"B" = "AB",在第3章我们会近一部讲述。

乘除运算符

    *,/不用多说,很简单的乘除运算符。注意的是 rem 和 mod。假如 N 表示一个自然数,A,B有下列的关系:

A = (A/B)*B + (A rem B);

A = B*N + (A mod B);

因此:

最高优先级运算符

** 表示取某数的幂,如 2**2 = 4,3**7 = 2187;

not 则表示非,如 not True = Flase;

abs 为取绝对值,因此 abs(-34) =34。