C#学习之C#入门学习

本篇博客开始,我们将开启一个新的篇章------C#的学习。

C#(读作"C Sharp")是由微软开发的一种现代、面向对象、类型安全的编程语言,它在2000年首次发布,至今已发展成为一个功能强大且应用广泛的编程平台。

目录

C#的介绍

C#的应用领域

C#程序编写

C#知识点

变量

声明与初始化

[值类型 vs 引用类型(防坑核心)](#值类型 vs 引用类型(防坑核心))

[变量修饰符与 Unity 的"化学反应"](#变量修饰符与 Unity 的“化学反应”)

常量

[编译时常量 const](#编译时常量 const)

[运行时常量 readonly](#运行时常量 readonly)

[const vs readonly 对比速查](#const vs readonly 对比速查)

使用枚举作为一组关联常量

注释

程序结构

引入命名空间

命名空间

主函数

输入输出

输出

输入

折叠代码

C#中的转义字符

[逐字字符串 @"" ------ 让反斜杠"变老实"](#逐字字符串 @"" —— 让反斜杠“变老实”)

字符串插值中的大括号转义

[原始字符串字面量(C# 11+)](# 11+))

类型转化

类型转换的本质

隐式转换

数值的隐式扩大转换

引用类型的隐式上行转换

[Unity 结构体的特殊隐式转换](#Unity 结构体的特殊隐式转换)

显式转换

数值的强制转换(缩小转换)

引用类型的下行转换(父类转子类)

更优雅的显式转换方式

字符串到数值的解析

[装箱 (Boxing) 与 拆箱 (Unboxing) ------ 性能隐形杀手](#装箱 (Boxing) 与 拆箱 (Unboxing) —— 性能隐形杀手)

枚举和整数之间的转换

算术表达式

条件表达式

逻辑表达式

短路求值机制(非常重要)

位运算表达式

[Unity 的核心应用场景](#Unity 的核心应用场景)


C#的介绍

C#诞生于世纪之交(1990s-2000)。是微软为了推动其革命性的 .NET Framework 战略,需要一款全新的、现代化的、专门为.NET平台设计的编程语言。由Anders Hejlsberg, Turbo Pascal 和 Delphi 之父,担纲C#的首席架构师

C#自诞生开始就有以下几个目标:

  • 简单易学: 比C++更简洁,减少复杂性(如指针的直接操作)。

  • 完全面向对象: 一切皆对象(比Java更彻底,连基本类型也是struct对象)。

  • 类型安全: 强大的编译时和运行时类型检查,提高代码健壮性。

  • 现代化特性: 内置支持垃圾回收 (自动内存管理)、异常处理委托 (类型安全的函数指针)、属性事件等。

  • 强大的组件化能力: 方便构建可复用的软件组件。

C#的应用领域

C#凭借其强大的功能和跨平台能力,在多个领域有着广泛应用:

  1. 桌面应用开发:通过WPF、WinForms等框架
  2. Web开发ASP.NET Core框架
  3. 游戏开发:Unity游戏引擎的主要编程语言
  4. 移动应用:Xamarin跨平台移动开发
  5. 云服务:Azure云平台开发
  6. 物联网(IoT):嵌入式系统开发

C#程序编写

像以前些C++那样,我们推荐以VS2022为编写C#的主平台。首先我们先创立一个项目

(一个解决方案可以有多个项目)

我们简单编写一段代码

cs 复制代码
using System;
namespace Primer
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("hello world");
        }
    }
}

结果为:

C#知识点

变量

变量是程序中最基本的存储单元,它的值在运行时可以被改变。但写好变量,远不止"声明后使用"这么简单。

声明与初始化

  • 语法类型 变量名 = 初始值;

  • 局部变量 必须显式初始化才能使用;成员变量(字段) 会获得默认值(数字为0,引用为null),但始终建议初始化,避免产生歧义。

  • 使用 var 关键字可以隐式类型推断,必须声明时赋值

cs 复制代码
float speed = 5.0f;          // float字面量必须有f后缀
var enemyList = new List<Enemy>(); // 编译器推断为List<Enemy>

值类型 vs 引用类型(防坑核心)

理解这个分类,是写出正确逻辑的关键。

分类 存储内容 赋值行为 Unity 典型案例
值类型 数据本身 完整拷贝,互不影响 int, float, bool, struct (如 Vector3, Color, Quaternion)
引用类型 堆上对象的地址 传递地址,多个变量指向同一对象 class (如 GameObject, Transform, MonoBehaviour), string, 数组

注意:string 虽然是引用类型,但因为不可变性,任何修改都会生成新串,行为上像值类型,但需注意拼接时的性能问题。

变量修饰符与 Unity 的"化学反应"

  • public vs privatepublic 字段会自动序列化并在 Inspector 中显示;private 则不会。

  • [SerializeField] :强制让 private 字段也显示在 Inspector 中,兼具封装与可视化调参,是强烈推荐的编码习惯。

    cs 复制代码
    [SerializeField] private float moveSpeed = 6f;
  • [HideInInspector] :在 Inspector 中隐藏 public 字段,保持面板简洁。

  • static 静态变量 :属于类本身,全局唯一。在 Unity 中,静态变量的生命周期贯穿整个游戏运行,切换场景也不会销毁 ,常用来做跨场景全局数据,但滥用会导致数据残留和强耦合,可用单例模式或 ScriptableObject 代替。

常量

常量一旦赋值,在程序运行时绝不可改变。合理使用常量,可以消灭"魔法数字",让代码意图清晰。

编译时常量 const

  • 是什么 :在编译时值就已经完全确定,编译器会直接把常量值内联到所有引用它的地方,类似 C++ 的宏替换。

  • 硬性限制

    • 只能修饰简单的值类型、stringnull

    • 不能使用 new 关键字,不能调用任何方法(包括 MathfLayerMask 等)。

    • 隐式为 static ,因此不能再加 static 关键字。

  • 适用场景:永远不会改变的数学常量、标签名、固定参数。

    cpp 复制代码
    public const string PlayerTag = "Player";
    public const float Gravity = -9.81f;
    public const int MaxEnemies = 20;
  • ⚠️ 严重陷阱 :由于常量值被编译进调用程序集,如果你修改了一个 const 的值,所有引用它的代码必须重新编译才会生效,否则会继续使用旧值。对于分模块开发或发布到 DLL 的项目,这会造成难以排查的 Bug。

运行时常量 readonly

  • 是什么 :只能在声明时或构造函数中赋值,之后不可更改。它是在运行时持有的常量,不会被内联。

  • 优势

    • 可以用运行时计算的结果赋值,比如从方法返回值初始化。

    • 可以是实例字段,让每个对象都有属于自己的一份不可变数据。

    • 可以修饰任何类型。

  • Unity 必用场景

    cs 复制代码
    // 编译时无法获取的物理层
    private readonly int enemyLayer = LayerMask.GetMask("Enemy");
    // 实例只读ID,构造函数确定
    private readonly string characterId;
    public Character(string id) { characterId = id; }
  • 静态运行时常量 static readonly:结合了全局唯一和运行时初始化,非常适合替代那些需要方法调用的"全局常量"。

    cs 复制代码
    public static readonly int GroundLayer = LayerMask.GetMask("Ground");

const vs readonly 对比速查

特性 const readonly
赋值时机 编译时 声明时 / 构造函数中
隐式静态 是,不能加 static 否,可声明为 static
修饰类型 简单值类型、stringnull 任何类型
运行时调用 不允许(如 Mathf.Sqrt(2) 允许
内联行为 值直接嵌入引用处 保持变量引用,取值在运行时
Inspector 显示 不序列化,不显示 不序列化,不显示

使用枚举作为一组关联常量

当你有几个互斥的固定选项时,枚举 是比 const 更专业的表达方式。

cs 复制代码
public enum GameState { MainMenu, Playing, Paused, GameOver }
public GameState currentState = GameState.Playing;

它比直接用 const int MAIN_MENU = 0; 更具可读性,也能利用 IDE 的代码补全和编译期检查。

注释

C#的注释有三种,除了与C/C++的注释相同外,还有一个三杠的注释。用于XML的注释

cs 复制代码
//这是一个注释
/*这是一个注释*/

XML注释

cs 复制代码
/// <summary>
/// 计算两个数字的和
/// </summary>
/// <param name="a">第一个加数</param>
/// <param name="b">第二个加数</param>
/// <returns>两个数字的和</returns>
/// <exception cref="ArgumentException">当参数无效时抛出</exception>
/// <example>
/// <code>
/// int result = Add(5, 3); // 返回 8
/// </code>
/// </example>
/// <remarks>
/// 这是一个简单的加法方法示例
/// </remarks>

同样在VS中我们可以用Ctrl+K和Ctrl+C注释,Ctrl+K和Ctrl+U取消注释

程序结构

我们的程序卸载语句块中。语句块就是在大括号包裹之中。不同语句块之间书写规则不同。这与之前学习C语言的规则相同。同时C#也和C/C++一样每个语句后都要有分号

以以下代码为例

cs 复制代码
using System;
//命名空间
namespace Primer
{
    //类
    //面向对象代码主要写在类里
    class Program
    {
        //函数
        static void Main(string[] args)
        {
            Console.WriteLine("hello world");
            //这是一个注释
            /*这是一个注释*/
        }
    }
}

以上代码与C++类似我们逐一拆解。

引入命名空间

using System为引用命名空间,如果没有的话就要这么写

cs 复制代码
namespace Primer
{
    //类
    //面向对象代码主要写在类里
    //这一点类似于Java
    class Program
    {
        //函数
        static void Main(string[] args)
        {
            System.Console.WriteLine("hello world");
            //这是一个注释
            /*这是一个注释*/
        }
    }
}

引用也可以单独引用命名空间的一部分

cs 复制代码
using System.Text;      // 引入文本处理命名空间

或者在C#6.0后,可以直接静态引用

cs 复制代码
using static System.Math;  // 静态引入,可直接使用数学函数

命名空间

命名空间与C++类似,可以嵌套定义

cs 复制代码
// 多层嵌套命名空间(两种写法等价):
namespace Outer.Inner
{
    class MyClass { }
}

// 等价于:
namespace Outer
{
    namespace Inner
    {
        class MyClass { }
    }
}

为了避免命名冲突可以这么写

cs 复制代码
using SysIO = System.IO;  // 创建别名
SysIO.File.WriteAllText(...);  // 使用别名

C#中,类内可以定义:字段(成员变量)、属性、方法(类内函数)、事件、索引器、运算符重载、嵌套类型。

类的修饰符也有三种

cs 复制代码
public class MyClass { }      // 任何地方可访问

internal class MyClass { }    // 同一程序集内可访问(默认)

private class MyInnerClass { }// 只能在包含类中访问

主函数

主函数必须为以下其一:

cs 复制代码
// 程序入口点,必须满足以下条件之一:
static void Main() { }
static void Main(string[] args) { }
static int Main() { }
static int Main(string[] args) { }
static async Task Main() { }
static async Task<int> Main() { }
static async Task Main(string[] args) { }

输入输出

主要学习以下四种

cs 复制代码
Console.WriteLine("hello world");
Console.Write("你好世界");
Console.ReadLine();
Console.ReadKey();

输出

cs 复制代码
//打印后换行
Console.WriteLine("hello world");
//打印后没有空行
Console.Write("你好世界");

输入

cs 复制代码
//打印后换行
Console.WriteLine("hello world");
//打印后没有空行
Console.Write("你好世界");
//检测用户输入的内容,忽略换行,直到回车才会结束
Console.ReadLine();
//检测用户是否按键,只要按任意键就结束
Console.ReadKey();

折叠代码

C#开发中,折叠代码(Code Folding / Outlining)是集成开发环境(如 Visual Studio)提供的一种功能,允许开发者将代码块收缩为一行或一个标题,从而在编辑器中隐藏细节,只显示代码的高层结构,主要的作用是:

  1. 提高可读性:将长方法、多个成员或逻辑相关的代码分组折叠,使文件结构更清晰。

  2. 隐藏实现细节:在查看类或模块的概览时,可以只关注公共接口,暂时屏蔽内部实现。

  3. 便于团队协作 :通过 #region 指令标记代码块,可以让团队成员快速定位到特定功能区域。

  4. 导航效率提升 :配合 IDE 的折叠/展开快捷键(如 Ctrl+M, Ctrl+O 折叠到定义,Ctrl+M, Ctrl+L 切换所有折叠),能快速在不同抽象层次间切换。

写法如下:

cs 复制代码
//引用命名空间
using System;
//命名空间
namespace Primer
{
    //类
    //面向对象代码主要写在类里
    //这一点类似于Java
    class Program
    {
        //函数
        static void Main(string[] args)
        {
            #region
            //这是一个区域
            #endregion

        }
    }
}

C#中的转义字符

转义序列 名称 功能效果 Unity 常用度与示例
\' 单引号 char 字面量中插入一个单引号 ⭐⭐⭐⭐⭐ char c = '\''; // 存储一个引号字符
\" 双引号 在字符串中插入一个双引号,避免语法冲突 ⭐⭐⭐⭐⭐ string s = "他说:\"你好\"";
\\ 反斜杠 插入一个真正的反斜杠字符 ⭐⭐⭐⭐⭐ 文件路径必备:"Assets\\Scripts" 但更推荐用逐字字符串:@"Assets\Scripts" 或直接使用正斜杠 /
\0 空字符 (Null) 字符值为 0 的字符,常用作字符串终止符 ⭐ 极少在游戏逻辑中使用,偶尔在与底层 API 交互时出现
\a 响铃 (警报) 触发系统提示音(如果硬件支持),ASCII 码 7 ⭐ 控制台程序偶尔会用到,Unity 中基本无实际意义,编辑器控制台可能产生"哔"声
\b 退格 将光标向左移动一个位置,模拟"删除"效果 ⭐ 控制台界面会用到,Unity 游戏内 UI 文本不处理退格,很少用
\f 换页 打印机走纸到下一页,ASCII 码 12 ⭐ 视觉上通常显示为奇怪符号,Unity 文本组件无法正确处理,基本弃用
\n 换行 将光标移动到下一行开头,跨平台的换行标准 ⭐⭐⭐⭐⭐ Debug.Log("第一行\n第二行"); Unity UI 的 TextTextMeshPro 组件都完美支持
\r 回车 将光标移动到当前行开头(常用于 \r\n 组合) ⭐⭐⭐ Windows 系统经典换行是 \r\n,单独使用 \r 可以覆盖当前行输出,如进度条效果
\t 水平制表符 插入一个制表符,产生空白对齐 ⭐⭐⭐⭐ Debug.Log($"名字:\t{name}") 可对齐输出,UI 文本中也能产生缩进
\v 垂直制表符 垂直方向制表,极少使用 ⭐ 历史上用于打印机,Unity 文本组件无法正常处理,基本忽略
\uXXXX Unicode 字符 (4位16进制) 插入一个 UTF-16 代码单元,XXXX 为 4 位十六进制数 ⭐⭐⭐⭐⭐ string heart = "\u2665"; // ♥ 非常适合在 UI 中显示特殊符号
\UXXXXXXXX Unicode 字符 (8位16进制) 插入一个 UTF-32 字符,自动转换为代理项对(需要时) ⭐⭐⭐ 输入表情符号或生僻字:string emoji = "\U0001F60A"; // 😊 注意:字符串长度可能为 2 (char 代理对)
\xH[H][H][H] 十六进制转义 (1-4位) 以十六进制值直接指定一个字符,后跟 1~4 个十六进制数字 ⭐⭐ "\x41" 即 'A',"\x0D\x0A"\r\n ⚠️ 极易出错:后面如果紧跟也是合法十六进制字符,会被吞噬,如 "\x41BC" 实际会解析成 \x41BC 然后报错,必须用 "\x41" + "BC" 隔开

逐字字符串 @"" ------ 让反斜杠"变老实"

文件路径、正则表达式这类包含大量反斜杠的字符串,如果每个都写成 \\ 会非常痛苦。在字符串前加 @ 即可让反斜杠不再具有转义功能 ,双引号本身使用两个连续双引号 "" 转义。

cpp 复制代码
string path = @"C:\Users\Unity\Documents"; // 干净清爽
string quote = @"他说:""你好""";            // 输出:他说:"你好"

Unity 最佳实践:任何文件系统路径、资源路径,优先使用逐字字符串。

Resources.Load("Prefabs/Player") 这类 Unity 内部路径中,使用正斜杠 / 即可跨平台,无需转义。

字符串插值中的大括号转义

当你使用 $"{变量}" 插值时,想要输出真正的大括号 {},需要用两个相同的大括号来转义:

cpp 复制代码
int count = 5;
string msg = $"{{{count}}}"; // 输出 "{5}"

原始字符串字面量(C# 11+)

如果你使用的是较新的 C# 版本(Unity 2022.2 开始支持 C# 9/10,最新版本已支持 C# 11),可以用三个以上的双引号包裹字符串,里面的内容几乎不需要任何转义,连引号都能直接写(只要不与边界冲突)。它非常适合内嵌 JSON 或代码片段。

cs 复制代码
string json = """
{
    "name": "Unity",
    "version": 2022
}
""";

类型转化

类型转换的本质

类型转换就是把一个值的类型 "看作""变作" 另一种类型。这分为两种情况:

  • 隐式转换 (Implicit) :你什么都不用写,编译器自动帮你完成。一定是安全的,不会丢数据,不会抛异常。

  • 显式转换 (Explicit) :你必须使用强制转换语法,告诉编译器"我知道我在做什么"。可能会丢失数据或引发异常

核心铁律:隐式转换必然安全,显式转换需要你主动承担风险。

隐式转换

数值的隐式扩大转换

规则:小范围转大范围、整数转浮点数,都安全无痛。

cs 复制代码
int hp = 100;
long longHp = hp;   // int → long 隐式
float floatHp = hp; // int → float 隐式

float speed = 5.5f;
double dSpeed = speed; // float → double 隐式

特别注意

  • intfloat 虽然隐式,但大整数(超过 16,777,216)转换时会损失精度,C# 不会警告你。

  • decimalfloat/double 之间没有隐式转换 ,必须显式,因为 decimal 高精度,来回转不安全。

引用类型的隐式上行转换

规则:派生类对象可以直接当作基类对象使用。

cs 复制代码
class Enemy : MonoBehaviour { }
class Orc : Enemy { }

Orc orc = GetComponent<Orc>();
Enemy enemy = orc; // 隐式,Orc 就是一个 Enemy

在 Unity 中,所有脚本都是 MonoBehaviour 的派生类,所以你可以把任何脚本赋给 MonoBehaviour 类型的变量。

Unity 结构体的特殊隐式转换

Unity 为了方便,在它的数学库中预置了一些隐式转换,非常常用:

  • Vector2Vector3Vector2 可以隐式转换为 Vector3,新增的 z 分量会自动设为 0

    cs 复制代码
    Vector2 v2 = new Vector2(1, 2);
    Vector3 v3 = v2; // 隐式,v3 = (1, 2, 0)
  • ColorVector4Color 可以隐式转换为 Vector4,分量对应 (r,g,b,a)。

  • UnityEngine.Object 的布尔隐式转换 :任何继承自 UnityEngine.Object 的变量(GameObjectComponent 等),都可以直接放进 if 语句,它重载了向 bool 的隐式转换,用来检查对象是否被销毁。

    cs 复制代码
    GameObject player;
    if (player) // 实际调用了 player != null 的引擎判空逻辑
    { ... }

显式转换

数值的强制转换(缩小转换)

语法(目标类型) 变量
风险:精度丢失、溢出。

复制代码
cs 复制代码
float damage = 9.9f;
int intDamage = (int)damage; // 显式,结果为 9,直接截断小数部分

int big = 1000;
byte small = (byte)big; // 显式,1000 超过 255,溢出后结果为 232
可在 checked 代码块中检测溢出。
引用类型的下行转换(父类转子类)

风险 :如果父类引用实际指向的不是那个子类,运行时会抛出 InvalidCastException

cs 复制代码
Enemy enemy = new Orc();
Orc orc = (Orc)enemy;      // 安全,因为 enemy 本质是 Orc

Enemy enemy2 = new Enemy();
Orc orc2 = (Orc)enemy2;    // 抛出 InvalidCastException!

Unity 高频场景

  • GetComponent<Transform>() 等内部已经做完了转换。

  • 老式写法 (Rigidbody)GetComponent(typeof(Rigidbody)) 非常不推荐,直接用泛型。

更优雅的显式转换方式

除了硬括号,C# 提供了更安全的操作符:

  • as 操作符 :只适用于引用类型,转换失败返回 null,不抛异常。

    cs 复制代码
    Orc orc = enemy as Orc;
    if (orc != null) { /* 安全使用 */ }
  • is 操作符 :类型检查,返回 bool,不转换。

    cs 复制代码
    if (enemy is Orc)
    {
        Orc orc = (Orc)enemy; // 这时候可以放心强制转
    }
    // 模式匹配版本(推荐)
    if (enemy is Orc myOrc)
    {
        myOrc.OrcSkill(); // 直接使用,免去二次转换
    }
字符串到数值的解析

玩家输入、配置文件读取经常需要把字符串变为数字,这是典型的显式转换。

cs 复制代码
string input = "123";
int num = int.Parse(input);              // 若格式错,抛异常
bool ok = int.TryParse(input, out int safeNum); // 安全版本,失败返回 false

Unity 常用的是 float.TryParse 处理 UI 输入文本。

装箱 (Boxing) 与 拆箱 (Unboxing) ------ 性能隐形杀手
  • 装箱 :值类型隐式转换为 object,在堆上复制一份数据,产生垃圾。

  • 拆箱 :从 object 显式转回值类型。

cs 复制代码
int score = 100;
object box = score;       // 装箱,隐式
int unbox = (int)box;     // 拆箱,必须显式,类型必须完全一致

Unity 注意Debug.Log(123) 这种古老 API 会触发装箱(新版已优化),string.Format 的值参数也是 object,循环中大量使用会产生 GC,影响性能。尽可能用字符串插值 $"{score}" 替代。

枚举和整数之间的转换

枚举与 int 等基础整型之间是显式转换。

cs 复制代码
GameState state = GameState.Playing;
int index = (int)state;              // 显式,索引 1
GameState restored = (GameState)index; // 显式

算术表达式

C#自带的算术表达式如下:

运算符 含义 特别注意
+ 加法 也用于字符串拼接,字符串与其他类型相加会调用 ToString(),可能产生装箱与内存分配。
- 减法 取负用单目 -,如 int a = -5;
* 乘法 两个 int 相乘可能溢出,可改用 longfloat
/ 除法 整数除法会截断小数部分5 / 2 结果是 2;至少有一个浮点数时才会得到浮点结果,如 5f / 2f2.5
% 取模(求余) 常用于循环索引、奇偶判断,例如 index % array.Length
++ / -- 自增 / 自减 有前缀 ++i(先增再用)和后缀 i++(先用再增)的区别,在表达式中谨慎使用。

此外,Unity 对 Vector3Vector2Color 等结构体重载了算术运算符,这让移动、颜色混合等逻辑变得非常自然。

cs 复制代码
// === 基础算术演示 ===
int baseDamage = 50;
float critMultiplier = 1.5f;
int totalDamage = (int)(baseDamage * critMultiplier); // 需要显式转换

float cooldown = 3.0f;
cooldown -= Time.deltaTime;     // 复合赋值,实现计时器
int index = (currentIndex + 1) % enemies.Length; // 循环索引

// === Unity 向量算术 ===
Vector3 move = (transform.forward * Input.GetAxis("Vertical") + 
                transform.right * Input.GetAxis("Horizontal")) 
                * speed * Time.deltaTime;
transform.position += move;     // Vector3 加法

Vector3 direction = (target.position - transform.position).normalized; // 减法得方向
float force = damage * 100f;    // 根据伤害算力

避坑指南

  • 向量之间不能直接相乘(没有 Vector3 * Vector3 重载),点乘用 Vector3.Dot,叉乘用 Vector3.Cross

  • 不要每帧都使用 Vector3.Distance 开根号,比较距离平方用 sqrMagnitude 更省性能。

条件表达式

语法结构如下:

cs 复制代码
条件 ? 结果1 : 结果2

C#中,该逻辑与C、C++中一致。如果"条件"为 true,则求值"结果1",否则求值"结果2"。它本质上是一个 if-else 的表达式版,可以返回一个值。且满足以下条件:

  • 两端的表达式类型必须一致,或存在隐式转换。

  • 右结合性:多个三元嵌套时从右向左分组,但为了可读性,强烈不建议嵌套超过一层。

  • 它是短路求值:只会执行满足条件的那一个分支。

    cs 复制代码
    // 1. 根据状态切换文本
    string stateText = isDead ? "阵亡" : "存活";
    healthBarText.text = $"HP: {currentHealth}/{maxHealth} ({(isLow ? "危险" : "健康")})";
    
    // 2. 根据距离改变颜色
    float dist = Vector3.Distance(player.position, enemy.position);
    Color alertColor = dist < 5f ? Color.red : Color.green;
    renderer.material.color = alertColor;
    
    // 3. 伤害公式分支
    int damage = isCritical ? (int)(baseDamage * 2.5f) : baseDamage;
    
    // 4. 安全处理 null
    string displayName = playerName != null ? playerName : "未知玩家";
    // 更现代的写法可以用 null 合并运算符:playerName ?? "未知玩家"

逻辑表达式

逻辑表达式组合多个布尔值(true/false)并产生最终的布尔结果,使用的运算符有:

  • &&(逻辑与):两边都为 true 时才为 true

  • ||(逻辑或):至少一边为 true 时即为 true

  • !(逻辑非):翻转布尔值。

操作数通常来自关系表达式==, !=, >, <, >=, <=)或本身就是布尔变量。

短路求值机制(非常重要)

&&|| 都是短路运算符

  • 对于 A && B,若 Afalse,则不会计算 B

  • 对于 A || B,若 Atrue,则不会计算 B

利用这一点可以写出安全且高效的代码,例如先判空再访问成员:

cs 复制代码
if (enemy != null && enemy.Health > 0) // 如果 enemy 为 null,不会执行 enemy.Health,避免空引用异常
{
    Attack(enemy);
}

示例代码:

cs 复制代码
// 典型输入判断(多重条件组合)
bool isGrounded = Physics.Raycast(transform.position, Vector3.down, 0.2f);
bool jumpPressed = Input.GetButtonDown("Jump");

if (isGrounded && jumpPressed && !isStunned)
{
    rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}

// 是否在攻击范围内
float attackRange = 2.0f;
bool inRange = Vector3.Distance(transform.position, target.position) <= attackRange;
bool hasLineOfSight = !Physics.Linecast(transform.position, target.position, obstacleLayer);

if (inRange && hasLineOfSight)
{
    PerformAttack();
}

// 逻辑非的妙用
bool isDead = health <= 0;
if (!isDead) // 等价于 isDead == false
{
    Patrol();
}

位运算表达式

位运算直接对整型(intlongenum 等)的二进制位进行操作,非常适合管理开关状态、层级掩码等。运算符包括:

| 运算符 | 名称 | 含义 | 示例 (设 a=5, b=3) |
|------|------|----------------|------------------------------------|--------------|
| & | 按位与 | 两位同时为1,结果位才为1 | 5 & 3 → 1 (0101 & 0011 = 0001) |
| | | 按位或 | 任意一位为1,结果位就是1 | 5 | 3 → 7 (0101 | 0011 = 0111) |
| ^ | 按位异或 | 两位不同时结果为1 | 5 ^ 3 → 6 (0101 ^ 0011 = 0110) |
| ~ | 按位取反 | 翻转所有位 | ~5 → -6 (取决于补码) |
| << | 左移 | 各二进制位左移,低位补0 | 5 << 1 → 10 (0101 << 1 = 1010) |
| >> | 右移 | 各二进制位右移,正数高位补0 | 5 >> 1 → 2 (0101 >> 1 = 0010) |

Unity 的核心应用场景

(1) 层掩码 (LayerMask)

物理检测时经常用到,通过位运算组合多个层。

cs 复制代码
// 获取 "Enemy" 层和 "Boss" 层的掩码
int enemyLayer = 1 << LayerMask.NameToLayer("Enemy");
int bossLayer = 1 << LayerMask.NameToLayer("Boss");
int targetMask = enemyLayer | bossLayer;  // 同时检测两个层

// 球形检测时使用
Collider[] hits = Physics.OverlapSphere(transform.position, 10f, targetMask);

(2) 枚举标志 (Flags)

将多个选项打包在一个变量里,用于状态管理、武器类型等。

cs 复制代码
[System.Flags]
public enum WeaponType
{
    None    = 0,
    Sword   = 1 << 0,  // 1
    Bow     = 1 << 1,  // 2
    Staff   = 1 << 2,  // 4
    Dagger  = 1 << 3   // 8
}

// 组合武器
WeaponType knightGear = WeaponType.Sword | WeaponType.Shield;
// 检查是否装备剑
bool hasSword = (knightGear & WeaponType.Sword) != 0;
// 添加匕首
knightGear |= WeaponType.Dagger;
// 移除弓(假设已有)
knightGear &= ~WeaponType.Bow;

(3) 快速运算

  • x << n 相当于 x * 2^nx >> n 相当于 x / 2^n(整数除法),效率极高,常用于一些算法优化(但现代编译器通常会自动优化简单的乘除法,不必刻意使用)。

  • 判断奇偶:(number & 1) == 0 为偶数,比 % 2 更快,但实际影响微乎其微。

本期内容到这里就结束了

封面图自取:

相关推荐
我命由我1234511 小时前
PHP - PHP 简易 Web 服务器、基础接口开发
服务器·开发语言·前端·php·intellij-idea·idea·intellij idea
Reload.11 小时前
CZ航司,shopping JS逆向 acw_sc__v2
开发语言·javascript·python·网络爬虫·ecmascript
hef28811 小时前
用REGEXP函数简化城市销售数据统计的实践与学习路径
学习
码界筑梦坊11 小时前
130-基于Python的体育用品销售数据可视化分析系统
开发语言·python·信息可视化·flask·毕业设计
海上彼尚11 小时前
Nodejs也能写Agent - 6.基础篇 - Agent
前端·人工智能·后端·node.js
码界筑梦坊11 小时前
131-基于Flask的美国新泽西州自动售货机销售数据可视化分析系统
开发语言·python·信息可视化·数据分析·flask·毕业设计
努力努力再努力wz11 小时前
【QT入门系列】QWidget 六大常用属性详解:windowOpacity、cursor、font、focus、toolTip 与 styleSheet
android·开发语言·数据结构·c++·qt·mysql·算法
Harm灬小海11 小时前
【云计算学习之路】学习Centos7系统:Linux磁盘管理
linux·运维·服务器·学习·云计算
神仙别闹11 小时前
基于MFC(C++)实现(界面)学委作业管理系统
开发语言·c++·mfc