【原创】自认为对C#语法了如指掌的你,来试试这些题目吧?
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"? A: Console.WriteLine(-p--);
B: Console.WriteLine(-(p)--);
刚才试了一下,A和B的输出并不一样啊,前者是-2后者是-1:( 为什么呢? :D为什么呢 根据经验,诡异的问题都是因为弱智的错误引起的……
页:
[1]