十、C# 复合赋值运算符
复合赋值运算符由两个运算符组成。他们是速记运算符。
a = a + 3; a += 3;
+= 复合运算符是这些速记运算符之一。上面两个表达式是相等的。值 3 被添加到 a 变量中。
其他复合运算符有:
-= *= /= %= &= |= <<= >>=
Program.cs
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
var b = new Being(); Console.WriteLine(b); var vals = new int[] { 1, 2, 3, 4, 5 }; Console.WriteLine(string.Join(" ", vals)); class Being { public Being() { Console.WriteLine("Being created"); } }
在示例中,我们使用 new 运算符创建了一个新的自定义对象和一个整数数组。
public Being() { Console.WriteLine("Being created"); }
这是一个构造函数。它在对象创建时被调用。
$ dotnet run Being created Being 1 2 3 4 5
十二、C# 访问运算符
访问运算符 [] 与数组、索引器和属性一起使用。
Program.cs
var vals = new int[] { 2, 4, 6, 8, 10 }; Console.WriteLine(vals[0]); var domains = new Dictionary() { { "de", "Germany" }, { "sk", "Slovakia" }, { "ru", "Russia" } }; Console.WriteLine(domains["de"]); oldMethod(); [Obsolete("Don't use OldMethod, use NewMethod instead", false)] void oldMethod() { Console.WriteLine("oldMethod()"); } 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()"); }
我们激活了内置的过时属性。该属性发出警告。
当我们运行程序时,它会产生警告:warning CS0618: 'oldMethod()' is obsolete: 'Don't use OldMethod, use NewMethod instead'。
十三、C# 索引符号 ^
结束运算符 ^ 的索引表示从序列末尾开始的元素位置。例如,^1 指向序列的最后一个元素,^n 指向偏移长度为 n 的元素。
Program.cs
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
十四、C# 范围操作符..
.. 运算符将索引范围的开始和结束指定为其操作数。左边的操作数是一个范围的包含开始。右手操作数是一个范围的唯一结束。
x.. is equivalent to x..^0 ..y is equivalent to 0..y .. is equivalent to 0..^0
.. 运算符的操作数可以省略以获得开放范围。
Program.cs
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]
十五、C# 类型信息
现在我们关注使用类型的运算符。
sizeof 运算符用于获取值类型的大小(以字节为单位)。 typeof 用于获取类型的 System.Type 对象。
Program.cs
Console.WriteLine(sizeof(int)); Console.WriteLine(sizeof(float)); Console.WriteLine(sizeof(Int32)); Console.WriteLine(typeof(int)); Console.WriteLine(typeof(float));
We use the sizeof
and typeof
operators.
$ dotnet run 4 4 4 System.Int32
我们可以看到 int 类型是 System.Int32 的别名,float 是 System.Single 类型的别名。
is 运算符检查对象是否与给定类型兼容。
Program.cs
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 { }
我们从用户定义的类型创建两个对象。
class Base {} class Derived : Base {}
我们有一个 Base 和一个 Derived 类。派生类继承自基类。
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 对象与 Derived 类无关。
$ dotnet run True True True False
as 运算符用于在兼容的引用类型之间执行转换。当无法进行转换时,运算符返回 null。与引发异常的强制转换操作不同。
Program.cs
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"); } } class Base { } class Derived : Base { }
在上面的示例中,我们使用 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
十六、C# 运算符优先级
运算符优先级告诉我们首先评估哪些运算符。优先级对于避免表达式中的歧义是必要的。
以下表达式的结果是 28 还是 40?
3 + 5 * 5
与数学一样,乘法运算符的优先级高于加法运算符。所以结果是28。
(3 + 5) * 5
要更改或提高优先级,我们可以使用括号。括号内的表达式总是首先被计算。
下表显示了按优先级排序的常见 C# 运算符(最高优先级在前)
Operator(s) | Category | Associativity |
---|---|---|
Primary | x.y x?.y, x?[y] f(x) a[x] x++ x-- new typeof default checked unchecked |
Left |
Unary | + - ! ~ ++x --x (T)x |
Left |
Multiplicative | * / % |
Left |
Additive | + - |
Left |
Shift | << >> |
Left |
Equality | == != |
Right |
Logical AND | & |
Left |
Logical XOR | ^ |
Left |
Logical OR | | |
Left |
Conditional AND | && |
Left |
Conditional OR | || |
Left |
Null Coalescing | ?? |
Left |
Ternary | ?: |
Right |
Assignment | = *= /= %= += -= <<= >>= &= ^= |= ??= => |
Right |
表同一行上的运算符具有相同的优先级。
Program.cs
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);
在这种情况下,否定运算符具有更高的优先级。首先,第一个真值被否定为假,然后 |运算符将 false 和 true 组合在一起,最终给出 true。
$ dotnet run 28 40 True False
十七、C# 关联规则
有时,优先级不能令人满意地确定表达式的结果。还有另一条规则称为关联性。运算符的关联性决定了具有相同优先级的运算符的评估顺序。
9 / 3 * 3
这个表达式的结果是 9 还是 1?乘法、删除和模运算符从左到右关联。所以表达式是这样计算的:(9 / 3) * 3,结果是 9。
算术、布尔、关系和位运算符都是从左到右关联的。
另一方面,赋值运算符是右关联的。
Program.cs
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
十八、 C# 空条件运算符
仅当操作数的计算结果为非 null 时,null 条件运算符才会将成员访问、?. 或元素访问、?[] 操作应用于其操作数。如果操作数的计算结果为 null,则应用运算符的结果为 null。
Program.cs
var users = new List<User>() { new User("John Doe", "gardener"), new User(null, null), new User("Lucia Newton", "teacher") }; users.ForEach(user => Console.WriteLine(user.Name?.ToUpper())); record User(string? Name, string? Occupation);
在示例中,我们有一个包含两个成员的用户类:姓名和职业。我们在 ? 的帮助下访问对象的名称成员。操作员。
var users = new List<User>() { new User("John Doe", "gardener"), new User(null, null), new User("Lucia Newton", "teacher") };
我们有一个用户列表。其中之一是用空值初始化的。
users.ForEach(user => Console.WriteLine(user.Name?.ToUpper()));
我们使用?。访问 Name 成员并调用 ToUpper 方法。这 ?。通过不对 null 值调用 ToUpper 来防止 System.NullReferenceException。
$ dotnet run JOHN DOE LUCIA NEWTON
在以下示例中,我们使用 ?[] 运算符。该运算符允许将空值放入集合中。
Program.cs
int?[] vals = { 1, 2, 3, null, 4, 5 }; int i = 0; while (i < vals.Length) { Console.WriteLine(vals[i]?.GetType()); i++; }
在此示例中,我们在数组中有一个空值。我们通过应用 ? 来防止 System.NullReferenceException。数组元素上的运算符。
十九、C# 空合并运算符
空合并运算符??用于定义可空类型的默认值。如果它不为空,则返回左操作数;否则返回正确的操作数。当我们使用数据库时,我们经常处理缺失值。这些值作为程序的空值出现。此运算符是处理此类情况的便捷方式。
Program.cs
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> 的简写。它允许将 null 值分配给 int 类型。
int z = x ?? y ?? -1;
我们想为 z 变量赋值。但它不能为空。这是我们的要求。我们可以很容易地为此使用空合并运算符。如果 x 和 y 变量都为空,我们将 -1 分配给 z。
$ dotnet run -1
二十、C# 空合并赋值运算符
仅当左侧操作数的计算结果为 null 时,null 合并赋值运算符 ??= 才将其右侧操作数的值分配给其左侧操作数。如果左侧操作数的计算结果为非空,则??= 运算符不计算其右侧操作数。它在 C# 8.0 及更高版本中可用。
Program.cs
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));
在示例中,我们对整数值列表使用空合并赋值运算符。
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};
我们尝试为变量分配一个新的列表对象。由于变量不再为空,因此未分配列表。
$ dotnet run 1, 2, 3, 4, 5, 6, 7, 8, 9 1, 2, 3, 4, 5, 6, 7, 8, 9
二十一、C# 三元运算符
三元运算符?: 是一个条件运算符。对于我们想要根据条件表达式选择两个值之一的情况,它是一个方便的运算符。
cond-exp ? exp1 : exp2
如果 cond-exp 为真,则计算 exp1 并返回结果。如果 cond-exp 为假,则计算 exp2 并返回其结果。
Program.cs
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
A 31 years old person is adult.
二十二、C# Lambda操作符号
=> 标记称为 lambda 运算符。它是取自函数式语言的运算符。该运算符可以使代码更短更清晰。另一方面,理解语法可能很棘手。特别是如果程序员以前从未使用过函数式语言。
只要我们可以使用委托,我们也可以使用 lambda 表达式。 lambda 表达式的定义是:lambda 表达式是一个匿名函数,可以包含表达式和语句。左侧是一组数据,右侧是表达式或语句块。这些陈述适用于每一项数据。
在 lambda 表达式中,我们没有 return 关键字。最后一条语句会自动返回。而且我们不需要为我们的参数指定类型。编译器将猜测正确的参数类型。这称为类型推断。
Program.cs
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
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
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;
By definition, 1 is not a prime
if (num == 2 || num == 3) { Console.Write(num + " "); continue; }
我们跳过 2 和 3 的计算:它们是素数。请注意相等和条件或运算符的用法。 == 的优先级高于 ||操作员。所以我们不需要使用括号。
int i = (int) Math.Sqrt(num);
如果我们只尝试小于所讨论数字的平方根的数字,我们就可以了。数学证明,考虑到所讨论数字的平方根的值就足够了。
while (i > 1) { ... i--; }
这是一个while循环。 i 是计算得出的数字的平方根。我们使用递减运算符在每个循环周期将 i 减一。当 i 小于 1 时,我们终止循环。例如,我们有数字 9。9 的平方根是 3。我们将 9 数字除以 3 和 2。
if (num % i == 0) { isPrime = false; }
这是算法的核心。如果余数除法运算符为任何 i 值返回 0,则所讨论的数字不是素数。