C# 之 常见和不常见的运算符汇总

834 阅读9分钟

C# 提供了许多运算符。 其中许多都受到内置类型的支持,可用于对这些类型的值执行基本操作。

一,算术运算符

算术运算符:

  • 一元: ++(增量)、–(减量)、+(加)和 -(减)运算符
  • 二元 :*(乘法)、/(除法)、%(余数)、+(加法)和 -(减法)运算符

算术运算符是非常简单和常用的运算符,直接看代码吧;

using System;

namespace CsharpOp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("C# -- 算数运算符");

            int num = 1;
            // 前缀增量 先计算后执行...
            Console.WriteLine(++num);   //输出: 2
            Console.WriteLine(num);     //输出: 2
            Console.WriteLine(--num);   //输出: 1
            Console.WriteLine(num);     //输出: 1 
            Console.WriteLine("---------------");
            // 后缀增量 先执行后计算...
            Console.WriteLine(num++);   //输出: 1
            Console.WriteLine(num);     //输出: 2
            Console.WriteLine(num--);   //输出: 2
            Console.WriteLine(num);     //输出: 1

            Console.WriteLine("---------------");
            int x = 3, y = 4;
            // 加,减,乘,除,取模 运算
            Console.WriteLine(x + y);     //输出: 7
            Console.WriteLine(x - y);     //输出: -1
            Console.WriteLine(x * y);     //输出: 12
            Console.WriteLine(x / y);     //输出: 0
            Console.WriteLine(x % y);     //输出: 3

            Console.ReadKey();
        }
    }
}

运算符优先级和关联性
优先级由高到低的顺序排序:

  • 后缀增量 x++ 和减量 x-- 运算符
  • 前缀增量 ++x 和减量 --x 以及一元 + 和 - 运算符
  • 乘法 *、/ 和 % 运算符
  • 加法 + 和 - 运算符

二元算数运算符是左结合运算符。 也就是说,具有相同优先级的运算符按从左至右的顺序计算。


二,逻辑运算符

逻辑运算:

  • 一元 !(逻辑非)运算符。
    我一般叫它为取反运算符,即计算结果为false,它是true,计算结果为true时它为true。
  bool bo = true;
  Console.WriteLine(!bo);       //输出: False
  Console.WriteLine(!(3 == 4)); //输出: True
  • 二元 &(逻辑与)、|(逻辑或)和 ^(逻辑异或)运算符。 这些运算符始终计算两个操作数。

    • & :运算符计算操作数的逻辑与。 如果 x 和 y 的计算结果都为 true,则 x & y 的结果为 true。 否则,结果为 false。
    • | 运算符计算操作数的逻辑或。 如果 x 或 y 的计算结果为 true,则 x | y 的结果为 true。 否则,结果为 false。
    • ^ 运算符计算操作数的逻辑异或(亦称为“逻辑 XOR”)。 对于 bool 操作数,^ 运算符的计算结果与不等运算符 != 相同,也就是说当X和Y同为true或false时,x^y 结果为 false;当X和Y不相同时,则其结果为true。
    // & 与运算
    Console.WriteLine(true & true);     //输出: True
    Console.WriteLine(true & false);    //输出: False
    Console.WriteLine(false & true);    //输出: True
    Console.WriteLine(false & false);   //输出: False
    // | 或运算
    Console.WriteLine(true | true);     //输出: True
    Console.WriteLine(true | false);    //输出: True
    Console.WriteLine(false | true);    //输出: True
    Console.WriteLine(false | false);   //输出: False
    // ^ 亦或运算
    Console.WriteLine(true ^ true);     //输出: False
    Console.WriteLine(true ^ false);    //输出: True
    Console.WriteLine(false ^ true);    //输出: True
    Console.WriteLine(false ^ false);   //输出: False
  • 二元 &&(条件逻辑与)和 ||(条件逻辑或)运算符。 这些运算符仅在必要时才计算右侧操作数。

    • && :也叫短路与,计算结果和 & 的执行结果一致。当x,y都为true时,结果为true,反之,结果为false;当x(左侧结果)为false时,不会计算y,直接得到结果false。
    • || :也叫短路,计算结果和 | 的执行结果一致。当x,y有一个为true时,结果为true,反之,结果为false;当x(左侧结果)为true时,不会计算y,直接得到结果true。

逻辑运算符优先级
按优先级从高到低的顺序排序:

  • 逻辑非运算符 !
  • 逻辑与运算符 &
  • 逻辑异或运算符 ^
  • 逻辑或运算符 |
  • 条件逻辑与运算符 &&
  • 条件逻辑或运算符 ||

