【从UnityURP开始探索游戏渲染】专栏-直达
C# 结构体:
- 可以单独赋值,但需要注意结构体是值类型
- 如果结构体变量是局部变量,需要先完全初始化后才能使用
- 如果是类成员字段,可以直接修改单个字段
在C#中,结构体(struct
)是值类型,作为局部变量时必须完全初始化后才能使用。
错误示例(未完全初始化)
cs
csharp
struct Point {
public int X;
public int Y;
}
void Demo() {
Point p;// 声明局部结构体变量(未初始化)
Console.WriteLine(p.X);// 编译错误:使用了未赋值的局部变量'p'
}
问题 :结构体p
的字段X
和Y
未被赋值,直接访问会导致编译错误。
正确示例(完全初始化)
方法1:逐个字段赋值
cs
csharp
Point p;
p.X = 10;// 显式初始化X
p.Y = 20;// 显式初始化Y
Console.WriteLine(p.X);// 输出:10
方法2:使用构造函数初始化
cs
csharp
Point p = new Point();// 调用默认构造函数(数值类型字段初始化为0)
Console.WriteLine(p.X);// 输出:0// 或自定义构造函数
Point p2 = new Point(10, 20);// 假设定义了构造函数
方法3:初始化时直接赋值
cs
csharp
Point p = new Point { X = 10, Y = 20 };// 对象初始化器
关键规则
- 局部变量必须显式初始化所有字段后才能使用。
- 类成员字段 会自动初始化为默认值(如
int
为0),无需手动初始化。 - 通过
new
调用默认构造函数时,所有字段会被置为默认值(等同于完全初始化)。
注意:C#的结构体设计强调明确性,避免意外使用未初始化的内存。
在C#中,结构体作为类成员字段和局部变量时的行为不同,主要体现在是否需要完全初始化后才能修改单个字段:
一、类成员字段(可直接修改单个字段)
cs
csharp
class MyClass {
Point p;// 结构体作为类成员字段(自动初始化为默认值)void Modify() {
p.X = 10;// 直接修改单个字段(无需完全初始化)
}
}
说明:
类中的结构体字段会被自动初始化为默认值(int
为0),因此可以直接修改单个字段。
二、非类成员字段(需先完全初始化)
情况1:局部变量(必须完全初始化)
cs
csharp
void Demo() {
Point p;// 局部结构体变量(未初始化)
p.X = 10;// 编译错误:使用了未赋值的局部变量'p'
// 必须先完全初始化
Point p2 = new Point();// 调用默认构造函数(所有字段置为0)
p2.X = 10;// 合法
}
情况2:方法参数(按值传递时)
cs
csharp
void ModifyPoint(Point p) {
p.X = 100;// 修改的是副本,不影响原始值
}
void Caller() {
Point p = new Point { X = 1, Y = 2 };
ModifyPoint(p);
Console.WriteLine(p.X);// 输出:1(原值未变)
}
情况3:ref
或out
参数(可修改原始值)
cs
csharp
void ModifyRefPoint(ref Point p) {
p.X = 100;// 直接修改原始值
}
void Caller() {
Point p = new Point { X = 1, Y = 2 };
ModifyRefPoint(ref p);
Console.WriteLine(p.X);// 输出:100
}
关键区别总结
场景 | 能否直接修改单个字段 | 原因 |
---|---|---|
类成员字段 | ✅ 可以 | 自动初始化为默认值 |
局部变量 | ❌ 不可以 | 未初始化的局部变量禁止访问(编译器强制要求) |
方法参数(值传递) | ❌ 修改的是副本 | 结构体按值传递,方法内修改不影响调用方 |
ref /out 参数 |
✅ 可以 | 直接操作原始值的引用 |
最佳实践:对结构体局部变量始终显式初始化(通过new或赋值所有字段),避免未定义行为。
在C#中,class
(类)和struct
(结构体)存在本质区别,与C++中的差异点(如默认访问权限)完全不同。C#的主要区别围绕 值类型(struct)与引用类型(class) 的特性展开:
一、核心行为差异
特性 | **Struct(结构体)** | **Class(类)** |
---|---|---|
类型本质 | 值类型(直接存储数据) | 引用类型(存储数据的内存地址) |
内存分配位置 | 栈或父对象内联存储 | 托管堆(由GC管理) |
赋值行为 | 复制整个数据(深拷贝) | 复制引用(浅拷贝) |
参数传递 | 默认按值传递(方法内修改不影响原变量) | 默认按引用传递(方法内修改影响原对象) |
是否支持继承 | ❌ 不可继承(只能实现接口) | ✅ 支持完整继承链和多态 |
**能否为null ** |
❌ 不可直接为null (需用Nullable<T> ) |
✅ 可直接赋值为null |
在C#中,结构体(struct
)是值类型,不能直接赋值为null
。若需要表示"无值"状态,需使用Nullable<T>
(简写为T?
)。以下是具体示例:
1. 直接赋值为null
(错误示例)
cs
csharp
struct Point { public int X, Y; }
Point p = null;// ❌ 编译错误:无法将null转换为值类型
2. 正确使用Nullable<T>
方法1:显式声明Nullable<T>
cs
csharp
Nullable<Point> nullablePoint = null;// ✅ 合法if (!nullablePoint.HasValue) {
Console.WriteLine("Point is null");
}
方法2:使用语法糖T?
cs
csharp
Point? nullablePoint = null;// ✅ 等价于Nullable<Point>
方法3:操作可空结构体
cs
csharp
Point? p = new Point { X = 10, Y = 20 };
p = null;// ✅ 可重新赋值为null// 安全访问(需判断HasValue或使用null条件运算符)
Console.WriteLine(p?.X ?? -1);// 输出:-1(因p为null)
3. 与引用类型对比
cs
csharp
class MyClass { public int Data; }
MyClass obj = null;// ✅ 类可直接为null(引用类型)
Point? p = null;// ✅ 结构体需通过Nullable<T>实现
关键点
- 值类型默认不可为null :如
int
、DateTime
、自定义struct
等。 - Nullable原理 :本质是结构体
System.Nullable<T>
,通过HasValue
和Value
属性管理状态。 - 应用场景 :数据库字段(可能为
NULL
)、可选参数等。
HLSL (着色器语言):
- 结构体字段可以单独赋值
- 但要注意HLSL中的结构体主要用于数据封装,通常在CPU端初始化后传入GPU
- 在着色器代码中可以修改单个字段值
C++ 结构体:
- 可以单独赋值结构体中的字段
- C++中结构体和类非常相似(主要区别是默认访问权限)
- 无论是栈上还是堆上分配的结构体实例,都可以直接访问和修改成员
在C++中,结构体(struct
)和类(class
)功能几乎完全一致,其核心区别在于默认访问权限 和默认继承方式:
核心区别对比
维度 | 结构体struct | 类class |
---|---|---|
默认成员访问权限 | 默认为 public (成员可被外部直接访问) |
默认为 private (成员仅内部/友元可访问) |
默认继承方式 | 默认为 public 继承(基类成员权限保持不变) |
默认为 private 继承(基类成员在派生类中变为 private ) |
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)