C#专题之C#的表达式

作者:思多雅[天行健] 发布时间:2008.12.024
上一章节,我们一起学习了C#的转换,这一章,我们来一起学习C#的表达式。

    一个表达式就是指定一个计算的一系列一个操作符和操作数。这一章,我们将来一起学习语法、求值的顺序和表达式的意义。

一、表达式分类
一个表达式可以归类为下面的一种:
一个数值。每个数值都有相应的类型。
一个变量。每个变量都有相关的类型,也就是变量声明的类型。
一个名称空间。通过这种归类的一个表达式只能表现为一个成员访问的左手部分。在任何其它上下文中,一个表达式被分类为一个名称空间会造成错误。
一种类型。通过这种归类的一个表达式只能表现为一个成员访问的左手部分。在任何其它上下文中,一个表达式被分类为一个类型会造成错误。
一个方法组,这是一系列由成员查找产生的重载方法。一个方法组可以有相关的实例表达式。
   当调用一个实例方法时,对实例表达式的求值的结果就变成用this修饰的实例。一个方法组只允许用于一个调用表达式或一个创建代表表达式中。在任何其它上下文中,一个表达式被分类为一个方法组会造成错误。
一个属性访问,每个属性访问都有相应的类型,也就是属性的类型。此外,一个属性访问也可以有一个相关的实例表达式。当一个实例属性访问的访问程序(get 或set 模块)被调用的时候,对实例表达式的求值就变为用this 修饰的实例。
一个事件访问。每个事件访问都有相应的类型,也就是事件的类型。此外,一个事件访问也可以有一个相关的实例表达式。一个事件访问可能被表现为+=和-=操作符的操作数的左手部分。在任何其它上下文中,一个表达式被分类为一个事件访问会造成错误。
一个索引访问。每个索引访问都有相应的类型,也就是索引的类型。此外,一个索引访问也可以有一个相关的实例表达式和一个相关的参数列表。当一个索引访问的访问程序 (get 或set 模块)被调用的时候,对实例表达式的求值就变为用this 修饰的参数列表空。这发生在表达式是一个返回类型为void 的方法的调用时。一个表达式被分类为空只在语句表达式的文字中有效。
一个表达式的最后结果不会是一个名称空间、类型、方法组或是事件访问。而且,如前面所述,这些表达式的分类只是一个中间结构,只允许在某些地方存在。
一个属性访问或索引访问总是在执行一个对get 访问符或set 访问符时作为数值被重分类。特殊的访问符由属性或者索引访问的上下文觉得:如果访问的目的是赋值,set 访问符就被调用来赋新的数值。否则,get 访问符被调用来获得当前的数值。

1.1 表达式的数值
大多数涉及到一个表达式的结构基本上都需要表达式给出一个数值。在那样的情况下,如果实际表达式给出一个名称空间、一个类型、一个方法组或空,就会产生错误。然而,如果表达式表示一个属性访问、一个索引访问或是一个变量,属性、索引或变量的值就会被隐含地替代:
*  变量的数值就是当前存储在由变量指定的存储位置的数值。一个变量必须在他的数值可以被获得前明确赋值(§错误!未找到引用源。),否则就会产生一个编译时的错误。
*  属性访问表达式的数值通过调用属性的get 访问符来获得。如果属性没有get 访问符,就会产生错误。否则,就会执行一个函数成员的调用,而且调用的结果变为属性访问表达式的数值。
*  索引访问表达式的数值通过调用索引的get 访问符来获得。如果索引没有get 访问符,就会产生错误。否则,就会执行一个与属性访问表达式相关的参数列表的函数成员的调用,而且调用的结果变为属性访问表达式的数值。

二、操作符
表达式由操作数和操作符来构造。表达式的操作符指示出对操作数采取哪种操作。操作符的例子包括+、-、*、/和new。操作数的例子包括文字、域、局部变量和表达式。

这里共有三种类型的操作符:
一元操作符。一元操作符有一个操作数并且或是使用前缀符号(例如-x )或是使用后缀符号(例如x++ )。
二元操作符。二元操作符有两个操作数并且使用中间符号(例如x+y)。
三元操作符。只有一个三元操作符?:。三元操作符有三个操作数并且使用中间符号 (c?x:y)。
表达式中操作符求值的顺序由操作符的优先级和结合顺序决定。
一些操作符可以被重载。操作符重载允许指定用户定义操作符的执行,这里一个或多个操作数为用户定义的类或结构类型。

