C# 语言入门(六)运算符重载、接口、预处理、异常、特性

本篇 核心知识点 :运算符重载、接口 interface、抽象类与接口核心区别、命名空间 namespace、预处理指令 (#define/#if/#endif 等)、正则表达式 Regex、异常处理 try-catch-finally-throw、自定义异常、装箱拆箱、特性 Attribute 基础

一、运算符重载

1. 概念

自定义类 / 结构体原本无法直接使用+ - * / == !=等运算符,通过operator关键字重载运算符,自定义运算逻辑,简化代码,替代冗余Add/Sub成员方法。

2. 核心规则

  1. 语法:public static 返回类型 operator 运算符(参数列表),二元运算符需两个参数;

  2. 只能重载 C# 允许的运算符,禁止重载 . ?: :: sizeof

  3. 相等==与不等!=必须成对重载,否则编译警告;

  4. 浮点数值判等不能直接用==,需判断差值极小阈值;

  5. 不改变运算符优先级、结合性、操作数数量。

3. 代码示例:二维向量 Point

复制代码
class Point{
    public float X, Y;
    public Point(float x = 0, float y = 0){
        X = x;
        Y = y;
    }
    // 重载 + 向量相加
    public static Point operator +(Point p1, Point p2)    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y);
    }
    // 重载 - 向量相减
    public static Point operator -(Point p1, Point p2){
        return new Point(p1.X - p2.X, p1.Y - p2.Y);
    }
    // 重载 == 相等判断(浮点容错)
    public static bool operator ==(Point p1, Point p2){
        float eps = 1e-6f;
        return Math.Abs(p1.X - p2.X) < eps && Math.Abs(p1.Y - p2.Y) < eps;
    }
    // 成对重载 !=
    public static bool operator !=(Point p1, Point p2){
        return !(p1 == p2);
    }
    public void Print(){
        Console.WriteLine($"X:{X}, Y:{Y}");
    }
}
// 测试
static void Main(){
    Point a = new Point(2, 3);
    Point b = new Point(1, 1);
    Point c = a + b;
    c.Print();
    Console.WriteLine(a != b);
}

4. 拓展

1 向量

数字、向量

向量均可分别重载;

2 class 默认==比较堆内存地址,重载后实现值对比;

3 TS 无运算符重载,C++/C# 支持,游戏 Vector2/3 大量使用。

二、接口 interface

1. 概念

接口是一套行为规范契约,仅声明方法 / 属性,无实现;类 / 结构体实现接口,必须完整实现所有接口成员,用于多行为统一约束。

2. 核心特性

  1. 接口所有成员默认且只能是 public,不能写访问修饰符;

  2. 只能包含声明(方法、属性、事件),无字段、无构造函数;

  3. 不能实例化,仅能被子类实现;

  4. C# 类单继承(仅一个父类) ,但可同时实现多个接口,弥补多继承缺失;

  5. 接口之间可多继承;

  6. 抽象类可写实现代码,接口全部为抽象无实现。

3. 抽象类 vs 接口(核心对比)

对比维度 abstract 抽象类 interface 接口
成员实现 可包含已实现普通方法、抽象方法 全部只有声明,无任何实现
构造函数 可写构造函数 无构造函数
继承规则 类只能继承一个抽象父类 一个类可实现 N 个接口
成员权限 public/protected/private 均可 强制全部 public
字段 可定义成员变量 不允许定义字段
使用场景 同类事物公共逻辑复用 不同事物统一行为标准(如点击、受伤)

4. 代码示例

复制代码
// 定义两个行为接口
interface IHit{
    void TakeDamage(int atk); // 受伤害
}
interface IAnim{
    void PlayIdle(); // 播放待机动画
}
// 植物类同时实现两个接口
class SunFlower : IHit, IAnim{
    private int hp = 100;
    // 实现接口受伤害方法
    public void TakeDamage(int atk){
        hp -= atk;
        Console.WriteLine("向日葵受伤");
    }
    // 实现动画接口
    public void PlayIdle(){
        Console.WriteLine("播放向日葵待机动画");
}
static void Main(){
    IHit plant = new SunFlower();
    plant.Take(10); // 仅能调用接口定义方法
}

5. 实战拓展(Unity/Cocos)

UI 点击、拖拽、碰撞统一使用接口;引擎回调全部基于接口,只关心对象是否具备对应行为,不关心具体类型。

三、命名空间 namespace

1. 概念

相当于代码文件夹,隔离同名类,解决多文件、多库类名冲突问题。

2. 特性

  1. 语法:namespace 名称 { 所有类/结构体 }

  2. 访问方式:命名空间.类名

  3. using 命名空间; 简写,无需完整路径;

  4. 支持多层嵌套(文件夹嵌套);

  5. 不同命名空间下同名类属于完全不同类型。

3. 代码示例

复制代码
namespace Lesson7{
    class Point { public int X; }
}
namespace Lesson8{
    class Point { public float X; }
}
static void Main(){
    // 完整路径区分同名类
    Lesson7.Point p1 = new Lesson7();
    Lesson8.Point p2 = new Lesson8();
}

拓展

Unity 大量命名空间区分引擎模块,冲突时必须写完整命名空间限定。

四、预处理指令 #define / #if / #endif

1. 概念

编译前执行的标记指令,不生成变量,仅定义符号,用于跨平台、Debug/Release 代码区分。

2. 核心指令

  1. #define 符号名:定义编译标记,必须放在文件最顶部;

  2. #if / #elif / #else / #endif:判断标记是否存在,不存在代码直接不参与编译;

  3. #undef:取消已定义标记;

  4. #warning / #error:编译时抛出警告 / 错误;

  5. #region / #endregion:代码折叠块。

3. 特性

