(delphi11最新学习资料) Object Pascal 学习笔记---第2章第一节

Object Pascal 手册,Delphi 11 编程语言的完整介绍 作者: Marco Cantu 笔记:豆豆爸

第2章 变量和数据类型

​ Object Pascal是一种强类型编程语言。在Object Pascal中,变量被声明为一种数据类型(或一种用户定义的数据类型)。变量的类型决定了变量可以保存的值以及可以对其执行的操作。这使得编译器能够识别您代码中的错误并生成更快的代码。

​ 这就是为什么Pascal中的类型概念比C或C++等语言更强的原因。后续的编程语言比如C#和Java,基于与C相同语法但与C不兼容,在类型上偏离了C并接受了Pascal强数据类型的概念。例如在C语言中,算术数据类型几乎可以互换使用。相比之下,BASIC的原始版本就没有类似的概念,并且在今天的许多脚本语言中(JavaScript是一个明显的例子),数据类型的概念又非常不同。

注解:实际上,有一些绕过类型安全的技巧,比如使用变体记录(variant record)类型。强烈不鼓励使用这些技巧,而且它们今天很少被使用。

​ 正如我上面提到的,从JavaScript开始,所有动态语言在数据类型概念上都不相同,或者(至少)对数据类型的概念相当松散。在其中一些语言中,类型是通过赋值给变量的值来推断的,而变量的类型又可以随时间改变。重点要指出的是,确保大型程序正确性的关键是在编译时对数据类型的检查,而不是依赖运行时检查。数据类型需要更多的秩序和结构,还需要对将要编写的代码进行一些规划,这显然有利有弊。

注解:毫无疑问,我更喜欢强类型语言,但无论如何,在本书中,我的目标是解释语言的工作原理,而不是鼓吹为什么我认为它是如此出色的编程语言。

2.1 变量和赋值

​ 与其他强类型语言一样,Object Pascal要求在使用变量之前必须先声明变量。每次声明变量时,必须指定数据类型。以下是一些变量声明的示例:

var
  Value: Integer;
  IsCorrect: Boolean;
  A, B: Char;

var 关键字可以在程序的多个位置使用,比如在函数或过程的开头,用于声明该代码段内的局部变量,或者在单元内用于声明全局变量。

注解:正如我们稍后将看到的,最新版本的Delphi增加了声明“内联”变量的能力即在编程语句中混合声明变量,这与传统的 Pascal 有很大不同。

​ 在var关键字之后是一个变量名称列表,后跟冒号和数据类型的名称。您可以在单行上写多个变量名称,如前面代码片段的最后一条语句中的A和B(这种风格相对于将声明拆分为两个单独行来说,现在已经不太常见了,后者有助于可读性、版本比较和合并)。

​ 一旦您定义了一个给定类型的变量,您只能对其执行数据类型支持的操作。例如,您可以在测试中使用Boolean变量,在数值表达式中使用Integer变量。您不能混合使用Boolean变量和Integer变量,除非调用特定的转换函数,例如,混合任何不兼容的数据类型(即使内部表示可能在物理上是兼容的,例如BooleanInteger变量的情况)。

​ 最简单的赋值是给变量一个实际的、具体的值。在上面的示例中,您可能希望Value变量保存数值10。但是,如何表示文字值呢?虽然这个概念虽然很明显,但值得详细研究。

2.1.1 字面值

​ 字面值(Literal Value)是您直接在程序源代码中键入的值。如果您需要一个值为20的数字,您可以简单地写:

20

同样,相同数字值还有基于十六进制的另一种表示形式,即:

$14

从Delphi 11开始,您还可以将相同的数字表示为二进制值,如下所示:

%10100;

也是从Delphi 11开始,对于非常大的值,您可以使用下划线作为数字分隔符,以使常量值更易读。编译器会忽略这个分隔符。例如,您可以将2000万的值写为:

20_000_000

这些是整数数字的字面值(或各种可用的整数类型之一)。如果要表示相同数值,但要使用浮点数字面值,通常需要添加一个小数点和零:

20.0

字面值不仅限于数字。您还可以有字符和字符串。两者都使用单引号(许多其他编程语言会同时使用双引号,或者对于字符使用单引号,对于字符串使用双引号):

// 文字字符
'K'
#55
// 文字字符串
'Marco'

如上所示,您还可以用相应的数值(最初是 ASCII 码,现在是 Unicode 码点值)来表示字符,在数值前加上 # 符号,如 #32 表示空格字符。这主要适用于在源代码中没有文本表示的控制字符,如退格或制表符。