三,按位和移位运算符

  • 一元 ~(按位求补)运算符:即二进制下0和1的相互变换;
    4的二进制是:00000100
    则按位求补 : 11111011
 byte a = 4;  // 4的二进制: 00000100
 byte b = (byte)~a;
 Console.WriteLine(a);   // 输出: 4
 Console.WriteLine(b);   // 输出: 251
 Console.WriteLine(Convert.ToString(a, toBase: 2));// 输出: 100
 Console.WriteLine(Convert.ToString(b, toBase: 2));// 输出: 11111011
  • 二进制 <<(向左移位)和 >>(向右移位)移位运算符;
    向左移位举例:
    4的二进制是 :00000100
    4左移两位是 :000100__ (下划线两位用0补位)
    左移两位结果:00010000
    向右移位同理
    4的二进制是 :00000100
    4右移两位是 :000000100 (多出来的直接舍弃)
    右移两位结果:00000001

    上面描述了二进制计算,那么对于十进制来说,左移几位就是乘以2的几次幂。(左移一位:2,两位:22,三位222);同理可知,右移时就是除以2的几次幂。

    因为位移比乘除速度快,对效率要求高,而且满足 2 的幂次方的乘除运方,可以采用位移的方式进行。

  // 左移两位
  byte a = 4;  // 4的二进制: 00000100
  byte b = (byte)(a << 2);
  Console.WriteLine(a);   // 输出: 4
  Console.WriteLine(b);   // 输出: 16
  Console.WriteLine(Convert.ToString(a, toBase: 2));// 输出: 100
  Console.WriteLine(Convert.ToString(b, toBase: 2));// 输出: 10000

  // 右移两位
  byte c = 4;  // 4的二进制: 00000100
  byte d = (byte)(a >> 2);
  Console.WriteLine(c);   // 输出: 4
  Console.WriteLine(d);   // 输出: 1
  Console.WriteLine(Convert.ToString(c, toBase: 2));// 输出: 100
  Console.WriteLine(Convert.ToString(d, toBase: 2));// 输出: 1
  • 二进制 &(逻辑 AND)、|(逻辑 OR)和 ^(逻辑异或)运算符

    • & :与运算,从第一个非0位开始计算,对应位两个都是1则结果为1,否则结果为0。举例:
      4: 00000100
      5: 00000101
      ---------------------
      4: 00000100
    • | :或运算,从第一个非0位开始计算,对应位有一个是1则结果为1,否则结果为0。举例:
      4: 00000100
      5: 00000101
      ---------------------
      5: 00000101
    • ^ :亦或运算,从第一个非0位开始计算,对应位两个不同则结果为1,否则结果为0。举例:
      4: 00000100
      5: 00000101
      ---------------------
      1: 00000001
   byte x = 4; // 4的二进制: 00000100
   byte y = 5; // 5的二进制: 00000101

   // & 与运算
   byte z = (byte)(x & y);
   Console.WriteLine(z);   // 输出: 4
   Console.WriteLine(Convert.ToString(z, toBase: 2));// 输出: 100
           
   // | 或运算
   z = (byte)(x | y);
   Console.WriteLine(c);   // 输出: 5
   Console.WriteLine(Convert.ToString(z, toBase: 2));// 输出: 101
                                                            
   // ^ 亦或运算
   z = (byte)(x ^ y);
   Console.WriteLine(c);   // 输出: 5
   Console.WriteLine(Convert.ToString(z, toBase: 2));// 输出: 1

四,关系运算符

  • ==(等于)、!=(不等于)、 <(小于)、>(大于)、<=(小于或等于)和 >=(大于或等于)关系运算符比较其操作数。

char 类型支持关系运算符。 在使用 char 操作数时,将比较对应的字符代码。

枚举类型也支持比较运算符。 对于相同枚举类型的操作数,基础整数类型的相应值会进行比较。

运算符可重载性:
用户定义类型可以重载 ==、!=、<、>、<= 和 >= 运算符。

如果某类型重载/ == 或 !=运算符之一,它必须同时重载 /== 和!=。
如果某类型重载 < 或 > 运算符之一,它必须同时重载 < 和 >。
如果某类型重载 <= 或 >= 运算符之一,它必须同时重载 <= 和 >=。


五,成员访问运算符

访问类型成员时,可以使用以下运算符和表达式:

  • .(成员访问):用于访问命名空间或类型的成员
double pai = System.Math.PI;
  • [](数组元素或索引器访问):用于访问数组元素或类型索引器
 int[] arr = new int[3] { 1, 2, 3 };
 Console.WriteLine(arr[1]); // 输出: 2
  • ?. 和 ?[](null 条件运算符):仅当操作数为非 null 时才用于执行成员或元素访问运算

以?[]访问为例:

 var arr = new List<int[]>() { new int[] { 1, 2, 3 }, null };
 Console.WriteLine(arr[0][1]);    // 输出: 2
 Console.WriteLine(arr?[0][1]);   // 输出: 2
 
 Console.WriteLine(arr[1]);       // 输出: 
 Console.WriteLine(arr?[1]?[0]);  // 输出: 
 //Console.WriteLine(arr?[1][0]); // 报错: System.NullReferenceException
 // 等价于
 if (arr != null)
 {
     Console.WriteLine(arr[1]);
     if (arr[1] != null)
     {
          Console.WriteLine(arr[1][0]);
     }
 }

