目 录CONTENT

文章目录

CSharp(五十九) 运算符重载详解

C# 运算符重载详解


一、什么是运算符重载?

1.1 生活比喻

运算符重载就像给运算符赋予"双重身份":

+ 号平时做加法:3 + 5 = 8。但如果两个字符串用 +"Hello" + "World" = "HelloWorld"——同一个 + 号,面对不同类型的数据,做了不同的事。

这就是运算符重载——让自定义类型也能用 +-== 这些运算符,就像内置类型一样自然。

1.2 为什么需要运算符重载?

// 没有运算符重载——只能用方法
public class Money
{
    public decimal Amount { get; set; }
}

Money a = new Money { Amount = 100 };
Money b = new Money { Amount = 50 };
// Money c = a + b;  // ❌ 编译错误!Money 不能直接用 + 号
// 只能用方法
Money c = a.Add(b);   // 不够自然

// 有了运算符重载——直接写 a + b
Money c = a + b;       // ✅ 和内置类型一样自然!

1.3 一句话理解

运算符重载 = 给自定义类型定义运算符行为,让你的类型可以像 int、string 一样使用 +-==> 等运算符。

1.4 你其实早就用过了

string s1 = "Hello";
string s2 = "World";
string s3 = s1 + s2;      // string 重载了 + 运算符

DateTime d1 = DateTime.Now;
DateTime d2 = d1.AddDays(1);
bool later = d2 > d1;      // DateTime 重载了 > 运算符

TimeSpan t1 = TimeSpan.FromHours(1);
TimeSpan t2 = TimeSpan.FromHours(2);
TimeSpan t3 = t1 + t2;     // TimeSpan 重载了 + 运算符

二、哪些运算符可以重载?

2.1 一元运算符(一个操作数)

运算符 说明 重载方法签名
+ 正号 public static T operator +(T x)
- 负号 public static T operator -(T x)
! 逻辑非 public static bool operator !(T x)
~ 按位取反 public static T operator ~(T x)
++ 自增 public static T operator ++(T x)
-- 自减 public static T operator --(T x)
true 判断是否为 true public static bool operator true(T x)
false 判断是否为 false public static bool operator false(T x)

2.2 二元运算符(两个操作数)

运算符 说明 重载方法签名
+ public static T operator +(T a, T b)
- public static T operator -(T a, T b)
* public static T operator *(T a, T b)
/ public static T operator /(T a, T b)
% 取模 public static T operator %(T a, T b)
== 等于 public static bool operator ==(T a, T b)
!= 不等于 public static bool operator !=(T a, T b)
< 小于 public static bool operator <(T a, T b)
> 大于 public static bool operator >(T a, T b)
<= 小于等于 public static bool operator <=(T a, T b)
>= 大于等于 public static bool operator >=(T a, T b)
& 按位与 public static T operator &(T a, T b)
| 按位或 public static T operator |(T a, T b)
^ 按位异或 public static T operator ^(T a, T b)

2.3 不能重载的运算符

运算符 原因
= 赋值运算符不能重载
. 成员访问不能重载
?: 三元条件不能重载
?? null 合并不能重载
-> 指针访问不能重载
=> Lambda 不能重载
new new 不能重载
typeof typeof 不能重载
sizeof sizeof 不能重载
is / as 类型检查不能重载

三、基本语法

3.1 语法格式

public static 返回类型 operator 运算符(参数列表)
{
    // 实现逻辑
}

核心规则:

  • 必须是 public static(公开的静态方法)
  • 方法名是 operator 关键字,后面跟运算符符号
  • 至少有一个参数是本类型
  • 不能用 refout 参数

3.2 第一个例子——重载 + 运算符

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    // 重载 + 运算符:两个 Point 相加
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y);
    }

    public override string ToString() => $"({X}, {Y})";
}

// 使用
Point p1 = new Point(3, 5);
Point p2 = new Point(1, 2);
Point p3 = p1 + p2;  // (4, 7),像数学坐标一样自然!
Console.WriteLine($"{p1} + {p2} = {p3}");

输出:

(3, 5) + (1, 2) = (4, 7)

四、常用运算符重载示例