如果需要在字符串中使用引号,则必须用另一个引号转义(加倍)。因此,如果我想用最后一个引号而不是重音符号来拼写我的姓和名,我可以这样写:

'My name is Marco Cantu'''

这两个引号表示字符串中的一个引号,而第三个连续引号表示字符串的结束。还要注意,字符串文字必须写在一行上,但您可以使用+符号连接多个字符串文字。如果要在字符串中有新行或换行符,您不能分两行写,而是使用sLineBreak系统常量(这是平台相关的)将两个元素连接在一起,例如:

'Marco' + sLineBreak + 'Cantu'''
2.1.2 赋值语句

Object Pascal 中的赋值使用冒号等号运算符(:=),对于习惯于其他语言的程序员来说,这是一个奇怪的符号。在许多其他语言中用于赋值的 = 运算符,在 Object Pascal 中用于测试是否相等。

历史 := 操作符来自 Pascal 的前身 Algol,现在的开发人员很少听说过这种语言(更不用说使用了)。如今的大多数语言都避免使用 := 符号,而倾向于使用 = 赋值符号。

赋值和相等测试使用不同的符号,Pascal 编译器就可以像 C 编译器一样更快地翻译源代码,因为它不需要检查运算符使用的上下文来确定其含义。使用不同的运算符也使代码更容易阅读。与 Pascal 相比,C 和派生语法(如 Java、C#、JavaScript)使用 = 表示赋值,使用 == 表示相等测试。

注解:为完整起见,我还应提及 JavaScript 也有一个 === 操作符(执行严格的类型和值相等测试),但这一点甚至连大多数 JavaScript 程序员都搞不清楚。

赋值的两个元素通常被称为 lvalue 和 rvalue,分别代表左值(你要赋值给的变量或内存位置)和右值(赋值表达式的值)。rvalue 可以是表达式,而 lvalue 必须(直接或间接)指向一个可以修改的内存位置。不过,有些数据类型有特定的赋值行为,我将在适当的时候介绍。

另一条规则是 lvalue 和 rvalue 的类型必须匹配,否则两者之间必须进行自动转换,这将在下一节中解释。

2.1.3 赋值和转换

​ 使用简单的赋值,我们可以编写以下代码(您可以在本章的VariablesTest项目中找到许多其他代码片段):

Value := 10;
Value := Value + 10;
IsCorrect := True;

​ 在前面的变量声明中,这三个赋值是正确的。然而,下一个语句是不正确的,因为两个变量具有不同的数据类型:

Value := IsCorrect; // 错误

​ 当您键入这行代码时,Delphi编辑器会立即显示一个红色波浪线指示错误,并提供适当的说明。如果尝试编译它,编译器将发布相同的错误,并提供类似于以下的描述:

[dcc32 Error]: E2010 Incompatible types: ‘Integer’ and ‘Boolean’

​ 编译器通知代码中有一些错误,即两个不兼容的数据类型。当然,在许多情况下,可以将一个变量的值从一种类型转换为另一种类型。在某些情况下,转换是自动的,例如将整数值分配给浮点变量(当然,反之不成立)。通常,您需要调用一个特定的系统函数,该函数更改数据的内部表示。

2.1.4 初始化全局变量

​ 对于全局变量,您可以在声明变量时使用常量赋值符号(=)而不是赋值运算符(:=)为变量赋初始值。例如,您可以写:

var
  Value: Integer = 10;
  Correct: Boolean = True;

​ 这种初始化技术仅适用于全局变量,因为全局变量无论如何都会初始化为它们的默认值(例如,数字为零)。

2.1.5 初始化局部变量

​ 与全局变量不同,在过程或函数的开头声明的变量不会初始化为默认值,并且没有赋值语法。对于这些变量,通常要在代码的开头添加显式的初始化代码:

var
  Value: Integer;
begin
  Value := 0; // 初始化

​ 再次说明,如果不对局部变量进行初始化,而是直接使用,那么该变量的值将完全随机(取决于该内存位置的字节数值)。在许多情况下,编译器会警告您存在潜在的错误,但并非总是如此。

​ 换句话说,如果您编写:

var
  Value: Integer;
begin
  ShowMessage(Value.ToString); // Value 未定义

​ 编译器会警告你 Value 未初始化,而当你运行程序时,你会发现输出将是一个完全随机的值,显示的内容恰好是值变量内存位置的字节内容。

2.1.6 内联变量

​ Delphi 的最新版本(从 10.3 Rio 开始)增加了一个概念,改变了自早期 Pascal 和 Turbo Pascal 以来声明变量的方法:声明内联变量。

​ 新的内联变量声明语法允许在代码块中直接声明变量(与传统的变量声明一样,也允许使用多个符号):

procedure Test;
begin
  var I, J: Integer;
  I := 22;
  J := I + 20;
  ShowMessage(J.ToString);
end;

​ 虽然从表面上看,这一变化似乎没什么大不了的,但这一变化会带来一些与初始化、类型推断和变量生命周期有关的影响。我将在本章接下来的章节中介绍这些内容。

初始化内联变量

​ 与旧的声明方式相比,第一个显著的变化是,内联声明和初始化变量可以在单个语句中完成。这使得与在函数开头初始化多个变量相比,代码更加清晰:

procedure Test;
begin
  var I: Integer := 22;
  ShowMessage(I.ToString);
end;

​ 这样做的主要好处是,如果变量的值在代码块的后面部分才可用,与其先设置一个初始值(如 0 或 nil),然后再分配实际值,还不如将变量声明延迟到需要计算这个变量适当初始值的时候,如下面的 K:

procedure Test1;
begin
  var I: Integer := 22;
  var J: Integer := 22 + I;
  var K: Integer := I + J;
  ShowMessage(K.ToString);
end;

​ 换句话说,在过去所有局部变量在整个代码块中都是可见的,而现在内联变量只在其声明位置到代码块的末尾可见。换句话说,在赋予适当的值前,变量K不能在代码块的前两行中使用。

内联变量的类型推断

​ 内联变量的另一个重要优势是,编译器现在在多种情况下可以通过查看分配给它的表达式或值的类型来推断内联变量的类型。这里举一个非常简单的例子:

procedure Test;
begin
  var I := 22;
  ShowMessage(I.ToString);
end;

​ 右值表达式的类型(即在:=之后的内容)被分析以确定变量的类型。通过字符串常量赋值,变量将成为字符串类型,但是如果将函数的结果或复杂表达式赋值给变量,也会发生相同的分析。

​ 请注意,变量仍然是强类型的,因为编译器会在编译时确定数据类型并进行赋值,而且不能通过赋新值来修改变量。类型推断只是为减少代码键入提供了方便(这与复杂数据类型(如复杂泛型类型)有关),但不会改变语言的静态和强类型性质,也不会导致运行时速度变慢。

注解:在推断类型时,某些数据类型会“扩展”为较大的类型,就像上面的例子中,数值22(ShortInt)扩展为Integer。作为一般规则,如果右侧表达式类型是小于32位的整数类型,那么变量将声明为32位整数。当然,如果要使用特定的较小数值类型,可以使用显式类型。

2.1.7 常量

​ Object Pascal还允许声明常量。这使您能够为常量值提供一个有意义的名字,这个值在程序执行期间不会更改(在编译代码中常量值不重复还可能减小程序的大小)。

​ 声明常量时,您不需要指定数据类型,而只需赋予一个初始值。编译器将检查该值并自动推断出适当的数据类型。 以下是一些示例声明(也来自VariablesTest示例):

const
  Thousand = 1_000;
  Pi = 3.14;
  AuthorName = 'Marco Cantu';

​ 编译器会根据常量的值确定其数据类型。在上面的示例中,Thousand常量被假定为 SmallInt 类型,即可以容纳值的最小整数类型。如果需要,只需将类型作为声明的一部分,就可以指示编译器使用指定的类型,如下所示:

const
  Thousand: Integer = 1_000;

警告: 在程序中进行假设通常是不好的,并且编译器可能随时间的推移而发生变化,不再符合开发人员的假设。如果你有办法更清晰地表达代码,并且不带任何假设,请使用它!

​ 在声明常量时,编译器可以选择是为常量指定一个内存位置并保存其值,还是在每次使用常量时复制实际值。第二种方法尤其适用于简单的常量。

​ 一旦声明了常量,就可以像使用其他变量一样使用它,但不能为其赋值。如果尝试赋值,编译器会出错。

注解: 奇怪的是,Object Pascal 确实允许在运行时更改类型常量的值,就像更改变量一样,但前提是必须启用 $J 编译器指令,或使用相应的 Assignable typed constants编译器选项。这种可选选项是为了向后兼容针对旧编译器编写的代码。这显然不是建议的编码风格,我在本注释中提及它,主要是作为有关此类编程技术的历史轶事。

内联常量

​ 就像我们前面看到的变量一样,您现在也可以声明内联常量值。这可以指定常量类型或未指定常量类型,此时类型被编译器推断(这是常量长时间以来可用的一个特性)。以下是一个简单的示例:

begin
  // 一些代码
  const M: Integer = (L + H) div 2; // 带类型说明符的标识符
  // 一些代码
  const M = (L + H) div 2; // 不带类型说明符的标识符

​ 请注意,常规常量声明只允许赋值一个常量值,而对于内联常量声明,您可以使用任何表达式。

资源字符串常量

​ 这是一个稍微高级的主题,在定义字符串常量时,您可以使用特定的指令resourcestring指示编译器和链接器将字符串视为Windows资源(或Object Pascal支持的非Windows平台上的等效数据结构):

const
  AuthorName = 'Marco';
  
resourcestring
  StrAuthorName = 'Marco';
  
begin
  ShowMessage(StrAuthorname);

​ 在这两种情况下,您都定义了常量,也就是说,在程序执行过程中,不会改变这个值。唯一的区别在于内部实现。使用resourcestring指令定义的字符串常量存储在程序的资源中,即字符串表中,该表可能会被资源编辑器(如本地化工具,用于将字符串翻译为其他语言的工具)编辑。

​ 使用资源的好处是 Windows 可以更有效地使用内存,Delphi 可以为其他平台提供相应的实现方式,而且无需修改源代码就能让程序更好地实现本地化。根据经验,您应使用 resourcestring 来处理任何要显示给用户且可能需要翻译的文本,并使用内部常量来处理程序内部其他所有字符串,如固定的配置文件名。

小贴士: IDE 编辑器具有自动重构功能,你可以用相应的 resourcestring 声明来替换代码中的字符串常量。将编辑光标置于字符串常量中,然后按下 按下 Ctrl+Shift+L 激活重构。

2.1.8 变量的生命周期和可见性

​ 根据定义变量的方式,变量将使用不同的内存位置,并在不同的时间内保持可用(一般称为变量的生命周期),而且将在代码的不同部分中可用(可见性一词指的就是这种特性)。

​ 现在,我们无法在书中的这个早期阶段完整地描述所有选项,但我们当然可以考虑最相关的几种情况:

  • 全局变量:如果在一个单元的接口部分中声明变量(或任何其他标识符),则其范围扩展到使用这个单元的任何其他单元。为该变量分配的内存在程序启动时分配,并一直在程序终止前存在。您可以为其分配一个默认值,或者在初始值需要以较复杂的方式计算时使用单元的 initialization 节区。

  • 全局隐藏变量:如果在单元的implementation节区声明变量,变量则不能在该单元之外使用,但可以在单元内定义的任何代码块和过程中使用它,从声明的位置开始算起。此类变量使用全局内存,其生命周期与第一组变量相同,唯一的区别在于其可见性。初始化方式与全局变量相同。

  • 局部变量:如果在定义函数、过程或方法的块内声明变量,则不能在该代码块之外使用该变量。标识符的范围涵盖整个函数或方法,包括嵌套的例程(除非嵌套例程声明在这些变量的前面,或者嵌套例程中的同名标识符隐藏了外层变量的定义)。当程序执行定义局部变量的例程时,会在堆栈中分配该变量的内存。一旦例程结束,堆栈中的内存就会自动释放。

  • 局部内联变量:如果在函数、过程或方法的代码块内声明内联变量,与传统局部变量相比,内联变量的可见性从被声明的行开始,延伸到函数或方法的末尾。

  • 块作用域的内联变量:如果在代码块(即嵌套的 begin-end 语句或 try-finally 代码块)中声明内联变量,变量的可见性将仅限于该嵌套代码块。这与大多数其他编程语言的情况一致,但 Object Pascal 只是在最近的内联变量声明语法中引入了这种情况。请注意,在代码块中声明的内联变量不能使用与父代码块中声明的变量相同的标识符。

    注解: 对于块作用域的内联变量,不仅可见性,而且变量的生命周期都仅限于块。托管数据类型(如接口、字符串或托管记录)将在子代码块结束时被弃置,而不是在过程或方法结束时。为保存表达式结果而创建的临时变量也是如此。

    ​ 如果程序的任何部分在uses子句部分引入了一个单元,那么程序就可以访问这个单元interface部分的任何声明。窗体类的变量也是以同样的方式声明,因此你可以从任何其他窗体的代码中引用一个窗体(及其公共字段、方法、属性和组件)。当然,将所有内容都声明为全局变量的编程方法并不可取。除了明显的内存消耗问题外,使用全局变量还会增加程序的维护和更新难度。简而言之,您应该使用尽可能少的全局变量,并避免访问 Delphi 创建的全局变量(如窗体的全局变量)的诱惑。

  • 38
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值