Solaris 动态跟踪指南

第 2 章 类型、运算符和表达式

D 提供了用于访问和处理多种数据对象的功能:可以创建和修改变量和数据结构,可以访问操作系统内核和用户进程中定义的数据对象,以及可以声明整型常量、浮点常量和字符串常量。D 提供了用于处理对象和创建复杂表达式的 ANSI-C 运算符的超集。本章详细介绍了类型、运算符和表达式的规则聚合。

标识符名称和关键字

D 标识符名称由大小写字母、数字和下划线组成,其中第一个字符必须为字母或下划线。将保留以下划线 (_) 开头的所有标识符名称以供 D 系统库使用。应避免在 D 程序中使用这类名称。根据约定,D 程序员通常对变量使用混合大小写的名称,对常量使用全部大写的名称。

D 语言关键字是保留的特殊标识符,以供在编程语言语法本身中使用。这些名称始终指定为小写,不能用作 D 变量的名称。

表 2–1 D 关键字

auto*

goto*

sizeof

break*

if*

static*

case*

import*+

string+

char

inline

stringof+

const

int

struct

continue*

long

switch*

counter*+

offsetof+

this+

default*

probe*+

translator+

do*

provider*+

typedef

double

register*

union

else*

restrict*

unsigned

enum

return*

void

extern

self+

volatile

float

short

while*

for*

signed

xlate+

D 将 ANSI-C 关键字的超集保留用作关键字。为了供 D 语言将来使用而保留的关键字带有 "*" 标记。如果尝试使用为了供将来使用而保留的关键字,则 D 编译器将生成语法错误。D 定义了但 ANSI-C 未定义的关键字带有 "+" 标记。D 提供了 ANSI-C 中出现的类型和运算符的完整聚合。D 编程语言的主要差别是没有控制流结构。将保留与 ANSI-C 中的控制流关联的关键字,以供将来在 D 中使用。

数据类型和大小

D 为整数和浮点常量提供了基础数据类型。D 程序中只可以对整数执行运算。浮点常量可用于初始化数据结构,但在 D 中不允许执行浮点运算。D 提供了 32 位和 64 位数据模型供编写程序时使用。执行程序时使用的数据模型是与活动操作系统内核关联的本机数据模型。可以使用 isainfo -b 确定系统的本机数据模型。

下表显示了两种数据模型中每一种的整数类型的名称及其大小。整数始终按系统的本机字节编码顺序以二的补码形式表示。

表 2–2 D 整数数据类型

类型名称 

32 位大小 

64 位大小 

char

1 字节 

1 字节 

short

2 字节 

2 字节 

int

4 字节 

4 字节 

long

4 字节 

8 字节 

long long

8 字节 

8 字节 

整数类型可以带有 signedunsigned 限定符的前缀。如果不存在任何符号限定符,则会将该类型假定为带符号。D 编译器还提供了下表中列出的类型别名:

表 2–3 D 整数类型别名

类型名称 

说明 

int8_t

1 字节带符号整数 

int16_t

2 字节带符号整数 

int32_t

4 字节带符号整数 

int64_t

8 字节带符号整数 

intptr_t

大小等于指针的带符号整数 

uint8_t

1 字节无符号整数 

uint16_t

2 字节无符号整数 

uint32_t

4 字节无符号整数 

uint64_t

8 字节无符号整数 

uintptr_t

大小等于指针的无符号整数 

这些类型别名等效于使用上一个表中的对应基本类型的名称,并相应地为每一种数据模型进行了定义。例如,类型名称 uint8_t 是类型 unsigned char 的别名。有关如何定义自己的类型别名以供在 D 程序中使用的信息,请参见第 8 章

D 提供了用于与 ANSI-C 声明和类型保持兼容性的浮点类型。D 中不支持浮点运算符,但可以使用 printf() 函数跟踪浮点数据对象并设置其格式。可以使用下表中列出的浮点类型:

表 2–4 D 浮点数据类型

类型名称 

32 位大小 

64 位大小 

float

4 字节 

4 字节 

double

8 字节 

8 字节 

long double

