• 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;
        }
    }

    使用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);
    }

    浅度比较式比较对象是否执行内存中的同一个位置,而深度比较是比较对象的值和属性是否相等。

    运算符

    可重载性

    +-!~++--true 和 false

    可以重载这些一元运算符。

    +-*/%&| ^<<>>

    可以重载这些二进制运算符。

    ==!=<><=>=

    比较运算符可以重载(但请参见本表后面的说明)。

    &&||

    条件逻辑运算符不能重载,但可使用能够重载的 & 和 | 进行计算。

    []

    不能重载数组索引运算符,但可定义索引器。

    (T)x

    不能重载转换运算符,但可定义新的转换运算符(请参见 explicit 和implicit)。

    +=-=*=/=%=&=|=^=<<=>>=

    赋值运算符不能重载,但 += 可使用 + 计算,等等。

    =, .,?:,??, - AMP_GT, =AMP_GT, f f(x),, checkeduncheckeddefaultdelegateisnewsizeof, typeof

    不能重载这些运算符。

    说明 说明

    比较运算符(如果重载)必须成对重载;也就是说,如果重载 ==,也必须重载 !=。 反之亦然,< 和 > 以及 <= 和 >= 同样如此。

    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);
        }
        
        //隐式转换
        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

      /    

    C        D

    如:定义可以放在C的类定义内部,或者在D的类定义内部,但不能放在其他地方定义。一旦在一个类中定义,就不能在另一个类中定义。

    public static explicit operator D(C value){...}

    基类和派生类之间的类型强制转换:

    MyDerived derivedObject = new MyDerived();
    MyBase baseCopy = derivedObject;

    从派生类隐式地强制转换为基类是可以的,因为对基类的任何引用都可以引用基类或派生类的对象。

    将基类显式强制转换为派生类是不可以的,比较合适的方法是将基类作为参数在初始化派生类时传入,让构造函数完成初始化。

    装箱和拆箱数据类型强制转换:

    值类型与引用类型一致,都是支持从派生类到基类的隐式转换。本质上进行了装箱。

    在使用装箱和拆箱时,这两个过程都把数据复制到新装箱和拆箱的对象上。

    12.多重类型强制转换

    如果在进行要求的数据类型转换时,C#编译器没有可用的直接强制转换方式,编译器会寻找一种转换方式,把几种强制转换合并起来。如,

    Currency balance = new Currency(10,50);
    long amount = (long)balance;
    double amoutD = balance;

    因为编译器知道存在Currency到float的隐式转换,而且它知道怎么从float显式地转换为long。

    转换当存在多条路径时,C#有一些严格的规则(详见MSDN),告诉编译器该如何确定哪条是最佳路径,但最好自己设计类型强制转换,让所有转换都得到相同的结果(但没精度损失)。

    13.当将类型强制转换和方法重载合并起来时,会出现不希望的错误源

    如果方法调用带有多个重载方法,并要给该方法传送参数,而该参数的数据类型不匹配任何重载方法,就可以迫使编译器确定使用哪些强制转换方式进行数据转换,从而决定使用哪个重载方法(并进行相应的数据转换)。当然,编译器总是按逻辑和严格的规则来工作,但结果可能不是我们所期望的。如果存在疑问,最好指定显式地使用哪种强制转换。如,

    Currency balance = new Currency(50,35);
    Console.WriteLine(balance);//50
    Console.WriteLine(balance.ToString());//50.35
    
    //因为Console.WriteLine找不到参数为Currency结构的重载版本,最后使用了参数是unit,显式unit的版本。
  • 相关阅读:
    表单序列化
    创建.ignore文件
    头条数学救火队长马丁的一道中山大学研究生入学考试数学分析题
    实数理论
    方法
    目标
    闭区间有限覆盖定理
    零值定理的确界原理证明方法,来自百度
    各种范例
    零值定理
  • 原文地址:https://www.cnblogs.com/KevinG/p/3556802.html
Copyright © 2020-2023  润新知