CSharp(三十六) 结构体类型数组 —— 定义与使用详解
一、先回忆:什么是结构体?
结构体是值类型,每个变量独立拥有一份数据。上一篇我们已经学过怎么定义和使用单个结构体变量,本篇我们学习如何把多个结构体放到数组里来管理。
打个比喻:
- 单个结构体 = 一张学生信息卡片
- 结构体数组 = 一个装着很多张卡片的文件夹,每张卡片格式都一样
二、定义结构体数组
2.1 最基本的方式
// 1. 先定义结构体
public struct Student
{
public string Name;
public int Age;
public double Score;
}
// 2. 创建有 3 个元素的数组
Student[] students = new Student[3];
new Student[3] 意思是:在内存中申请 3 个 "Student 格子",每个格子都是一个独立的结构体。
注意:数组创建后,每个元素的值就是默认值(数字为 0,字符串为 null)。
2.2 图解内存长什么样
students[0] ──> [Name: null, Age: 0, Score: 0.0]
students[1] ──> [Name: null, Age: 0, Score: 0.0]
students[2] ──> [Name: null, Age: 0, Score: 0.0]
每个元素都是独立的——这和引用类型数组完全不同!
三、给数组元素赋值和使用
3.1 逐个赋值
Student[] students = new Student[3];
// 给第 1 个学生填数据
students[0].Name = "张三";
students[0].Age = 18;
students[0].Score = 92.5;
// 给第 2 个学生填数据
students[1].Name = "李四";
students[1].Age = 19;
students[1].Score = 85;
// 给第 3 个学生填数据
students[2].Name = "王五";
students[2].Age = 20;
students[2].Score = 76;
3.2 遍历输出
for (int i = 0; i < students.Length; i++)
{
Console.WriteLine($"第{i + 1}个学生:{students[i].Name}, "
+ $"年龄{students[i].Age}, 分数{students[i].Score}");
}
输出:
第1个学生:张三, 年龄18, 分数92.5
第2个学生:李四, 年龄19, 分数85
第3个学生:王五, 年龄20, 分数76
3.3 用 foreach 遍历(更简洁)
foreach (Student s in students)
{
Console.WriteLine($"{s.Name} - {s.Age}岁 - {s.Score}分");
}
四、初始化数组的多种方式
方式一:一个一个赋值
Student[] students = new Student[3];
students[0] = new Student() { Name = "张三", Age = 18, Score = 92.5 };
students[1] = new Student() { Name = "李四", Age = 19, Score = 85 };
students[2] = new Student() { Name = "王五", Age = 20, Score = 76 };
方式二:声明时直接初始化(对象初始化器)
Student[] students = new Student[]
{
new Student() { Name = "张三", Age = 18, Score = 92.5 },
new Student() { Name = "李四", Age = 19, Score = 85 },
new Student() { Name = "王五", Age = 20, Score = 76 }
};
方式三:更简写(省略大小和类型)
Student[] students =
{
new Student() { Name = "张三", Age = 18, Score = 92.5 },
new Student() { Name = "李四", Age = 19, Score = 85 },
new Student() { Name = "王五", Age = 20, Score = 76 }
};
五、完整示例:学生成绩管理系统
这是一个可以直接运行的完整例子,包含了定义、赋值、遍历、查找、统计等常见操作:
using System;
// 定义学生结构体
public struct Student
{
public string Name;
public int Age;
public double Score;
public void ShowInfo()
{
Console.WriteLine($"姓名: {Name,-6} 年龄: {Age} 分数: {Score}");
}
}
class Program
{
static void Main()
{
// 1. 创建并初始化学生数组
Student[] students =
{
new Student() { Name = "张三", Age = 18, Score = 92.5 },
new Student() { Name = "李四", Age = 19, Score = 85 },
new Student() { Name = "王五", Age = 20, Score = 76 },
new Student() { Name = "赵六", Age = 18, Score = 58 },
new Student() { Name = "孙七", Age = 19, Score = 88 }
};
Console.WriteLine("===== 全部学生信息 =====");
foreach (Student s in students)
{
s.ShowInfo();
}
// 2. 计算平均分
double total = 0;
for (int i = 0; i < students.Length; i++)
{
total += students[i].Score;
}
double average = total / students.Length;
Console.WriteLine($"\n平均分: {average:F1}");
// 3. 找出最高分的学生
Student topStudent = students[0]; // 先假设第一个最高
for (int i = 1; i < students.Length; i++)
{
if (students[i].Score > topStudent.Score)
{
topStudent = students[i];
}
}
Console.WriteLine($"\n最高分学生: {topStudent.Name}, 分数: {topStudent.Score}");
// 4. 统计及格人数
int passCount = 0;
foreach (Student s in students)
{
if (s.Score >= 60)
{
passCount++;
}
}
Console.WriteLine($"及格人数: {passCount}/{students.Length}");
// 5. 列出不及格的学生
Console.WriteLine("\n===== 不及格学生 =====");
foreach (Student s in students)
{
if (s.Score < 60)
{
s.ShowInfo();
}
}
}
}
输出:
===== 全部学生信息 =====
姓名: 张三 年龄: 18 分数: 92.5
姓名: 李四 年龄: 19 分数: 85
姓名: 王五 年龄: 20 分数: 76
姓名: 赵六 年龄: 18 分数: 58
姓名: 孙七 年龄: 19 分数: 88
平均分: 79.9
最高分学生: 张三, 分数: 92.5
及格人数: 4/5
===== 不及格学生 =====
姓名: 赵六 年龄: 18 分数: 58
六、重要概念:修改数组元素的正确姿势
6.1 直接通过索引修改(数组可以!)
Student[] students = new Student[3];
students[0].Name = "张三"; // ✅ 可以,数组索引直接操作原数据
students[0].Age = 18; // ✅ 可以
因为结构体数组在内存中每个元素是真实存在的值,数组索引 [i] 直接指向那块内存。
6.2 foreach 中不能修改!⚠️
foreach (Student s in students)
{
s.Score = 100; // ❌ 编译错误!foreach 中的迭代变量是只读的
}
// 想修改,必须用 for 循环
for (int i = 0; i < students.Length; i++)
{
students[i].Score = 100; // ✅ 可以
}
七、结构体数组 vs 类的数组(核心区别)
这是一个必须理解透彻的知识点!
7.1 类的数组——每个元素是"门牌号"
public class PersonClass
{
public string Name;
}
// 创建类的数组
PersonClass[] classArr = new PersonClass[3];
// 这时候每个元素都是 null!必须 new 才能用
classArr[0] = new PersonClass();
classArr[0].Name = "张三"; // ✅ 正常运行
类的数组内存图:
classArr[0] ──> null
classArr[1] ──> null
classArr[2] ──> null
↓ 需要 new 之后
classArr[0] ──> [Name: "张三"] ← 在堆上
classArr[1] ──> null
classArr[2] ──> null
7.2 结构体的数组——每个元素直接是值
public struct PersonStruct
{
public string Name;
}
// 创建结构体数组
PersonStruct[] structArr = new PersonStruct[3];
// 不用 new,每个元素已经存在了!直接用!
structArr[0].Name = "张三"; // ✅ 可以直接用
结构体的数组内存图:
structArr[0] ──> [Name: "张三"] ← 值就在数组里
structArr[1] ──> [Name: null]
structArr[2] ──> [Name: null]
7.3 对比表
| 特性 | 结构体数组 | 类的数组 |
|---|---|---|
| 创建后元素状态 | 已有默认值,可以立刻用 | 元素是 null,必须 new |
| 元素存储位置 | 值在数组内部 | 数组里存的是引用地址 |
| 赋值行为 | arr[0] = arr[1] 复制整份数据 |
arr[0] = arr[1] 复制引用,指向同一对象 |
| 修改一个元素 | 不影响其他 | 如果两个索引指向同一对象,会互相影响 |
八、实用技巧:给结构体加构造函数
有了构造函数,初始化数组更简洁:
public struct Student
{
public string Name;
public int Age;
public double Score;
// 构造函数
public Student(string name, int age, double score)
{
Name = name;
Age = age;
Score = score;
}
public void ShowInfo()
{
Console.WriteLine($"{Name}, {Age}岁, {Score}分");
}
}
// 初始化数组时直接用构造函数,干净利落!
Student[] students =
{
new Student("张三", 18, 92.5),
new Student("李四", 19, 85),
new Student("王五", 20, 76),
};
foreach (Student s in students)
{
s.ShowInfo();
}
九、常见操作速查
排序——按分数从高到低
// 用 Array.Sort + 自定义比较
Array.Sort(students, (a, b) => b.Score.CompareTo(a.Score));
// 或更直观的写法
Array.Sort(students, (a, b) =>
{
if (a.Score > b.Score) return -1;
if (a.Score < b.Score) return 1;
return 0;
});
查找——找到指定姓名的学生
string target = "李四";
Student found = Array.Find(students, s => s.Name == target);
Console.WriteLine($"找到了:{found.Name}, 分数: {found.Score}");
// 如果找不到,found 会是默认值
if (found.Name == null)
{
Console.WriteLine("没找到!");
}
筛选——找出所有及格的学生
Student[] passed = Array.FindAll(students, s => s.Score >= 60);
Console.WriteLine($"及格人数: {passed.Length}");
修改某个元素的值
// 给第 2 个学生加 5 分
students[1].Score += 5;
// 或者先取出来,改完再放回去
Student temp = students[1];
temp.Score += 5;
students[1] = temp; // 必须放回去!
十、总结
| 操作 | 写法 |
|---|---|
| 定义数组 | Student[] arr = new Student[5]; |
| 定义+初始化 | Student[] arr = { new Student() { ... }, ... }; |
| 访问元素 | arr[0].Name = "张三"; |
| 遍历(for) | for(int i=0; i<arr.Length; i++) { arr[i]... } |
| 遍历(foreach) | foreach(Student s in arr) { s... } (只读) |
| 查找 | Array.Find(arr, s => 条件) |
| 筛选 | Array.FindAll(arr, s => 条件) |
| 排序 | Array.Sort(arr, (a,b) => 比较式) |
一句话总结:结构体数组就像一个"带格子的收纳盒",每个格子都是一个独立的结构体值。数组一创建,所有格子就存在了,可以直接往里塞数据,不需要像类那样还要
new每个元素。修改一个元素,其他元素不受任何影响——这就是值类型的魅力!
评论区