C# 运算符
运算符是程序里"做计算"的语法,基础但有几个 C# 特有的现代特性值得专门讲——空合并 ??、空条件 ?.、模式匹配。这一篇带你一次过完。
一、算术运算符
int a = 10, 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(取余)
// 注意:整数 / 整数 = 整数(舍去小数)
Console.WriteLine(7 / 2); // 3
// 要小数结果,至少一个操作数是浮点
Console.WriteLine(7.0 / 2); // 3.5
Console.WriteLine(7 / 2.0); // 3.5
Console.WriteLine((double)7 / 2); // 3.5
// 自增自减
int i = 5;
Console.WriteLine(i++); // 5(先用后加)
Console.WriteLine(i); // 6
Console.WriteLine(++i); // 7(先加后用)几个常见坑:
- 整数除法:7 / 2 = 3,不是 3.5。要小数结果,需要至少一个浮点操作数。
- 溢出:整数超出范围会"环绕"(不报错)。财务/大数据场景用
checked关键字启用溢出检查。 - ++ 位置:前置
++i先加后用,后置i++先用后加。
二、关系运算符
比较两个值,结果为 bool。C# 的字符串相等比较按内容(string 重写了 ==),这点和 Java 不同:
int a = 10, b = 20;
Console.WriteLine(a > b); // False
Console.WriteLine(a < b); // True
Console.WriteLine(a >= 10); // True
Console.WriteLine(a <= 5); // False
Console.WriteLine(a == 10); // True
Console.WriteLine(a != 10); // False
// 字符串相等:== 比较内容(不是引用)
string s1 = "hello";
string s2 = "hello";
Console.WriteLine(s1 == s2); // True(重写了 ==)
// 通用相等比较用 Equals
Console.WriteLine(s1.Equals(s2)); // True注意浮点比较要小心精度。判断两个 double 相等不要直接 ==,而是用 Math.Abs(a - b) < 1e-9。
三、逻辑运算符与位运算
bool a = true, b = false;
Console.WriteLine(a && b); // False(逻辑与)
Console.WriteLine(a || b); // True(逻辑或)
Console.WriteLine(!a); // False(逻辑非)
// 短路特性:&& 第一个 false 时第二个不计算
bool ok = false && ExpensiveCheck(); // 不调 ExpensiveCheck
// 位运算(整数)
int x = 0b1100; // 12
int y = 0b1010; // 10
Console.WriteLine(x & y); // 8(0b1000 与)
Console.WriteLine(x | y); // 14(0b1110 或)
Console.WriteLine(x ^ y); // 6(0b0110 异或)
Console.WriteLine(~x); // -13(取反)
Console.WriteLine(x << 2); // 48(左移 2 位 = 乘 4)
Console.WriteLine(x >> 1); // 6(右移 1 位 = 除 2)&& 和 || 有短路特性:第一个能确定结果时第二个不计算。这点很重要,比如 obj != null && obj.IsValid 必须先判空,短路保证不会 NullReferenceException。
位运算对整数按二进制操作。Flags 枚举、位掩码、加密算法常用。日常业务开发用得少,做底层时频繁。
四、赋值与复合赋值
int a = 10;
a += 5; // a = a + 5 = 15
a -= 3; // a = 12
a *= 2; // a = 24
a /= 4; // a = 6
a %= 4; // a = 2
a &= 0xFF; // 位与赋值
a |= 0x10; // 位或赋值
a <<= 2; // 左移赋值
// C# 8+ 复合空合并
string name = null;
name ??= "默认名"; // 等价 name = name ?? "默认名"
Console.WriteLine(name); // "默认名"五、三元运算符
三元运算符 ? : 是简化版 if-else:
int age = 20;
string type = age >= 18 ? "成人" : "未成年";
// "成人"
// 等价于
string type2;
if (age >= 18)
type2 = "成人";
else
type2 = "未成年";三元运算符简洁但别嵌套太深,可读性会下降。一个三元够用,两层嵌套就改用 if-else 或 switch 表达式。
六、空合并运算符 ??(C# 特色)
这是 C# 处理 null 的大杀器。原理:左边为 null 时返回右边,否则返回左边:
string name = null;
// ?? 空合并:null 时返回默认值
string display = name ?? "匿名"; // "匿名"
// 链式空合并
string nickname = null;
string username = null;
string show = nickname ?? username ?? "游客"; // "游客"
// ?. 空条件运算符:null 时不调用方法,返回 null
int? length = name?.Length; // null(不抛异常)
// 组合使用
int safeLength = name?.Length ?? 0; // 0
// 老写法(等价但啰嗦)
int oldLength;
if (name != null)
oldLength = name.Length;
else
oldLength = 0;实际场景极多——API 默认值、配置项兜底、用户输入处理。配合空条件 ?. 可以写出非常干净的 null 安全代码,告别冗长的 if 判空。
七、空条件运算符 ?.
对象为 null 时不抛异常,直接返回 null:
// ❌ 容易 NullReferenceException
// int length = name.Length;
// ✅ 安全写法
int? length = name?.Length; // null 或具体长度
// 链式
int? cityLength = user?.Address?.City?.Length;
// 中间任一为 null,结果为 null
// 配合 ??
int safeLen = user?.Address?.City?.Length ?? 0;
// 调用事件(线程安全)
handler?.Invoke(this, args);事件触发 handler?.Invoke() 是经典用法,多线程下避免"订阅者刚取消订阅"的崩溃。
八、is 运算符与模式匹配
C# 7+ 引入了强大的模式匹配,远比传统 is 强:
object obj = "hello";
// is 类型检查
if (obj is string)
{
string s = (string)obj;
Console.WriteLine(s.Length);
}
// is 模式匹配 + 变量声明(C# 7+)
if (obj is string s)
{
Console.WriteLine(s.Length); // 5
}
// switch 表达式(C# 8+):基于模式
int score = 78;
string grade = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 60 => "C",
_ => "D"
};
// "C"
// 关系模式 + when 守卫
string describe(int n) => n switch
{
< 0 => "负数",
0 => "零",
> 0 and < 10 => "个位正数",
>= 10 => "多位正数"
};模式匹配是现代 C# 的杀手锏。switch 表达式 + 关系模式 + when 守卫,让复杂分支变得极简。功能上和 if-else 等价,但可读性和表达力强得多。
九、typeof 与运算符优先级
// typeof:获取类型的 Type 对象(反射)
Type t = typeof(string);
Console.WriteLine(t.FullName); // System.String
// typeof vs GetType(运行时类型)
string s = "hello";
Type t1 = s.GetType(); // System.String(运行时)
Type t2 = typeof(string); // System.String(编译时)
// sizeof:获取值类型大小(仅 unsafe 上下文外有限类型可用)
int size = sizeof(int); // 4
// nameof:获取变量/类型/成员名字(重构友好)
throw new ArgumentException(nameof(param)); // "param"运算符优先级和数学一致:乘除 > 加减 > 比较 > 逻辑。复杂表达式建议加括号明确意图,不要让人记优先级表。
十、checked 与 unchecked
默认整数运算溢出会"环绕"(不报错),用 checked 可以让溢出抛 OverflowException:
int max = int.MaxValue; // 2147483647
// 默认 unchecked:溢出环绕
int wrap = max + 1;
Console.WriteLine(wrap); // -2147483648(负数,危险!)
// checked:抛异常
try
{
checked
{
int bad = max + 1; // 抛 OverflowException
}
}
catch (OverflowException)
{
Console.WriteLine("溢出了");
}财务计算、安全相关代码应启用 checked。项目级可在 csproj 配置 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>。
小结
算术、关系、逻辑是基础,和 C/Java 没大差别。C# 的特色运算符是空合并 ??、空条件 ?.、模式匹配 is/switch——学会这三个,你写的 C# 代码立刻"地道"很多。下一篇我们看控制流:if、switch、循环。
← 上一篇 C# 数据类型
下一篇 C# 条件与循环 →