找回密码
 立即注册

QQ登录

只需一步,快速开始

Carl

版主

51

主题

217

帖子

976

积分

版主

Rank: 7Rank: 7Rank: 7

积分
976

活字格认证微信认证勋章

QQ
Carl
版主   /  发表于:2009-12-16 11:02  /   查看:11498  /  回复:4
Post by "dujid", 2009-09-16, 8:47
-----------------------------------------------------

最近在实验解析csharp时发现了一些有趣的语法现象,将他们作为题目来考考大家。

一定要回复你的答案啊Orz,后面附有解答(一家之见),多谢支持!

注意:所有题目都是单选。



【题1】

int p=2;

A: Console.WriteLine(-p--);

B: Console.WriteLine(-(p)--);

C: Console.WriteLine(p=-new int());

D: Console.WriteLine(p=-3--);

问:上面哪一个语句不能被正确编译?





【题2】

A: int b = 4, a = + b+++++b;

B: int b = 4, a = +b+++++b;

C: int b = 4, a = +b+++ ++b;

D: int b = 4, a = +b++ +++b;

问:上面哪一个语句能被正确编译?

(ps:还有谁比我+多?对于两个变量构成的表达式)



【题3】

问:题2里,a的值是?

A: 8

B: 9

C: 10

D: 11





【题4】

int m = 0, n = m = --m+m--;

问:n的值是?

A: 0

B: -1

C: -2

D: -3





【题5】

for (int k = 0; k++ < 10; ++k, k--) Console.WriteLine(k);

问:下面哪句话是正确的?

A: 此语句可以被正确编译,但将导致无限循环;

B: 此语句可以被正确编译,输出k的范围是0~9;

C: 此语句可以被正确编译,输出k的范围是1~9;

D: 此语句可以被正确编译,输出k的范围是1~10。





【题6】

bool br = false;

switch (br) {

              case true:            break;   // 1

              case !true:           break;   // 2

              case !(!br):           break;   // 3

              case 1 == 1:         break;   // 4

              case !(1 == 1):     break;   // 5

              case 1 > 2:           break;   // 6

}

问:下面选项中正确的项目是?

A: 此语句虽然没什么问题,可以编译通过,但是有些case语句没有意义;

B: 此语句没什么问题,可以编译通过,且没有意义的case语句将被编译器自动优化;

C: 此语句不能正确编译,但删除了case 3则可以正确编译;

D: 此语句不能正确编译,但删除了case 1,2以外的case后则可以正确编译;





【题7】

1: if (2 > 1) { int o = 1; }

2: if (2 > 1) int o = 1;

问:下面哪句话是正确的?

A: 1不能正确编译;

B: 2不能正确编译;

C: 1和2都能正确编译,但1会警告局部变量未被使用;

D: 1和2都能正确编译,但1和2都会警告局部变量未被使用。



-----------------------------------------------------------------------------------------------------------



















-----------------------------------------------------------------------------------------------------------





【关于题1】

此题的重点是负号是一个普通运算符还是一个对于变量的修饰符,答案是普通运算符,并可用于任何表达式(不计较表达式值类型的话)。而--运算符的使用对象是变量,属性或者索引。

语句A可以被编译,但要注意--的运算符比负号运算符的优先级高,这个语句的返回值是-2。

语句B也可以被编译,原理与语句A相同,多出来的括号并不会改变词义,在词法分析时编译器就能够将没有意义的括号忽略掉。稍后的题目也会看到编译器的一部分优化功能;

语句C也可以被编译,因为负号是普通运算符,可用于表达式上,并不一定必须用于常量或变量上;

语句D则不能被正确编译,因为--运算符不能用在常量上。D是正确答案。





【关于题2】

