Skip to content

C# 逻辑运算符与关系运算符:从购物满减到权限校验 ​

开篇:程序的「判断力」 ​

想象你正在超市购物。结账时,收银员会判断:「购物满 100 元吗?满的话减 20 元。」——这就是关系运算(比较大小)。如果你还想叠加会员折扣,系统会判断:「是会员并且满 100 元吗?」——这就是逻辑运算(组合条件)。

C# 中的关系运算符和逻辑运算符,就是程序的「判断力」。它们让代码能够根据条件做出决策,是 if 语句、while 循环、数据筛选等所有控制流的基础。掌握它们,你的程序才能真正「聪明」起来。


一、关系运算符:比较的艺术 ​

关系运算符用于比较两个值的大小或相等性,返回 bool 类型(true 或 false)。就像天平,两边放上东西,告诉你「哪边重」或「是否平衡」。

1.1 六种基础关系运算符 ​

csharp
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 适用数据类型 ​

csharp
// 数值类型:直接比较值
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 字符串与值类型使用 == 的底层区别 ​

这是新手最容易混淆的知识点!

csharp
// 值类型(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 逻辑与 &&:同时满足 ​

csharp
// 生活场景:购物满 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;  // false

2.2 逻辑或 ||:满足其一 ​

csharp
// 生活场景:购物满 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;  // false

2.3 逻辑非 !:取反 ​

csharp
bool isRaining = false;
bool isSunny = !isRaining;    // true(不下雨就是晴天)

// 常见用法:取反判断
if (!string.IsNullOrEmpty(userName))
{
    Console.WriteLine("用户名有效");
}

// 双重否定(少见,但语法合法)
bool original = true;
bool doubleNegation = !!original; // true

2.4 短路求值:性能优化与风险控制 ​

短路求值是 && 和 || 的核心特性:一旦结果确定,就不再计算右侧表达式。

csharp
// && 短路:左边为 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 按位与 &、按位或 |:不短路的逻辑运算 ​

& 和 | 也可以用于逻辑运算,但不短路——无论左边结果如何,右边都会执行。

csharp
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。

csharp
// 基础逻辑
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 优先级验证代码 ​

csharp
// 示例 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 的区别 ​

csharp
// 值类型:== 和 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 短路运算导致的变量未赋值 ​

csharp
// 错误示例
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 值参与关系运算 ​

csharp
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:用户登录权限校验 ​

csharp
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:成绩等级判定 ​

csharp
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:商品折扣计算 ​

csharp
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 比较:用 ?. 或先判空
│  └─ 混淆 && 与 &:逻辑判断禁用 &
│
└─ 实战场景
   ├─ 用户登录:多条件组合校验
   ├─ 成绩判定:范围匹配
   └─ 折扣计算:规则叠加与保底