一、什么是运算符与表达式?
- 运算符(Operator) :是一种特殊的符号,用于执行特定的数学、逻辑或位运算。例如
+是加法运算符,==是相等运算符。 - 操作数(Operand) :是参与运算的数据。例如,在
5 + 3中,5和3就是操作数。 - 表达式(Expression) :是由操作数和运算符组成的序列,其计算结果会产生一个新值。例如
5 + 3是一个表达式,它的计算结果是8。x > y也是一个表达式,它的计算结果是true或false。
表达式是C#程序的基本执行单元。 理解它们,就是理解程序如何思考。
二、基础运算符
1. 算术运算符
它们负责处理基本的数学计算,与你在小学数学课上学到的几乎一样。
| 运算符 | 名称 | 示例 | 结果 |
|---|---|---|---|
+ |
加法 | 10 + 5 |
15 |
- |
减法 | 10 - 5 |
5 |
* |
乘法 | 10 * 5 |
50 |
/ |
除法 | 10 / 5 |
2 |
% |
取模 (求余数) | 10 % 3 |
1 |
csharp
int a = 10;
int b = 3;
Console.WriteLine($"加法: {a + b}"); // 输出: 13
Console.WriteLine($"减法: {a - b}"); // 输出: 7
Console.WriteLine($"乘法: {a * b}"); // 输出: 30
Console.WriteLine($"除法: {a / b}"); // 输出: 3 (注意:整数除法)
Console.WriteLine($"取模: {a % b}"); // 输出: 1
注意事项:整数除法
当两个整数相除时,结果也是一个整数,小数部分会被直接截断 (不是四舍五入)。例如
10 / 3的结果是3。如果你想得到精确的小数结果,至少要有一个操作数是浮点类型。
csharpdouble result = 10.0 / 3; // 结果是 3.333... double result2 = (double)10 / 3; // 结果也是 3.333...
2. 赋值运算符
赋值运算符用于给变量分配一个值。最基本的是 =,但C#提供了一系列复合赋值运算符,让代码更简洁。
| 运算符 | 示例 | 等价于 |
|---|---|---|
= |
x = 5 |
x = 5 |
+= |
x += 5 |
x = x + 5 |
-= |
x -= 5 |
x = x - 5 |
*= |
x *= 5 |
x = x * 5 |
/= |
x /= 5 |
x = x / 5 |
%= |
x %= 5 |
x = x % 5 |
csharp
int score = 100;
score += 10; // score 现在是 110
score -= 20; // score 现在是 90
score *= 2; // score 现在是 180
score /= 3; // score 现在是 60
使用复合赋值运算符不仅代码更短,而且可读性更好,是专业代码的标志之一。
3. 一元运算符
这些运算符只需要一个操作数。
| 运算符 | 名称 | 示例 | 描述 |
|---|---|---|---|
+ |
正号 | +5 |
表示一个正数 (通常省略) |
- |
负号 | -5 |
表示一个负数 (取反) |
++ |
递增 | x++ 或 ++x |
将变量的值加1 |
-- |
递减 | x-- 或 --x |
将变量的值减1 |
++ 和 -- 有两种形式:前缀 和后缀 ,它们的区别在于表达式求值的时机。
- 前缀 (Prefix)
++x: 先将x的值加1,然后返回加1后的值。 - 后缀 (Postfix)
x++: 先返回x的原始值 ,然后再将x的值加1。
csharp
int i = 5;
int j = 5;
// 前缀:先自增,再赋值
int prefixResult = ++i; // i 变成 6, prefixResult 也被赋值为 6
Console.WriteLine($"i: {i}, prefixResult: {prefixResult}"); // 输出: i: 6, prefixResult: 6
// 后缀:先赋值,再自增
int postfixResult = j++; // postfixResult 被赋值为 5, 然后 j 变成 6
Console.WriteLine($"j: {j}, postfixResult: {postfixResult}"); // 输出: j: 6, postfixResult: 5
三、逻辑与比较
1. 关系运算符
也叫比较运算符,用于比较两个操作数,其结果总是一个布尔值 (true 或 false)。
| 运算符 | 名称 | 示例 | 结果为true的条件 |
|---|---|---|---|
== |
等于 | a == b |
a 和 b 的值相等 |
!= |
不等于 | a != b |
a 和 b 的值不相等 |
> |
大于 | a > b |
a 的值大于 b |
< |
小于 | a < b |
a 的值小于 b |
>= |
大于等于 | a >= b |
a 的值大于或等于 b |
<= |
小于等于 | a <= b |
a 的值小于或等于 b |
csharp
int age = 20;
bool isAdult = age >= 18; // isAdult 的值为 true
bool isVoter = age == 21; // isVoter 的值为 false
这些运算符是 if 语句、while 循环等所有控制流语句的核心。
2. 逻辑运算符
用于组合多个布尔表达式,构建更复杂的判断逻辑。
| 运算符 | 名称 | 示例 | 描述 |
|:----:|:---------:|:--------------------:|:-----------------------------|------------|---|---------|----------------------------|
| ! | 逻辑非 (NOT) | !isValid | 如果操作数为true,结果为false;反之亦然 |
| && | 逻辑与 (AND) | isLogin && isAdmin | 两个操作数都为true时,结果才为true |
| ` | | ` | 逻辑或 (OR) | `isMember | | isVIP` | 只要有一个操作数为true,结果就为true |
短路求值 (Short-circuiting)
&& 和 || 有一个非常重要的特性叫"短路"。
- 对于
expr1 && expr2:如果expr1的计算结果为false,那么整个表达式的结果必定是false,此时expr2将不会被计算。 - 对于
expr1 || expr2:如果expr1的计算结果为true,那么整个表达式的结果必定是true,此时expr2将不会被计算。
这个特性非常有用,常用于避免空引用异常或减少不必要的计算:
csharp
string name = null;
// 如果不使用短路,当 name 为 null 时,name.Length 会抛出 NullReferenceException
// if (name != null & name.Length > 0) { ... } // 注意这里是 &,非短路
// 使用短路 &&,当 name 为 null 时,第一个条件为 false,第二个条件根本不会执行,非常安全
if (name != null && name.Length > 0)
{
Console.WriteLine("Name is not empty.");
}
四、位运算符
| 运算符 | 名称 | 描述 |
|:----:|:----:|:---------------------------|--------------------------|
| & | 按位与 | 两个操作数中,对应位都为1时,结果位才为1 |
| ` | ` | 按位或 | 两个操作数中,对应位只要有一个为1,结果位就为1 |
| ^ | 按位异或 | 两个操作数中,对应位不同时,结果位为1 |
| ~ | 按位取反 | 单目运算符,将操作数的所有位反转 (0变1,1变0) |
| << | 左移 | 将操作数的所有位向左移动指定的位数,右侧补0 |
| >> | 右移 | 将操作数的所有位向右移动指定的位数 |
应用场景:权限管理 枚举经常与位运算符结合,用于管理一组开关状态(Flags)。
csharp
[Flags]
public enum Permissions
{
None = 0, // 0000
Read = 1, // 0001
Write = 2, // 0010
Execute = 4, // 0100
All = Read | Write | Execute // 0111
}
Permissions userPermissions = Permissions.Read | Permissions.Write;
// 检查是否包含写权限
if ((userPermissions & Permissions.Write) == Permissions.Write)
{
Console.WriteLine("User has Write permission.");
}
// 添加执行权限
userPermissions |= Permissions.Execute;
// 移除写权限
userPermissions &= ~Permissions.Write;
五、那些强大的"语法糖"
随着C#语言的演进,出现了许多新的运算符,它们极大地简化了代码,使其更具表现力和健壮性。
1. Null 合并运算符 (?? 和 ??=)
用于处理 null 值的利器。
??(Null-Coalescing Operator) :a ?? b如果a不为null,则表达式的结果是a;如果a为null,则结果是b。
csharp
string userName = null;
string displayName = userName ?? "Guest"; // displayName 的值将是 "Guest"
string userName2 = "Admin";
string displayName2 = userName2 ?? "Guest"; // displayName2 的值将是 "Admin"
这完美地替代了冗长的 if 或三元表达式 (userName != null) ? userName : "Guest"。
??=(Null-Coalescing Assignment Operator) (C# 8.0+)variable ??= value仅当variable为null时,才将value赋给variable。
csharp
List<int> numbers = null;
numbers ??= new List<int>(); // 因为 numbers 是 null,所以为其创建一个新实例
numbers.Add(1);
numbers ??= new List<int>(); // 因为 numbers 不再是 null,所以这条语句什么也不做
这对于延迟初始化(Lazy Initialization)非常有用。
2. Null 条件运算符 (?. 和 ?[])
用于优雅地避免 NullReferenceException,告别层层嵌套的 if (obj != null) 检查。
?.(Null-Conditional Member Access) : 在访问对象成员(方法或属性)之前,检查对象是否为null。如果是null,整个表达式直接返回null,而不会抛出异常。
csharp
string street = "";
User user = null; // GetUser();
// 传统方式,需要层层检查
if (user != null)
{
if (user.UserAddress != null)
{
street = user.UserAddress.Street;
}
}
// 使用 ?. 运算符,一行搞定!
// 如果 user 或 user.UserAddress 是 null,street 将被赋值为 null
string streetElegant = user?.UserAddress?.Street;
public class User { public Address UserAddress { get; set; } }
public class Address { public string Street { get; set; } }
?[](Null-Conditional Element Access): 用于访问数组或索引器。
csharp
List<string> names = null;
string firstName = names?[0]; // 如果 names 为 null,firstName 为 null,不抛异常
3. 条件运算符 (?:) - 三元表达式
它是 if-else 语句的紧凑形式,适用于简单的条件赋值。
语法 : condition ? first_expression : second_expression; 如果 condition 为 true,则计算 first_expression 并将其作为结果;否则计算 second_expression。
csharp
int age = 20;
string status = (age >= 18) ? "Adult" : "Minor"; // status 的值为 "Adult"
4. 类型相关运算符
| 运算符 | 名称 | 描述 |
|---|---|---|
is |
类型判断 | 检查对象是否与给定类型兼容,返回布尔值。C# 7.0+支持模式匹配。 |
as |
类型转换 | 尝试将对象转换为指定类型,如果转换失败,返回null而不是抛出异常。 |
typeof |
获取类型 | 返回一个表示类型的 System.Type 对象。 |
sizeof |
获取大小 | (仅限非托管类型)返回给定类型值在内存中占用的字节数。 |
csharp
object obj = "Hello World";
if (obj is string)
{
Console.WriteLine("It's a string.");
}
if (obj is string s) // 如果是string,则直接转换并赋值给 s
{
Console.WriteLine($"The string has {s.Length} characters.");
}
string str = obj as string; // 转换成功,str 为 "Hello World"
StringBuilder sb = obj as StringBuilder; // 转换失败,sb 为 null
六、运算符的规则 - 优先级与结合性
当一个表达式中包含多个运算符时,谁先计算?这就是**优先级(Precedence)和结合性(Associativity)**要解决的问题。
- 优先级 :决定了不同运算符的计算顺序。例如,
*和/的优先级高于+和-,所以2 + 3 * 4的结果是14而不是20。 - 结合性 :当多个具有相同优先级的运算符在一起时,决定它们的计算方向。
- 左结合性 (Left-associative) :从左到右计算。例如
a - b - c等价于(a - b) - c。大多数二元运算符都是左结合的。 - 右结合性 (Right-associative) :从右到左计算。例如赋值运算符
a = b = c等价于a = (b = c)。三元运算符也是右结合的。
- 左结合性 (Left-associative) :从左到右计算。例如
C#运算符优先级(由高到低摘录)
- 主要 :
x.y,f(x),a[i],x++,x--,new,typeof,sizeof - 一元 :
+,-,!,~,++x,--x,(T)x - 乘法 :
*,/,% - 加法 :
+,- - 移位 :
<<,>> - 关系 :
<,>,<=,>=,is,as - 相等 :
==,!= - 位与 :
& - 位异或 :
^ - 位或 :
| - 逻辑与 :
&& - 逻辑或 :
|| - Null合并 :
?? - 条件 :
?: - 赋值与Lambda :
=,*=,/=,+=,-=,=>
结语
点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文!