此题的目的不是在语言语义上,而是为了说明csharp以及类似词法分析器的词法解释过程。类似csharp的语法定义上,负号都被定义为“(+|-) exp”,则读取到的-b可以被正确解释,此后继续读取到+b++,由于发现了“变量|属性|索引 ++”的语法定义而将 b++ 也做了正确的分析,这样得到一个 (+ ( b ++ ) ) 的树,分析完全正确,再继续读取到了+运算符,也做了正确分析,而从这里开始再往后读取时,由于紧跟着又发现了一个+号,这个+和刚才的加法运算符合在一起构成了一个++运算符,此时由于“++ 变量|属性|索引”的语法定义,解释器向后读取时需要一个“变量|属性|索引”,而此时再往后读取时,读取到的是第三个加号,则词法分析器报错。在语句3中,空格的位置促使词法分析器进行断言,而正确匹配到了后面的++b。本题中,任何一个语句都可以将后面的++b括起来而不使用空格。这里的括号是有意义的,虽然它在运算符的优先顺序上并无异议,但是却促使分析器进行了断言。在语义定义上,exp (op exp)* 则是一个右递归,而括号则迫使分析器进入下一个匹配过程(递归),从而正确匹配下一个语法。因此 -2, -2+3, -(2+3) 都是正确的表达式,而- -(2+3)不是,只要写成 -(-(2+3)) 则是正确的表达式。正确答案是C。





【关于题3】

首先8和11肯定是错误的,无论如何++b或者b++也至少要有一个反应。

那么正确答案是9么?因为+b+++ ++b抛掉绕眼并无用的符号以后是:

b + ++b。将4代入b,得到 4 + ++4 = 4 + 5 = 9

而正确答案是10也合乎情理,试着理解csharp的运算过程:首先此语法构成的语法树是:

( +   b   (++ b)  )

csharp表达式运算确实是自左向右没错,遇到树顶 + 运算符,取出 左右节点,此时发现右节点是一个 (++ b) 的子树,则先运算子树,子树的运算结果是5,同时,子树也将b的值置为5并返回5,子树的的运算退出,先前的树继续运算,取出左右节点,此时左右节点的值都是5,然后相加,结果是10。正确答案是C。





【关于题4】

这题与上题类似,都涉及到了表达式的运算顺序问题,但要注意,a = b = c 这样的语句并不是表达式,而就是符合语法的赋值语句。在csharp的语法定义里,赋值语句被这样描述:

(变量|属性|索引 =)+ exp

也就是说,a = 1+1 = 2 这样的语句是无法编译的,也是没有意义的。

另外,MSDN中强调赋值语句的运算顺序是“自右向左”,因此此题的运算过程可理解为:

n = m = --m+m--

n = ( m = (-1 + -1) )

n = ( m = (-1 -1) )

n = ( m = -2 )

n = -2

正确答案是C。与题3一样,m的值在+运算符左右被同时改变,是--m的结果和作用,不要误认为是m--的结果被反应了。m--也被计算,但结果并未马上反应。

此题中如果误认为赋值语句的执行顺序是“自左向右”则会认为0是正确答案。(在某些语言里确实是这样。)





【关于题5】

答案是D。此题涉及2个问题。

问题1是for语句的循环条件定义部分,也就是第一个分号和第二个分号之间,这个条件定义语句在每次循环一圈都会被执行,因此也改变了k的值。由此也可看出,写for(; i<size();i++) 这样的语句有多么不好

问题2是for语句的第二个分号后可以撰写多个statement-expression,并用逗号隔开,这称为statement-expression-list。每个statement-expression则都会依次得到执行,则++k和k--正好抵消。这里要注意的是,这些statement-expression虽然使用逗号隔开而不是正规的终止符分号,但不管k++还是++k,都会在statement-expression被执行完后马上反应,也就是碰到逗号时。在这里逗号的作用与分号一样。

++k,k--其实是引人耳目的无用语句。由于k++<10每次都被执行,但被执行时是使用k变化前的值来判断,而Console.WriteLine(k)会打印k变化后的值,所以k的范围则是:1~10。

但是++k<10这样的语法会先反应k的值再进行比较,因此范围是1~9。

或者这样看比较明显:

for(int i=0;i<10;i++) Console.WriteLine(k);    范围:0~9

for(int i=0;++i<10;) Console.WriteLine(k);     范围:1~9

for(int i=0;i++<10;) Console.WriteLine(k);     范围:1~10

这些微妙的变化导致了k范围发生的变化。





【关于题6】

此题也是包含了2个问题。

