========================== 第3章 QRunes表达式 ========================== QRunes提供了丰富的操作符,并定义操作数为内置类型时这些操作符的含义。掌握这些内容,我们在编程时就能有效的规避错误、提高效率。本章将重点介绍QRunes定义的操作符,他们使用内置类型的操作数。 表达式由一个或多个操作数(operand)通过操作符(operator)组合而成。最简单的表达式(expression)仅包含一个字面值常量或变量。较复杂的表达式则由操作符以及一个或多个操作数构成。 每个表达式都有一个结果(result)。如果表达式中没有操作符,则其结果就是操作数本身(例如,字面值常量或变量)的值。当一个对象用在需要使用其值的地方,则计算该对象的值。例如,假设ival是一个int型对象: :: if(ival) //evaluate ival as a condition // ........ 上述语句将ival作为if语句的条件表达式。当ival为非零值时,if条件成立;否则条件不成立。 对于含有操作符的表达式,它的值通过对操作数做指定操作获得。除了特殊用法外,表达式的结果是右值(第2章已介绍),可以读取该结果值,但是不允许对它进行赋值。 由我们已有的编程经验不难得知,操作符的含义就是该操作符可以执行什么操作以及操作结果的类型,它取决于操作数的类型。 除非已知道操作数的类型,否则无法确定一个特定表达式的含义。下面的表达式: :: i + j 既可能是整数的加法操作或者浮点数的加法操作,也完全可能是其他的操作。如何计算该表达式的值,完全取决于i和j的数据类型。 QRunes提供了一元操作符(unary operator)利二元操作符(binary operator)两种操作符。作用在一个操作数上的操作符称为一元操作符, 如自增操作符(++);而二元操作符则作用于两个操作数上,如加法操作符(+)和减法操作符(-)。除此之外,QRunes还提供了个使用三个操作数的三元操作符(ternary operator),我们将在本小节最后介绍它。 操作符对其操作数的类型有要求,如果操作符应用于内置或复合类型的操作数,则由QRunes语言定义其类型要求。例如,自增操作符要求其操作数必须是辅助类型,对任何其他内置类型或复合类型对象进行自增操作将导致错误的产生。 对于操作数为内置或复合类型的二元操作符,通常要求它的两个操作数具有相同的数据类型。 要理解由多个操作符组成的表达式,必须先理解操作符的优先级(precedence)结合性(associativity)和操作数的求值顺序(order of evaluation)。 本章节及后续的第四章来详细叙述下QRunes中的表达式和语句,因为当前的量子编程涉及三个部分:经典计算机模块,测控系统模块和量子芯片模块,故这种混合(量子、经典、辅助类型)程序各自分别运行在其对应的硬件模块上,他们的编译和运行方式也将会不同。 3.1 操作符 ******************************* 3.1.1 一元操作符 ******************************* ============== ============================================================= ======================= ====================== 一元操作符 含义 示例 支持类型 ============== ============================================================= ======================= ====================== ~ 取反操作符 ~x A ! 逻辑非操作符 !x A C ++ 一元递增操作符 x++ A -- 一元递减操作符 x-- A . 方法调用操作符 x.fun() A C [] 取下标操作符 x[value] 数组类型 ============== ============================================================= ======================= ====================== 3.1.2 二元操作符 ******************************* - 基本的赋值操作符是“=”。它的优先级别低于其他的操作符,所以对该操作符往往最后读取。 ============== ============================================================= ======================= ====================== 赋值操作符 含义 示例 支持类型 ============== ============================================================= ======================= ====================== = 将右操作数的值赋给左操作数 x=y;将x的值赋为y A C += 加后赋值 x+=y;即x=x+y A C -= 减后赋值 x-=y;即x=x-y A C *= 乘后赋值 x*=y;即x=x*y A C /= 除后赋值 x/=y;即x=x/y A C %= 取余后赋值 x%=y;即x=x%y A C &= 按位与后赋值 x&=y;即x=x%y A C \|= 按位或后赋值 x\|=y;即x=x\|y A C ============== ============================================================= ======================= ====================== - 算术操作符即算术操作符号。是完成基本的算术运算 (arithmetic operators) 符号,就是用来处理四则运算的符号。 ============== ============================================================= ======================= ====================== 算术操作符 含义 示例 支持类型 ============== ============================================================= ======================= ====================== \+ 两个操作数相加 x + y A C \- 第一个操作数减去第二个操作数 x - y A C \* 两个操作数相乘 x * y A C \/ 第一个操作数除第二个操作数 x / y A C % 第一个操作数整除第二个操作数之后的余数 x % y A ^ 第一个操作数的第二个操作数幂次方 x ^ y A ============== ============================================================= ======================= ====================== - 关系操作符有6种关系,分别为小于、小于等于、大于、等于、大于等于、不等于。关系操作符的值只能是0或1。关系操作符的值为真时,结果值都为1。关系操作符的值为假时,结果值都为0。 ============== ============================================================= ======================= ====================== 关系操作符 含义 示例 支持类型 ============== ============================================================= ======================= ====================== == 判断两个操作数是否相等,相等则返回真值 x == y A C != 判断两个数是否相等,不相等则返回真值 x != y A C > 判断左操作数是否大于右操作数,大于则返回真值 x > y A C < 判断左操作数是否小于右操作数,小于则返回真值 x < y A C >= 判断左操作数是否大于等于右操作数,大于等于则返回真值 x > y A C <= 判断左操作数是否小于等于右操作数,小于等于则返回真值 x <= y A C ============== ============================================================= ======================= ====================== - 在形式逻辑中,逻辑操作符或逻辑联结词把语句连接成更复杂的复杂语句 ============== ============================================================= ======================= ====================== 逻辑操作符 含义 示例 支持类型 ============== ============================================================= ======================= ====================== && 如果两个操作数都非零,则返回真值 x && y A C || 如果两个操作数任意一个非零,则返回真值 x || y A C ! 如果操作数为零 !x A C ============== ============================================================= ======================= ====================== - 位操作是程序设计中对位模式按位或二进制数的一元和二元操作。在许多古老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多。在现代架构中, 情况并非如此:位运算的运算速度通常与加法运算相同(仍然快于乘法运算)。 ============== ============================================================= ======================= ====================== 位操作符 含义 示例 支持类型 ============== ============================================================= ======================= ====================== & 按位与 x & y A \| 按位或 x | y A ^ 异或操作符 x ^ y A << 二进制左移操作符 x << y A >> 二进制右移操作符 x >> y A ============== ============================================================= ======================= ====================== 3.1.3 三元操作符 ******************************* =========== =================================== ==================== =============== =============== 三元操作符 含义 示例 类别 支持类型 =========== =================================== ==================== =============== =============== ?: 根据计算的值结果选择true还是false a > b ? a : b 三元操作符 A =========== =================================== ==================== =============== =============== 3.1.4 逗号操作符 ******************************* 逗号操作符的作用是将几个表达式放在一起,用逗号分割,这些表达式从左向右依次计算。逗号操作符最后的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。此操作经常用于循环中。 注:支持 A Q C 3.1.5 操作符详解 ******************************* 3.1.5.1 逻辑操作符与位操作符 相信有过C语言经验的读者知道,逻辑操作符将其操作数视为条件表达式,首先对操作数求值;若结果为0,则条件为假(false),否则为真(true)。仅当逻辑与操作符(&&)的两个操作数都为true,其结果才得true。对于逻辑或操作符(||),只要两个操作数之一为true,它的值就为true.逻辑非操作符(!)产生与其操作数相反的条件值。如果其操作数为非0值,则做!操作后的结果为false。例如: :: expr1 && expr2 //logical AND expr1 && expr2 //logical OR !expr1 //logical NOT 仅当由expr1不能确定表达式的值时,才会求解expr2(笔者注:这种求值策略被称为短路求值)。也就是说,当且仅当下列情况出现时,必须确保expr2是可以计算的。 (1) 在逻辑与表达式中,expr1的计算结果为true.如果expr1的值为false,则无论expr2的值为什么,逻辑与表达式的值就为false。当expr1的值为true时,只有expr2的值也是true,逻辑与表达式的值才为true. (2) 在逻辑或表达式中,expr1的计算结果为false,则逻辑或表达式的值取决于expr2的值是否为true. 辅助类型在内存中以补码的形式存储,取反操作符执行按位取反操作:二进制每一位取反,0变1,1变0.按位与操作符"&"是双目操作符,其功能是参与运算的两数各对应的二进位相与,只有对应的两个二进位均为1时,结果位才为1,否则为0。按位或操作符“|”是双目操作符。 其功能是参与运算的两数各对应的二进位相或,只要对应的两个二进位有一个为1时,结果位就为1。按位异或操作符“^”为双目操作符, 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。左移操作符“<<”是双目操作符,左移n位就是乘以2的n次方,其功能把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。右移操作符“>>”是双目操作符。右移n位就是除以2的n次方,其功能是把“>>”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。位运算操作与C语言中的位运算操作并无差别,这里便不再过于赘述,只提供几个简单的小例子供读者参考。 :: 00001001 & 00000101 = 00000001 // 9&5=1 00001001 | 00000101 = 00001101 // 9|5=13 00001001 ^ 00000101 = 00001100 // 9^5=12 ~(1001) = 0110 x>>1; //equivalent to x/=2 x<<1; //equivalent to x*=2 x>>2; //equivalent to x/=4 x<<2; //equivalent to x*=4 x>>3; //equivalent to x/=8 x<<3; //equivalent to x*=8 需要注意的是:位运算将操作数视为二进制位的集合,为每一位提供检验和设置的功能,它只适用于辅助类型,对其它数据类型不适用;逻辑运算表达式的结果只能是1或0,而位运算的结果可以取0或1以外的值。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数值。否则,操作的效果未定义。 3.1.5.2 关系操作符 关系操作符使用辅助类型或经典类型的操作数,并返回布尔型的值。关系操作符的值为true时,结果值为1;关系操作符的值为false时,结果值为0。点操作符(.)一般运用于方法调用,取下标运算([])用于获取数组类型中某个特定索引对象的具体信息。 3.1.5.3 赋值操作符 赋值操作符的操作与C语言相同,在这里我们屏蔽其具体操作,而详细阐述其合法性要求。 赋值操作符的左操作数必须是非常量的左值,下面的赋值语句是不合法的: :: let i=1,j=2,ival=3; let ci=i; // ok:initialization not assignment 2048=ival; // error:literal are rvalues i + j = ival; //error:arithmetic expressions are rvalues ci = ival; //error:can't write to ci 在数组类型中,数组名是不可修改的左值:因此数组不可以做赋值操作的目标。而下标操作符也返回左值,因此这种操作位于非静态数组时,其结果可以做为赋值结果的左操作数: :: vector ives; ives[0] = 1024; // ok: subscript is an value 我们应该注意到,赋值表达式的值是其左操作数的值,其结果的类型为左操作数的类型。通常情况下,赋值操作将其右操作数的值赋给左操作数,然而当左右操作符类型不同时,该操作将无法实现。因此我们在编程时,可以事先人工检查类型是否一致。 3.1.5.4 算术操作符 对于算术操作符来说,我们首先考虑此类操作符的优先级,一元操作符的优先级最高。其次是乘、除操作,接着是二元的加、减法操作。高优先级的操作符要比低优先级的结合的更紧密。上述表格中的所有算符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。关于算符的优先级关系,我们将在下面仔细阐述。以下是一个简单的小例子: :: 3 + 10 * 20 / 2; 考虑到优先级和结合性,可知该表达式先做乘法(*)操作,其操作数是10和20,然后以该操作的结果和2为操作数做除法(/)操作,其结果最后与操作数3做加法(+)操作。 关于算符表达式,考虑到其中的数学特性,我们应避免诸如除零操作等违法操作,对于计算机特性而言,比如溢出和截断,在这里我们将不予考虑。 3.1.5.5 自增和自减操作符 自增(++)和自减(- -)操作符为对象加1或减1操作提供了方便简短的实现方式。和C一样,在QRunes中它们也有前置和后置两种使用形式。这里在简单重复一下前置和后置形式的微小差别。前自增操作使其操作数加1,操作结果是修改后的值。同理,前自减操作执行操作数减1 操作。这两种操作符的后置形式同样对其操作数加1(或减1),但操作后产生操作数原来的、未修改的值作为表达式的结果,此类操作只支持辅助类型对象: :: let i = 0; let j = ++i;// j=1,i=1.prefix yields incremented value let k = --i;// k=1,i=2.prefix yields unzincremented value 我们不难发现,前自增操作要做的工作更少,只需要加1后返回加1的结果即可。而后自增操作则需要保存操作数原来的值,以便返回未加1之前的值作为操作的结果。因此,有经验的程序员在处理复杂类型对象时,都会优先使用前置操作。 对于在单个表达式中组合使用此类操作,和C++ 的操作一致。 3.2 表达式 ******************************* **在QRunes中,表达式由操作符和操作数组成,主要的作用是:** - 计算辅助类型操作数的值 - 指定函数 **操作数可以是常量或者一个数据对象。比如:** - 常量:3.14,1 - 数据对象:标识符,表达式本身 3.2.1 主表达式 ******************************* **它是构造其他表达式的基本块。** 语法构成: :: 主表达式:标识符 | 常量 | 括号表达式 primary_expression: idetifier | constant |parenthesis_expression 例如:qubit_s1,3.1415,(c1 + c2) 注:支持量子类型,经典类型,辅助类型 3.2.2 括号表达式 ******************************* 语法构成: :: parenthesis_expression:( expression ) 它表示在不更改括号封闭里面的表达式类型或值的情况下来构造表达式的分组方式。 例如: ( 2 + 3 )/5 与 2 + 3 / 5 注:支持量子类型,经典类型,辅助类型 3.2.3 复合表达式 ******************************* 含有两个或更多操作符的表达式称为复合表达式(compound expression)。在复合表达式中,操作数与操作符的结合方式决定了整个表达式的值。表达式的结果会因为操作符与操作数的分组结合方式的不同而不同。操作数的分组方式取决于操作符的优先级和结合性。也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数。如果程序员不想考虑这些规则,可以在复合表达式中使用圆括号强制实现某个特定的分组。一般情况下,优先级规定的是操作数的结合方式,但并没有说明操作数的计算顺序。 3.2.3.1 优先级和结合性 表达式的值取决于其子表达式如何分组;优先级规定了具有相同优先级的操作符如何分组。下表给出了QRunes中操作符的优先级和结合性。 ==================================== ================================ 操作符 结合性 ==================================== ================================ [ ] . ( )(方法调用) 从左向右 ! ~ ++ \- \- +(一元运算)-(一元运算) 从右向左 \* / % 从左向右 \+\- 从左向右 << >> 从左向右 < <= > >= 从左向右 == != 从左向右 && || 从左向右 ?: 从右向左 = += -= *= /= %= 从右向左 ==================================== ================================ 当然,我们可以使用圆括号来推翻操作符优先级的限制,使用圆括号的表达式将用圆括号括起来的子表达式视为独立单元先进行计算,其他的部分则以普通优先级规则处理。下面对于操作符的优先级和结合性给出几个例子: :: ival1 = ival2 = ival3 = ival4 //right associative (ival1 = (ival2 =(ival3 =ival4))) //equivalent,parenthsized version ival1 * ival2 / ival3 * ival4 //left associative (((ival1 * ival2)/ ival3 * ival4) //equivalent,parenthsized version 3.2.3.2 注意事项 大多数操作符没有规定其操作数的求值顺序:由编译器自由选择先计算左操作数还是右操作数。通常操作数的求值顺序不会影响表达式的结果。但是,如果操作符的两个操作数都与同一个对象有关,而且其中一个操作数改变了该对象的值,则程序将会因此而产生严重的错误,并且此类错误很难被发现。 3.2.4 常量表达式 ******************************* :: 常量表达式是在编译时计算而不是在运行时计算。 注:支持 A 3.2.5 lambda表达式 ********************* | 匿名函数lambda:是指一类无需定义标识符(函数名)的函数或子程序。 | lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。 | lambda匿名函数的格式:冒号前是参数,可以有多个,用逗号隔开,冒号右边的为表达式或是语法块。其实lambda返回值是一个函数的地址,也就是函数对象。 示例: :: circuit,qubit> generate_two_qubit_oracle(vector oracle_function){ return lambda (vector qlist,qubit qubit2):{ if (oracle_function[0] == false && oracle_function[1] == true){ // f(x) = x; CNOT(qlist[0], qubit2); }else if (oracle_function[0] == true && oracle_function[1] == false){ // f(x) = x + 1; CNOT(qlist[0], qubit2); X(qubit2); }else if (oracle_function[0] == true && oracle_function[1] == true){ // f(x) = 1 X(qubit2); }else{ // f(x) = 0, do nothing } }; } Deutsch_Jozsa_algorithm(vector qlist,qubit qubit2,vector clist,circuit,qubit> oracle){ X(qubit2); apply_QGate(qlist, H); H(qubit2); oracle(qlist,qubit2); apply_QGate(qlist, H); measure_all(qlist,clist); } | 注意:lambda表达式包含的语法块或表达式不能超过一个