4.1 算术运算符(+、-、*、/)

public class Vector2D
{
    public double X { get; set; }
    public double Y { get; set; }

    public Vector2D(double x, double y) { X = x; Y = y; }

    // 向量加法
    public static Vector2D operator +(Vector2D a, Vector2D b)
        => new Vector2D(a.X + b.X, a.Y + b.Y);

    // 向量减法
    public static Vector2D operator -(Vector2D a, Vector2D b)
        => new Vector2D(a.X - b.X, a.Y - b.Y);

    // 标量乘法(向量 × 数字)
    public static Vector2D operator *(Vector2D v, double scalar)
        => new Vector2D(v.X * scalar, v.Y * scalar);

    // 标量乘法(数字 × 向量)——交换律的需要
    public static Vector2D operator *(double scalar, Vector2D v)
        => new Vector2D(v.X * scalar, v.Y * scalar);

    // 取负
    public static Vector2D operator -(Vector2D v)
        => new Vector2D(-v.X, -v.Y);

    public override string ToString() => $"({X}, {Y})";
}

// 使用
Vector2D v1 = new Vector2D(3, 4);
Vector2D v2 = new Vector2D(1, 2);
Console.WriteLine($"v1 + v2 = {v1 + v2}");   // (4, 6)
Console.WriteLine($"v1 - v2 = {v1 - v2}");   // (2, 2)
Console.WriteLine($"v1 * 2 = {v1 * 2}");     // (6, 8)
Console.WriteLine($"3 * v2 = {3 * v2}");     // (3, 6)
Console.WriteLine($"-v1 = {-v1}");           // (-3, -4)

4.2 比较运算符(==、!=、<、>、<=、>=)

public class StudentScore : IComparable<StudentScore>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public StudentScore(string name, int score)
    {
        Name = name;
        Score = score;
    }

    // ===== 比较运算符(必须成对出现!) =====

    // == 和 != 必须成对
    public static bool operator ==(StudentScore a, StudentScore b)
    {
        if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
            return true;
        if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
            return false;
        return a.Score == b.Score;
    }

    public static bool operator !=(StudentScore a, StudentScore b)
        => !(a == b);

    // < 和 > 必须成对
    public static bool operator <(StudentScore a, StudentScore b)
        => a.Score < b.Score;

    public static bool operator >(StudentScore a, StudentScore b)
        => a.Score > b.Score;

    // <= 和 >= 必须成对
    public static bool operator <=(StudentScore a, StudentScore b)
        => a.Score <= b.Score;

    public static bool operator >=(StudentScore a, StudentScore b)
        => a.Score >= b.Score;

    // ===== 重载 == 和 != 时必须重写 Equals 和 GetHashCode =====
    public override bool Equals(object obj)
    {
        if (obj is StudentScore other)
            return this == other;
        return false;
    }

    public override int GetHashCode() => Score.GetHashCode();

    public override string ToString() => $"{Name}: {Score}分";
}

// 使用
StudentScore s1 = new StudentScore("张三", 92);
StudentScore s2 = new StudentScore("李四", 85);
StudentScore s3 = new StudentScore("王五", 92);

Console.WriteLine($"s1 == s2: {s1 == s2}");   // False
Console.WriteLine($"s1 == s3: {s1 == s3}");   // True(同分)
Console.WriteLine($"s1 > s2: {s1 > s2}");     // True
Console.WriteLine($"s2 < s1: {s2 < s1}");     // True
Console.WriteLine($"s1 >= s2: {s1 >= s2}");   // True

4.3 自增/自减运算符(++、--)

public class Counter
{
    public int Value { get; private set; }

    public Counter(int value) => Value = value;

    public static Counter operator ++(Counter c)
    {
        c.Value++;
        return c;
    }

    public static Counter operator --(Counter c)
    {
        c.Value--;
        return c;
    }

    public override string ToString() => $"Count: {Value}";
}

// 使用
Counter c = new Counter(5);
c++;  // Value 变成 6
Console.WriteLine(c);  // Count: 6

Counter result = ++c;  // Value 变成 7,result 指向同一个对象
Console.WriteLine(c);       // Count: 7
Console.WriteLine(result);  // Count: 7(和 c 是同一个对象!)