16 字节 

16 字节 

D 还提供了特殊类型 string 用于表示 ASCII 字符串。第 6 章中将更详细地讨论字符串。

常量

可以采用十进制 (12345)、八进制 (012345) 或十六进制 (0x12345) 表示整型常数。八进制(以 8 为基数)常量必须使用前导零作为前缀。十六进制(以 16 为基数)常量必须使用 0x0X 作为前缀。在能够表示值的 intlonglong long 中,会为整型常数指定最小类型。如果值为负数,则将使用该类型的带符号版本。如果值为正数,并且太大以致不适合用带符号的类型表示,则将使用不带符号的类型表示。您可以将以下后缀之一应用于任何整型常数,以显式指定整型常数的 D 类型:

uU

编译器选择的类型的unsigned 版本

lL

long

ulUL

unsigned long

llLL

long long

ullULL

unsigned long long

浮点常量始终采用十进制表示,必须包含小数点 (12.345) 或指数 (123e45),或者同时包含二者 (123.34e-5)。缺省情况下,对浮点常量指定的类型为 double。您可以将以下后缀之一应用于任何浮点常量,以显式指定浮点常量的 D 类型:

fF

float

lL

long double

字符常量表示为单个字符或使用一对单引号 ('a') 引起来的转义序列。对字符常量指定的类型为 int,字符常量等效于值由 ASCII 字符集中该字符的值确定的整型常数。有关字符及其值的列表,可以参阅 ascii(5)。也可以在字符常量中使用下表所示的任何特殊转义序列。D 支持的转义序列与 ANSI-C 中相同。

表 2–5 D 字符转义序列

\a

警报 

\\

反斜杠 

\b

backspace 键 

\?

问号 

\f

换页 

\'

单引号 

\n

新行 

\”

双引号 

\r

回车 

\0oo

八进制值 0oo

\t

水平制表符 

\xhh

十六进制值 0xhh

\v

垂直制表符 

\0

空字符 

可以在单引号中包括多个字符说明符,以创建根据相应的字符说明符初始化各个字节的整数。将从左向右读取字符常量中的字节,并按与操作环境的本机字节存储顺序对应的顺序将各个字节分配到产生的整数。单个字符常量中最多可以包括八个字符说明符。

任何长度的字符串常量都可以通过引在一对双引号中 ("hello") 来构成。字符串常量可能不含字面值换行符。要创建包含换行符的字符串,请使用 \n 转义序列来代替字面换行符。字符串常量可以包含上面所示字符常量的任何特殊字符转义序列。与 ANSI-C 类似,字符串表示为由空字符 (\0) 结尾的字符数组,该空字符隐式添加到所声明的每个字符串常量中。对字符串常量指定了特殊的 D 类型 string。D 编译器提供了一组特殊功能,用于比较和跟踪被声明为字符串的字符数组,如第 6 章中所述。

算术运算符

D 提供了下表中所示的二元算术运算符,以供在程序中使用。对于整数,这些运算符含义与在 ANSI-C 中的完全一样。

表 2–6 D 二元算术运算符

+

整数相加 

-

整数相减 

*

整数相乘 

/

整数相除 

%

整数取模 

D 中只可以对整数操作数或指针(如第 5 章中所讨论)执行运算。D 程序中不可以对浮点操作数执行运算。DTrace 执行环境不对整数溢出或下溢采取任何操作。在可能出现溢出和下溢的情况下,您必须自己检查这些情况。

DTrace 执行环境不会自动检查和报告由于不正确使用 /% 运算符而导致的被零除错误。如果 D 程序执行无效的相除操作,则 DTrace 将自动禁用受影响的检测过程并报告错误。DTrace 检测到的错误不会对其他 DTrace 用户或操作系统内核产生影响,因此,不必担心如果 D 程序中包含其中某个未注意到的错误会导致任何破坏。

除了这些二元运算符,+- 运算符也可用作一元运算符;这些运算符比任何二元算术运算符具有更高的优先级。表 2–11 中说明了所有 D 运算符的优先级顺序和关联属性。可以通过使用括号 ( ) 对表达式分组来控制优先级。

