本篇 核心知识点 :C# 内存划分(栈 / 堆)、unsafe 指针使用规范、值类型 & 引用类型、装箱拆箱、类型转换(隐式 / 显式)、变量、常量、readonly 只读、字符串转义与 @原生字符串、运算符(算术 / 关系 / 逻辑 / 位 / 赋值 / 三元)、浮点精度坑、短路运算、流程控制(if/switch/for/while/do-while)
一、C# 内存分区与指针相关
1. 栈内存、堆内存划分
概念
-
栈 (Stack) :自动分配释放,函数执行结束自动回收,存放值类型变量、局部变量、函数形参;
-
堆 (Heap) :手动 GC 垃圾回收管理,存放引用类型对象;
特性
值类型:变量直接存储数据本体,存栈;
引用类型:变量仅存堆内存地址,真实数据存堆;
多个引用变量存同一地址,修改一处所有变量同步变化。
2. unsafe 不安全代码与指针
概念
C# 默认安全托管环境,禁止直接操作原生指针;如需使用 C/C++ 风格指针,必须开启不安全代码。
使用步骤
-
项目设置:VS 项目属性→生成→勾选「允许不安全代码」;
-
代码用
unsafe修饰代码块 / 函数;
特性
-
语法与 C++ 指针完全一致,支持
*解引用、&取地址; -
sizeof获取类型字节大小,仅能放在 unsafe 块内
-
日常开发极少使用,面试常提问「C# 是否支持指针」。
代码示例
unsafe void TestPointer()
{
int a = 10;
int* p = &a; // 取地址
Console.WriteLine(*p); // 解引用取值
Console.WriteLine(sizeof(int)); // int占4字节
}
二、值类型 & 引用类型
2.1 值类型
概念
派生自System.ValueType,数据直接存储在栈内存,赋值时完整拷贝一份数据副本。
包含类型
bool、char、byte/sbyte、short/ushort、int/uint、long/ulong、float (32 位单精度)、double (64 位双精度)、enum 枚举、struct 结构体。
特性
1 赋值互不干扰,修改副本不影响原变量;
2 函数传参默认值拷贝,内部修改不会影响外部实参;
3 内存自动回收,无 GC 开销。
2.2 引用类型
概念
变量仅保存堆内存地址,真实数据存堆,赋值仅拷贝地址,多个变量指向同一块数据。
包含类型
object、string、自定义 class、dynamic 动态类型、数组、集合 List 等。
特性
1 赋值共享同一份堆数据,一处修改全部同步;
2 依靠垃圾回收 GC 释放堆内存;
3 string 特殊:字符串不可变,修改会生成新堆对象。
3. 装箱 & 拆箱(面试高频)
1)装箱
概念:栈内值类型 拷贝到堆,转为object引用类型,隐式自动转换。
内存变化:栈复制数据→堆开辟 object 包装。
int num = 10;
object obj = num; // 自动装箱
2)拆箱
概念:堆中 object 转回栈内值类型,必须强制显式转换,转换类型不匹配报错。
int res = (int)obj; // 强制拆箱
拓展
频繁装箱拆箱会产生大量堆临时对象,GC 压力大,高性能游戏项目尽量避免。
三、类型转换
3.1 隐式转换(自动转换)
概念
编译器自动完成,无需强转;小范围 / 低精度 → 大范围 / 高精度,无数据丢失风险。
示例规则
int → long / int → double;
易错点
double num = 8.0; float f = num; 编译报错,double 精度高于 float,无法隐式转 float;
浮点常量默认 double,单精度必须后缀f:float f = 5.5f;
3.2 显式强制转换
概念 大范围→小范围,手动强制转换,存在精度丢失、数值溢出风险
double d = 3.14;
int i = (int)d; // 强制截断小数
3.3 内置转换工具类
Convert.ToBoolean() / Convert.ToByte() / Convert.ToInt32()等,可实现字符串、任意基础类型互转;
字符串转数字必须用Convert.ToInt32("123"),C# 无 JS/TS 隐式数字转换。
四、变量、常量、只读 readonly
4.1 变量
概念
内存空间别名,必须显式指定类型(强类型),运行中可修改值。
作用域(由{}代码块决定)
1 局部变量:方法 / 循环 /if 内部,仅当前代码块有效,出块销毁;
2 成员变量:写在类内部、方法外部,整个类所有方法可访问;
3 静态 static 变量:类变量,不依赖对象,类名直接访问。
左值 / 右值
左值:可放在赋值号左边(变量);
右值:仅能读取、不可赋值(常量、表达式结果)。
4.2 常量 const
概念
编译期确定固定值,定义时必须初始化,全程不可修改。
特性
1 关键字const修饰;
2 仅支持基础值类型;
3 类中常量直接类名访问,无需实例;
const int MAX_HP = 1000;
4.3 readonly 只读
概念
运行时赋值(构造函数内可改),构造完毕后永久只读。
区别 const
const 编译期定值;readonly 运行期赋值,支持对象类型,单例模式高频使用。
五、字符串两种写法与转义字符
1 普通双引号字符串
支持\n换行、\t制表、\\反斜杠等转义字符。
string str = "D:\\Game\\test.txt";
2 @原生字符串
前缀@,所有反斜杠不再转义,路径、SQL 语句专用。
string path = @"D:\Game\test.txt";
拓展
字符串是引用类型,但具备值语义,修改会新建堆对象,不会修改原字符串。
六、运算符全分类与易错点
6.1 算术运算符 + - * / % ++ --
1 自增++i先加再运算,i++先运算后加;
2 取模%仅整数适用;
3 浮点除法:5 / 2.0结果 2.5;整数相除5/2直接截断得 2。
6.2 关系运算符 > < >= <= == !=
重点易错:浮点数不能直接==判等
浮点数存储存在精度误差,如 3.4 实际存储为 3.3999999,直接==大概率判断失败。
标准写法
取绝对值,差值小于极小阈值即判定相等:
double a = 3.4;
double b = 3.4;
if(Math.Abs(a - b) < 1e-6)
Console.WriteLine("相等");
6.3 逻辑运算符 && || ! 短路运算
短路规则
1 &&:左边条件 false,右侧代码完全不执行;
2 ||:左边条件 true,右侧代码完全不执行;
作用:减少无意义运算,提升性能。
6.4 位运算符 & | ^ ~ << >>
游戏高频用途:RGBA 颜色拆分、碰撞分组标记
1 &按位与:提取指定字节(如取 RGB 红色通道 color & 0xFF);
2 |按位或:合并标记、叠加颜色通道;
3 <<左移、>>右移:等价乘 2 / 除 2,效率极高。
示例:32 位 int 存储 RGBA 四色通道,单字节 0~255。
6.5 赋值运算符 = += -= *= /= %=
等价简写:a += 5 → a = a + 5;
Lua 等脚本无复合赋值运算符,后期学习注意区分。
6.6 三元条件运算符 条件 ? 真值 : 假值
唯一三目运算符,简化 if-else 取值,常用于取最大 / 最小值
int min = a < b ? a : b;
6.7 运算符优先级
乘除 > 加减 > 位运算 > 关系 > 逻辑 > 三元 > 赋值;
复杂表达式建议括号提升可读性。
七、流程控制语句
7.1 if / else if / else
易错规范
if 条件后禁止加分号,分号代表空语句,逻辑失效;
// 错误写法
if(a > 10);
{ }
7.2 switch 分支
规则
1 case 后必须是常量,不支持变量;
2 支持 int、char、字符串 string 常量;
3 每个 case 末尾建议加break,无 break 会穿透到下一分支;
4 多个 case 执行同一段代码可堆叠 case;
5 default所有分支不匹配时执行,可选写;
6 case 内定义变量需包裹{}代码块,防止重定义报错。
int key = 2;
switch(key){
case 1:
case 2: // 1、2执行相同逻辑
Console.WriteLine("选中");
break;
default:
Console.WriteLine("无匹配");
break;
}
7.3 循环语句 for /while/do-while
1 for 循环
格式for(初始化;条件;迭代);三部分用分号分隔;
循环变量仅循环内有效,外部无法访问;
循环头禁止加分号。
2 while 循环
先判断条件,不成立一次都不执行;
初始化写循环外部,迭代写循环体内。
3 do-while 循环
先执行一次循环体,再判断条件;至少执行 1 次;
末尾必须加分号do {} while(条件);
八、拓展工程 & 面试考点
1 内存区分:值栈引用堆、装箱拆箱原理必考题;
2 浮点数判等坑,笔试高频改错;
3 短路运算、位运算游戏开发实际用途;
4 unsafe 指针使用前提(项目设置 + 代码块修饰);
5 const 与 readonly 核心区别;
6 switch、if、for 通用代码书写规范,规避语法陷阱。