注意:对于引用类型,++-- 通常直接修改原对象并返回它

4.4 true / false 运算符

public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public bool HasPermission { get; set; }

    public User(string name, bool active, bool permission)
    {
        Name = name;
        IsActive = active;
        HasPermission = permission;
    }

    // true/false 必须成对重载
    public static bool operator true(User user)
        => user.IsActive && user.HasPermission;

    public static bool operator false(User user)
        => !(user.IsActive && user.HasPermission);

    public override string ToString() => Name;
}

// 使用——可以像 bool 一样用在条件判断中!
User admin = new User("管理员", true, true);
User guest = new User("游客", true, false);

if (admin)
    Console.WriteLine($"{admin} 可以访问");   // ✅ 管理员 可以访问

if (guest)
    Console.WriteLine($"{guest} 可以访问");   // 不会执行
else
    Console.WriteLine($"{guest} 没有权限");   // ✅ 游客 没有权限

// 甚至可以写在短路运算中
Console.WriteLine(admin ? "允许" : "拒绝");  // 允许

五、转换运算符 —— implicit 和 explicit

5.1 隐式转换(implicit)——自动转,不丢失数据

public class Celsius
{
    public double Degrees { get; }

    public Celsius(double degrees) => Degrees = degrees;

    // 隐式转换:Celsius → double(自动,安全)
    public static implicit operator double(Celsius c) => c.Degrees;

    // 隐式转换:double → Celsius(自动,安全)
    public static implicit operator Celsius(double d) => new Celsius(d);

    public override string ToString() => $"{Degrees}°C";
}

// 使用——自动转换,不需要任何标记
Celsius temp = 36.5;          // double 自动转 Celsius
double degrees = temp;        // Celsius 自动转 double
Console.WriteLine(temp);      // 36.5°C
Console.WriteLine(degrees);   // 36.5

5.2 显式转换(explicit)——可能丢数据,必须显式写

public class Money
{
    public decimal Amount { get; }

    public Money(decimal amount) => Amount = amount;

    // 显式转换:Money → double(可能丢失精度)
    public static explicit operator double(Money m) => (double)m.Amount;

    // 显式转换:Money → int(丢失小数部分)
    public static explicit operator int(Money m) => (int)m.Amount;

    public override string ToString() => $"¥{Amount:F2}";
}

// 使用——必须显式强转
Money money = new Money(99.99m);
double d = (double)money;   // 必须写 (double)
int i = (int)money;         // 必须写 (int),小数部分丢失

Console.WriteLine($"Money: {money}");   // ¥99.99
Console.WriteLine($"double: {d}");      // 99.99
Console.WriteLine($"int: {i}");         // 99

5.3 implicit vs explicit 选择表

转换类型 适用场景 示例
implicit 转换是安全的,不会丢数据 Celsius → doubleint → long
explicit 可能丢失数据,需要提醒调用者 Money → int(丢小数)、double → int

六、完整的实战示例——银行账户系统

using System;

public class BankAccount : IComparable<BankAccount>
{
    public string Owner { get; }
    public decimal Balance { get; private set; }

    public BankAccount(string owner, decimal balance)
    {
        Owner = owner;
        Balance = balance;
    }

    // ===== 算术运算符 =====

    // 存款:账户 + 金额 = 新余额的账户
    public static BankAccount operator +(BankAccount account, decimal amount)
    {
        return new BankAccount(account.Owner, account.Balance + amount);
    }

    // 取款:账户 - 金额
    public static BankAccount operator -(BankAccount account, decimal amount)
    {
        if (amount > account.Balance)
            throw new InvalidOperationException("余额不足!");
        return new BankAccount(account.Owner, account.Balance - amount);
    }

    // ===== 比较运算符 =====
    public static bool operator >(BankAccount a, BankAccount b)
        => a.Balance > b.Balance;

    public static bool operator <(BankAccount a, BankAccount b)
        => a.Balance < b.Balance;

    public static bool operator >=(BankAccount a, BankAccount b)
        => a.Balance >= b.Balance;

    public static bool operator <=(BankAccount a, BankAccount b)
        => a.Balance <= b.Balance;

