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