04_2.operator.md 25 KB

运算符和表达式

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

通常可以重载这些运算符,也就是说,可以为用户定义类型的操作数指定运算符行为。

运算符和表达式教程地址

算术运算符

以下运算符对数值类型的操作数执行算术运算:

增量运算符++

一元增量运算符 ++ 按 1 递增其操作数。

增量运算符以两种形式进行支持:后缀增量运算符 x++ 和前缀增量运算符 ++x

后缀递增运算符

x++ 的结果是此操作前的 x 的值,如以下示例所示:

int i = 3;
Console.WriteLine(i);   // output: 3
Console.WriteLine(i++); // output: 3
Console.WriteLine(i);   // output: 4

前缀增量运算符

++x 的结果是此操作后的 x 的值,如以下示例所示:

double a = 1.5;
Console.WriteLine(a);   // output: 1.5
Console.WriteLine(++a); // output: 2.5
Console.WriteLine(a);   // output: 2.5

减量运算符--

一元减量运算符 -- 按 1 递减其操作数。

减量运算符以两种形式进行支持:后缀减量运算符x-- 和前缀减量运算符 --x

后缀递减运算符

x-- 的结果是此操作前的 x 的值,如以下示例所示:

int i = 3;
Console.WriteLine(i);   // output: 3
Console.WriteLine(i--); // output: 3
Console.WriteLine(i);   // output: 2

前缀减量运算符

--x 的结果是此操作后的 x 的值,如以下示例所示:

double a = 1.5;
Console.WriteLine(a);   // output: 1.5
Console.WriteLine(--a); // output: 0.5
Console.WriteLine(a);   // output: 0.5

一元加减运算符

一元 + 运算符返回其操作数的值。 一元 - 运算符对其操作数的数值取负。

Console.WriteLine(+4);     // output: 4

Console.WriteLine(-4);     // output: -4
Console.WriteLine(-(-4));  // output: 4

uint a = 5;
var b = -a;
Console.WriteLine(b);            // output: -5
Console.WriteLine(b.GetType());  // output: System.Int64

Console.WriteLine(-double.NaN);  // output: NaN

ulong 类型不支持一元 - 运算符。

乘法运算符*

乘法运算符 * 计算其操作数的乘积:

Console.WriteLine(5 * 2);         // output: 10
Console.WriteLine(0.5 * 2.5);     // output: 1.25
Console.WriteLine(0.1m * 23.4m);  // output: 2.34

除法运算符/

除法运算符 / 用它的左侧操作数除以右侧操作数。

整数除法

对于整数类型的操作数,/ 运算符的结果为整数类型,并且等于两个操作数之商向零舍入后的结果:

Console.WriteLine(13 / 5);    // output: 2
Console.WriteLine(-13 / 5);   // output: -2
Console.WriteLine(13 / -5);   // output: -2
Console.WriteLine(-13 / -5);  // output: 2

若要获取浮点数形式的两个操作数之商,请使用 floatdoubledecimal 类型:

Console.WriteLine(13 / 5.0);       // output: 2.6

int a = 13;
int b = 5;
Console.WriteLine((double)a / b);  // output: 2.6

浮点除法

对于 floatdoubledecimal 类型,/ 运算符的结果为两个操作数之商:

Console.WriteLine(16.8f / 4.1f);   // output: 4.097561
Console.WriteLine(16.8d / 4.1d);   // output: 4.09756097560976
Console.WriteLine(16.8m / 4.1m);   // output: 4.0975609756097560975609756098

如果操作数之一为 decimal,那么另一个操作数不得为 floatdouble,因为 floatdouble 都无法隐式转换为 decimal。 必须将 floatdouble 操作数显式转换为 decimal 类型。 如需详细了解数值类型之间的转换,请参阅内置数值转换

余数运算符%

余数运算符 % 计算左侧操作数除以右侧操作数后的余数。

整数余数

于整数类型的操作数,a % b 的结果是 a - (a / b) * b 得出的值。 非零余数的符号与左侧操作数的符号相同,如下例所示:

