• C#.Net筑基-运算符Family


    image.png

    C#运算符 内置了丰富的运算符操作类型,使用方便,极大的简化了编码,同时还支持多种运算符重载机制,让自定义的类型也能支持运算符行为。

    01、运算符概览

    运算符分类 描述
    数学运算 基础的加减乘除,及++、--
    赋值运算 =,及各种复合赋值op=x+=20; 等效于x=x+20;
    比较运算 比较相等、大小,内置类型大多支持,自定类型需要自己重载运算符才能支持
    逻辑运算符 常用的就是非!、短路逻辑与&&、短路逻辑或 ||。
    位运算 二进制位运算,适当使用可极大提高数据处理性能
    类型相关运算符 类型判断is、类型转换astypeof...
    指针操作运算符 指针相关运算符:*、&、->
    其他运算符 ^..范围运算、nameofdefault(默认值)、await/async...
    运算符重载 public static Point operator +(Point p1, Point p2) {}
    隐式转换 public static implicit operator int(Point p1){}
    显示转换 public static explicit operator string(Point p){}


    02、运算符汇总🔣

    2.1、算数运算

    运算符 描述 示例
    + 加,字符串加为字符串连接,推荐字符内插$"{var}"
    - 减,同+可用于委托,也可表示负数-100
    *
    /
    % 取余数 6%3; //=0
    ++ 自加1,符号在前面的先运算,再赋值,在后面的反之 x=++y; //y=1+y; x=y;
    x=y++; //x=y; y=1+y;
    -- 自减1,同上

    • 整数除以0会引发 DivideByZeroException,浮点数不会,结果为无穷∞/Infinity
    • 整数运算超出范围,默认情况下不会引发异常,会被自动截断(循环),除非使用检查语句(checked),此时超出范围会引发OverflowException。而常量表达式默认检查。
    • 浮点数floatdouble不会出现超出范围异常问题。
    • decimal 类型溢出始终会已发异常 OverflowException,被零除总是引发 DivideByZeroException
    • 整数除以整数,结果依然是整数,向下(截断)取整。
    int a = 3;
    int b = a +int.MaxValue; //-2147483646
    int c = checked(a + int.MaxValue); //System.OverflowException

    2.2、赋值运算

    运算符 描述 示例/备注
    = 赋值运算符,一个神圣的仪式
    += 加法赋值 x+=y //x=x+y
    -= 减法赋值 x-=y //x=x-y
    *= 乘法赋值 x*=y //x=x*y
    /= 除法赋值 x/=y //x=x/y
    %= 取余赋值 a%=3 //a=a%3
    op= 复合赋值x op = y = x = x op y 上面这些都是复合赋值
    = ref 按引用赋值,可看做是别名,是同一个地址的别名 ref int b = ref a;
    ??
    ??=
    Null 合并赋值,为null时就用后面的表达式值。
    ??=可用来设置默认值,或为null时抛出异常
    c = b1 ?? 5; name??= "sam";
    _= sender ??throw...

    2.3、比较运算

    运算符 描述 示例/备注
    == 相等比较,值类型比较值,应用类型比较地址 if(x == 0){}
    != !=不等于比较,与==对应 if(x != 1){}
    >,< 大于,小于
    >=,<= 大于等于,小于等于

    🔸对于相等(==、!=)比较

    • 值类型比较的是数据值是否相等。
    • 引用类型==比较的是其引用地址(对象),同 Object.ReferenceEquals
    • 字符串比较的字符串值,虽然他是引用类型(一个特殊的引用类型)。
    • 用户定义的 struct 类型默认情况下不支持 == 运算符,需要自己重载运算符。可以用Equals()方法,会box装箱。
    • C# 9.0 中的值类型 记录类型 record struct 支持 == 和 != 运算符,比较的是其内部值。这是因为record 内部实现了相等的运算符重载,比较了每个属性的值。
    • 委托,如果其内部列表长度相同,且每个位置的元素相同则二者相等。

    🔸对于大小比较

    • char 比较的是字符码值。

    2.4、逻辑运算符

    运算符 描述 示例/备注
    ! !逻辑非运算符,取反,颠掉黑白! !false //true
    & 逻辑与,都为true结果为true,都会计算,前面false还是会计算后面的 true & false //false
    ^ 逻辑异或,不同则为ture,相同false true ^ false //true
    | 逻辑或,只要有一个true,则为true,都会计算 true | false //true
    && 条件逻辑与,也叫短路运算符,都为true才为true,先遇到false就返回了 效果同&,推荐用&&
    || 条件逻辑或,也是短路的,只要有true就返回true,先遇到true就返回了 效果同"|",推荐用"||"
    ?: 三元表达式判断条件 ?条件为真 :条件为假 age = a > 2 ? 4 : 2
    • 上面运算符除了&&|| 都支持可空类型的bool?,只是结果可能为null
    • 一般逻辑与、逻辑或都用支持短路的 &&|| ,可提前返回,减少运算。
    void Main()
    {
    Console.WriteLine(F()&T()); // f t False 都运算了
    Console.WriteLine(F()&&T()); // f False 发现fasle就返回了,后面就不运算了
    Console.WriteLine(T()|F()); // t f True 都运算了
    Console.WriteLine(T()||F()); // t True 发现true就返回true了,后面就不运算了
    Console.WriteLine(true^true); // False
    Console.WriteLine(true^false); // True 不同则为true
    Console.WriteLine(false^false); // False
    }
    bool T()
    {
    Console.Write("t ");
    return true;
    }
    bool F()
    {
    Console.Write("f ");
    return false;
    }

    image.png

    2.5、位运算

    运算符 描述 示例/备注
    ~ 按位求补,每位(所有位)反转
    申明终结器(析构函数),~className(){}
    uint b = ~a;
    << 左移位,向左移动右侧操作数定义的位数 uint y = 1 << 5;,//32(1*2的5次方)
    >> 右移位,向右移动右侧操作数定义的位数 8>>2//2(8除以2的2次方)
    >>> 无符号右移 0b100>>>2//1
    & 逻辑与,计算每一位的逻辑与 0b100 & 0b111 //100
    | 逻辑或,计算每一位的逻辑或 0b100 | 0b111 //111
    ^ 逻辑异或,计算每一位的逻辑异或 0b100 ^ 0b111 //11

    左移位<< n,相当于乘以2的n次方,同样的,右移位意味着除以2的n次方。移位运算比乘法、除法运算效率要高很多!所以在合适的场景可考虑使用位运算。

    Console.WriteLine(123456*Math.Pow(2,6)); //7901184
    Console.WriteLine(123456<<6); //7901184

    用左移位给Flags枚举赋值:

    [Flags]
    public enum Interest
    {
    None = 0, //0
    Study = 1, //1
    Singing = 1 << 1, //2
    Dancing = 1 << 2, //4
    Sports = 1 << 3, //8
    Games = 1 << 4, //16
    }

    2.6、类型、成员运算符

    运算符 描述 示例/备注
    Object.prop 成员访问表达式,命名空间、类、成员访问 user.name
    ?.?[] Null 条件运算符,仅非null时才继续,否则返回null 简化null判断,arr?[1]??1
    new 🔸 new Object()创建对象实例,C#9可省略后面的类型。
    🔸 重新实现父类的方法、属性。
    🔸 创建匿名实例new {n=1}
    User u1 = new();
    var a = new {Name="sam"}
    is(E is T) 类型兼容性检查,如果兼容则为True,否则false。支持可空泛型、装箱的兼容转换。is 支持各种模式匹配,参考《C#模式匹配 if(obj is int i)//True
    as(E as T) 显式转换类型,如果转换失败则返回null,不会报错。只支持引用类型、可空类型、装箱拆箱。 int b =i as User
    (T)E 强制类型转换,如果不匹配会引发异常 var b = (int)b1
    typeof 获取类型System.Type,参数只能是类型、T,不支持dynamic、string?,可用Object.GetType代替。 typeof(int?) b1?.GetType()
    sizeof 获取类型所需大小(字节数),只能用于值类型、不安全类型 sizeof(int) //4
    () 🔸 调用表达式,调用方法,执行委托/方法,new构造函数。
    🔸创建一个Tuple
    🔸 强制类型转换(Type)v,转换失败抛出异常
    🔸Foo();
    🔸var b = (1,2,"ss");
    🔸var b = (int)a
    [] 🔸 数组下标,必须为整数
    🔸 索引器,可任意类型,如Dictionary
    🔸 特性Attribute的使用
    🔸arr[0] =1;
    🔸dict["one"] = 1;
    🔸[Serializable]

    2.7、其他运算符

    运算符 描述 示例/备注
    [value]! 在表达式后面,告诉编译器不会为空,不要在提示告警了 p.Message!.Length
    ^ 范围运算符,从末尾开始索引,倒数 arr[^1] // 倒数第一个
    .. 范围运算符,.NET Standard 2.1/C#8,表达式 a..b 属于 System.Range 类型,表示ab的范围。a.. 等效于 a..^0 arr[1..3] // 索引1到3的值
    with C#9非破坏性创建+修改,创建一个副本,并修改特定属性,只支持recordstruct类型。 n = o with { age = 2 }
    nameof 获取变量、类型、成员的名称作为字符串常量,编译时获取 nameof(numbers.Add)
    stackalloc 堆栈上分配内存块,用于System.Spanunsafe代码 arr = stackalloc int[10]
    (p) => {} Lambda表达式创建匿名函数,左侧为参数,右侧为表达式 var f = ()=>x=100;
    default 默认值,获取类型的默认值,T initialValue = default 数值类型默认0、false,引用类型默认null default(int) //0 int x = default;
    :: 命名空间别名的使用,using t = System.Text; t::Json.
    await、async 异步编程,async 标记方法为异步方法
    &、* 指针操作:*获取指针指向的变量,&获取变量地址 int n = 100; int* n1 = &n; int n2 = *n1; unsafe上下文中运行

    03、运算符优先级

    运算符优先级,最简单的方法就是加括号()

    image.png


    04、operator 运算符重载

    上面这么多运算符,有些是可以自定义重载实现的,让自定义的类型(class、struct)也支持运算符操作,比如让自定的类型支持相加、相减、大小比较。

    void Main()
    {
    var p1 =new Point(1,2);
    var p2 =new Point(3,4);
    var p = p1+p2; //4,6
    p +=p; //8,12
    }
    public class Point //struct也是一样的
    {
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x, int y)
    {
    X = x;
    Y = y;
    }
    public static Point operator +(Point p1, Point p2)
    {
    return new Point(p1.X + p2.X, p1.Y + p2.Y);
    }
    }

    上面的示例是重载实现+运算符,实现运算符重载的基本语法:public static T operator +(T arg)

    • 方法必须是public + static的,然后operator关键字,“方法名”就是运算符,后面就是正常的方法参数。
    • 所以不难看出重载的运算符本质上就是静态方法调用,下面是上面示例的IL代码。

    image.png

    4.1、可重载的运算符

    运算符 说明
    +x, -x, !x, ~x, ++, --, true, false true和 false 运算符必须一起重载(比较少见)。
    x + y, x - y, x * y, x / y, x % y,
    x & y, x | y, x ^ y,
    x << y, x >> y, x >>> y
    x == y, x != y, x < y, x > y, x <= y, x >= y 必须按如下方式成对重载: == 和 !=、 < 、 ><= 和 >=

    还有些不可重载运算符

    • 成对重载:有些运算符的重载要求需成对实现,如==!=<><=>=
    • Equals 和 GetHashCode:如果重载了相等运算符,则也应该重载Equals、GetHashCode方法才更完整、合理。
    • IComparable:如果重载了大小比较运算符,则应当实现IComparable接口。

    4.2、显式和隐式转换运算

    隐式转换 顾名思义就是自动匹配类型并完成转换,显示类型转换就是要指定转换的类型T(T)value

    • 隐式转换多用于无损、必定转换成功的场景,如intdoublelongshort 转换为int
    • 强制转换则可能存在信息损失,或转换可能存在不确定性,如上面示例的相反方向转换。
    void Main()
    {
    int a =100;
    float f = a; //int隐式转换为了float
    char c1 = a; //报错,不支持隐式转换
    char c2 =(char)a; //强制转换
    Console.WriteLine(c2); //d
    }

    自定义实现隐式、显示类型转换主要是下面两个关键字:

    • implicit (/ɪmˈplɪsɪt/ 隐式),隐式类型转换运算,用于无损数据类型转换,一般用于“小”数据类型转换为“大”数据类型,如int转换为longfloat
    • explicit (/ɪkˈsplɪsɪt/ 显示),显示类型转换运算,可能会导致数据丢失的转换。
    void Main()
    {
    Point p1 = 2; //2,2
    int a = p1; //2
    string pstr = (string)p1; //(2,2)
    }
    public class Point
    {
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x, int y)
    {
    X = x;
    Y = y;
    }
    public static implicit operator int(Point p1) //Point隐式转换为int
    {
    return (p1.X + p1.Y) / 2;
    }
    public static implicit operator Point(int v) //int隐式转换为Point
    {
    return new Point(v, v);
    }
    public static explicit operator string(Point p)//Point显示转换为string
    {
    return $"({p.X},{p.Y})";
    }
    }

    📢 一般建议强相关的类型可实现显示、隐式转换,对于弱相关类型的转换则建议编写专门的转换函数,如果ToXXXParse方法。


    参考资料


    ©️版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀

  • 相关阅读:
    关于GBDT算法、XGBoost算法的基本原理概述
    js单行代码-----dom
    1043 Is It a Binary Search Tree
    原生js 多用途三元表达式 点赞按钮
    python之计算平面点集的的面积
    React 如何添加路由懒加载
    人工智能知识全面讲解:回归分析
    [附源码]Java计算机毕业设计SSM东北鹿产品售卖网站
    安装 eslint 配置指南 及 遇到的一些问题记录
    【Buildroot】记一次编译出错gzip: popt-1.16.tar.gz: not in gzip format--更改br里面的默认下载地址
  • 原文地址:https://www.cnblogs.com/anding/p/18170514