In 表达式关系表达式 in 移位表达式
RelExpression in ShiftExpression
可以检测一个元素是否在关联数组中:int foo[char[]];
……
if ("hello" in foo)
……
in 表达式同关系表达式 <、<= 等有相同的优先级。
移位表达式移位表达式 << 和表达式
移位表达式 >> 和表达式
移位表达式 >>> 和表达式
ShiftExpression << AddExpression
ShiftExpression >> AddExpression
ShiftExpression >>> AddExpression
操作数必须是整数类型,并且会使用常用的整数提升。结果的类型是左操作数提升后的类型。结果的值是左操作数移动右操作数指定的位得到的值。
<< 是左移,>> 是有符号右移(译注:也叫算术右移)。>>> 是无符号右移。(译注:也叫逻辑右移)
如果要移动的位数超过了左操作数的位数,会被认为是非法的: int c;
c << 33;error
和表达式和表达式 + 积表达式
和表达式 - 积表达式
和表达式 ~ 积表达式
AddExpression + MulExpression
AddExpression - MulExpression
AddExpression ~ MulExpression
如果操作数是整数类型,会应用整数提升,然后会通过常用的算术转换提升为它们的公共类型。
如果有操作数为浮点类型,另一个操作数会被隐式地转换为浮点类型,然后会通过常用的算术转换提升为它们的公共类型。
如果运算符是 + 或 - ,第一个操作数是指针,并且第二个操作数是整数类型,结果的类型就是第一个操作数的类型,结果的值是指针加上(或减去)第二个操作数乘以指针所指类型的大小得到的值。
如果指针所指的类型是 bit ,结果的值是第二个操作数除以 8 后与指针相加得到的指。如果第二个操作数不能被 8 整除,会被视为非法。bit* p;
p += 1;// 错误,1%8 不等于零
p += 8;// ok
如果第二个操作数是指针,第一个操作数是整数类型,并且运算符是 + ,会按照上面所说的方式进行指针运算,只不过操作数的顺序反过来。
浮点操作数的和表达式不是可结合的。
积表达式积表达式 * 一元表达式
积表达式 / 一元表达式
积表达式 % 一元表达式
MulExpression * UnaryExpression
MulExpression / UnaryExpression
MulExpression % UnaryExpression
操作数必须是算术类型。先会执行整数提升,然后会通过常用的算术转换提升为它们的公共类型。
对于整数操作数来说,*、/ 和 % 对应于乘、除和取模运算。对于乘运算,会忽略溢出,结果会简单地截取为整数类型。如果除或者取模运算的右操作数为 0 ,会抛出一个 DivideByZero 异常。
对于浮点操作数来说,各种运算同对应的 IEEE 754 浮点运算相同。取模运算只能用于实数类型,不能用于虚数或者复数类型。
浮点数的积表达式不是可结合的。
一元表达式& 一元表达式
++ 一元表达式
-- 一元表达式
* 一元表达式
- 一元表达式
+ 一元表达式
! 一元表达式
~ 一元表达式
delete 一元表达式
New表达式
cast ( 类型 ) 一元表达式
( 类型 ) . 标志符
( 表达式 )
& UnaryExpression
++ UnaryExpression
-- UnaryExpression
* UnaryExpression
- UnaryExpression
+ UnaryExpression
! UnaryExpression
~ UnaryExpression
delete UnaryExpression
NewExpression
cast ( Type ) UnaryExpression
( Type ) . Identifier
( Expression )
New 表达式New 表达式用来在垃圾收集堆(默认情况)上或使用类指定的分配器分配内存。
在为多维数组分配内存时,声明按照同读取后缀数组声明顺序相同的顺序读取声明。 char[][] foo;// 字符串动态数组
...
foo = new char[][30];// 分配 30 个字符串数组
转型表达式在 C 和 C++ 中,类型转换的形式为:(类型) 一元表达式
(type) unaryexpression
但是,这会在语法上造成歧义。考虑:(foo) - p;
这是将负 p 转型为 foo ,还是 foo 减去 p ?如果不通过查找符号表以确定它究竟是一个类型还是一个变量,就不可能作出判断。但 D 的设计目标就是使语法为上下文无关的——应该无需检索符号表就能进行语法分析。所以,为了区分转型和带括号的字表达式,需要引入不同的语法。
C++ 通过引入:dynamic_cast<类型>(表达式)
dynamic_cast<type>(expression)
解决这个问题,但这写起来又丑陋又笨拙。D 引入了 cast 关键字:cast(foo) -p;// c将 (-p) 转型为 foo
(foo) - p;// foo 减去 p
cast 具有很好的性质,可以很容易地通过文本查找方式找到它,还会减轻那些被无情地重载的‘()’运算符的负担。
在转型的其他方面,D 同 C/C++ 也有所不同。任何从类引用到派生类引用的转换都会执行运行时检查以确保转型是适当的。这种行为等价于 C++ 中的 dynamic_cast 运算符。class A { ... }
class B : A { ... }
void test(A a, B b)
{
B bx = a;// 错误,需要类型转换
B bx = cast(B) a;// 如果 a 不是 B 类型,bx 就为 null
A ax = b;// 不需要类型转换
A ax = cast(A) b;// 不需要向上类型转换的运行时检查
}
如果想要检测一个对象 o 是否是类 B 的一个实例,可以使用转型:if (cast(B) o)
{
// o 是 B 的一个实例
}
else
{
// o 不是 B 的一个实例
}
后缀表达式后缀表达式 . 标志符
后缀表达式 -> 标志符
后缀表达式 ++
后缀表达式 --
后缀表达式 ( 参数列表 )
后缀表达式 [ 参数列表 ]
后缀表达式 [ 赋值表达式 .. 赋值表达式 ]
PostfixExpression . Identifier
PostfixExpression -> Identifier
PostfixExpression ++
PostfixExpression --
PostfixExpression ( ArgumentList )
PostfixExpression [ ArgumentList ]
PostfixExpression [ AssignExpression .. AssignExpression ]
索引表达式后缀表达式 [ 参数列表 ]
PostfixExpression [ ArgumentList ]
后缀表达式 会被计算。如果 后缀表达式 的类型为静态或者动态数组,会隐式地声明变量 length ,并将数组的长度赋给它。参数列表 有自己独立的声明作用域,length 只出现在这个作用域中。
切片表达式后缀表达式 [ 赋值表达式 .. 赋值表达式 ]
PostfixExpression [ AssignExpression .. AssignExpression ]
或缀表达式 会被计算。如果 后缀表达式 的类型为静态或者动态数组,会隐式地声明变量 length ,并将数组的长度赋给它。赋值表达式 有自己独立的声明作用域,length 和 赋值表达式 只出现在这个作用域中。
第一个 赋值表达式 是切片的闭的下界,第二个 赋值表达式 是切片的开的上界。(译注:也就是 [...) ,学过数学的都知道,呵呵)。表达式的结果是 后缀表达式 数组的一个切片。
基本表达式标志符
.标志符
this
super
null
true
false
数值文字量
字符文字量
字符串文字量
函数文字量
断言表达式
基本类型 . 标志符
typeid ( 类型 )
Identifier
.Identifier
this
super
null
true
false
NumericLiteral
CharacterLiteral
StringLiteral
FunctionLiteral
AssertExpression
BasicType . Identifier
typeid ( Type )
.标志符会在模块作用域内查找 标志符 ,而不是在当前词法的嵌套作用域内。
this在非静态成员函数内,this 是指向调用此函数的对象的指针。如果成员函数是显式地通过引用 typeof(this) 调用的,会生成一个非虚函数调用:class A
{
char get() { return 'A'; }
char foo() { return typeof(this).get(); }
char bar() { return this.get(); }
}
class B : A
{
char get() { return 'B'; }
}
void main()
{
B b = new B();
b.foo();// 返回 'A'
b.bar();// 返回 'B'
}
super在非静态成员函数内,super 是指向调用此函数的对象的指针,而这个指针被转换为它的基类类型的指针。如果不存在相应的基类,就会被认为是错误。super 不允许出现在结构的成员函数中。如果成员函数是显式地通过引用 super 调用的,会生成一个非虚函数调用。
null关键字 null 表示空指针;从技术上说,它的类型是 (void *) 。它可以被隐式地转换为任何指针类型。整数 0 不能被转换为空指针。null 也用于空数组。
true, false它们都是 bit 型的,值分别为 1 和 0 。
字符文字量字符文字量是单个的字符,类型是 char、 wchar 或者 dchar 。如果文字量是 \u 转义序列,类型就是 wchar 。如果文字量是 \U 转义序列,类型就是 dchar 。否则,它的类型是能够容纳它的最小的类型。
函数文字量函数文字量
function 函数体
function ( 参数列表 ) 函数体
function 类型 ( 参数列表 ) 函数体
delegate 函数体
delegate ( 参数列表 ) 函数体
delegate 类型 ( 参数列表 ) 函数体
FunctionLiteral
function FunctionBody
function ( ParameterList ) FunctionBody
function Type ( ParameterList ) FunctionBody
delegate FunctionBody
delegate ( ParameterList ) FunctionBody
delegate Type ( ParameterList ) FunctionBody
有了 函数文字量 ,就可以直接将匿名函数和匿名委托嵌入到表达式中。类型 是函数或委托的返回类型,如果忽略的话,会被认为是 void 。( 参数列表 ) 是传递给函数的参数。如果忽略的话,会被认为是空参数列表 () 。函数文字量的类型是指向函数或者委托的指针。
例如: int function(char c) fp;// 声明函数指针
void test()
{
static int foo(char c) { return 6; }
fp = &foo;
}
精确地等价于:int function(char c) fp;
void test()
{
fp = function int(char c) { return 6;} ;
}
而:int abc(int delegate(long i));
void test()
{ int b = 3;
int foo(long c) { return 6 + b; }
abc(&foo);
}
精确地等价于:int abc(int delegate(long i));
void test()
{ int b = 3;
abc( delegate int(long c) { return 6 + b; } );
}
匿名委托的行为就像任意的语句文字量。例如,下面的 loop 可以执行任何语句: double test()
{ double d = 7.6;
float f = 2.3;
void loop(int k, int j, void delegate() statement)
{
for (int i = k; i < j; i++)
{
statement();
}
}
loop(5, 100, delegate { d += 1; } );
loop(3, 10, delegate { f += 1; } );
return d + f;
}
与 嵌套函数 相比,function 形式类似于静态或者非嵌套函数,而 delegate 形式类似于非静态嵌套函数。换句话说,委托文字量可以访问它外围函数的堆栈,而函数文字量则不能。
断言表达式断言表达式:
assert ( 表达式 )
AssertExpression:
assert ( Expression )
断言会计算 表达式 。如果结果为假,会抛出一个 AssertError 异常。如果结果为真,不会抛出任何异常。如果 表达式 包含程序所依赖的任何副作用,就是一个错误。通过编译时的命令行选项,编译器可以根本不对断言表达式求值。断言表达式的结果的类型是 void 。断言是 D 支持 契约式编程 的一个基础。
Typeid 表达式Typeid表达式:
typeid ( 类型 )
TypeidExpression:
typeid ( Type )
返回同 类型 对应的 TypeInfo 类的实例。