Console.WriteLine(5 % 4);   // output: 1
Console.WriteLine(5 % -4);  // output: 1
Console.WriteLine(-5 % 4);  // output: -1
Console.WriteLine(-5 % -4); // output: -1

浮点余数

对于 floatdouble 操作数,有限的 xyx % y 的结果是值 z,因此

  • z(如果不为零)的符号与 x 的符号相同。
  • z 的绝对值是 |x| - n * |y| 得出的值,其中 n 是小于或等于 |x| / |y| 的最大可能整数,|x||y| 分别是 xy 的绝对值。
Console.WriteLine(-5.2f % 2.0f); // output: -1.2
Console.WriteLine(5.9 % 3.1);    // output: 2.8
Console.WriteLine(5.9m % 3.1m);  // output: 2.8

加减法运算符

加法运算符 + 计算其操作数的和,减法运算符 - 从其左侧操作数中减去其右侧操作数:

Console.WriteLine(5 + 4);       // output: 9
Console.WriteLine(5 + 4.3);     // output: 9.3
Console.WriteLine(5.1m + 4.2m); // output: 9.3

Console.WriteLine(47 - 3);      // output: 44
Console.WriteLine(5 - 4.3);     // output: 0.7
Console.WriteLine(7.5m - 2.3m); // output: 5.2

运算符的优先级

以下列表对优先级由高到低的顺序对算术运算符进行排序:

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

逻辑运算符

以下运算符使用 bool 操作数执行逻辑运算:

对于整型数值类型的操作数,&|^ 运算符执行位逻辑运算。 有关详细信息,请参阅位运算符和移位运算符

逻辑非运算符!

一元前缀 ! 运算符计算操作数的逻辑非。

bool passed = false;
Console.WriteLine(!passed);  // output: True
Console.WriteLine(!true);    // output: False

逻辑 AND 运算符 &

& 运算符计算操作数的逻辑与。如果 xy 的计算结果都为 true,则 x & y 的结果为 true。 否则,结果为 false

即使左侧操作数计算结果为 false& 运算符也会计算这两个操作数,而在这种情况下,无论右侧操作数的值为何,运算结果都为 false

对于整型数值类型的操作数,& 运算符计算其操作数的位逻辑 AND

逻辑异或运算符 ^

^ 运算符计算操作数的逻辑异或(亦称为“逻辑 XOR”)。 如果 x 计算结果为 truey 计算结果为 false,或者 x 计算结果为 falsey 计算结果为 true,那么 x ^ y 的结果为 true。 否则,结果为 false

逻辑或运算符 |

| 运算符计算操作数的逻辑或。 如果 xy 的计算结果为 true,则 x | y 的结果为 true。 否则,结果为 false

即使左侧操作数计算结果为 true| 运算符也会计算这两个操作数,而在这种情况下,无论右侧操作数的值为何,运算结果都为 true

对于整型数值类型的操作数,| 运算符计算其操作数的位逻辑 OR

条件逻辑 AND 运算符 &&

条件逻辑与运算符 &&(亦称为“短路”逻辑与运算符)计算操作数的逻辑与。 如果 xy 的计算结果都为 true,则 x && y 的结果为 true。 否则,结果为 false。 如果 x 的计算结果为 false,则不计算 y

条件逻辑或运算符 ||

条件逻辑或运算符 ||(亦称为“短路”逻辑或运算符)计算操作数的逻辑或。 如果 xy 的计算结果为 true,则 x || y 的结果为 true。 否则,结果为 false。 如果 x 的计算结果为 true,则不计算 y

运算符优先级

以下列表按优先级从高到低的顺序对逻辑运算符进行排序:

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

使用括号 () 可以更改运算符优先级决定的计算顺序。

位运算符和移位运算符

以下运算符使用整数类型char 类型的操作数执行位运算或移位运算:

这些运算符是针对 intuintlongulong 类型定义的。

按位求补运算符 ~

~ 运算符通过反转每个位产生其操作数的按位求补:

uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011

左移位运算符 <<

<< 运算符将其左侧操作数向左移动右侧操作数定义的位数

