3.5 运算符
C#的运算符用于指定在表达式中执行操作的符号。表达式是由运算符和运算对象按照一定的规则组合起来的运算式。
3.5.1 C#支持的运算符
与C语言一样,如果按照运算符所作用的操作数个数来分,C#语言的运算符可以分为以下几种类型。
● 一元运算符:一元运算符作用于一个操作数,如-X、++X、X--等。
● 二元运算符:二元运算符对两个操作数进行运算,如x+y。
● 三元运算符:三元运算符只有一个,即x? y:z。
C#语言运算符的详细分类及运算符从高到低的优先级顺序,如表3-8所示。
表3-8 运算符表
1. 二元运算符
二元运算符包含有两个操作数。
1)算术运算符
C#中提供的算术运算符有下面5种。
● +,加法运算符。
● -,减法运算符。
● *,乘法运算符。
● /,除法运算符。
● %,取余运算符。
(1)加法运算符。
加法运算是形如“x+y”表达式的操作。其中的操作数被转换成所选运算符的参数类型,结果类型是该运算符的返回类型。加/减法运算符可以用于整型、浮点型、枚举类型和字符串类型等。例如:
3+5 //结果为8 "a"+"b" //结果为"ab"
对于数字和枚举类型,预定义的加法运算符会计算两个操作数的和。
对于整数加法运算,在“cheked”环境中,如果其和在结果类型的数值范围之外,将产生OverflowException异常(上溢出异常)。在“unchecked”环境中,不报告有溢出,结果值的有效高位将被丢弃。
对于字符串连接加法,当一个或两个操作数为string类型时,加法运算符会进行字符串连接。如果字符串连接的一个操作数为null,则用一个空字符串代替。另外,通过调用从类型object继承来的方法ToString(),任何非字符串参数都将被转换成字符串表示法。如果ToString返回null,则用一个空字符串代替。
(2)减法运算符。
减法运算是形如“x-y”表达式的操作。其中的操作数被转换成所选运算符的参数类型,结果的类型是该运算符的返回类型。例如:
5-2 //结果为3
在“checked”环境中,如果差在结果类型的数值范围之外,将产生OverflowException异常(上溢出异常)。在“unchecked”环境中,不报告有溢出,结果值的有效高位将被舍弃。
(3)乘法运算符。
乘法运算符用于执行整数和实数的乘法运算。
5*2 //结果为10
(4)除法运算符。
在除法运算过程中,默认的返回值类型与精度最大的操作数类型相同。比如:
7/2 //结果是3 7/2.0 //结果为3.5
对小数除法来说,如果操作数的值为零,则产生DivideByZeroException异常(除零异常)。如果结果值太小而不能用decimal格式表示,则结果为零。
如果两个整数类型的变量相除而又不能整除,那么返回的结果是绝对值不大于相除值的最大整数。
(5)取余运算符。
“%”运算符用来求除法的余数。C#的取余运算既适用于整数类型,也同样适用于十进制类型和浮点型。比如:
8%3 //结果为2 8%1.5 //结果为0.5
【例3-9】 下面用一个示例来说明算术运算符的使用。
新建控制台程序项目“ex_ysf1”,具体代码如下:
static void Main(string[] args) {int a = 5+4;//a=9 int b = a*2; //b=18 int c = b/4; //c=4 int d = b-c; //d=14 int e = d%4;//f=2 Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(c); Console.WriteLine(d); Console.WriteLine(e); Console.ReadLine(); }
运行效果如图3-23所示。
图3-23 运行效果
2)关系运算符
关系运算符用于判断运算值之间的关系。它可以对字符类型、数值类型、布尔类型和引用类型进行比较,运算结果为布尔值,也就是说只能为true或false。
(1)比较运算。
C#中定义的比较操作符有以下6种。
● = = 等于。
● != 不等于。
● < 小于。
● > 大于。
● <= 小于或等于。
● >= 大于或等于。
例如:
8<3 //结果为false
(2)is运算符。
Is运算符用于动态地检查表达式是否为指定类型。使用格式为“e is T”,其中e是一个表达式,T是一个类型,该式判断e是否为T类型,返回值是一个布尔值。
【例3-10】 is运算符示例。
新建控制台程序项目“ex_is”,具体代码如下:
using System; class Test { public static void Main(string[] args) {Console.WriteLine(1 is int); Console.WriteLine(1 is float); Console.WriteLine(1.0f is float); Console.WriteLine(1.0d is double); Console.ReadLine(); } }
运行效果如图3-24所示。
图3-24 运行效果
(3)逻辑运算符。
C#语言主要提供了下面3种逻辑运算符。
● &&(逻辑与):指两个条件必须同时满足才能为true。
● ||(逻辑或):指两个条件只要有一个条件满足就可以为true。
● !(逻辑非):指将当前逻辑值取反。
【例3-11】 &&运算符示例。
新建控制台程序项目“ex_logic_add”,具体代码如下:
class Program { static bool print1() { Console.WriteLine("返回真值。"); return true; } static bool print2() { Console.WriteLine("返回假值。"); return false; } static void Main(string[] args) { Console.WriteLine("相与的结果是:"); Console.WriteLine(print1() && print2()); Console.ReadLine(); } }
运行结果如图3-25所示。
图3-25 运行效果
(4)位运算。
在计算机中,信息都是以二进制形式保存的,位运算就是运用位运算符对数据按二进制位进行运算。C#语言中的位操作符有以下6种。
● &(与)。
● |(或)。
● ^(异或)。
● ~(取补)。
● <<(左移)。
● >>(右移)。
其中,取补只有一个操作数,而其他的位运算符都有两个操作数。这些运算都不会产生溢出。位操作数为整型或可以转换为整型的任何其他类型。
① 与运算。
操作数按照二进制进行与运算,运算规则如下:
0&0=0;
0&1=0;
1&0=0;
1&1=1。
由此可见,“与”运算中,只有两个位均为1 时,与运算的结果才为1;在其他情况下,与运算结果均为0。
② 或运算。
或运算的运算规则如下所示:
0|0=0;
0|1=1;
1|0=1;
1|1=1。
也就是说,除了两个均为“0”,或运算结果为0外,其他情况下或运算结果均为1。
③ 异或运算。
操作数按照二进制位进行异或运算,其运算规则如下:
0^0=0;
0^1=1;
1^0=1;
1^1=0。
上面规则表明,当两个位相同时,异或运算结果为0,否则为1。
④ 取补运算。
取补运算对操作数的每一位取补。
⑤ 移位运算。
移位运算即指左移或右移操作。左移运算将操作数按位左移,高位被丢弃,低位则补0。
右移运算时,操作数x是int或long型时,x的低位被丢弃,其他各位顺序依次右移;如果x是非负数,则最高位设为零;如果x是负数,则最高位设为1。而当x的类型为uint或ulong型时,x的低位将被丢弃,其他各位顺序依次右移,高位设为0。
2. 三元运算符
三元运算符“?:”有时也称为条件运算符。
条件表达式b?x:y,先计算条件b,然后进行判断。如果b的值为真,那么计算x的值,运算结果为x的值;否则,计算y,运算结果为y的值。条件运算符是向右关联的,也就是说,它从左向右分组计算。
该运算符的每一个操作数必须是一个可以隐式转换成布尔型的表达式或者执行操作符true类型的表达式,如果上述这两个条件都不满足,则在运行时会发生错误。
该运算符的第二个和第三个操作数控制了条件表达式的类型,如果用x和y分别表示第二个和第三个操作数类型,那么:
● 如果x和y为同一类型,则该类型即是条件表达式的类型;
● 如果从x到y存在一个隐式转换,但不存在y到x的转换,那么y是条件表达式的类型;
● 如果从y到x存在一个隐式转换,但不存在x到y的转换,那么x是条件表达式的类型;
● 没有定义任何表达式类型,发生编译时错误。
恰当地使用三元运算符,可以使程序非常简洁。它特别适合于给被调用的函数提供两个参数中的一个。使用它可以把Boolean值转换为字符串值true或false。它也很适合于显示正确的单数形式或复数形式。
【例3-12】 三元运算符示例。
新建控制台程序项目“ex_logic_add”,具体代码如下:
int x = 1; string s = x.ToString() + " "; s += (x == 1 ? "man" : "men"); Console.WriteLine(s); Console.ReadLine();
运行结果如图3-26所示。
图3-26 运行效果
3. 一元运算符
一元运算符只有一个操作数,并且或使用前缀符号(如x-),或使用后缀符号(如x++)。
1)自增和自减运算符
自增运算符“++”对变量的值加1,而自减运算符“--”对变量值减1。它们适用于sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal和任何enum类型。例如,对一个值为9的整数变量x进行x++操作后,其值变为10。
自增和自减运算符又有前后缀之分。对于前缀运算符,所应遵循的原则是“先增减,后使用”,而后缀运算符则正好相反,“先使用,后增减”。
【例3-13】 ++增量运算符示例。
新建控制台程序项目“ex_add”,具体代码如下:
int x = 5; if (++x == 6) { Console.WriteLine("This will execute"); } if (x++ == 7) { Console.WriteLine("This won't"); } Console.ReadLine();
运行结果如图3-27所示。
图3-27 运行效果
2)new运算符
new运算符被用来创建类型的新实例。它有以下3种形式。
(1)对象创建表达式被用来创建类类型和数值类型的新实例。
(2)数组创建表达式被用来创建数组类型的新实例。
(3)代表创建表达式被用来创建代表类型的新实例。
下面的3个式子分别表示创建了一个对象、一个数组和一个代表实例。
class A{}; A a = new A; int[] int_arr=new int[10]; delegate double Func(int x); Func f = new Func(5);
3)typeof运算符
typeof运算符用于获取系统原型对象的类型。通常用于获得指定类型在system名字空间中定义的类型名字,下面给出了一个典型的示例。
【例3-14】 typeof运算符示例。
新建控制台程序项目“ex_typeof”,具体代码如下:
static void Main(string[] args) { Console.WriteLine(typeof(int)); Console.WriteLine(typeof(System.Int32)); Console.WriteLine(typeof(string)); Console.WriteLine(typeof(double[])); Console.ReadLine(); }
运行结果如图3-28所示。
图3-28 运行效果
由输出可知int和System.int32是同一类型。
4)checked和unchecked运算符
typeof运算符用于获得指定类型在system名字空间中定义的类型名字,例如:
byte b = 255; b++; Console.WriteLine(b.ToString());
byte数据类型只能包含0~255的数,所以b值的增量会导致溢出。CLR如何处理这个溢出取决于许多方面,包括编译器选项,所以无论溢出有什么样的风险,都需要用某种方式确保得到我们希望的结果。
为此,C#提供了checked和unchecked运算符。如果把一个代码块标记为checked,CLR就会执行溢出检查,如果发生溢出,就抛出异常。如果改变代码,使之包含checked运算符:
byte b = 255; checked { b++; } Console.WriteLine(b.ToString());
则运行这段代码,就会得到一个错误信息:
Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow. at Wrox.ProCSharp.Basics.OverflowTest.Main(String[] args)
注意:用checked编译器选项进行编译,就可以检查程序中所有未标记代码中的溢出。
如果要禁止溢出检查,可以把代码标记为unchecked:
byte b = 255; unchecked { b++; } Console.WriteLine(b.ToString());
在本例中,不会抛出异常,但会丢失数据,因为byte数据类型不能包含256,溢出的位会被丢掉,所以b变量得到的值是0。
3.5.2 运算符的优先级
当一个表达式包含多个运算符时,表达式的值就要由运算符的优先级来决定了。当一个操作数出现在两个相同优先级的运算符之间时,运算符按照出现的顺序由左至右执行。如果无法确定运算符的有效顺序,则可以在编程时采用括号进行指定,从而保证运算顺序的正确。运算符的优先级可以参考运算符表。