多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# C# 运算符 > 原文: [https://zetcode.com/lang/csharp/operators/](https://zetcode.com/lang/csharp/operators/) 在 C# 教程的这一部分中,我们将讨论运算符。 表达式是根据操作数和运算符构造的。 表达式的运算符指示将哪些运算应用于操作数。 表达式中运算符的求值顺序由运算符的优先级和关联性确定 运算符是特殊符号,表示已执行某个过程。 编程语言的运算符来自数学。 程序员处理数据。 运算符用于处理数据。 操作数是运算符的输入(参数)之一。 ## C# 运算符列表 下表显示了 C# 语言中使用的一组运算符。 | 类别 | 符号 | | --- | --- | | 符号运算符 | `+ -` | | 算术 | `+ - * / %` | | 逻辑(布尔和按位) | `& &#124; ^ ! ~ && &#124;&#124; true false` | | 字符串连接 | `+` | | 递增,递减 | `++ --` | | 移位 | `<< >>` | | 关系 | `== != < > <= >=` | | 赋值 | `= += -= *= /= %= &= &#124;= ^= ??= <<= >>=` | | 成员访问 | `. ?.` | | 索引 | `[] ?[]` | | 调用 | `()` | | 三元 | `?:` | | 委托连接和删除 | `+ -` | | 对象创建 | `new` | | 类型信息 | `as is sizeof typeof` | | 异常控制 | `checked unchecked` | | 间接地址 | `* -> [] &` | | Lambda | `=>` | 一个运算符通常有一个或两个操作数。 那些仅使用一个操作数的运算符称为一元运算符。 那些使用两个操作数的对象称为二进制运算符。 还有一个三元运算符`?:`,它可以处理三个操作数。 某些运算符可以在不同的上下文中使用。 例如+运算符。 从上表中我们可以看到它在不同情况下使用。 它添加数字,连接字符串或委托; 表示数字的符号。 我们说运算符是重载。 ## C# 一元运算符 C# 一元运算符包括:+,-,++,-,强制转换运算符()和否定!。 ### C# 符号运算符 有两个符号运算符:`+`和`-`。 它们用于指示或更改值的符号。 `Program.cs` ```cs using System; namespace MinusSign { class Program { static void Main(string[] args) { Console.WriteLine(2); Console.WriteLine(+2); Console.WriteLine(-2); } } } ``` `+`和`-`符号指示值的符号。 加号可以用来表示我们有一个正数。 可以将其省略,并且通常可以这样做。 `Program.cs` ```cs using System; public class MinusSign { static void Main() { int a = 1; Console.WriteLine(-a); Console.WriteLine(-(-a)); } } ``` 减号更改值的符号。 ```cs $ dotnet run -1 1 ``` 这是输出。 ### C# 增减运算符 将值递增或递减一个是编程中的常见任务。 C# 为此有两个方便的运算符:`++`和`--`。 ```cs x++; x = x + 1; ... y--; y = y - 1; ``` 上面两对表达式的作用相同。 `Program.cs` ```cs using System; namespace IncrementDecrement { class Program { static void Main(string[] args) { int x = 6; x++; x++; Console.WriteLine(x); x--; Console.WriteLine(x); } } } ``` 在上面的示例中,我们演示了两个运算符的用法。 ```cs int x = 6; x++; x++; ``` 将`x`变量初始化为 6。然后将`x`递增两次。 现在变量等于 8。 ```cs x--; ``` 我们使用减量运算符。 现在变量等于 7。 ```cs $ dotnet run 8 7 ``` ### C# 显式强制转换运算符 显式强制转换运算符()可用于将一个类型强制转换为另一个类型。 请注意,此运算符仅适用于某些类型。 `Program.cs` ```cs using System; namespace CastOperator { class Program { static void Main(string[] args) { float val = 3.2f; int num = (int) val; System.Console.WriteLine(num); } } } ``` 在该示例中,我们将`float`类型显式转换为`int`。 ### 求反运算符 取反运算符(!)反转其操作数的含义。 `Program.cs` ```cs using System; namespace Negation { class Program { static void Main(string[] args) { var isValid = false; if (!isValid) { Console.WriteLine("The option is not valid"); } } } } ``` 在该示例中,我们建立了一个否定条件:如果表达式的逆数有效,则执行该条件。 ## C# 赋值运算符 赋值运算符`=`将值赋给变量。 变量是值的占位符。 在数学中,`=`运算符具有不同的含义。 在等式中,`=`运算符是一个相等运算符。 等式的左侧等于右侧。 ```cs int x = 1; ``` 在这里,我们为`x`变量分配一个数字。 ```cs x = x + 1; ``` 先前的表达式在数学上没有意义。 但这在编程中是合法的。 该表达式将`x`变量加 1。 右边等于 2,并且 2 分配给`x`。 ```cs 3 = x; ``` 此代码示例导致语法错误。 我们无法为字面值分配值。 ## C# 连接字符串 +运算符还用于连接字符串。 `Program.cs` ```cs using System; namespace Concatenate { class Program { static void Main(string[] args) { Console.WriteLine("Return " + "of " + "the king."); } } } ``` 我们使用字符串连接运算符将三个字符串连接在一起。 ```cs $ dotnet run Return of the king. ``` 这是该计划的结果。 ## C# 算术运算符 下表是 C# 中的算术运算符表。 | 符号 | 名称 | | --- | --- | | `+` | 加法 | | `-` | 减法 | | `*` | 乘法 | | `/` | 除法 | | `%` | 余数 | 以下示例显示了算术运算。 `Project.cs` ```cs using System; namespace Arithmetic { class Program { static void Main(string[] args) { int a = 10; int b = 11; int c = 12; int add = a + b + c; int sb = c - a; int mult = a * b; int div = c / 3; int rem = c % a; Console.WriteLine(add); Console.WriteLine(sb); Console.WriteLine(mult); Console.WriteLine(div); Console.WriteLine(rem); } } } ``` 在前面的示例中,我们使用加法,减法,乘法,除法和余数运算。 这些都是数学所熟悉的。 ```cs int rem = c % a; ``` `%`运算符称为余数或模运算符。 它找到一个数除以另一个的余数。 例如`9 % 4`,9 模 4 为 1,因为 4 两次进入 9 且余数为 1。 ```cs $ dotnet run 33 2 110 4 2 ``` 这是示例的输出。 接下来,我们将说明整数除法和浮点除法之间的区别。 `Program.cs` ```cs using System; namespace Division { class Program { static void Main(string[] args) { int c = 5 / 2; Console.WriteLine(c); double d = 5 / 2.0; Console.WriteLine(d); } } } ``` 在前面的示例中,我们将两个数字相除。 ```cs int c = 5 / 2; Console.WriteLine(c); ``` 在这段代码中,我们完成了整数除法。 除法运算的返回值为整数。 当我们将两个整数相除时,结果是一个整数。 ```cs double d = 5 / 2.0; Console.WriteLine(d); ``` 如果值之一是`double`或`float`,则执行浮点除法。 在我们的例子中,第二个操作数是双精度数,因此结果是双精度数。 ```cs $ dotnet run 2 2.5 ``` 我们看到了程序的结果。 ## C# 布尔运算符 在 C# 中,我们有三个逻辑运算符。 `bool`关键字用于声明布尔值。 | 符号 | 名称 | | --- | --- | | `&&` | 逻辑与 | | <code>&#124;&#124;</code> | 逻辑或 | | `!` | 否定 | 布尔运算符也称为逻辑运算符。 `Program.cs` ```cs using System; namespace Boolean { class Program { static void Main(string[] args) { int x = 3; int y = 8; Console.WriteLine(x == y); Console.WriteLine(y > x); if (y > x) { Console.WriteLine("y is greater than x"); } } } } ``` 许多表达式导致布尔值。 布尔值用于条件语句中。 ```cs Console.WriteLine(x == y); Console.WriteLine(y > x); ``` 关系运算符始终导致布尔值。 这两行分别显示`false`和`true`。 ```cs if (y > x) { Console.WriteLine("y is greater than x"); } ``` 仅在满足括号内的条件时才执行`if`语句的主体。 `y > x`返回`true`,因此消息`"y`大于`x"`被打印到终端。 `true`和`false`关键字表示 C# 中的布尔字面值。 `Program.cs` ```cs using System; namespace AndOperator { class Program { static void Main(string[] args) { bool a = true && true; bool b = true && false; bool c = false && true; bool d = false && false; Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(c); Console.WriteLine(d); } } } ``` 示例显示了逻辑和运算符。 仅当两个操作数均为`true`时,它的求值结果为`true`。 ```cs $ dotnet run True False False False ``` `True`只产生一个表达式。 如果两个操作数中的任何一个为`true`,则逻辑或`||`运算符的计算结果为`true`。 `Program.cs` ```cs using System; namespace OrOperator { class Program { static void Main(string[] args) { bool a = true || true; bool b = true || false; bool c = false || true; bool d = false || false; Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(c); Console.WriteLine(d); } } } ``` 如果运算符的任一侧为真,则操作的结果为真。 ```cs $ dotnet run True True True False ``` 四个表达式中的三个表示为`true`。 否定运算符`!`将`true`设为`false`,并将`false`设为`false`。 `Program.cs` ```cs using System; namespace NegationEx { class Program { static void Main(string[] args) { Console.WriteLine(!true); Console.WriteLine(!false); Console.WriteLine(!(4 < 3)); } } } ``` 该示例显示了否定运算符的作用。 ```cs $ dotnet run False True True ``` 这是`negation.exe`程序的输出。 `||`和`&&`运算符经过短路求值。 短路求值意味着仅当第一个参数不足以确定表达式的值时,才求值第二个参数:当逻辑的第一个参数的结果为`false`时,总值必须为`false`; 当逻辑或的第一个参数为`true`时,总值必须为`true`。 短路求值主要用于提高性能。 一个例子可以使这一点更加清楚。 `Program.cs` ```cs using System; namespace ShortCircuit { class Program { static void Main(string[] args) { Console.WriteLine("Short circuit"); if (One() && Two()) { Console.WriteLine("Pass"); } Console.WriteLine("#############"); if (Two() || One()) { Console.WriteLine("Pass"); } } public static bool One() { Console.WriteLine("Inside one"); return false; } public static bool Two() { Console.WriteLine("Inside two"); return true; } } } ``` 在示例中,我们有两种方法。 它们在布尔表达式中用作操作数。 我们将看看它们是否被调用。 ```cs if (One() && Two()) { Console.WriteLine("Pass"); } ``` `One()`方法返回`false`。 短路`&&`不求值第二种方法。 没有必要。 一旦操作数为`false`,则逻辑结论的结果始终为`false`。 仅将`"Inside one"`打印到控制台。 ```cs Console.WriteLine("#############"); if (Two() || One()) { Console.WriteLine("Pass"); } ``` 在第二种情况下,我们使用`||`运算符,并使用`Two()`方法作为第一个操作数。 在这种情况下,`"Inside two"`和`"Pass"`字符串将打印到终端。 再次不必求值第二操作数,因为一旦第一操作数求值为`true`,则逻辑或始终为`true`。 ```cs $ ShortCircuit>dotnet run Short circuit Inside one ############# Inside two Pass ``` We see the result of the program. ## C# 关系运算符 关系运算符用于比较值。 这些运算符总是产生布尔值。 | 符号 | 含义 | | --- | --- | | `<` | 小于 | | `<=` | 小于或等于 | | `>` | 大于 | | `>=` | 大于或等于 | | `==` | 等于 | | `!=` | 不等于 | 关系运算符也称为比较运算符。 `Program.cs` ```cs using System; namespace Relational { class Program { static void Main(string[] args) { Console.WriteLine(3 < 4); Console.WriteLine(3 == 4); Console.WriteLine(4 >= 3); Console.WriteLine(4 != 3); } } } ``` 在代码示例中,我们有四个表达式。 这些表达式比较整数值。 每个表达式的结果为`true`或`false`。 在 C# 中,我们使用`==`比较数字。 某些语言(例如 Ada,Visual Basic 或 Pascal)使用`=`比较数字。 ## C# 按位运算符 小数对人类是自然的。 二进制数是计算机固有的。 二进制,八进制,十进制或十六进制符号仅是相同数字的符号。 按位运算符使用二进制数的位。 像 C# 这样的高级语言很少使用按位运算符。 | 符号 | 含义 | | --- | --- | | `~` | 按位取反 | | `^` | 按位异或 | | `&` | 按位与 | | <code>&#124;</code> | 按位或 | 按位取反运算符分别将 1 更改为 0,将 0 更改为 1。 ```cs Console.WriteLine(~ 7); // prints -8 Console.WriteLine(~ -8); // prints 7 ``` 运算符恢复数字 7 的所有位。这些位之一还确定数字是否为负。 如果我们再一次对所有位取反,我们将再次得到 7。 按位,运算符在两个数字之间进行逐位比较。 仅当操作数中的两个对应位均为 1 时,位位置的结果才为 1。 ```cs 00110 & 00011 = 00010 ``` 第一个数字是二进制表示法 6,第二个数字是 3,结果是 2。 ```cs Console.WriteLine(6 & 3); // prints 2 Console.WriteLine(3 & 6); // prints 2 ``` 按位或运算符在两个数字之间进行逐位比较。 如果操作数中的任何对应位为 1,则位位置的结果为 1。 ```cs 00110 | 00011 = 00111 ``` 结果为`00110`或十进制 7。 ```cs Console.WriteLine(6 | 3); // prints 7 Console.WriteLine(3 | 6); // prints 7 ``` 按位互斥或运算符在两个数字之间进行逐位比较。 如果操作数中对应位中的一个或另一个(但不是全部)为 1,则位位置的结果为 1。 ```cs 00110 ^ 00011 = 00101 ``` 结果为`00101`或十进制 5。 ```cs Console.WriteLine(6 ^ 3); // prints 5 Console.WriteLine(3 ^ 6); // prints 5 ``` ## C# 复合赋值运算符 复合赋值运算符由两个运算符组成。 他们是速记员。 ```cs a = a + 3; a += 3; ``` `+=`复合运算符是这些速记运算符之一。 以上两个表达式相等。 将值 3 添加到变量 a 中。 其他复合运算符是: ```cs -= *= /= %= &= |= <<= >>= ``` `Program.cs` ```cs using System; namespace CompoundOperators { class Program { static void Main(string[] args) { int a = 1; a = a + 1; Console.WriteLine(a); a += 5; Console.WriteLine(a); a *= 3; Console.WriteLine(a); } } } ``` 在示例中,我们使用两个复合运算符。 ```cs int a = 1; a = a + 1; ``` `a`变量被初始化为 1。 使用非速记符号将 1 添加到变量。 ```cs a += 5; ``` 使用`+=`复合运算符,将 5 加到`a`变量中。 该语句等于`a = a + 5;`。 ```cs a *= 3; ``` 使用`*=`运算符,将`a`乘以 3。该语句等于`a = a * 3;`。 ```cs $ dotnet run 2 7 21 ``` 这是示例输出。 ## C# `new`运算符 `new`运算符用于创建对象和调用构造器。 `Program.cs` ```cs using System; namespace NewOperator { class Being { public Being() { Console.WriteLine("Being created"); } } class Program { static void Main(string[] args) { var b = new Being(); Console.WriteLine(b); var vals = new int[] { 1, 2, 3, 4, 5 }; System.Console.WriteLine(string.Join(" ", vals)); } } } ``` 在示例中,我们使用`new`运算符创建了一个新的自定义对象和一个整数数组。 ```cs public Being() { Console.WriteLine("Being created"); } ``` 这是一个构造器。 在创建对象时调用它。 ```cs $ dotnet run Being created NewOperator.Being 1 2 3 4 5 ``` This is the output. ## C# 访问运算符 访问运算符`[]`与数组,索引器和属性一起使用。 `Program.cs` ```cs using System; using System.Collections.Generic; namespace AccessOperator { class Program { static void Main(string[] args) { var vals = new int[] { 2, 4, 6, 8, 10 }; Console.WriteLine(vals[0]); var domains = new Dictionary<string, string>() { { "de", "Germany" }, { "sk", "Slovakia" }, { "ru", "Russia" } }; Console.WriteLine(domains["de"]); oldMethod(); } [Obsolete("Don't use OldMethod, use NewMethod instead", false)] public static void oldMethod() { Console.WriteLine("oldMethod()"); } public static void newMethod() { Console.WriteLine("newMethod()"); } } } ``` 在该示例中,我们使用`[]`运算符获取数组的元素,字典对的值,并激活内置属性。 ```cs var vals = new int[] { 2, 4, 6, 8, 10 }; Console.WriteLine(vals[0]); ``` 我们定义一个整数数组。 我们用`vals[0]`获得第一个元素。 ```cs var domains = new Dictionary<string, string>() { { "de", "Germany" }, { "sk", "Slovakia" }, { "ru", "Russia" } }; Console.WriteLine(domains["de"]); ``` 创建字典。 使用`domains["de"]`,我们获得具有`"de"`键的货币对的值。 ```cs [Obsolete("Don't use OldMethod, use NewMethod instead", false)] public static void oldMethod() { Console.WriteLine("oldMethod()"); } ``` 我们激活了内置的`Obsolete`属性。 该属性发出警告。 ```cs $ dotnet run Program.cs(22,13): warning CS0618: 'Program.oldMethod()' is obsolete: 'Don't use OldMethod, use NewMethod instead' [C:\Users\Jano\Documents\csharp\tutorial\AccessOperator\AccessOperator.csproj] 2 Germany oldMethod() ``` This is the output. ## C# 索引的最终运算符`^` 结束运算符^的索引指示从序列结尾开始的元素位置。 例如,`^1`指向序列的最后一个元素,`^n`指向偏移为`length - n`的元素。 `Program.cs` ```cs using System; using System.Linq; namespace IndexFromEnd { class Program { static void Main(string[] args) { int[] vals = { 1, 2, 3, 4, 5 }; Console.WriteLine(vals[^1]); Console.WriteLine(vals[^2]); var word = "gray falcon"; Console.WriteLine(word[^1]); } } } ``` 在示例中,我们将运算符应用于数组和字符串。 ```cs int[] vals = { 1, 2, 3, 4, 5 }; Console.WriteLine(vals[^1]); Console.WriteLine(vals[^2]); ``` 我们打印数组的最后一个元素和最后一个元素。 ```cs var word = "gray falcon"; Console.WriteLine(word[^1]); ``` 我们打印单词的最后一个字母。 ```cs $ dotnet run 5 4 n ``` This is the output. ## C# 范围运算符`..` `..`运算符指定索引范围的开始和结束作为其操作数。 左侧操作数是范围的一个包含范围的开始。 右侧操作数是范围的排他端。 ```cs x.. is equivalent to x..^0 ..y is equivalent to 0..y .. is equivalent to 0..^0 ``` 可以省略`..`运算符的操作数以获取开放范围。 `Program.cs` ```cs using System; namespace RangeOperator { class Program { static void Main(string[] args) { int[] vals = { 1, 2, 3, 4, 5, 6, 7 }; var slice1 = vals[1..4]; Console.WriteLine("[{0}]", String.Join(", ", slice1)); var slice2 = vals[..^0]; Console.WriteLine("[{0}]", String.Join(", ", slice2)); } } } ``` 在示例中,我们使用`..`运算符获取数组切片。 ```cs var range1 = vals[1..4]; Console.WriteLine("[{0}]", String.Join(", ", range1)); ``` 我们创建一个从索引 1 到索引 4 的数组切片; 最后一个索引 4 不包括在内。 ```cs var slice2 = vals[..^0]; Console.WriteLine("[{0}]", String.Join(", ", slice2)); ``` 在这里,我们基本上创建了数组的副本。 ```cs $ dotnet run [2, 3, 4] [1, 2, 3, 4, 5, 6, 7] ``` This is the output. ## C# 类型信息 现在,我们将关注使用类型的运算符。 `sizeof`运算符用于获取值类型的字节大小。 `typeof`用于获取类型的`System.Type`对象。 `Program.cs` ```cs using System; namespace SizeType { class Program { static void Main(string[] args) { Console.WriteLine(sizeof(int)); Console.WriteLine(sizeof(float)); Console.WriteLine(sizeof(Int32)); Console.WriteLine(typeof(int)); Console.WriteLine(typeof(float)); } } } ``` 我们使用`sizeof`和`typeof`运算符。 ```cs $ dotnet run 4 4 4 System.Int32 ``` 我们可以看到`int`类型是`System.Int32`的别名,`float`是`System.Single`类型的别名。 `is`操作符检查对象是否与给定类型兼容。 `Program.cs` ```cs using System; namespace IsOperator { class Base { } class Derived : Base { } class Program { static void Main(string[] args) { Base _base = new Base(); Derived derived = new Derived(); Console.WriteLine(_base is Base); Console.WriteLine(_base is Object); Console.WriteLine(derived is Base); Console.WriteLine(_base is Derived); } } } ``` 我们根据用户定义的类型创建两个对象。 ```cs class Base {} class Derived : Base {} ``` 我们有一个`Base`和`Derived`类。 `Derived`类继承自`Base`类。 ```cs Console.WriteLine(_base is Base); Console.WriteLine(_base is Object); ``` `Base`等于`Base`,因此第一行显示`True`。 `Base`也与`Object`类型兼容。 这是因为每个类都继承自所有类的母体`Object`类。 ```cs Console.WriteLine(derived is Base); Console.WriteLine(_base is Derived); ``` 派生对象与`Base`类兼容,因为它显式继承自`Base`类。 另一方面,`_base`对象与`Derived`类无关。 ```cs $ dotnet run True True True False ``` 这是示例的输出。 `as`运算符用于在兼容的引用类型之间执行转换。 如果无法进行转换,则运算符将返回`null`。 与强制转换操作不同,后者引发异常。 `Program.cs` ```cs using System; namespace AsOperator { class Base { } class Derived : Base { } class Program { static void Main(string[] args) { object[] objects = new object[6]; objects[0] = new Base(); objects[1] = new Derived(); objects[2] = "ZetCode"; objects[3] = 12; objects[4] = 1.4; objects[5] = null; for (int i = 0; i < objects.Length; i++) { string s = objects[i] as string; Console.Write("{0}:", i); if (s != null) { Console.WriteLine(s); } else { Console.WriteLine("not a string"); } } } } } ``` 在上面的示例中,我们使用`as`运算符执行转换。 ```cs string s = objects[i] as string; ``` 我们尝试将各种类型转换为字符串类型。 但只有一次转换有效。 ```cs $ dotnet run 0:not a string 1:not a string 2:ZetCode 3:not a string 4:not a string 5:not a string ``` This is the output of the example. ## C# 运算符优先级 运算符优先级告诉我们首先求值哪个运算符。 优先级对于避免表达式中的歧义是必要的。 以下表达式 28 或 40 的结果是什么? ```cs 3 + 5 * 5 ``` 像数学中一样,乘法运算符的优先级高于加法运算符。 结果是 28。 ```cs (3 + 5) * 5 ``` 要更改求值的顺序,可以使用括号。 括号内的表达式始终首先被求值。 下表显示了按优先级排序的通用 C# 运算符(优先级最高): | 运算符 | 类别 | 关联性 | | --- | --- | --- | | 主要 | `x.y x?.y, x?[y] f(x) a[x] x++ x-- new typeof default checked unchecked` | 左 | | 一元 | `+ - ! ~ ++x --x (T)x` | 左 | | 乘法 | `* / %` | 左 | | 加法 | `+ -` | 左 | | 移位 | `<< >>` | 左 | | 相等 | `== !=` | 右 | | 逻辑与 | `&` | 左 | | 逻辑异或 | `^` | 左 | | 逻辑或 | <code>&#124;</code> | 左 | | 条件与 | `&&` | 左 | | 条件或 | <code>&#124;&#124;</code> | 左 | | 空合并 | `??` | 左 | | 三元 | `?:` | 右 | | 赋值 | `= *= /= %= += -= <<= >>= &= ^= &#124;= ??= =>` | 右 | 表的同一行上的运算符具有相同的优先级。 `Program.cs` ```cs using System; namespace Precedence { class Program { static void Main(string[] args) { Console.WriteLine(3 + 5 * 5); Console.WriteLine((3 + 5) * 5); Console.WriteLine(! true | true); Console.WriteLine(! (true | true)); } } } ``` 在此代码示例中,我们显示一些表达式。 每个表达式的结果取决于优先级。 ```cs Console.WriteLine(3 + 5 * 5); ``` 该行打印 28。乘法运算符的优先级高于加法。 首先,计算`5*5`的乘积,然后加 3。 ```cs Console.WriteLine(! true | true); ``` 在这种情况下,否定运算符具有更高的优先级。 首先,将第一个`true`值取反为`false`,然后`|`运算符将`false`和`true`组合在一起,最后给出`true`。 ```cs $ dotnet run 28 40 True False ``` 这是`precedence.exe`程序的结果。 ## C# 关联规则 有时,优先级不能令人满意地确定表达式的结果。 还有另一个规则称为关联性。 运算符的关联性确定优先级与相同的运算符的求值顺序。 ```cs 9 / 3 * 3 ``` 此表达式的结果是 9 还是 1? 乘法,删除和模运算符从左到右关联。 因此,该表达式的计算方式为:`(9 / 3) * 3`,结果为 9。 算术,布尔,关系和按位运算符都是从左到右关联的。 另一方面,赋值运算符是正确关联的。 `Program.cs` ```cs using System; namespace Associativity { class Program { static void Main(string[] args) { int a, b, c, d; a = b = c = d = 0; Console.WriteLine("{0} {1} {2} {3}", a, b, c, d); int j = 0; j *= 3 + 1; Console.WriteLine(j); } } } ``` 在该示例中,有两种情况,其中关联性规则确定表达式。 ```cs int a, b, c, d; a = b = c = d = 0; ``` 赋值运算符从右到左关联。 如果关联性从左到右,则以前的表达式将不可能。 ```cs int j = 0; j *= 3 + 1; ``` 复合赋值运算符从右到左关联。 我们可能期望结果为 1。但是实际结果为 0。由于有关联性。 首先求值右边的表达式,然后应用复合赋值运算符。 ```cs $ dotnet run 0 0 0 0 0 ``` This is the output. ## C# 空条件运算符 空条件运算符仅在该操作数的值为非空时才将成员访问`?.`或元素访问 `?[]`应用于其操作数。 如果操作数的值为`null`,则应用运算符的结果为`null`。 `Program.cs` ```cs using System; using System.Collections.Generic; namespace NullConditional { class User { public User() { } public User(string name, string occupation) { this.name = name; this.occupation = occupation; } public string name { get; set; } public string occupation { get; set; } public override string ToString() => $"{name} {occupation}"; } class Program { static void Main(string[] args) { var users = new List<User>() { new User("John Doe", "gardener"), new User(), new User("Lucia Newton", "teacher") }; users.ForEach(user => Console.WriteLine(user.name?.ToUpper())); } } } ``` 在示例中,我们有一个带有两个成员的`User`类:`name`和`occupation`。 我们在`?.`运算符的帮助下访问对象的`name`成员。 ```cs var users = new List<User>() { new User("John Doe", "gardener"), new User(), new User("Lucia Newton", "teacher") }; ``` 我们有一个用户列表。 其中一个未初始化,因此其成员为`null`。 ```cs users.ForEach(user => Console.WriteLine(user.name?.ToUpper())); ``` 我们使用`?.`访问`name`成员并调用`ToUpper()`方法。 `?.`通过不调用`null`值上的`ToUpper()`来防止`System.NullReferenceException`。 ```cs $ dotnet run JOHN DOE LUCIA NEWTON ``` This is the output. 在下面的示例中,我们使用`[].`运算符。 `Program.cs` ```cs using System; namespace NullConditional2 { class Program { static void Main(string[] args) { int?[] vals = { 1, 2, 3, null, 4, 5 }; int i = 0; while (i < vals.Length) { Console.WriteLine(vals[i]?.GetType()); i++; } } } } ``` 在此示例中,我们在数组中有一个`null`值。 我们通过在数组元素上应用`[].`运算符来防止`System.NullReferenceException`。 ## C# 空值运算符 空合并运算符`??`用于定义`nullable`类型的默认值。 如果不为`null`,则返回左侧操作数;否则返回 0。 否则返回正确的操作数。 当我们使用数据库时,我们经常处理缺失的值。 这些值在程序中为空。 该运算符是处理此类情况的便捷方法。 `Program.cs` ```cs using System; namespace NullCoalescing { class Program { static void Main(string[] args) { int? x = null; int? y = null; int z = x ?? y ?? -1; Console.WriteLine(z); } } } ``` 空合并运算符的示例程序。 ```cs int? x = null; int? y = null; ``` 两种可为空的`int`类型被初始化为`null`。 `int?`是`Nullable<int>`的简写。 它允许将空值分配给`int`类型。 ```cs int z = x ?? y ?? -1; ``` 我们要为`z`变量分配一个值。 但是它一定不是`null`。 这是我们的要求。 我们可以轻松地为此使用`null`折叠运算符。 如果`x`和`y`变量均为空,我们将 -1 分配给`z`。 ```cs $ dotnet run -1 ``` 这是程序的输出。 ## C# 空折叠赋值运算符 仅当左侧操作数的值为`null`时,空合并赋值运算符`??=`才将其右侧操作数的值分配给其左侧操作数。 如果`??=`运算符的左手操作数取值为非空,则不计算其右手操作数。 它在 C# 8.0 和更高版本中可用。 `Program.cs` ```cs using System; using System.Collections.Generic; namespace NullCoalescingAssignment { class Program { static void Main(string[] args) { List<int> vals = null; vals ??= new List<int>() {1, 2, 3, 4, 5, 6}; vals.Add(7); vals.Add(8); vals.Add(9); Console.WriteLine(string.Join(", ", vals)); vals ??= new List<int>() {1, 2, 3, 4, 5, 6}; Console.WriteLine(string.Join(", ", vals)); } } } ``` 在该示例中,我们在整数值列表上使用`null`折叠赋值运算符。 ```cs List<int> vals = null; ``` 首先,将列表分配给`null`。 ```cs vals ??= new List<int>() {1, 2, 3, 4, 5, 6}; ``` 我们使用`??=`将新的列表对象分配给变量。 由于它是`null`,因此分配了列表。 ```cs vals.Add(7); vals.Add(8); vals.Add(9); Console.WriteLine(string.Join(", ", vals)); ``` 我们将一些值添加到列表中并打印其内容。 ```cs vals ??= new List<int>() {1, 2, 3, 4, 5, 6}; ``` 我们尝试为变量分配一个新的列表对象。 由于该变量不再是`null`,因此不会分配该列表。 ```cs $ dotnet run 1, 2, 3, 4, 5, 6, 7, 8, 9 1, 2, 3, 4, 5, 6, 7, 8, 9 ``` This is the output. ## C# 三元运算符 三元运算符`?:`是条件运算符。 对于要根据条件表达式选择两个值之一的情况,它是一个方便的运算符。 ```cs cond-exp ? exp1 : exp2 ``` 如果`cond-exp`为`true`,则求值`exp1`并返回结果。 如果`cond-exp`为`false`,则求值`exp2`并返回其结果。 `Program.cs` ```cs using System; namespace Ternary { class Program { static void Main(string[] args) { int age = 31; bool adult = age >= 18 ? true : false; Console.WriteLine("Adult: {0}", adult); } } } ``` 在大多数国家/地区,成年取决于您的年龄。 如果您的年龄超过特定年龄,则您已经成年。 对于三元运算符,这是一种情况。 ```cs bool adult = age >= 18 ? true : false; ``` 首先,对赋值运算符右侧的表达式进行求值。 三元运算符的第一阶段是条件表达式求值。 因此,如果年龄大于或等于 18,则返回`?`字符后的值。 如果不是,则返回`:`字符后的值。 然后将返回值分配给成人变量。 ```cs $ dotnet run Adult: True ``` 31 岁的成年人是成年人。 ## C# Lambda 运算符 `=>`令牌称为 lambda 运算符。 它是从函数式语言中提取的运算符。 该运算符可以使代码更短,更清晰。 另一方面,理解语法可能很棘手。 特别是如果程序员以前从未使用过函数式语言。 只要可以使用委托,我们都可以使用 lambda 表达式。 lambda 表达式的定义是:lambda 表达式是一个匿名函数,可以包含表达式和语句。 左边是一组数据,右边是表达式或语句块。 这些语句应用于数据的每个项目。 在 lambda 表达式中,我们没有`return`关键字。 最后一条语句自动返回。 而且,我们不需要为参数指定类型。 编译器将猜测正确的参数类型。 这称为类型推断。 `Program.cs` ```cs using System; using System.Collections.Generic; namespace LambdaOperator { class Program { static void Main(string[] args) { var list = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 }; var subList = list.FindAll(val => val > 3); foreach (int i in subList) { Console.WriteLine(i); } } } } ``` 我们有一个整数列表。 我们打印所有大于 3 的数字。 ```cs var list = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 }; ``` 我们有一个通用的整数列表。 ```cs var subList = list.FindAll(val => val > 3); ``` 在这里,我们使用 lambda 运算符。 `FindAll()`方法采用谓词作为参数。 谓词是一种特殊的委托,它返回布尔值。 该谓词适用于列表中的所有项目。 `val`是没有类型指定的输入参数。 我们可以明确指定类型,但这不是必需的。 编译器将期望使用`int`类型。 `val`是列表中的当前输入值。 比较它是否大于 3 并返回布尔值`true`或`false`。 最后,`FindAll()`将返回所有符合条件的值。 它们被分配给子列表集合。 ```cs foreach (int i in subList) { Console.WriteLine(i); } ``` 子列表集合的项目将打印到终端。 ```cs $ dotnet run 8 6 4 7 9 5 ``` 大于 3 的整数列表中的值。 `Program.cs` ```cs using System; using System.Collections.Generic; namespace AnonymousDelegate { class Program { static void Main(string[] args) { var nums = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 }; var nums2 = nums.FindAll( delegate(int i) { return i > 3; } ); foreach (int i in nums2) { Console.WriteLine(i); } } } } ``` 这是相同的例子。 我们使用匿名委托代替 lambda 表达式。 ## C# 计算素数 我们将计算素数。 `Program.cs` ```cs using System; namespace Primes { class Program { static void Main(string[] args) { int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 }; Console.Write("Prime numbers: "); foreach (int num in nums) { if (num == 1) continue; if (num == 2 || num == 3) { Console.Write(num + " "); continue; } int i = (int) Math.Sqrt(num); bool isPrime = true; while (i > 1) { if (num % i == 0) { isPrime = false; } i--; } if (isPrime) { Console.Write(num + " "); } } Console.Write('\n'); } } } ``` 在上面的示例中,我们处理了许多不同的运算符。 质数(或质数)是一个自然数,它具有两个截然不同的自然数除数:1 和它本身。 我们拾取一个数字并将其除以数字,从 1 到拾取的数字。 实际上,我们不必尝试所有较小的数字。 我们可以将数字除以所选数字的平方根。 该公式将起作用。 我们使用余数除法运算符。 ```cs int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 }; ``` 我们将从这些数字计算素数。 ```cs if (num == 1) continue; ``` 根据定义,1 不是质数 ```cs if (num == 2 || num == 3) { Console.Write(num + " "); continue; } ``` 我们跳过 2 和 3 的计算,它们是质数。 请注意等式和条件或运算符的用法。 `==`的优先级高于`||`运算符。 因此,我们不需要使用括号。 ```cs int i = (int) Math.Sqrt(num); ``` 如果我们仅尝试小于所讨论数字的平方根的数字,那么我们可以。 ```cs while (i > 1) { ... i--; } ``` 这是一个`while`循环。 `i`是计算出的数字的平方根。 我们使用减量运算符将每个循环周期的`i`减 1。 当`i`小于 1 时,我们终止循环。 例如,我们有 9。9 的平方根是 3。我们将 9 的数字除以 3 和 2。这对于我们的计算就足够了。 ```cs if (num % i == 0) { isPrime = false; } ``` 这是算法的核心。 如果余数除法运算符对于任何`i`值返回 0,则说明所讨论的数字不是质数。 在 C# 教程的这一部分中,我们介绍了 C# 运算符。