左移运算会放弃超出结果类型范围的高阶位,并将低阶空位位置设置为零,如以下示例所示:

uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2)}");

uint y = x << 4;
Console.WriteLine($"After:  {Convert.ToString(y, toBase: 2)}");
// Output:
// Before: 11001001000000000000000000010001
// After:  10010000000000000000000100010000

由于移位运算符仅针对 intuintlongulong 类型定义,因此运算的结果始终包含至少 32 位。 如果左侧操作数是其他整数类型(sbytebyteshortushortchar),则其值将转换为 int 类型,如以下示例所示:

byte a = 0b_1111_0001;

var b = a << 8;
Console.WriteLine(b.GetType());
Console.WriteLine($"Shifted byte: {Convert.ToString(b, toBase: 2)}");
// Output:
// System.Int32
// Shifted byte: 1111000100000000

右移位运算符 >>

>> 运算符将其左侧操作数向右移动右侧操作数定义的位数

右移位运算会放弃低阶位,如以下示例所示:

uint x = 0b_1001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2), 4}");

uint y = x >> 2;
Console.WriteLine($"After:  {Convert.ToString(y, toBase: 2), 4}");
// Output:
// Before: 1001
// After:    10

高顺序空位位置是根据左侧操作数类型设置的,如下所示:

  • 如果左侧操作数的类型是 intlong,则右移运算符将执行 算术移位:左侧操作数的最高有效位(符号位)的值将传播到高顺序空位位置。 也就是说,如果左侧操作数为非负,高顺序空位位置设置为零,如果为负,则将该位置设置为 1。
  int a = int.MinValue;
  Console.WriteLine($"Before: {Convert.ToString(a, toBase: 2)}");
  
  int b = a >> 3;
  Console.WriteLine($"After:  {Convert.ToString(b, toBase: 2)}");
  // Output:
  // Before: 10000000000000000000000000000000
  // After:  11110000000000000000000000000000
  • 如果左侧操作数的类型是 uintulong,则右移运算符执行逻辑移位:高顺序空位位置始终设置为零。
  uint c = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
  Console.WriteLine($"Before: {Convert.ToString(c, toBase: 2), 32}");
  
  uint d = c >> 3;
  Console.WriteLine($"After:  {Convert.ToString(d, toBase: 2), 32}");
  // Output:
  // Before: 10000000000000000000000000000000
  // After:     10000000000000000000000000000

相等运算符

==(相等)!=(不等) 运算符检查其操作数是否相等。

相等运算符==

如果操作数相等,等于运算符 == 返回 true,否则返回 false

值类型的相等性

如果内置值类型的值相等,则其操作数相等:

int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b);  // output: True

char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2);  // output: False
Console.WriteLine(c1 == char.ToLower(c2));  // output: True

引用类型相等性

默认情况下,如果两个非记录引用类型操作符引用同一对象,则这两个操作符相等:

public class ReferenceTypesEquality
{
    public class MyClass
    {
        private int id;

        public MyClass(int id) => this.id = id;
    }

    public static void Main()
    {
        var a = new MyClass(1);
        var b = new MyClass(1);
        var c = a;
        Console.WriteLine(a == b);  // output: False
        Console.WriteLine(a == c);  // output: True
    }
}

记录相等性

在 C# 9.0 和更高版本中提供,记录类型支持 ==!= 运算符,这些运算符默认提供值相等性语义。 也就是说,当两个记录操作数均为 null 或所有字段的对应值和自动实现的属性相等时,两个记录操作数都相等。

public class RecordTypesEquality
{
    public record Point(int X, int Y, string Name);
    public record TaggedNumber(int Number, List<string> Tags);

    public static void Main()
    {
        var p1 = new Point(2, 3, "A");
        var p2 = new Point(1, 3, "B");
        var p3 = new Point(2, 3, "A");

        Console.WriteLine(p1 == p2);  // output: False
        Console.WriteLine(p1 == p3);  // output: True

        var n1 = new TaggedNumber(2, new List<string>() { "A" });
        var n2 = new TaggedNumber(2, new List<string>() { "A" });
        Console.WriteLine(n1 == n2);  // output: False
    }
}

