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+ 放开) | 始终支持 |
| 析构函数 | 不支持 | 支持 |
| 性能 | 小数据量更高效 | 大数据量更合适 |
微软官方建议:满足以下条件时用结构体
- 数据量小(通常不超过 16 字节)
- 逻辑上是单一值,比如坐标、颜色、复数
- 创建和销毁很频繁
- 不需要继承
常见使用场景:
Point、Size、Color等几何/图形数据DateTime、TimeSpan等时间数据Vector2、Vector3等数学计算- 游戏开发中的位置、旋转等数据
七、只读结构体(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# 中"实实在在拿在手里的值",每个变量拥有自己独立的数据;而类只是"指向数据的地址卡片",多个人(变量)可能指向同一份数据。