1.概念
结构类型(或 struct type)是一种可封装数据和相关功能的值类型。简单来说:当你创建一个 struct变量时,这个变量直接存储了整个数据的内容。
struct主要用于封装轻量级的数据集合(如坐标、点、颜色、日期),它通常包含的数据不多,且没有复杂的行为,举例如下:
public struct Coords
{
//这是一个构造函数,用于创建Coords结构的新实例
//它接受两个double类型的参数:x和y,赋给对应的属性X和Y
public Coords(double x, double y)
{
X = x;
Y = y;
}
public double X { get; set; } // 自动实现属性 X
public double Y { get; set; } // 自动实现属性 Y
}
1.1 public
public这个关键字,它是 C# 中非常重要的一个概念——访问修饰符
想象一下你住的房子:
-
有些房间(比如客厅)是 公开 (public) 的,任何人都可以进来。
-
有些房间(比如你的卧室)是 私有 (private) 的,只有你自己或者你允许的人才能进。
-
还有一些区域(比如家里的书房)可能是 受保护 (protected) 的,只有你和你的家人(子类)可以用。
-
或者像小区里的公共设施,只有本小区的住户(同一个程序集)可以用,这叫 内部 (internal)。
如果例子中去掉public:
struct Coords // 没有 public -> 默认 private!
{
Coords(double x, double y) // 没有 public -> 默认 private!
{
X = x;
Y = y;
}
double X { get; set; } // 没有 public -> 默认 private!
double Y { get; set; } // 没有 public -> 默认 private!
}
如果你想在 Coords结构外部创建一个坐标点,会报错,因为Coords的构造函数是 private的,没有权限调用它!
Coords myPoint = new Coords(1.0, 2.0); // 这行会报错!
如何选择呢?
-
想让别人(其他代码)能用你的东西(类、方法、属性)? ➡️ 加上 public。
-
想把这个东西藏起来,只给自己(当前类/结构)用? ➡️ 用 private(或者不写修饰符)。
-
想只给同一个项目(程序集)里的代码用? ➡️ 用 internal。
-
想只给继承你的子类用? ➡️ 用 protected。
1.2 自动实现属性
例子中的自动实现属性(Auto-Implemented Property)声明:public double X { get; set; }
double:表示这个属性的数据类型是双精度浮点数。X:属性的名称。
{ get; set; }:这是属性的访问器(accessors)声明。
get:表示获取属性值的访问器。当你读取X的值时(例如 var value = obj.X;),就会调用这个get访问器。
set:表示设置属性值的访问器。当你给X赋值时(例如 obj.X = 3.14;),就会调用这个set访问器。
自动实现:这种只有get;和set;而没有显式实现的方式,是C#的自动实现属性。
编译器会自动生成一个隐藏的、我们看不到的私有字段,用来存储属性的实际值。
只读自动属性:如果只有get访问器,没有set访问器,那么这个属性就是只读的:
public double X { get; } // 只读属性
2. 结构初始化和默认值
2.1 默认初始化(零值)
-
所有字段/属性被初始化为零值(数值类型为 0)
-
不调用任何构造函数
Coords point1 = default;
Console.WriteLine($"{point1.X}, {point1.Y}"); // 0, 0
2.2 数组初始化
-
数组元素自动初始化为零值
-
不调用任何构造函数
var points = new Coords[3];
Console.WriteLine($"{points[0].X}, {points[0].Y}"); // 0, 0
补充:当你创建数组:var points = new Coords[3];
C# 会自动初始化每个数组元素为类型的默认值(零值),对于 Coords结构:
// 相当于每个元素都执行了:
points[0] = default(Coords); // X=0, Y=0
points[1] = default(Coords); // X=0, Y=0
points[2] = default(Coords); // X=0, Y=0
2.3 使用new但不传参数(错误)
-
因为没有无参数构造函数
-
如果添加无参数构造函数(C#10+),行为会改变
// 编译错误:没有无参数构造函数
Coords point2 = new Coords();
2.4 使用自定义构造函数
-
正常调用自定义构造函数
Coords point3 = new Coords(5, 10);
Console.WriteLine($"{point3.X}, {point3.Y}"); // 5, 10
2.5 手动初始化(不使用new)
-
必须在使用前初始化所有字段
-
不调用任何构造函数
Coords point4;
point4.X = 15;
point4.Y = 20;
Console.WriteLine($"{point4.X}, {point4.Y}"); // 15, 20
补充:部分初始化问题(编译错误)
Coords point5;
point5.X = 3; // 只初始化一个
Console.WriteLine(point5.Y); // 错误: 使用了未赋值的变量
总结:
default(Coords) |
否 |
X=0, Y=0 |
安全 |
数组元素 |
否 |
X=0, Y=0 |
安全 |
new Coords(1,2) |
是 |
根据构造函数赋值 |
安全 |
手动初始化 |
否 |
手动赋值 |
需完全初始化 |
new Coords() |
错误(无此构造函数) |
– |
编译错误 |
3.“值语义”
3.1 赋值就是复制
当你将一个 struct变量 (A) 赋值给另一个 struct变量 (B) 时,会发生什么?
A变量里存储的整个数据内容会被完整地、逐字节地复制一份放到 B变量里。修改 B不会影响原来的 A。我们继续使用上述的Coords结构举例:
Coords point1 = new Coords(10, 20);
Coords point2 = point1; // 将 point1 的数据 (10,20) 整个复制一份给 point2
point2.X = 30; // 修改 point2 的 X
Console.WriteLine(point1.X + "," + point1.Y); // 输出: (10, 20) -> point1 没变!
Console.WriteLine(point2.X + "," + point2.Y); // 输出: (30, 20) -> 只有 point2 变了
3.2 传参时也是复制
当你将一个 struct变量作为参数传递给一个方法时,传递的也是它的数据副本。方法内部对这个参数的修改,通常不会影响方法外面原来的那个变量(除非用了 ref或 out)。
void ModifyCoord(Coords coord)
{
coord.X = 100; // 修改的是传入的那个副本
}
Coords myPoint = new Coords(5, 5);
ModifyCoord(myPoint);
Console.WriteLine(myPoint); // 输出: (5, 5) -> myPoint 没变!
3.3 返回时也是复制
当一个方法返回一个 struct时,返回的也是它的一个副本。(了解即可)
4. readonly
可以使用 readonly 修饰符来声明结构类型为不可变。 readonly 结构的所有数据成员都必须是只读的,如下所示:
任何字段声明都必须具有 readonly 修饰符。
任何属性(包括自动实现的属性)都必须是只读的,或者是 init 只读的。
用 readonly修饰的结构体,从设计上保证它创建后内容绝对不可变。类似你给结构体套了个"金钟罩"。
public readonly struct Coords // ← 关键声明
{
public Coords(double x, double y)
{
X = x;
Y = y;
}
//public double X { get; set; } // 编译错误!不能有可写属性(set)
public double X { get; } // 只读属性(无set)
public double Y { get; init; } // init-only属性(仅构造时可赋值)
public override string ToString() => $"({X}, {Y})";
}
5.非破坏性变化(with表达式)
- 非破坏性:原始对象不会被修改
- 语法:源对象 with { 要修改的属性 = 新值 }
- 要求:
- 结构体必须是 readonly
- 属性必须有 init访问器
// 使用
var p1 = new Coords(0, 0);
var p2 = p1 with { X = 30 }; // 创建 p1 的副本,只修改 X
public readonly struct Coords // 必须是只读结构
{
public double X { get; init; } // init 允许初始化后安全修改副本
public double Y { get; init; }
}
6.记录结构(record struct)
这是 with表达式的增强版:
- 超级简洁:一行代码定义整个结构体
- 自动功能:
- 自动生成 ToString()方法
- 自动生成值比较(Equals)
- 自动支持 with表达式
- 两种类型:
- record struct:可变记录
- readonly record struct:不可变记录
//自动生成 ToString()方法
var point = new Coords(3, 4);
Console.WriteLine(point); // 输出:Coords { X = 3, Y = 4 }
//自动生成值比较逻辑
var p1 = new Coords(1, 2);
var p2 = new Coords(1, 2);
var p3 = new Coords(3, 4);
Console.WriteLine(p1 == p2); // 输出:True(值相同)
Console.WriteLine(p1.Equals(p3)); // 输出:False(值不同)
// 自动支持 with表达式
var p1 = new Coords(10, 20);
var p2 = p1 with { X = 30 };
public record struct Coords(double X, double Y); // 一行定义!
学到了这里,咱俩真棒,记得按时吃饭(希望你今天是开心的~)
【本篇结束,新的知识会不定时补充】
感谢你的阅读!如果内容有帮助,欢迎 点赞❤️ + 收藏⭐ + 关注 支持! 😊
评论前必须登录!
注册