云计算百科
云计算领域专业知识百科平台

C#知识学习-008(​​结构类型)

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); // 一行定义!

学到了这里,咱俩真棒,记得按时吃饭(希望你今天是开心的~)

【本篇结束,新的知识会不定时补充】

感谢你的阅读!如果内容有帮助,欢迎 ​​点赞❤️ + 收藏⭐ + 关注​​ 支持! 😊

赞(0)
未经允许不得转载:网硕互联帮助中心 » C#知识学习-008(​​结构类型)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!