在这里可以理解为:问号再谁后面就判断谁是否为null,为null则不执行后面操作。

  • ^(从末尾开始索引):指示元素位置来自序列的末尾。
    a[^]就是倒数第个的意思,使用时主要下别越界了。
 int[] array = new int[5] { 1, 2, 3, 4, 5 };
 Console.WriteLine(array[1]);  // 输出: 2
 Console.WriteLine(array[^1]); // 输出: 5
 //Console.WriteLine(array[^0]); // 报错:数组索引越界,System.IndexOutOfRangeException
  • . .(范围):指定可用于获取一系列序列元素的索引范围;
    写法为[startIndex. .endIndex) --> [开始索引. .结束索引) 是一个左闭右开区间;若开始索引不写,则默认0开始;若结束索引不写,则默认取到数组结束;若都不写,则就是整个数组。
    动态写法(. . ^):范围结束还可以这样定义,[startIndex. .^Index)–> 解释为从第startIndex个元素索引开始倒数第Index个元素结束。
 int[] array = new int[5] { 1, 2, 3, 4, 5 };
 Console.WriteLine();
 for (int i = 0; i < array[1..3].Length; i++)
 {
     Console.Write(array[1..3][i] + " "); //输出2 3
 }
 Console.WriteLine();
 // 开始索引不写,默认从0索引开始
 for (int i = 0; i < array[..3].Length; i++)
 {
     Console.Write(array[..3][i] + " "); //输出2 3 4
 }
 Console.WriteLine();
 // 结束索引不写,默认到数组长度结束
 for (int i = 0; i < array[1..].Length; i++)
 {
     Console.Write(array[1..][i] + " "); //输出2 3 4 5
 }
 Console.WriteLine();
 // 从1索引到倒数第二个元素的索引的区间。
 int[] indexArr = array[1..^2];
 for (int i = 0; i < indexArr.Length; i++)
 {
     Console.Write(indexArr[i] + " "); //输出2 3
 }

六,类型测试运算符

  • is 运算符:用于检查表达式的运行时类型是否与给定类型兼容
    X is Y: 若X可以通过引用转换,装箱,拆箱来转换为类型Y,则X is Y返回True,否则,则返回False;is 运算符不会考虑用户定义的转换。
    // 基类
    public class BaseClass { }
	// 派生类
    public class DerivedClass : BaseClass { }

    class Program
    {
        static void Main(string[] args)
        {
            var b = new BaseClass();
            Console.WriteLine(b is BaseClass);    // 输出: True
            Console.WriteLine(b is DerivedClass); // 输出: False

            var d = new DerivedClass();
            Console.WriteLine(d is BaseClass);    // 输出: True
            Console.WriteLine(d is DerivedClass); // 输出: True

            Console.ReadKey();
        }
    }
  • as 运算符:用于将表达式显式转换为给定类型(如果其运行时类型与该类型兼容)。 如果无法进行转换,则 as 运算符返回 null。 与强制转换表达式 不同,as 运算符永远不会引发异常。
    // 基类
 	public class BaseClass { }
	// 派生类
    public class DerivedClass : BaseClass { }

    class Program
    {
        static void Main(string[] args)
        {
            BaseClass b = new BaseClass();        
            // as
            DerivedClass d = b as DerivedClass;
            // 强制转换
            DerivedClass d1 = (DerivedClass) b;

            Console.ReadKey();
        }
    }
  • 运算符:用于获取某个类型的 System.Type 实例
	// 基类
 	public class BaseClass { }
	// 派生类
    public class DerivedClass : BaseClass { }

    class Program
    {
        static void Main(string[] args)
        {
            var d = new DerivedClass();
            Console.WriteLine(d is BaseClass);      // 输出: True
            Console.WriteLine(d.GetType() == typeof(BaseClass));    // 输出: True
            Console.WriteLine(d is DerivedClass);   // 输出: True
            Console.WriteLine(d.GetType() == typeof(DerivedClass));    // 输出: True
            
            Console.ReadKey();
        }
     }

七,?? 和 ??= 运算符

  • 如果左操作数的值不为 null,则 null 合并运算符 ?? 返回该值;否则,它会计算右操作数并返回其结果。 如果左操作数的计算结果为非 null,则 ?? 运算符不会计算其右操作数。

??举例:

 int? a = null;
 int b = a ?? -1;
 Console.WriteLine(b);  // output: -1
 
 // 等价于 三目运算符
 b = (a == null ? (int)a : -1);

 // 等价于 if else
 if (a == null) 
     b = -1;
 else
     b = (int)a;

??=举例:变量!=null,则被表达式赋值

variable ??= expression;
//等价于
if (variable is null)
{
    variable = expression;
}