目 录CONTENT

文章目录

CSharp(三十六) 结构体类型数组 —— 定义与使用详解

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 每个元素。修改一个元素,其他元素不受任何影响——这就是值类型的魅力!

0

评论区