2.1 操作符优先级和结合顺序
当一个表达式包含多个操作符,操作符的优先级控制单个操作符求值的顺序。例如,表达式x+y *z 被求值为x+ (y*z),因为*操作符比+操作符有更高的优先级。操作符的优先级是由与它相关的语法创建确定的。例如,由一个乘法表达式序列组成的加法表达式被+或-分开,这时就会给+或-比*、/和%操作符低一些的优先级。
下面的表中从高到低总结了所有操作符的优先级顺序:
种类             操作符
初级的            (x)  x.y  f(x)  a[x]  x++ x--  new
                         typeof  sizeof  checked  unchecked
一元的               +  -  ! ~  ++x  --x  (T)x
乘法的               *  /  %
加法的               +  -
移位                <<  >>
关系的               <  >  <=  >=  is
相等的               ==  !=
逻辑与               &
逻辑异或              ^
逻辑或               |
条件与               &&
条件或               ||
条件的               ?:
赋值                =  *=  /= %=  +=  -=  <<=  >>=  &= ^=  |=

当一个操作数在两个有相同优先级的操作符中间时,操作符的结合顺序控制操作怎样实现:
*  除了赋值操作符,所有二元操作符都是左结合的,意思就是操作从左向右完成。例如,x+y+z被求值为(x+y)+z。
*  赋值操作符和条件操作符都是右结合的,意思就是操作从右向左完成。例如,x=y=z被求值为x= (y=z)。
优先级和结合顺序可以通过使用括号来控制。例如x+y *z先把y和z相乘,然后再把结果和x 相加,但是(x+y) *z先把x和y 相加,然后在把结果和z相乘。

2.2 操作符重载
所有一元和二元操作符都有预定义的执行方式,在任何表达式中都会自动实行。除了预定义的执行方式外,用户定义的执行方式可以通过包括类和结构(§10.9)中的操作符声明来引入。用户定义的操作符执行通常比预定义操作符声明的优先级高:只有当没有可使用的用户定义的操作符执行存在时才会考虑预定义的操作符执行。
可重载一元操作符有:
     +   -  !   ~   ++   --  true   false
可重载二元操作符有:
   +   -  *   /   %   &   |  ^   <<   >>   ==  !=   >   <   >=  <=
只有上面列出的操作符可以被重载。另外,不能重载成员访问、方法调用或=、&&、||、?:、new、typeof、sizeof和is操作符。
当一个二元操作符被重载,相应的赋值操作符也被隐式地重载。例如,一个操作符*的重载同时也是操作符*=的重载。这在§错误!未找到引用源。中将被说明。注意赋值操作符自己(=)不能被重载。一个赋值通常把一个数值的位方式的复制放到变量里。
某些操作,如(T)x,通过提供用户定义的转换(§错误!未找到引用源。)重载。
元素访问,例如a[x],也被看做是一个重载操作符。用户定义的索引通过索引来支持。
在表达式中,使用操作符符号来实行操作符,而在声明中,用功能符号来实现操作符。下面的表中介绍了操作符和一元、二元操作符的功能符号之间的关系。在第一行,表示任何可重载一元操作符。在第二行,op 表示二元操作符++和— 。在第三行,op 表示任何可重载操作符。
   操作符号            功能符号
   op x            操作符op(x)
   xop             操作符op(x)
   xop y           操作符op(x,y)
用户定义的操作符声明通常需要至少一个参数是包含操作声明的类或类型。这样,就不允许用户定义的操作符与预定义的操作符有相同的签名。
用户定义的操作符声明不能修改操作符的语法、优先级或结合顺序。例如,*操作符通常是一个二元操作符,通常有中指出的优先级等级,并且通常是左结合。
虽然用户定义的操作符可以实现任何它希望的计算,与那些直觉上希望不同的产生结果的执行是很让人失望的。例如,操作符==的执行应该比较两个操作数并且返回相应的结果。
描述的独立操作符指出了操作符的预定义执行和使用在每个操作符
上的任何附加规则。这个描述采用了一元操作符重载协议、二元操作符重载协议和数字升级 (numeric promot ion )的形式,对它们的定义会在后面的章节找到。

2.3 一元操作符重载分析
一个有op x 或xop 形式的操作,按照下面过程进行,这里op 是一个可重载的一元操作符,而x 是类型X 的一个表达式:。
由x 提供的为op(x)操作候选的用户定义操作符集是使用§7.2.5中的规则决定的。
如果候选的用户定义操作符集不是空的,那么这个就会称为操作的候选操作符集。否则,预定义的一元操作符op 就成为候选操作符集。所给的操作符的预定义执行在祥述操作符时会介绍。
重载分析规则被应用于候选操作符集,来选择关于参数列表(x)的最好的操作符,而这个操作符变为重载分析结过程的结果。如果重载分析在选择一个最好的操作符时失败了,就会产生一个错误。

