C# 逻辑运算符与关系运算符:从购物满减到权限校验
开篇:程序的「判断力」
想象你正在超市购物。结账时,收银员会判断:「购物满 100 元吗?满的话减 20 元。」——这就是关系运算(比较大小)。如果你还想叠加会员折扣,系统会判断:「是会员并且满 100 元吗?」——这就是逻辑运算(组合条件)。
C# 中的关系运算符和逻辑运算符,就是程序的「判断力」。它们让代码能够根据条件做出决策,是 if 语句、while 循环、数据筛选等所有控制流的基础。掌握它们,你的程序才能真正「聪明」起来。
一、关系运算符:比较的艺术
关系运算符用于比较两个值的大小或相等性,返回 bool 类型(true 或 false)。就像天平,两边放上东西,告诉你「哪边重」或「是否平衡」。
1.1 六种基础关系运算符
int a = 10, b = 20;
// == 相等:判断两边是否相同
bool isEqual = (a == b); // false(10 不等于 20)
Console.WriteLine($"a == b : {isEqual}");
// != 不等:判断两边是否不同
bool isNotEqual = (a != b); // true(10 不等于 20)
Console.WriteLine($"a != b : {isNotEqual}");
// > 大于:左边是否严格大于右边
bool isGreater = (a > b); // false(10 不大于 20)
Console.WriteLine($"a > b : {isGreater}");
// < 小于:左边是否严格小于右边
bool isLess = (a < b); // true(10 小于 20)
Console.WriteLine($"a < b : {isLess}");
// >= 大于等于:左边大于或等于右边
bool isGreaterOrEqual = (a >= b); // false(10 不大于等于 20)
Console.WriteLine($"a >= b : {isGreaterOrEqual}");
// <= 小于等于:左边小于或等于右边
bool isLessOrEqual = (a <= b); // true(10 小于等于 20)
Console.WriteLine($"a <= b : {isLessOrEqual}");1.2 适用数据类型
// 数值类型:直接比较值
int x = 5, y = 10;
Console.WriteLine(x < y); // true
double d1 = 3.14, d2 = 2.71;
Console.WriteLine(d1 > d2); // true
// 字符类型:比较 Unicode 编码
char c1 = 'A', c2 = 'B';
Console.WriteLine(c1 < c2); // true(A 的编码 65 小于 B 的 66)
// 字符串:== 和 != 比较内容(C# 特殊处理)
string s1 = "hello";
string s2 = "hello";
string s3 = "Hello";
Console.WriteLine(s1 == s2); // true(内容相同)
Console.WriteLine(s1 == s3); // false(大小写不同)
Console.WriteLine(s1 != s3); // true
// 引用类型:== 默认比较引用地址
object obj1 = new object();
object obj2 = new object();
object obj3 = obj1;
Console.WriteLine(obj1 == obj2); // false(不同对象)
Console.WriteLine(obj1 == obj3); // true(同一引用)1.3 字符串与值类型使用 == 的底层区别
这是新手最容易混淆的知识点!
// 值类型(int/double/bool 等):== 比较的是「值」
int v1 = 100;
int v2 = 100;
Console.WriteLine(v1 == v2); // true(值相同)
// 字符串:== 被「重载」了,比较的是「内容」
string str1 = "abc";
string str2 = "abc";
string str3 = new string(new[] { 'a', 'b', 'c' }); // 手动创建的新对象
Console.WriteLine(str1 == str2); // true(内容相同)
Console.WriteLine(str1 == str3); // true(内容相同,即使引用不同)
Console.WriteLine(ReferenceEquals(str1, str3)); // false(引用不同!)
// 引用类型(默认):== 比较的是「引用地址」
Person p1 = new Person { Name = "张三" };
Person p2 = new Person { Name = "张三" };
Person p3 = p1;
Console.WriteLine(p1 == p2); // false(不同对象,即使属性相同)
Console.WriteLine(p1 == p3); // true(同一引用)
Console.WriteLine(p1.Equals(p2)); // 默认也是 false(除非重写 Equals)新手小贴士:字符串的 == 是「特例」,微软为了让字符串比较更符合直觉,重载了 == 运算符使其比较内容。但自定义类的 == 默认仍比较引用地址,需要手动重写 Equals 和 == 才能实现「值相等」判断。
二、逻辑运算符:条件的组合与否定
逻辑运算符用于组合多个条件,返回 bool 类型。就像购物规则:「满 100 元 且 是会员」才能打 8 折,「满 200 元 或 有优惠券」就能减 50 元。
2.1 逻辑与 &&:同时满足
// 生活场景:购物满 100 元 AND 是会员,才能打 8 折
double totalAmount = 150;
bool isMember = true;
// && 要求两边都为 true,结果才为 true
bool canDiscount = (totalAmount >= 100) && isMember;
Console.WriteLine($"可以享受 8 折:{canDiscount}"); // true
// 只要一边为 false,结果就是 false
bool test1 = true && true; // true
bool test2 = true && false; // false
bool test3 = false && true; // false
bool test4 = false && false; // false2.2 逻辑或 ||:满足其一
// 生活场景:购物满 200 元 OR 有优惠券,就能减 50 元
double amount = 150;
bool hasCoupon = true;
// || 只要一边为 true,结果就为 true
bool canReduce = (amount >= 200) || hasCoupon;
Console.WriteLine($"可以减 50 元:{canReduce}"); // true(有优惠券)
// 两边都为 false,结果才是 false
bool test1 = true || true; // true
bool test2 = true || false; // true
bool test3 = false || true; // true
bool test4 = false || false; // false2.3 逻辑非 !:取反
bool isRaining = false;
bool isSunny = !isRaining; // true(不下雨就是晴天)
// 常见用法:取反判断
if (!string.IsNullOrEmpty(userName))
{
Console.WriteLine("用户名有效");
}
// 双重否定(少见,但语法合法)
bool original = true;
bool doubleNegation = !!original; // true2.4 短路求值:性能优化与风险控制
短路求值是 && 和 || 的核心特性:一旦结果确定,就不再计算右侧表达式。
// && 短路:左边为 false,右边不执行
int x = 0;
bool result = (x != 0) && (10 / x > 1); // x != 0 为 false,右边不执行,不会除零!
Console.WriteLine(result); // false
// 若用 & 则危险
// bool danger = (x != 0) & (10 / x > 1); // 抛出 DivideByZeroException!
// || 短路:左边为 true,右边不执行
string? name = null;
bool isValid = (name == null) || (name.Length > 0); // name == null 为 true,右边不执行,不会空引用!
Console.WriteLine(isValid); // true
// 若用 | 则危险
// bool danger2 = (name == null) | (name.Length > 0); // 抛出 NullReferenceException!新手小贴士:短路求值不仅是性能优化,更是安全保护。利用 && 的短路特性,可以把「可能出错」的条件放在右侧,左侧先做安全检查。
2.5 按位与 &、按位或 |:不短路的逻辑运算
& 和 | 也可以用于逻辑运算,但不短路——无论左边结果如何,右边都会执行。
int a = 0;
// &&:短路,右边不执行
bool r1 = (a != 0) && (10 / a > 1); // false,安全
// &:不短路,右边执行,抛出异常
// bool r2 = (a != 0) & (10 / a > 1); // DivideByZeroException!
// 但 & 在「位运算」场景是主角
int flags = 0b_1010; // 二进制:1010
int mask = 0b_1100; // 二进制:1100
int result = flags & mask; // 按位与:1000(二进制)= 8
Console.WriteLine(result); // 8新手小贴士:逻辑判断永远用 && 和 ||,& 和 | 留给位运算。新手常犯的错误就是在逻辑判断中误用 &,导致右侧表达式意外执行。
2.6 异或 ^:相同为假,不同为真
异或的规则:两边相同为 false,不同为 true。
// 基础逻辑
Console.WriteLine(true ^ true); // false(相同)
Console.WriteLine(true ^ false); // true(不同)
Console.WriteLine(false ^ true); // true(不同)
Console.WriteLine(false ^ false); // false(相同)
// 特殊场景 1:变量交换(不借助临时变量)
int a = 10, b = 20;
a = a ^ b; // a = 10 ^ 20
b = a ^ b; // b = (10 ^ 20) ^ 20 = 10
a = a ^ b; // a = (10 ^ 20) ^ 10 = 20
Console.WriteLine($"a={a}, b={b}"); // a=20, b=10
// 特殊场景 2:状态标记(开关切换)
bool isEnabled = false;
isEnabled = isEnabled ^ true; // true(开)
isEnabled = isEnabled ^ true; // false(关)
// 等价于 isEnabled = !isEnabled,但 ^ 可以与其他标记组合
// 特殊场景 3:找唯一成单数(算法题)
int[] nums = { 1, 2, 3, 2, 1 }; // 3 只出现一次
int single = 0;
foreach (int n in nums)
single ^= n; // 相同数字异或得 0,0 与任何数异或得本身
Console.WriteLine(single); // 3三、运算符优先级:谁先算?
当多个运算符混合时,优先级决定计算顺序。优先级高的先算,同级的从左到右。
3.1 完整优先级排序表
| 优先级 | 运算符 | 说明 | 结合性 |
|---|---|---|---|
| 1(最高) | () | 括号 | 从左到右 |
| 2 | ! | 逻辑非 | 从右到左 |
| 3 | * / % | 乘除余 | 从左到右 |
| 4 | + - | 加减 | 从左到右 |
| 5 | < <= > >= | 关系比较 | 从左到右 |
| 6 | == != | 相等比较 | 从左到右 |
| 7 | & | 按位与 | 从左到右 |
| 8 | ^ | 按位异或 | 从左到右 |
| 9 | | | 按位或 | 从左到右 |
| 10 | && | 逻辑与 | 从左到右 |
| 11 | || | 逻辑或 | 从左到右 |
| 12(最低) | = += -= 等 | 赋值 | 从右到左 |
3.2 优先级验证代码
// 示例 1:关系运算优先于逻辑运算
bool result1 = 1 + 2 > 2 && 3 < 5;
// 等价于:(1 + 2) > 2 && 3 < 5
// 等价于:3 > 2 && 3 < 5
// 等价于:true && true
// 结果:true
Console.WriteLine(result1); // true
// 示例 2:&& 优先于 ||
bool result2 = true || false && false;
// 等价于:true || (false && false)
// 等价于:true || false
// 结果:true
Console.WriteLine(result2); // true
// 示例 3:括号改变优先级
bool result3 = (true || false) && false;
// 等价于:true && false
// 结果:false
Console.WriteLine(result3); // false
// 示例 4:! 优先级最高
bool flag = false;
bool result4 = !flag && true;
// 等价于:(!flag) && true
// 等价于:true && true
// 结果:true
Console.WriteLine(result4); // true新手小贴士:不确定优先级时,加括号是最保险的做法。括号不仅让计算顺序明确,还能提高代码可读性。
四、新手高频踩坑点
4.1 == 与 Equals 的区别
// 值类型:== 和 Equals 都比较值
int a = 100, b = 100;
Console.WriteLine(a == b); // true
Console.WriteLine(a.Equals(b)); // true
// 字符串:== 和 Equals 都重载为比较内容
string s1 = "abc", s2 = "abc";
Console.WriteLine(s1 == s2); // true
Console.WriteLine(s1.Equals(s2)); // true
// 自定义引用类型:== 比较引用,Equals 默认也比较引用
Person p1 = new Person { Name = "张三", Age = 25 };
Person p2 = new Person { Name = "张三", Age = 25 };
Console.WriteLine(p1 == p2); // false(不同对象)
Console.WriteLine(p1.Equals(p2)); // false(默认实现)
// 正确做法:重写 Equals 和 ==
public class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
public override bool Equals(object? obj)
{
if (obj is Person other)
return Name == other.Name && Age == other.Age;
return false;
}
public override int GetHashCode() => HashCode.Combine(Name, Age);
public static bool operator ==(Person? left, Person? right)
{
if (left is null) return right is null;
return left.Equals(right);
}
public static bool operator !=(Person? left, Person? right) => !(left == right);
}
// 重写后
Person p3 = new Person { Name = "张三", Age = 25 };
Person p4 = new Person { Name = "张三", Age = 25 };
Console.WriteLine(p3 == p4); // true(值相等)
Console.WriteLine(p3.Equals(p4)); // true错误原因:自定义引用类型的 == 和 Equals 默认都比较引用地址,而非内容。若需「值相等」语义,必须重写。
4.2 短路运算导致的变量未赋值
// 错误示例
int result;
bool condition = false;
// condition 为 false,右侧不执行,result 未被赋值
if (condition && (result = 10) > 5)
{
// ...
}
// Console.WriteLine(result); // 编译错误!result 可能未赋值
// 正确做法:确保所有路径都赋值
int safeResult = 0;
if (condition)
{
safeResult = 10;
}
if (safeResult > 5)
{
// ...
}4.3 null 值参与关系运算
string? nullStr = null;
string normalStr = "hello";
// 关系运算:null 与任何值比较都返回 false
Console.WriteLine(nullStr == normalStr); // false
Console.WriteLine(nullStr != normalStr); // true
Console.WriteLine(nullStr == null); // true
// 危险:调用方法或属性会抛出 NullReferenceException
// Console.WriteLine(nullStr.Length > 0); // 运行时异常!
// 安全做法:使用空条件运算符 ?.
Console.WriteLine(nullStr?.Length > 0); // false(null 传播,不抛异常)
// 逻辑运算:null 不能参与 && ||(编译错误)
// bool b = nullStr && true; // 编译错误!五、实战场景应用
场景 1:用户登录权限校验
public class LoginService
{
public bool ValidateUser(string? username, string? password, bool isActive, int failCount)
{
// 关系运算:检查空值
bool hasUsername = !string.IsNullOrEmpty(username);
bool hasPassword = !string.IsNullOrEmpty(password);
// 逻辑运算:组合多个条件
// 用户名非空 AND 密码非空 AND 账户激活 AND 失败次数 < 5
bool canLogin = hasUsername
&& hasPassword
&& isActive
&& (failCount < 5);
// 额外规则:管理员不受失败次数限制
bool isAdmin = username == "admin";
bool finalResult = canLogin || (isAdmin && hasUsername && hasPassword && isActive);
return finalResult;
}
}
// 测试
var service = new LoginService();
Console.WriteLine(service.ValidateUser("user1", "pass123", true, 3)); // true
Console.WriteLine(service.ValidateUser("user1", "pass123", true, 10)); // false(失败次数过多)
Console.WriteLine(service.ValidateUser("admin", "pass123", true, 10)); // true(管理员特权)
Console.WriteLine(service.ValidateUser(null, "pass123", true, 0)); // false(用户名为空)场景 2:成绩等级判定
public class GradeEvaluator
{
public string GetGrade(int score)
{
// 关系运算:范围判断
if (score >= 90 && score <= 100)
return "优秀";
else if (score >= 80 && score < 90)
return "良好";
else if (score >= 60 && score < 80)
return "及格";
else if (score >= 0 && score < 60)
return "不及格";
else
return "无效分数";
}
// 更简洁的写法(C# 8.0+ switch 表达式)
public string GetGradeModern(int score) => score switch
{
>= 90 and <= 100 => "优秀",
>= 80 and < 90 => "良好",
>= 60 and < 80 => "及格",
>= 0 and < 60 => "不及格",
_ => "无效分数"
};
}
// 测试
var evaluator = new GradeEvaluator();
Console.WriteLine(evaluator.GetGrade(95)); // 优秀
Console.WriteLine(evaluator.GetGrade(85)); // 良好
Console.WriteLine(evaluator.GetGrade(70)); // 及格
Console.WriteLine(evaluator.GetGrade(45)); // 不及格
Console.WriteLine(evaluator.GetGrade(-5)); // 无效分数场景 3:商品折扣计算
public class DiscountCalculator
{
public decimal CalculateFinalPrice(decimal originalPrice, int quantity, bool isMember, bool hasCoupon)
{
decimal finalPrice = originalPrice * quantity;
// 规则 1:满 100 减 20(关系运算)
if (finalPrice >= 100)
{
finalPrice -= 20;
}
// 规则 2:会员额外 9 折(逻辑运算)
if (isMember && finalPrice > 0)
{
finalPrice *= 0.9m;
}
// 规则 3:优惠券减 10 元(可与会员叠加)
if (hasCoupon && finalPrice >= 50)
{
finalPrice -= 10;
}
// 规则 4:最低消费 1 元(保底逻辑)
if (finalPrice < 1)
{
finalPrice = 1;
}
return finalPrice;
}
}
// 测试
var calc = new DiscountCalculator();
Console.WriteLine(calc.CalculateFinalPrice(50, 2, false, false)); // 80(100-20)
Console.WriteLine(calc.CalculateFinalPrice(50, 2, true, false)); // 72(80*0.9)
Console.WriteLine(calc.CalculateFinalPrice(50, 2, true, true)); // 62(72-10)
Console.WriteLine(calc.CalculateFinalPrice(10, 1, true, true)); // 1(保底)知识点思维导图
C# 逻辑运算符与关系运算符
│
├─ 关系运算符(比较,返回 bool)
│ ├─ == 相等:值类型比较值,字符串比较内容,引用类型比较引用
│ ├─ != 不等:== 的取反
│ ├─ > 大于 / < 小于 / >= 大于等于 / <= 小于等于
│ ├─ 适用类型:数值、字符、字符串(仅 == !=)、引用类型
│ └─ 易错点:自定义类型需重写 Equals 和 ==
│
├─ 逻辑运算符(组合条件,返回 bool)
│ ├─ && 逻辑与:全真才真,短路求值
│ ├─ || 逻辑或:一真即真,短路求值
│ ├─ ! 逻辑非:取反
│ ├─ & 按位与:不短路,也用于位运算
│ ├─ | 按位或:不短路,也用于位运算
│ ├─ ^ 异或:相同为假,不同为真
│ │ └─ 特殊用途:变量交换、状态切换、找唯一数
│ └─ 核心规则:逻辑判断用 && ||,位运算用 & |
│
├─ 运算符优先级
│ ├─ 算术 > 关系 > 逻辑(&& > ||)
│ ├─ ! 优先级最高,赋值最低
│ └─ 不确定时加括号
│
├─ 常见坑点
│ ├─ == vs Equals:值相等 vs 引用相等
│ ├─ 短路运算:右侧可能不执行,变量未赋值
│ ├─ null 比较:用 ?. 或先判空
│ └─ 混淆 && 与 &:逻辑判断禁用 &
│
└─ 实战场景
├─ 用户登录:多条件组合校验
├─ 成绩判定:范围匹配
└─ 折扣计算:规则叠加与保底