关系运算符

D 提供了下表中所示的二元关系运算符供在程序中使用。这些运算符都与在 ANSI-C 中具有相同的含义。

表 2–7 D 关系运算符

<

左边的操作数小于右边的操作数 

<=

左边的操作数小于或等于右边的操作数 

>

左边的操作数大于右边的操作数 

>=

左边的操作数大于或等于右边的操作数 

==

左边的操作数等于右边的操作数 

!=

左边的操作数不等于右边的操作数 

关系运算符通常用于编写 D 谓词。每个运算符的计算结果都为 int 类型的值,如果条件为 true,则该值等于 1,如果条件为 false,则该值等于 0。

关系运算符可以应用于整数、指针或字符串对。如果比较指针,则结果等于解释为无符号整数的两个指针的整数比较。如果比较字符串,则结果如同是通过对两个操作数执行 strcmp(3C) 来确定。以下是一些 D 字符串比较及其结果的示例:

"coffee" < "espresso"

... 返回 1 (true) 

"coffee" == "coffee"

... 返回 1 (true) 

"coffee" >= "mocha"

... 返回 0 (false) 

也可使用关系运算符比较与枚举类型关联的数据对象和由枚举定义的任何枚举器标记。枚举是用于创建命名的整型常数的工具,第 8 章中对其进行了详细介绍。

逻辑运算符

D 提供了以下二元逻辑运算符供在程序中使用。前两个运算符等效于相应的 ANSI-C 运算符。

表 2–8 D 逻辑运算符

&&

逻辑 AND:两个操作数都为 true 时,结果为 true

||

逻辑 OR:一个或两个操作数为 true 时,结果为 true

^^

逻辑 XOR:只有一个操作数为 true 时,结果为 true

逻辑运算符通常用于编写 D 谓词。逻辑运算符 AND 使用简化求值法:如果左边的操作数为 false,则不会计算右边的表达式。逻辑运算符 OR 也使用简化求值法:如果左边的操作数为 true,则不会计算右边的表达式。逻辑 XOR 运算符不使用简化求值法:始终会计算两个表达式操作数。

除了二元逻辑运算符,一元 ! 运算符也可用于对单个操作数执行逻辑否定:它将零操作数转换为一,将非零操作数转换为零。根据约定,D 程序员在处理要用于表示布尔值的整数时使用 !,而在处理非布尔整数时使用 == 0,尽管两种表达式在含义上相同。

逻辑运算符可以应用于整数或指针类型的操作数。逻辑运算符将指针操作数解释为无符号整数值。与 D 中的所有逻辑和关系运算符一样,如果操作数具有非零整数值,则将解释为 true,如果具有零整数值,则将解释为 false。

按位运算符

D 提供了以下二元运算符用于处理整数操作数中的各个位。这些运算符都与在 ANSI-C 中具有相同的含义。

表 2–9 D 按位运算符

&

按位 AND 

|

按位 OR 

^

按位 XOR 

<<

将左边的操作数向左移动右边的操作数指定的位数 

>>

将左边的操作数向右移动右边的操作数指定的位数 

二元 & 运算符用于从整数操作数中清除位。二元 | 运算符用于在整数操作数中设置位。二元 ^ 运算符在只设置了相应操作数位之一的每个位的位置返回 1。

移位运算符用于在指定的整数操作数中向左或向右移位。向左移位将使用零填充结果右边空位的位置。使用无符号整数操作数向右移位将使用零填充结果左边空位的位置。使用带符号的整数操作数向右移位将使用符号位的值填充左边空位的位置,也称为算术移位操作。

用负位数将整数值移位,或者移位的位数大于左边操作数本身的位数,将产生未定义的结果。如果在编译 D 程序时,编译器可以检测到此条件,则 D 编译器将产生错误消息。

除了二元逻辑运算符,一元 ~ 运算符也可用于对单个操作数执行按位否定:它将操作数中的每个“零”位转换为“一”位,并将操作数中的每个“一”位转换为“零”位。

赋值运算符