2.4 二元操作符重载分析
一个有xop y 形式的二元操作按下面进行:(这里op 是一个可重载的二元操作符,x 是一个类型X 的表达式,y 是一个类型Y 的表达式)
为了操作op(x,y)由X 和Y 提供的候选用户定义操作符集是确定的。由S提供的候选操作符和由Y 提供的候选操作符联合组成了候选操作符集是通过使用§7.2.5的规则确定的。如果X 和Y 是相同的类型,或者如果X 和Y 是从一个公共基础类型派生的,那么共享的候选操作符只在联合集中出现一次。
如果候选用户操作符集不是空的,那么就会变成操作的候选操作符集。否则,预定义的一元操作符op 就成为候选操作符集。所给的操作符的预定义执行在祥述操作符时会介绍。
重载分析规则被应用于候选操作符集,来选择关于参数列表(x,y)的最好的操作符,而这个操 作符变为重载分析结过程的结果。如果重载分析在选择一个最好的操作符时失败了,就会产生一个错误。

2.5 候选用户定义操作符
给出一个类型T 和一个操作符op(A),这里op 是一个可重载操作符而A 是一个参数列表,这个为了操作符op(A)由T 提供的候选用户定义操作符集按下面确定:
*  对于T 中声明的所有操作符op ,如果对于参数列表A 来说至少有一个操作符是可用的,那么候选操作符集就会包括所有T 中声明的可用操作符op 。
*  否则,如果T 是object,候选操作符即为空。
*  否则,由T 提供的候选操作符集就是由T 的直接基类提供的候选操作符集。

2.6 数字升级
数字升级由自动进行某种预定义的一元和二元数字操作符的操作数的转换组成。数字升级不是独立的机制,而是一种使用重载分析来预定义操作符的效果。数字升级不会影响用户定义的操作符的求值,虽然用户定义的操作符可以被执行类展示相似的效果。
作为一个数字升级的例子,考虑二元操作符*的预定义执行:
      int operator *(int x, int y);
      uint operator *(uint x, uint y);
      long operator *(long x, long y);
      ulong operator *(ulong x, ulong y);
      float operator *(float x, float y);
      double operator *(double x, double y);
      decimal operator *(decimal x, decimal y);
当重载分析规则被应用于这个操作符集的时候,作用是选择第一个操作符,为此存在操作数类型隐式的转换。例如,对于操作b*s ,这里b 是byte 而s 是short,重载分析选择操作符*(int,int)作为最好的操作符。这样,效果为b和s被转换为int而结果的类型是int。与此类似,对于操作i*d ,这里i 是一个int 而d 是一个double,重载分析选择操作符*(double,double)作为最好的操作符。
2.6.1 一元数字升级
一元数字升级在进行预定义的+、-和~一元操作符操作时发生。一元数字升级简单地由类型sbyte、byte、short、ushort或char的操作数转换为int类型组成。另外,对于一元操作符,一元数字升级把uint类型操作数转换为long类型。

2.6.2 二元数字升级
二元数字升级在操作符为预定义的+、–、*、/、%、&、|、^、==、!=、>、<、>=和<=二元操作符时发生。二元数字升级隐式地把所有操作数转换为一个统一的类型,而有些不相关的操作符也变为操作结果的类型。二元数字升级有下面应用组成,按顺序列在下面:
*  如果操作数是十进制类型,其它操作数就被转换为十进制类型,而如果其它的操作数为float 或 double 类型,就会产生一个错误。
另外,如果操作数是double 类型,其它操作数就被转换为double 类型。
另外,如果操作数是float 类型,其它操作数就被转换为float 类型。
如果操作数是ulong 类型,其它操作数就被转换为ulong 类型,而如果其它的操作数为sbyte、
short、int或long类型,就会产生一个错误。
另外,如果操作数是long 类型,其它操作数就被转换为long 类型。
如果操作数是uint 类型,其它的操作数为sbyte、short或int类型,所有的操作数都会被转换为long。
另外,如果操作数是uint 类型,其它操作数就被转换为uint 类型。
另外,所有操作数都被转换为int 类型。
注意,第一个规则不接受任何掺杂了double 和float 类型的十进制类型。这个规则是根据在十进制类型与double 和float 类型间没有隐式转换来确定的。

也要注意,当其它操作数是有符号整数类型时,不允许操作数为ulong 类型。原因是没有一个整数类型可以同时满足ulong 和有符号整数类型的全部取值范围。
在上面的所有情况中,一个表达式可以被用来显式地把一个操作数转换为另一种和其它操作数一致的类型。
比如在下面的这个例子:
     decimal AddPercent(decimal x, double percent) {
         return x * (1.0 + percent / 100.0);
      }
会产生一个编译时错误,因为一个十进制数不能乘以double。这个操作可以通过显式地把第二个操作数转换为十进制类解决:

      decimal AddPercent(decimal x, double percent) {
         return x * (decimal)(1.0 + percent / 100.0);
      }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值