    public static bool operator ==(BankAccount a, BankAccount b)
    {
        if (ReferenceEquals(a, null) && ReferenceEquals(b, null)) return true;
        if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
        return a.Owner == b.Owner && a.Balance == b.Balance;
    }

    public static bool operator !=(BankAccount a, BankAccount b)
        => !(a == b);

    // ===== 自增/自减 =====
    public static BankAccount operator ++(BankAccount account)
    {
        account.Balance += 1;  // 加 1 元利息
        return account;
    }

    public static BankAccount operator --(BankAccount account)
    {
        account.Balance -= 1;  // 扣 1 元手续费
        return account;
    }

    // ===== 转换运算符 =====
    public static implicit operator decimal(BankAccount account)
        => account.Balance;

    public static explicit operator double(BankAccount account)
        => (double)account.Balance;

    // ===== 必需的重写 =====
    public override bool Equals(object obj)
    {
        if (obj is BankAccount other)
            return this == other;
        return false;
    }

    public override int GetHashCode()
        => (Owner, Balance).GetHashCode();

    public override string ToString()
        => $"{Owner} 的账户: ¥{Balance:F2}";
}

class Program
{
    static void Main()
    {
        Console.WriteLine("===== 银行账户系统 — 运算符重载演示 =====\n");

        BankAccount zhangSan = new BankAccount("张三", 1000);
        BankAccount liSi = new BankAccount("李四", 500);

        Console.WriteLine($"初始状态:");
        Console.WriteLine($"  {zhangSan}");
        Console.WriteLine($"  {liSi}");

        // 存款(+ 运算符)
        Console.WriteLine("\n--- 存款 200 ---");
        zhangSan += 200;
        Console.WriteLine($"  {zhangSan}");

        // 取款(- 运算符)
        Console.WriteLine("\n--- 取款 100 ---");
        liSi -= 100;
        Console.WriteLine($"  {liSi}");

        // 比较(>、< 运算符)
        Console.WriteLine("\n--- 比较余额 ---");
        Console.WriteLine($"  张三 > 李四? {zhangSan > liSi}");
        Console.WriteLine($"  李四 < 张三? {liSi < zhangSan}");

        // 自增(+1 元利息)
        Console.WriteLine("\n--- 发放利息(++) ---");
        zhangSan++;
        Console.WriteLine($"  {zhangSan}");

        // 自减(-1 元手续费)
        Console.WriteLine("\n--- 扣除手续费(--) ---");
        zhangSan--;
        Console.WriteLine($"  {zhangSan}");

        // 隐式转换(当成 decimal 用)
        Console.WriteLine("\n--- 转换 ---");
        decimal balance = zhangSan;  // 隐式转换
        Console.WriteLine($"  余额转为 decimal: {balance:C}");

        double d = (double)liSi;     // 显式转换
        Console.WriteLine($"  余额转为 double: {d:F2}");

        // 比较账户(用重载的 ==)
        Console.WriteLine("\n--- 相等判断 ---");
        BankAccount zhangSanCopy = new BankAccount("张三", 1200);
        Console.WriteLine($"  zhangSan 和 copy 余额相等? {zhangSan == zhangSanCopy}");
    }
}

输出:

===== 银行账户系统 — 运算符重载演示 =====

初始状态:
  张三 的账户: ¥1000.00
  李四 的账户: ¥500.00

--- 存款 200 ---
  张三 的账户: ¥1200.00

--- 取款 100 ---
  李四 的账户: ¥400.00

--- 比较余额 ---
  张三 > 李四? True
  李四 < 张三? True

--- 发放利息(++) ---
  张三 的账户: ¥1201.00

--- 扣除手续费(--) ---
  张三 的账户: ¥1200.00

--- 转换 ---
  余额转为 decimal: ¥1,200.00
  余额转为 double: 400.00

--- 相等判断 ---
  zhangSan 和 copy 余额相等? True

七、常见易错点(避坑指南)

坑1:重载 == 时忘记重写 Equals 和 GetHashCode

