C#中的运算符
关于C#的基本运算符其实不需要介绍太多,但是这里还是有几个值得我们注意一下的运算符,多的先不说,先贴上C#中所有的运算符
其中的sizeof和寻址运算符我们不需要了解但多,因为CLR帮我们做了,.netframerwork是类型安全的。
下面介绍几个我们需要注意的运算符。
增量运算符和减量运算符
++,--
我们来看一个例子,X++和++X的区别
X++和++X可以用做表达式,那么什么是表达式,表达式就是可以计算且结果为单个值、对象、方法或命名空间的代码片段,也可以用作代码块。当用作代码块的时候,就是他们单独占一行的时候,X++跟++X的效果是一样的,都是X=X+1,但是如果他们在表达式中时所表示的意义就完全不同。先看一个例子。
static void Main(string[] args) { int op = 1; int op1 = ++op; int op2 = op++; Console.WriteLine(op1); Console.WriteLine(op1); Console.Read(); }
很简单的一个例子,我们可以看到输出是2,2.那么为会造成这个结果呢。原因是当增量运算符如果用在表达式中。++X会先计算X=X+1,然后再把X用作表达式的结果。而X++呢,会直接把X用作表达式的结果。
三目运算符(条件运算符号)
三目运算符其实就是if-else的简写形式。它带有3个操作数。它所想表达的意思是。计算一个条件,如果条件为真返回一个值,不为真则返回另外一个值。看一个代码就知道了
int iSex = 0; string sSex ="You Sex is:"; sSex+=iSex == 1 ? "Man" : "Men"; Console.WriteLine(sSex); Console.Read();
checked和unchecked
checked能检查空间溢出。就是超出数据类型的存储空间,比如byte只能包含0-255的数,如果拿它存储大于255的数就会发生异常。如果把一个代码块标记为checked。CLR就会执行溢出检查。unchecked正好相反。unchecked不会抛出异常,但是会丢失数据,会按位丢失数据。
先看代码
byte b = 255; b++; Console.WriteLine(b.ToString()); Console.Read();
输出为0
byte b = 255; b++; b++; Console.WriteLine(b.ToString()); Console.Read();
输出为1,所以是按位丢失。
这里我们就可以使用使用checked执行溢出检查。
try { checked { byte b = 255; b++; b++; Console.WriteLine(b.ToString()); } } catch (Exception e) { Console.WriteLine(e.Message); } Console.Read();
异常信息为:算数运算导致溢出
try { checked { unchecked { byte b = 255; b++; b++; Console.WriteLine(b.ToString()); } } } catch (Exception e) { Console.WriteLine(e.Message); } Console.Read();
然后我们又可以把代码标记为unchecked,这样就不会执行检查了。这里需要注意一点的是。unchecked是默认值,如果要使用unckecked,要在checked代码块里标记。因为默认执行的代码就是unckecked。
我们也可以这样当做运算符用。
try { int i = 2147483647; int y =unchecked(i*2); Console.WriteLine(y); int z = checked(i * 2); Console.WriteLine(z); } catch (Exception e) { Console.WriteLine(e.Message); } Console.Read();
Is和as
我个人觉得is和as运算符是很重要的。
is:检查对象是否与特定的对象兼容
as:执行引用类型之间的转换,如果要转换的内容与指定的类型兼容,转换就会成功,如果不兼容,就会返回null
对象兼容:兼容表示对象是该类型或者派生于该类型。
所以is通俗的说,就是判断对象是属于该类型或者派生于该类,as就是is的后续操作,如果属于就转换,不属于就返回null
还是来看看代码吧。
string s = "this is a string"; if (s is object) { object o = s as object; Console.WriteLine(o.ToString()); } else { Console.WriteLine("string is not object"); } Console.Read();
sizeoof运算符
sizeof运算符可以确定堆栈中值类型需要的长度
unsafe { Console.WriteLine(sizeof(int)); }
这里只是举个例子,当然我这个代码没有运行。
typeof我会在反射里面说。
我们还需要了解运算符的优先级
运算符的重载
什么是运算符重载,运算符重载实际上就是让我们的对象能够理解我们的定义的符号的逻辑.它使我们的对象能够理解重载的运算符。比如我有一个类是person.按照一般的情况。person+person.在我们的程序中是不能理解的。但是我们可以通过重载运算符让程序理解这样的意图。让我们的对象懂得如何响应运算符。
重载运算符不仅仅限于算数运算符,还需要考虑比较运算符,比如==。==在比较引用类型的时候会比较对象的引用,比较引用是不是是否指向同一个地址。而对于比较string则是比较字符串的内容是不是相同。所以,string和一般的引用类型对==的响应是不同的。这样一来我们也可以重载==让我们的对象也有不同的行为。
我们先看看如何重载运算符,让我们的对象具有不同的行为。
首先定义类
public class Person { private int _personId; public int PersonId { get { return _personId; } set { _personId = value; } } private string _firstName; public string FirstName { get { return _firstName; } set { _firstName = value; } } private string _lastName; public string LastName { get { return _lastName; } set { _lastName = value; } } public Person() { } public Person(int personId, string firstName, string lastName) { this._personId = personId; this._firstName = firstName; this._lastName = lastName; } public Person(Person p) { this._personId = p.PersonId; this._firstName = p.FirstName; this._lastName = p.LastName; } public override string ToString() { return string.Format("PersonId:{0},FirstName:{1},LastName:{2}",PersonId,FirstName,LastName); } public static Person operator + (Person person1,Person person2) { Person result = new Person(); result.PersonId = person1.PersonId + person2.PersonId; result.FirstName = person1.FirstName + person2.FirstName; result.LastName = person1.LastName + person2.LastName; return result; } }
很简单的一个实体类,然后里面重载了运算符。我们再看看调用代码
Person p1 = new Person(1,"Edrick","Liu"); Person p2 = new Person(2,"Meci","Luo"); Person p = p1 + p2; Console.WriteLine(p.ToString()); Console.Read();
这样我们的对象就能理解+号运算符的行为了。那么在这里编译器是怎么做的呢,首先我们在调用p1+p2的时候,编译器会查找最匹配的+号重载。就是我们定义的。然后按照我们重载的逻辑,操作对象。那如果没有重载+号运算符,然后又对我们的对象使用+号呢?编译器就会报错,不能对操作数使用+号,也就是找不到合适的重载。
所以这里有两点需要注意
1,重载运算符必须是public和static。这表示他们跟类型相关联而不是与实例相关联。
2,如果重载了+,那么+=也会自动被重载。
下面我们来重载一下==运算符,还是在Person这个实体类里面
public static bool operator ==(Person p1, Person p2) { bool result = false; if (p1.PersonId == p2.PersonId && p1.FirstName == p2.FirstName && p1.LastName == p2.LastName) { result = true; } return result; }
编译,然后出错,错误是如果重载了==就必须显示重载!=,其实这里不只是==和!=,<和>,>=和<=也必须显示成对重载。
我们在看看完整的代码
public static bool operator ==(Person p1, Person p2) { bool result = false; if (p1.PersonId == p2.PersonId && p1.FirstName == p2.FirstName && p1.LastName == p2.LastName) { result = true; } return result; } public static bool operator !=(Person p1, Person p2) { bool result = false; if (p1.PersonId != p2.PersonId || p1.FirstName != p2.FirstName || p1.LastName != p2.LastName) { result = true; } return result; }
调用代码
Person p1 = new Person(1,"Edrick","Liu"); Person p2 = new Person(2,"Meci","Luo"); Person p3 = new Person(2,"Meci","Luo"); if (p1 == p2) { Console.WriteLine("p1==p2"); } else { Console.WriteLine("p1!=p2"); } if (p2 == p3) { Console.WriteLine("p2==p3"); } else { Console.WriteLine("p2!=p3"); } Console.Read();
实际上我们还应该注意一点,重载了==和!=运算符,还必须重载System.Object的Equals()方法和GetHasCode()方法,原因是Equals方法执行与==运算符相同的逻辑。而他们有是通过GetHasCode取得HasCode。关于重载Equals方法,可以参考我们的Equals,ReferenceEquals和==的区别一文。比较运算符需要特别注意的就是运算符必须成对的重载。
最后给一张可以重载的运算符和重载他们的一些限制
虽然运算符可以重载,但是有些场景是不适合的,比如不需要计算的类或者方法。比如时间,我们用时间*时间就没什么意义了。