D 提供了以下二元赋值运算符用于修改 D 变量。您只可以修改 D 变量和数组。不可以使用 D 赋值运算符修改内核数据对象和常量。赋值运算符与在 ANSI-C 中具有相同的含义。

表 2–10 D 赋值运算符

=

将左边的操作数设置为等于右边的表达式值 

+=

将左边的操作数递增右边的表达式值 

-=

将左边的操作数递减右边的表达式值 

*=

将左边的操作数与右边的表达式值相乘 

/=

将左边的操作数与右边的表达式值相除 

%=

将左边的操作数按右边的表达式值取模 

|=

将左边的操作数与右边的表达式值进行按位 OR 运算 

&=

将左边的操作数与右边的表达式值进行按位 AND 运算 

^=

将左边的操作数与右边的表达式值进行按位 XOR 运算 

<<=

将左边的操作数向左移动右边的表达式值指定的位数 

>>=

将左边的操作数向右移动右边的表达式值指定的位数 

除了赋值运算符 = 以外,所提供的其他赋值运算符是将 = 运算符与先前说明的某个其他运算符配合使用的简写形式。例如,表达式 x = x + 1 等效于表达式 x += 1,不同的是表达式 x 计算一次。这些赋值运算符遵守与前面说明的二元格式相同的操作类型规则。

任何赋值运算符的结果都是等于左边表达式的新值的表达式。您可以将赋值运算符或到现在为止说明的任何运算符与任意复杂度的格式表达式配合使用。也可以使用括号 ( ) 来对复杂表达式中的条件分组。

递增和递减运算符

D 提供了特殊的一元 ++-- 运算符用于递增和递减指针和整数。这些运算符的含义与在 ANSI-C 中相同。这些运算符仅可应用于变量,可以在变量名称之前或之后应用。如果运算符显示在变量名称之前,则将首先修改变量,于是产生的表达式等于变量的新值。例如,以下两个表达式产生相同的结果:

x += 1;

y = ++x;

y = x;

 

如果运算符显示在变量名称之后,则在返回变量的当前值以供在表达式中使用之后,将修改该变量。例如,以下两个表达式产生相同的结果:

y = x;

y = x--;

x -= 1;

 

您可以使用递增和递减运算符来创建新的变量,同时可以不声明这些变量。如果省略变量声明,并将递增或递减运算符应用于变量,则该变量将隐式声明为 int64_t 类型。

递增和递减运算符可以应用于整数或指针变量。应用于整数变量时,运算符将对相应的值递增或递减一。应用于指针变量时,运算符将对指针地址递增或递减指针所引用的数据类型的大小。D 中的指针和指针运算将在第 5 章中讨论。

条件表达式

尽管 D 不支持 if-then-else 结构,但它支持使用 ?: 运算符的简单条件表达式。这些运算符可以关联三元表达式(其中,第一个表达式用于根据条件计算另两个表达式中的一个)。例如,以下 D 语句可用于根据 i 的值将变量 x 设置为两个字符串之一。

x = i == 0 ? "zero" : "non-zero";

在此示例中,将首先计算表达式 i == 0 以确定该表达式为 true 还是 false。如果第一个表达式为 true,则将计算第二个表达式,并且 ?: 表达式将返回其值。如果第一个表达式为 false,则将计算第三个表达式,并且 ?: 表达式将返回其值。

与任何 D 运算符一样,可以在单个表达式中使用多个 ?: 运算符来创建更复杂的表达式。例如,以下表达式将采用一个 char 变量 c,该变量包含 0-9、a-z 或 A-Z 范围中的一个字符,然后在将此字符解释为十六进制(以 16 为基数)整数格式的数字时返回此字符的值。

hexval = (c >= '0' && c <= '9') ? c - '0' :
    (c >= 'a' && c <= 'z') ? c + 10 - 'a' : c + 10 - 'A';

?: 配合使用的第一个表达式必须为指针或整数,以便计算表达式的实际值。第二个和第三个表达式可以为任何兼容类型。在一个路径返回字符串,另一个路径返回整数的情况下,不可以构造条件表达式。第二个和第三个表达式也不可以调用跟踪函数(如 trace()printf())。如果要根据条件跟踪数据,请改为使用第 1 章中讨论的谓词。