  1. C# 预处理无 C++ 宏替换功能,仅做条件编译判断;

  2. 系统自带内置标记DEBUG(调试模式自动定义);

  3. 未定义符号区间代码灰色,编译直接忽略,不报错。

4 跨平台实战代码

复制代码
#define IOS // 定义IOS平台标记
static void GameInit()
{
#if IOS
    Console.WriteLine("IOS平台初始化");
#elif ANDROID
    Console.WriteLine("安卓平台初始化");
#else
    Console.WriteLine("Windows通用逻辑");
#endif
}

拓展

游戏跨平台生命周期、SDK 适配全部使用预处理区分;Release 模式DEBUG标记自动消失。

五、正则表达式 Regex

1. 概念

用于文本匹配、提取、校验字符串的规则模板,C# 内置System.Text.RegularExpressions.Regex类实现。

2. 常用元字符

\d 数字 0-9;\D 非数字

\w 字母数字下划线;\b 单词边界

+ 至少 1 次;* 0 或多次;? 0 或 1 次

{n} 精准匹配 n 次;[] 字符集

3. 基础代码示例(提取数字)

复制代码
using System.Text.RegularExpressions;
static void TestRegex()
{
    string str = "编号995 分数05";
    // 匹配连续两位数字
    Regex reg = new Regex(@"\d{2}");
    MatchCollection mc = reg.Matches(str);
    foreach(Match m in mc)
    {
        Console.WriteLine(m.Value);
    }
}

实战场景

账号校验、配置文本解析、日志关键字提取。

六、异常处理 try-catch-finally-throw

1. 核心概念

程序运行错误称为异常,若不捕获会直接程序崩溃;通过try监控危险代码,catch捕获处理,finally无论是否异常必执行,throw主动抛出异常。

2 关键字分工

  1. try:包裹可能报错的代码块;

  2. catch(异常类型 ex):捕获对应异常,可获取异常信息ex.Message

  3. finally:无论正常 / 异常,代码一定会执行(释放资源专用);

  4. throw new Exception("提示"):手动抛出异常,向上层传递。

3. 基础示例(除数为零异常)

复制代码
static float Div(float a, float b)
{
    if (b == 0){
        // 主动抛出异常
        throw new Exception("除数不能为0");
    }
    return a / b;
}
static void Calc(){
    float res = 0;
    try    {
        res = Div(10, 0);
    }
    catch(Exception ex){
        Console.WriteLine("捕获异常:" + ex.Message);
        res = -1; // 异常默认值
    }
    finally{
        Console.WriteLine("计算结束,释放临时资源");
    }
    Console.WriteLine("结果:" + res);
}

4. 自定义异常

概念

继承Exception自定义异常类,区分不同错误类型,精准分类捕获处理。

复制代码
// 数组下限越界异常
class IndexLowException : Exception{
    public IndexLowException(string msg) : base(msg) { }
}
// 数组上限越界异常
class IndexHighException : Exception{
    public IndexHighException(string msg) : base(msg) { }
}
static void SetArr(int[] arr, int idx, int val){
    if(idx < 0) throw new IndexLowException("下标小于0");
    if(idx >= arr.Length) throw new IndexHighException("下标超出数组长度");
    arr[idx] = val;
}
// 分层捕获
static void TestArr(){
    int[] data = new int[5];
    try{
        SetArr(data, 8, 99);
    }
    catch(IndexLowException ex){
        Console.WriteLine("下限错误:"+ex.Message);
    }
    catch(IndexHighException ex){
        Console.WriteLine("上限错误:"+ex.Message);
    }
}

拓展

1 无法处理的异常可再次throw向上抛给上层调用者;

2 文件、网络、数据库资源释放写在finally,保证资源关闭。

七、装箱 & 拆箱

1. 装箱(隐式)

值类型 → object 引用类型,栈数据拷贝到堆,自动完成。

复制代码
int num = 10;
object obj = num; // 装箱

2. 拆箱(显式强制转换)

堆 object 转回栈值类型,必须强制转换,类型不匹配抛异常。

复制代码
int res = (int)obj; // 拆箱

特性

频繁装箱拆箱产生大量堆临时对象,GC 压力大,高性能游戏尽量避免。

八、特性 Attribute(基础)

1. 概念

附加在类 / 方法 / 属性上的元数据标记,不影响程序运行,用于编译器提示、反射读取配置信息。

2. 系统内置常用特性

  1. [Obsolete("提示文本", true)] 标记弃用 API,true 编译报错,false 仅警告;

  2. [Conditional("DEBUG")] 条件编译,仅定义标记时方法生效。

3. 自定义特性

继承Attribute实现自定义标记,存储开发备注、Bug 记录等元数据,通过反射读取。

代码示例(弃用特性)

复制代码
[Obsolete("该方法已废弃,请使用CalcNew()", false)]
static void OldCalc(){
    Console.WriteLine("旧计算逻辑");
}

九、反射前置

1. 概念

通过字符串类型名动态获取类、方法、属性信息,运行时创建对象、调用函数,依赖特性读取元数据。

核心 API

  1. typeof(类) 获取类型Type对象;

  2. Type.GetMethods() 获取所有方法;

  3. Type.GetCustomAttributes() 读取附加特性。

拓展

游戏热更新、配置表解析、框架插件系统底层全部基于反射。

十、拓展

1 接口与抽象类核心区分,多接口实现作用;

2 预处理指令作用,Debug/Release 区分原理;

3 异常三层结构 try-catch-finally 使用场景,自定义异常优势;

4 运算符重载成对规则、浮点判等坑;

5 特性本质元数据,反射读取流程;

6 装箱拆箱性能缺陷优化方案。