目 录CONTENT

文章目录

CSharp(三十五) 结构体(Struct)详解

C# 结构体(Struct)详解


一、什么是结构体?

结构体 是一种 值类型,用于封装一组相关的数据。你可以把它理解为一个"轻量级的类"。

打个比方:

  • 类(class) 像一本借书证——你拿着借书证去图书馆,通过借书证找到书架上的书。
  • 结构体(struct) 像是直接把书拿在手里——书就在你手上,不需要间接去找。

这就是 C# 中引用类型(class)值类型(struct) 最核心的区别。


二、定义一个结构体

结构体用 struct 关键字定义,语法和类非常相似:

// 定义一个"点"的结构体
public struct Point
{
    // 字段
    public int X;
    public int Y;
}

稍微复杂一点的例子——定义一个"学生":

public struct Student
{
    // 字段(直接存储数据)
    public string Name;
    public int Age;
    public double Score;

    // 方法(行为)
    public void SayHello()
    {
        Console.WriteLine($"大家好,我叫{Name},今年{Age}岁");
    }

    // 属性(对字段的封装)
    public bool IsPassed
    {
        get { return Score >= 60; }
    }
}

三、如何使用结构体

3.1 创建结构体变量

创建结构体变量有两种方式:

// 方式一:使用 new 关键字(推荐,会调用构造函数初始化)
Point p1 = new Point();
p1.X = 10;
p1.Y = 20;

// 方式二:直接声明(必须先给所有字段赋值才能使用)
Point p2;
p2.X = 30;
p2.Y = 40;
// 现在才能使用 p2
Console.WriteLine(p2.X);  // 输出: 30

3.2 完整的使用示例

using System;

public struct Student
{
    public string Name;
    public int Age;
    public double Score;

    public void ShowInfo()
    {
        Console.WriteLine($"姓名: {Name}");
        Console.WriteLine($"年龄: {Age}");
        Console.WriteLine($"分数: {Score}");
        Console.WriteLine($"是否及格: {(Score >= 60 ? "是" : "否")}");
    }
}

class Program
{
    static void Main()
    {
        // 创建学生1
        Student s1 = new Student();
        s1.Name = "张三";
        s1.Age = 18;
        s1.Score = 85.5;
        s1.ShowInfo();

        Console.WriteLine("---");

        // 创建学生2
        Student s2;
        s2.Name = "李四";
        s2.Age = 19;
        s2.Score = 55;
        s2.ShowInfo();
    }
}

输出:

姓名: 张三
年龄: 18
分数: 85.5
是否及格: 是
---
姓名: 李四
年龄: 19
分数: 55
是否及格: 否

四、结构体中的构造函数