问题1是switch的case条件……的条件到底是什么?答案是case的条件只能是常量表达式,所谓常量表达式,就是由常量和运算符构成的表达式,只要不出现变量,无所谓有多少个运算符,比如1+1就是一个简单的常量表达式,而上面的case 3则不是常量表达式。因此选项C是错误的。而常量表达式也通常会在编译时期就被求值从而转换为一个原子(atom)常量。(注:并不需要在运行时求值,编译器在词法解释期间就可认定一个表达式是不是常量表达式。)

问题2是csharp的编译器确实会优化switch语句,只要把这个语句复制到VS里,然后删除case 3编译就可以看到错误信息提示说:由于case 1 和case 2 已经覆盖了boolean的所有可能的情况,所以 case 4开始全部都是重复 case ,不能编译。因此只有选项D是正确的。另外请忽略它们是否有意义,就单从语法上,这些case的条件部分都是可以被正确解释的表达式语句,如果放在if,for 或者while的条件部分,则可以被正确分析解释。





【关于题7】

if 后面的 statement 也是embed-statement,一个embed-statement是不可以声明本地变量,因此本题的语句2 不能编译。embed-statement 在被用大括号括起时语法性质变为 block,只有在 block 里才可以声明本地变量,因此语句2不能正确编译。选项C的后半是正确的,而前半则是错误的。D也是一样,说对了一半。我在自己的实验里也定义如下语法:

block : { statement* }

statement: local-variable-declaration | embed-statement

embed-statement: block | assignment | expression | …

而if的定义如下:

if ( boolean-BLOCKED EXPRESSION embed-statement (else embed-statement)?

从而可以看出,如果在if ( … ) 之后不写 { } 在语义上则被理解为 embed-statement,而 embed-statement的定义里不包括 local-variable-declaration,当写下 { } 时,因为embed-statement 的定义里包括 block 而语义定性为 block,block的定义里又包括 statement,而statement的定义里包括 local-variable-declaration。所以语句2不能被编译通过,在语法check上就被报错了。本题正确答案是B。





【尾声】

1.关于详尽的csharp的语法定义,可以看一下MSDN的C# Grammar:

http://msdn.microsoft.com/en-us/library/aa664812(VS.71).aspx



2.这些问题写出来的语句其实大多情况都不会碰到,在此作为题目也不是为了“耍酷”和制造难度,通过“试探”csharp在语法的极端情况下的处理,了解词法分析和编译器的一些功能,加深对基础语法的印象。事先被问住才能更好的获取所需知识。(绝大部分题目是我在实验中用来测试自己的解释器时使用。)



3.“答对上面的题”与“技术高低”没有直接联系,相反答不对也不说明就对csharp不熟。不管是不是对运算符的优先级和表达式的执行顺序上了如指掌,不管对csharp有多熟悉,在任何情况下,都强烈建议在程序里加更多的括号。同时避免写出晦涩或理解上可能产生二义性的代码,例如(a = b = c)。



4.这些都是关于Csharp的基础语法,最新版本的Csharp又添加如构造赋值语句delegate,new XXX { Property = Value },lambda表达式,属性attribute构造表达式等,要定义完全兼容csharp的语法是相当困难、相当庞大的工程。但也许随着继续实验,以后还会补充新题…



-----

The "BSD licence"?
愿 Engine 归于沉寂,Timer 停止运动,Message Queue 不再流淌,Data Source 为我掌握

4 个回复

倒序浏览
Valentine
版主   /  发表于:2010-3-23 11:45:00
沙发
A: Console.WriteLine(-p--);

B: Console.WriteLine(-(p)--);

刚才试了一下,A和B的输出并不一样啊,前者是-2后者是-1:(
回复 使用道具 举报
Colin
社区贡献组   /  发表于:2010-3-29 18:02:00
板凳
为什么呢?
回复 使用道具 举报
gerry
论坛元老   /  发表于:2010-4-26 13:19:00
地板
为什么呢
回复 使用道具 举报
Carl
版主   /  发表于:2010-4-26 13:39:00
5#
根据经验,诡异的问题都是因为弱智的错误引起的……
愿 Engine 归于沉寂,Timer 停止运动,Message Queue 不再流淌,Data Source 为我掌握
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 立即注册
返回顶部