类型转换

使用不同但兼容的类型构造表达式时,将会执行类型转换以便确定所产生表达式的类型。类型转换的 D 规则与 ANSI-C 中整数的算术转换规则相同。这些规则有时又称为常用算术转换

以下是说明转换规则的简单方法:每个整数类型按 char、short、int、long、long long 顺序排列,相应的无符号类型分配的级别高于其带符号类型,但低于下一个整数类型。使用两个整数操作数(如 x + y)构造表达式,且操作数为不同的整数类型时,具有最高级别的操作数类型将用作结果类型。

如果需要转换,则首先将低级别的操作数提升为高级别的类型。提升不会实际更改操作数的值:它仅根据符号将值扩展为更大的容器。如果提升无符号的操作数,则所产生整数的未用高序位将使用零填充。如果提升带符号操作数,则将通过执行符号扩展填充未用的高序位。如果将带符号类型转换为无符号类型,则将首先对带符号类型进行符号扩展,然后分配由转换确定的新的无符号类型。

也可以将整数和其他类型从一种类型显式地强制转换为另一种类型。在 D 中,可以将指针和整数强制转换为任何整数或指针类型,但不能转换为其他类型。强制转换以及提升字符串和字符数组的规则将在第 6 章中讨论。整数和指针强制转换使用表达式构成,如下所示:

y = (int)x;

其中,目标类型括在括号中,置于源表达式的前面。可以通过执行提升将整数强制转换为高级别的类型。可以通过将整数多余的高顺序位填充零来强制将整数转换为低级别的类型。

因为 D 不允许浮点运算,所以不允许执行浮点操作数转换或强制转换,也没有定义隐式浮点转换的规则。

优先级

下表说明了运算符优先级和关联性的 D 规则。这些规则有点复杂,但为了保持与 ANSI-C 运算符优先级规则的完全兼容性,这是必需的。表中项的顺序为从最高优先级到最低优先级。

表 2–11 D 运算符优先级和关联性

运算符 

关联性 

() [] -> .

从左到右 

! ~ ++ -- + - * & (type) sizeof stringof offsetof xlate

从右到左 

* / %

从左到右 

+ -

从左到右 

<< >>

从左到右 

< <= > >=

从左到右 

== !=

从左到右 

&

从左到右 

^

从左到右 

|

从左到右 

&&

从左到右 

^^

从左到右 

||

从左到右 

?:

从右到左 

= += -= *= /= %= &= ^= |= <<= >>=

从右到左 

,

从左到右 

表中还有多个运算符我们尚未讨论;后续章节中将对它们进行讨论:

sizeof

计算对象的大小(第 7 章

offsetof

计算类型成员的偏移(第 7 章

stringof

将相应操作数转换为字符串(第 6 章

xlate

转换数据类型(第 40 章

一元 &

计算对象的地址(第 5 章

一元 *

取消引用指向对象的指针(第 5 章

->.

访问结构类型或联合类型的成员(第 7 章

表中列出的逗号 (,) 运算符用于与 ANSI-C 逗号运算符兼容,可以使用该运算符按照从左到右的顺序计算一组表达式的值,然后返回最右边表达式的值。提供此运算符是为了与 C 严格兼容,通常不应使用。

运算符优先级表中的 () 项表示函数调用;第 1 章 中提供了函数调用(如 printf()trace())的示例。D 中也使用逗号列出函数的参数以及用于构成关联数组键的列表。此逗号与逗号运算符不同,它一定从左向右计算。D 编译器不提供对函数参数或关联数组键的计算顺序的保证。在这些上下文中使用会产生负面影响的表达式(如表达式 ii++ 对)时务必小心。

运算符优先级表中的 [] 项表示数组或关联数组引用。第 1 章中提供了关联数组的示例。第 9 章中介绍了称为聚合的特殊类型的关联数组。[] 运算符同样可用于对大小固定的 C 数组建立索引,如第 5 章中所述。