C#⾼编-运算符和类型强制转换
摘要
C#中的运算符
处理引⽤类型和值类型时相等的含义
基本数据类型之间的数据转换
使⽤装箱技术把值类型转换为引⽤类型
通过类型强制转换在引⽤类型之间转换
重载标准的运算符以⽀持⾃定义类型
给⾃定义类型添加类型强制转换运算符
1.运算符
算数运算符:+ - * / %
逻辑运算符:& | ^ ~ && || !
字符串连接运算符:+
增量和减量运算符:++ --
移位运算符:<< >>
⽐较运算符:== != <> <= >=
赋值运算符:= += -= *= /= %= &= |= ^= <<= >>=
成员访问运算符:.
索引运算符:[]
类型转换运算符:()
条件运算符(三元运算符):?:
委托连接和删除运算符:+ -
对象创建运算符:new
类型信息运算符:sizeof is typeof as
溢出异常控制运算符:checked unchecked
间接寻址运算符:[]
名称空间别名限定符:::
空合并运算符:??
其中:sizeof、*、->、&:只能⽤于不安全的代码
2.checked和unchecked运算符
把⼀个代码块标记为checked,CLR就会执⾏溢出检查,如果发⽣溢出,就抛出OverflowException异常。如,byte b = 255;
checked
{
b++;
}
Console.WriteLine(b.ToString());
注意:⽤/checked编译器选项进⾏编译,就可以检查程序中所有未标记代码中的溢出。
相反,要禁⽌溢出检查,则⽤unchecked,不会抛出异常,但会丢失数据,溢出的位会别丢弃。unchecked是默认⾏为,当需要在标记为checked的代码块中增加⼏⾏未检查的代码时才会使⽤。
3.is运算符
可以检查对象是否与特定的类型兼容,“兼容”表⽰对象或者该类型,或者派⽣⾃该类型。如,
int i = 10;
if(i is object)
{
Console.WriteLine("i is an object");
}
4.as运算符
⽤于执⾏引⽤类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功;如果类型不兼容,as运算符就会返回null值。
5.sizeof运算符
确定栈中值类型需要的长度(单位是字节)
对于复杂类型(和⾮基元类型)使⽤sizeof运算符,就需要把safe放在unsafe块中,如,
unsafe
{
Console.WriteLine(sizeof(Customer));
}
6.可空类型和运算符
通常可控类型与⼀元或⼆元运算符⼀起使⽤时,如果其中⼀个操作数或两个操作数都是null,其结果就是null。如
int? a = null;
int? b = a+4;//b = null
在⽐较可空类型时,只要有⼀个操作数是null,⽐较结果就是false。即不能因为⼀个条件是false,就认为条件的对⽴⾯是true,这在使⽤⾮可空类型的程序中很常见。
7.空合并运算符
提供了⼀种快捷⽅式,可以在处理可空类型和引⽤类型时表⽰null可能的值。
第⼀个数必须是⼀个可空类型或引⽤类型;第⼆个数必须与第⼀个的类型相同,或者可以隐含地转换,
如果第⼀个操作不是null,整个表达式就等于第⼀个操作的值。
如果第⼀个操作数是null,整个表达式就等于第⼆个操作数的值。
int? a = null;
int b;
b = a ?? 10;//b has the value 10
a = 3;
b = a ?? 10;//b has the value 3
8.类型转换
隐式转换:可以从较⼩的整数类型隐式转换成较⼤的整数类型,反之不可以。也可以在整数和浮点数之间转换,但会丢失4个字节的数据。
⽆符号的变量值可以转换为有符号的变量,只要⼤⼩在有符号的变量的范围之内即可。
可空类型转换为其它可空类型,如int?转换为long?,或者⾮可空类型转换为可空类型,遵循⾮可空类型的转换规则。
byte value1 = 10;
byte value2 = 20;
long value;
total = value1 + value2;
Console.WriteLine(total);
显式转换:
以下是部分不能隐式转换类型的场合,需要进⾏强制转换,如int i = (int)val;
int>>short、int>>uint、uint>>int、float>>int、任何数字>>char、decimal>>任何数字、int?>>int
使⽤checked运算符可以检查类型强制转换是否安全,不安全会迫使运⾏库抛出⼀个溢出异常 int i = checked((int)val);
所有的显式转换都可能不安全。如果从可空类型强制转换为⾮可空类型,且变量的值是null,就会抛出InvalidOperationException异常。
显式类型转换的限制:值类型只能在数字、char类型和enum类型之间转换,不能直接把布尔类型强制转
换为其他类型,也不能把其他类型转换为布尔类型。
当需要分析字符串,以检索⼀个数字或布尔值,可以使⽤所有预定义值类型都⽀持的Parse()⽅法。
拆箱与装箱:值类型和引⽤类型之间的相互转换
装箱可以隐式地进⾏也可以显式地转换,拆箱是显式进⾏的。
当拆箱后得到的值变量⽆⾜够空间存储时,会导致⼀个InvalidCastException的异常。
9.⽐较对象的相等性
⽐较引⽤类型的相等性:
System.Object定义了3个⽅法来⽐较对象的相对性,ReferenceEquals()和两个版本的Equals()。再加上⽐较运算符==。
静态的ReferenceEquals()⽅法:测试两个引⽤是否引⽤类的同⼀个实例,是否包含内存中的相同地址,是则返回true,但它认为null等于null。
虚拟的Equals()⽅法:可以⽐较引⽤,可以在类中重写从⽽实现按值⽐较。如,如果希望类的实例⽤作
字典的键就需要重写此⽅法。
静态的Equals()⽅法:与虚拟的版本作⽤相同,区别是带有两个参数,并对它们进⾏相等性⽐较。这个⽅法可以处理两个对象中有⼀个是null的情况。当有⼀个为null,可以抛出异常。
⽐较运算符(==):看做严格的值⽐较和严格的引⽤⽐较之间的中间选项。可以重写⽐较运算符(运算符重载),以执⾏值的⽐较,如System.String(微软重写了).
⽐较值类型的相等性:
在⽐较值类型的相等性时,采⽤与引⽤类型相同的规则:
ReferenceEquals()⽤于⽐较引⽤,在⽐较值类型时,总是返回false,需要将值类型装箱才能⽐较引⽤。如bool b =
ReferenceEquals(v,v);但是v会被单独装箱,所以结果仍为false,所以⽤它来⽐较值类型不好。
Equals()⽤于⽐较值:微软在System.ValueType类中重载了实例⽅法Equals(),以便对值类型进⾏合适的相等性测试。如
sA.Equals(sB);sA和sB是某个结构的实例,根据sA和sB是否在其所有的字段中包含相同的值返回true或false。也可重写,以提⾼性能。如果值类型包含作为字段的引⽤类型就需要重写,因为默认只⽐较地址。
⽐较运算符:值类型需要装箱,才能转换为引⽤,进⽽才能对它们执⾏⽅法。
10.运算符重载
重载不仅仅限于算术运算符,还需要考虑⽐较运算符==、<、>、!=、>=、<=
==运算符:默认⽐较内存地址、对于string,则重写为⽐较值、对于值类型默认不⼯作并产⽣编译错误,除⾮显式重载
运算符实现过程:查最佳匹配的运算符重载版本>>查可以将某个运算符重载版本参数隐式转换成当前参数的版本>>不到则产⽣编译错误
运算符⽤于结构或类时,⼯作⽅式是⼀样的,如
//x表⽰到原点x⽅向距离,y表⽰到原点y⽅向距离,z表⽰⾼度
struct Vector
{
public double x,y,z;
public Vector(double x,double y,double z)//⽤坐标初始化对象
{
this.x = x;
this.y = y;
this.z = z;
}
public Vector(Vector rhs)//⽤三维⽮量对象初始化
{
x = rhs.x;
y = rhs.y;
z = rhs.z;
}
public override string ToString()
{
return"(" + "," + y + "," + z +")";
}
//运算符重载⽅法
public static Vector operator + (Vector lhs,Vector rhs)
{
Vector result = new Vector(lhs);
result.x += rhs.x;
result.y += rhs.y;
result.z += rhs.z;
return result;
}
}
运算符
可重载性、、、、、、 和
可以重载这些⼀元运算符。, , , , , ,  , , ,
可以重载这些⼆进制运算符。, , , , ,
⽐较运算符可以重载(但请参见本表后⾯的说明)。, 条件逻辑运算符不能重载,但可使⽤能够重载的 & 和 | 进⾏计算。
不能重载数组索引运算符,但可定义索引器。不能重载转换运算符,但可定义新的转换运算符(请参见  和)。, , , , , , , , ,
赋值运算符不能重载,但 += 可使⽤ + 计算,等等。, ,?:,??, , , ,, checked , unchecked ,default , delegate , is , new ,, 不能重载这些运算符。 说明⽐较运算符(如果重载)必须成对重载;也就是说,如果重载 ==,也必须重载 !=。 反之亦然,< 和 > 以及 <= 和 >= 同样如此。使⽤operator 关键字:⾃定义的运算符重载。⼀般将运算符左边的参数命名为lhs ,右边的的命名为rhs 。
C#要求所有的运算符重载都声明为public 和static ,因此运算符重载的代码体不能访问⾮静态类成员,也不能访问this 标识符。
与C++不同,C#不允许重载“=”运算符,但如果重载“+”运算符,编译器就会⾃动使⽤“+”运算符的重载来执⾏“+=”运算符的操作。-=、&=、*=、/=等所有赋值运算符也遵循此规则。
⽐较运算符的重载:
C#中有6个⽐较运算符,分为3对:
==和!=
>和<
>=和<=
C#语⾔要求成对重载⽐较运算符。即,如果重载了"==",也就必须重载"!=",否则会产⽣编译错误。
⽐较运算符必须返回布尔类型的值,否则没有意义。这是与算术运算符的根本区别。
public  static  bool  operator  ==(Vector lhs,Vector rhs)
{
if (lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z)
return  true ;
else
return  false ;
}
还需要重载运算符"!=",
public  static  bool  operator  !=(Vector lhs,Vector rhs)
{
return  ! (lhs == rhs);
}
浅度⽐较式⽐较对象是否执⾏内存中的同⼀个位置,⽽深度⽐较是⽐较对象的值和属性是否相等。
11.⽤户定义的类型强制转换
C#允许⽀持在⾃定义的数据类型之间进⾏类型强制转换。⽅法是把类型强制转换运算符定义为相关类的⼀个成员运算符,类型强制转换运
算符必须标记为隐式或显式。
如果知道⽆论在源变量中存储什么值,类型强制转换总是安全的,就可以把它定义为隐式强制转换。//implicit
如果某些数值可能会出错,如丢失数据或抛出异常,就可以把它定义为显式强制转换。//explicit
定义类型强制转换的语法如下,必须同时声明为public和static,下⾯代码表⽰可以将Currenty对象隐式转换为float
public static implicit operator float(Currency value)
{
//processing
}
⽰例
struct Currency
{
public uint Dollars;
public ushort Cents;
public Currency(uint dollars,ushort cents)
{
this.Dollars = dollars;
this.Cents = cents;
}
public override string ToString()
{
return string.Format("${0}.{1,-2:00}",Dollars,Cents);
}
equals不等于//隐式转换
public static implicit operator float(Currency value)
{
return value.Dollars + (value.Cents/100.0f);
}
//显式转换,但这样写会产⽣⼩数舍⼊错误,转换成ushort将截断(详见188页案例说明)
//public static explicit operator Currentcy(float value)
//{
//uint dollars = (uint)value;
//ushort cents = (ushort)((value - dollars) * 100);
//return new Currency(dollars,cents);
//}
//显式转换,将四舍五⼊转换为uInt16,使⽤checked抛出溢出异常
public static explicit operator Currency(float value)
{
checked
{
uint dollars = (uint)value;
ushort cents = Convert.ToUInt16((value - dollars) * 100);
return new Currency(dollars, cents);
}
}
}
使⽤checked检测溢出异常的代码不应该写在调⽤强制类型转换时,应给像上例⼀样放在强制转换⽅法体中,因为这是在强制转换运算符的代码中发⽣的。
类之间的类型强制转换:
定义不同的结构或类的实例之间的类型强制转换有两个限制:
如果某个类派⽣⾃另⼀个类,就不能定义这两个类之间的类型强制转换(这些类型的类型转换已经存在)
类型强制转换必须在源数据类型或⽬标数据类型的内部定义
如以下层次结构,从上到下依次继承,则唯⼀合法的⾃定义类型强制转换就是类C和D之间的转换,因为它们没有互相派⽣。
System.Object
    |
    A
    |
    B

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。