C#运算符与表达式终极指南:从入门到精通的万字长文

一、什么是运算符与表达式?

  • 运算符(Operator) :是一种特殊的符号,用于执行特定的数学、逻辑或位运算。例如 + 是加法运算符,== 是相等运算符。
  • 操作数(Operand) :是参与运算的数据。例如,在 5 + 3 中,53 就是操作数。
  • 表达式(Expression) :是由操作数和运算符组成的序列,其计算结果会产生一个新值。例如 5 + 3 是一个表达式,它的计算结果是 8x > y 也是一个表达式,它的计算结果是 truefalse

表达式是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。如果你想得到精确的小数结果,至少要有一个操作数是浮点类型。

csharp 复制代码
double 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. 关系运算符

也叫比较运算符,用于比较两个操作数,其结果总是一个布尔值 (truefalse)。

运算符 名称 示例 结果为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;如果 anull,则结果是 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 仅当 variablenull 时,才将 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; 如果 conditiontrue,则计算 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)。三元运算符也是右结合的。

C#运算符优先级(由高到低摘录)

  1. 主要 : x.y, f(x), a[i], x++, x--, new, typeof, sizeof
  2. 一元 : +, -, !, ~, ++x, --x, (T)x
  3. 乘法 : *, /, %
  4. 加法 : +, -
  5. 移位 : <<, >>
  6. 关系 : <, >, <=, >=, is, as
  7. 相等 : ==, !=
  8. 位与 : &
  9. 位异或 : ^
  10. 位或 : |
  11. 逻辑与 : &&
  12. 逻辑或 : ||
  13. Null合并 : ??
  14. 条件 : ?:
  15. 赋值与Lambda : =, *=, /=, +=, -=, =>

结语

点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文!

相关推荐
消失的旧时光-19432 小时前
Kotlinx.serialization 对多态对象(sealed class )支持更好用
java·服务器·前端
少卿2 小时前
React Compiler 完全指南:自动化性能优化的未来
前端·javascript
广州华水科技2 小时前
水库变形监测推荐:2025年单北斗GNSS变形监测系统TOP5,助力基础设施安全
前端
广州华水科技2 小时前
北斗GNSS变形监测一体机在基础设施安全中的应用与优势
前端
七淮2 小时前
umi4暗黑模式设置
前端
8***B2 小时前
前端路由权限控制,动态路由生成
前端
军军3603 小时前
从图片到点阵:用JavaScript重现复古数码点阵艺术图
前端·javascript
znhy@1233 小时前
Vue基础知识(一)
前端·javascript·vue.js
terminal0073 小时前
浅谈useRef的使用和渲染机制
前端·react.js·面试
我的小月月3 小时前
🔥 手把手教你实现前端邮件预览功能
前端·vue.js