// ❌ 只重载了 == 和 !=,没重写 Equals/GetHashCode
// 编译器会警告!
public class Person
{
    public string Name { get; set; }
    public static bool operator ==(Person a, Person b) { ... }
    public static bool operator !=(Person a, Person b) { ... }
    // ⚠️ 警告:'Person' defines operator == or operator != but does not override Object.Equals(object o)
}

// ✅ 必须同时重写
public class Person
{
    public string Name { get; set; }
    public static bool operator ==(Person a, Person b) { ... }
    public static bool operator !=(Person a, Person b) { ... }
    public override bool Equals(object obj) { ... }    // ← 必须
    public override int GetHashCode() { ... }           // ← 必须
}

坑2:比较运算符必须成对定义

// ❌ 只重载了 >,没重载 <
// public static bool operator >(T a, T b) { ... }
// ⚠️ 编译错误!> 和 < 必须成对定义

// ✅ 必须成对
public static bool operator >(T a, T b) { ... }
public static bool operator <(T a, T b) { ... }   // ← 必须一起

// 同样,>= 和 <= 也必须成对
public static bool operator >=(T a, T b) { ... }
public static bool operator <=(T a, T b) { ... }  // ← 必须一起

成对规则汇总:

如果重载 必须同时重载
== !=
< >
<= >=
true false

坑3:重载 == 时没有处理 null

// ❌ 没判断 null——可能 NullReferenceException
public static bool operator ==(Person a, Person b)
{
    return a.Name == b.Name;  // 如果 a 或 b 是 null 就崩了!
}

// ✅ 先处理 null 情况
public static bool operator ==(Person a, Person b)
{
    if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
        return true;           // 两个都是 null,相等
    if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
        return false;          // 一个是 null,不相等
    return a.Name == b.Name;   // 都不是 null,正常比较
}

坑4:运算符重载不能改变优先级和结合性

// 运算符重载只能改变行为,不能改变优先级
// a + b * c  中 * 的优先级还是高于 +,无法改变
// a = b = c  的结合性还是从右到左,无法改变

坑5:运算符重载必须是 static

// ❌ 错误
// public Point operator +(Point other) { ... }  // 不可以是实例方法

// ✅ 正确
public static Point operator +(Point a, Point b) { ... }  // 必须是静态

坑6:ref 和 out 不能在运算符中使用

// ❌ 不允许
// public static Point operator +(ref Point a, ref Point b) { ... }

// ✅ 正确
public static Point operator +(Point a, Point b) { ... }

坑7:过度重载导致歧义

// ❌ 不要用运算符做"不直观"的事
// public static Person operator +(Person p, string address) { ... }
// + 号用来"添加地址"不直观,应该用方法名

// ✅ 运算符的含义应该符合数学或逻辑直观
// Point + Point → 坐标相加 ✅
// Money + decimal → 存款      ✅
// Person + string → 不直观    ❌

八、总结

运算符重载速查

类别 运算符 关键字 必成对?
算术 + - * / % operator +
一元 + - ! ~ ++ -- operator ++
比较 == != operator ==
比较 < > operator <
比较 <= >= operator <=
逻辑 true false operator true
位运算 & | ^ operator &
转换 隐式 implicit operator
转换 显式 explicit operator

核心规则速记

1. public static —— 必须是公开的静态方法
2. 成对出现 —— ==/!=、</>、<=/>=、true/false
3. 重载 == 必须重写 Equals 和 GetHashCode
4. 处理 null —— == 和 != 必须先判空
5. 语义合理 —— 运算符含义要符合直觉
6. 不能改变优先级 —— 只能改行为,不能改语法规则

记忆口诀

运算符重载有门道,public static 少不了
operator 关键字后跟符号,参数至少有一个本类型

大于小于要成对,等于不等也要配
true 和 false 一起写,重载等号重写 Equals

隐式转换 implicit,数据安全自动转
显式转换 explicit,可能丢数要强转
算术比较看语义,直觉不对别乱加

一句话总结:运算符重载让你的自定义类型可以像内置类型一样使用 +-==> 等运算符。语法是 public static T operator X(T a, T b)。关键规则是成对重载(==/!=、>/<、true/false),重载 == 必须重写 Equals/GetHashCode,运算符含义要符合数学直觉。

0
博主关闭了当前页面的评论