字符串相等性

如果两个字符串均为 null 或者两个字符串实例具有相等长度且在每个字符位置有相同字符,则这两个字符串]操作数相等:

string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower());  // output: True

string s3 = "Hello!";
Console.WriteLine(s1 == s3);  // output: False

不等运算符 !=

如果操作数不相等,不等于运算符 != 返回 true,否则返回 false。 对于内置类型的操作数,表达式 x != y 生成与表达式 !(x == y) 相同的结果。

int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b);  // output: True

string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2);  // output: False

object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2);  // output: True

比较运算符

<(小于)>(大于)<=(小于或等于)>=(大于或等于)比较(也称为关系)运算符比较其操作数。 所有整型浮点数值类型都支持这些运算符。

小于运算符 <

如果左侧操作数小于右侧操作数,< 运算符返回 true,否则返回 false

Console.WriteLine(7.0 < 5.1);   // output: False
Console.WriteLine(5.1 < 5.1);   // output: False
Console.WriteLine(0.0 < 5.1);   // output: True

Console.WriteLine(double.NaN < 5.1);   // output: False
Console.WriteLine(double.NaN >= 5.1);  // output: False

大于运算符 >

如果左侧操作数大于右侧操作数,> 运算符返回 true,否则返回 false

Console.WriteLine(7.0 > 5.1);   // output: True
Console.WriteLine(5.1 > 5.1);   // output: False
Console.WriteLine(0.0 > 5.1);   // output: False

Console.WriteLine(double.NaN > 5.1);   // output: False
Console.WriteLine(double.NaN <= 5.1);  // output: False

小于或等于运算符 <=

如果左侧操作数小于或等于右侧操作数,<= 运算符返回 true,否则返回 false

Console.WriteLine(7.0 <= 5.1);   // output: False
Console.WriteLine(5.1 <= 5.1);   // output: True
Console.WriteLine(0.0 <= 5.1);   // output: True

Console.WriteLine(double.NaN > 5.1);   // output: False
Console.WriteLine(double.NaN <= 5.1);  // output: False

大于或等于运算符 >=

如果左侧操作数大于或等于右侧操作数,>= 运算符返回 true,否则返回 false

Console.WriteLine(7.0 >= 5.1);   // output: True
Console.WriteLine(5.1 >= 5.1);   // output: True
Console.WriteLine(0.0 >= 5.1);   // output: False

Console.WriteLine(double.NaN < 5.1);   // output: False
Console.WriteLine(double.NaN >= 5.1);  // output: False

默认表达式

default value 表达式生成类型的默认值。 有两种类型的 default value 表达式:default 运算符调用和 default 文本

default 运算符的实参必须是类型或类型形参的名称,如以下示例所示:

Console.WriteLine(default(int));  // output: 0
Console.WriteLine(default(object) is null);  // output: True

void DisplayDefaultOf<T>()
{
    var val = default(T);
    Console.WriteLine($"Default value of {typeof(T)} is {(val == null ? "null" : val.ToString())}.");
}

DisplayDefaultOf<int?>();
DisplayDefaultOf<System.Numerics.Complex>();
DisplayDefaultOf<System.Collections.Generic.List<int>>();
// Output:
// Default value of System.Nullable`1[System.Int32] is null.
// Default value of System.Numerics.Complex is (0, 0).
// Default value of System.Collections.Generic.List`1[System.Int32] is null.

nameof表达式

nameof 表达式可生成变量、类型或成员的名称作为字符串常量

Console.WriteLine(nameof(System.Collections.Generic));  // output: Generic
Console.WriteLine(nameof(List<int>));  // output: List
Console.WriteLine(nameof(List<int>.Count));  // output: Count
Console.WriteLine(nameof(List<int>.Add));  // output: Add

var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine(nameof(numbers));  // output: numbers
Console.WriteLine(nameof(numbers.Count));  // output: Count
Console.WriteLine(nameof(numbers.Add));  // output: Add

Lambda表达式

见后续Lambda表式专门培训章节