结构体可以有构造函数,但不能定义无参构造函数(C# 10.0 之前)。从 C# 10.0 开始,允许定义无参构造函数。

public struct Rectangle
{
    public int Width;
    public int Height;

    // 有参构造函数
    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public int GetArea()
    {
        return Width * Height;
    }
}

// 使用
Rectangle rect = new Rectangle(5, 3);
Console.WriteLine(rect.GetArea());  // 输出: 15

注意:在构造函数中,必须给所有字段赋值,否则编译器会报错。


五、核心概念:值类型 vs 引用类型

这是理解结构体最最重要的一点!

5.1 值类型的赋值 = 复制一份

public struct Person
{
    public string Name;
    public int Age;
}

Person p1 = new Person();
p1.Name = "张三";
p1.Age = 20;

Person p2 = p1;  // ✨ 这里是把 p1 的整个数据复制一份给 p2
p2.Name = "李四";  // 修改 p2,不会影响 p1

Console.WriteLine(p1.Name);  // 输出: 张三(不变!)
Console.WriteLine(p2.Name);  // 输出: 李四

图解:

p1 = [Name: "张三", Age: 20]
p2 = [Name: "张三", Age: 20]   ← 独立的一份拷贝
     ↓ 改了 p2.Name
p2 = [Name: "李四", Age: 20]   ← 只有 p2 变了
p1 = [Name: "张三", Age: 20]   ← p1 完全不受影响

5.2 对比:类(class)是引用类型

public class PersonClass
{
    public string Name;
    public int Age;
}

PersonClass c1 = new PersonClass();
c1.Name = "张三";

PersonClass c2 = c1;  // ⚠️ 这里是把引用(地址)复制给 c2
c2.Name = "李四";     // c1 和 c2 指向同一个对象,所以 c1 也会变!

Console.WriteLine(c1.Name);  // 输出: 李四(变了!)
Console.WriteLine(c2.Name);  // 输出: 李四

5.3 方法传参时的影响

// 传递 struct:修改不会影响原变量
static void ChangeStruct(Person p)
{
    p.Name = "被修改了";
}

// 传递 class:修改会影响原变量
static void ChangeClass(PersonClass p)
{
    p.Name = "被修改了";
}

// 测试
Person sp = new Person();
sp.Name = "原始名";
ChangeStruct(sp);
Console.WriteLine(sp.Name);  // 输出: 原始名(没变!)

PersonClass cp = new PersonClass();
cp.Name = "原始名";
ChangeClass(cp);
Console.WriteLine(cp.Name);  // 输出: 被修改了(变了!)

记忆口诀:结构体传参是"复印一份给你",类传参是"把地址告诉你"。


六、什么时候用结构体?什么时候用类?

特性 结构体(struct) 类(class)
类型 值类型 引用类型
存储在 栈(通常)
赋值 复制整个数据 复制引用
继承 只能实现接口,不能继承 支持继承
无参构造函数 有限制(C# 10.0+ 放开) 始终支持
析构函数 不支持 支持
性能 小数据量更高效 大数据量更合适

微软官方建议:满足以下条件时用结构体

  1. 数据量小(通常不超过 16 字节)
  2. 逻辑上是单一值,比如坐标、颜色、复数
  3. 创建和销毁很频繁
  4. 不需要继承

常见使用场景

  • PointSizeColor 等几何/图形数据
  • DateTimeTimeSpan 等时间数据
  • Vector2Vector3 等数学计算
  • 游戏开发中的位置、旋转等数据

七、只读结构体(readonly struct)

如果你想确保结构体创建后不可修改,用 readonly

public readonly struct Point
{
    public int X { get; }  // 只读属性
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public double DistanceFromOrigin()
    {
        return Math.Sqrt(X * X + Y * Y);
    }
}

// 使用
Point p = new Point(3, 4);
// p.X = 5;  ← 编译错误!不能修改只读结构体的属性
Console.WriteLine(p.DistanceFromOrigin());  // 输出: 5

八、常见易错点(避坑指南)

坑1:没赋值就使用

Point p;
// Console.WriteLine(p.X);  ← 编译错误!p 还没完全赋值
p.X = 10;
p.Y = 20;
Console.WriteLine(p.X);  // 现在 OK 了

坑2:数组中修改结构体

Point[] points = new Point[10];
points[0].X = 5;  // 可以,数组索引返回的是引用

// 但是如果从 List 中取:
List<Point> pointList = new List<Point>();
pointList.Add(new Point());
// pointList[0].X = 5;  ← 编译错误!因为索引器返回的是拷贝

// 正确做法:
Point temp = pointList[0];
temp.X = 5;
pointList[0] = temp;  // 整体替换回去

坑3:结构体中的可变引用类型字段

public struct MyStruct
{
    public int[] Numbers;  // 数组是引用类型!
}

MyStruct s1 = new MyStruct();
s1.Numbers = new int[] { 1, 2, 3 };

MyStruct s2 = s1;
s2.Numbers[0] = 999;  // 修改了数组的内容

Console.WriteLine(s1.Numbers[0]);  // 输出: 999 ← 两个结构体共享同一个数组!

九、总结

记住这个 解释
定义 struct 名称 { ... }
创建 结构体名 变量 = new 结构体名();
核心特点 值类型,赋值=复制,互不影响
和类的区别 类是引用类型,赋值=共享
何时用 小数据、简单值、频繁创建的场景

一句话总结:结构体就是 C# 中"实实在在拿在手里的值",每个变量拥有自己独立的数据;而类只是"指向数据的地址卡片",多个人(变量)可能指向同一份数据。

0
博主关闭了当前页面的评论