Skip to Content
Week 01Day 6 - 数组与 List

Day 6 - 数组与 List

建议用时:180-210 分钟

你将学会什么

  • 为什么程序需要保存一组数据
  • 数组是什么,List 是什么
  • 数组和 List 的区别
  • 下标为什么从 0 开始
  • LengthCount 分别是什么意思
  • 如何添加、读取、修改、删除 List 里的数据
  • 如何用 forforeach 遍历集合
  • 如何统计总和、平均值、最高分和最低分
  • 如何避免空集合和下标越界

本页只讲数组和 List。更复杂的集合放到第二周,这里先把“一组数据怎么保存和遍历”打牢。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

从 Day 5 走到 Day 6

前五天的变量大多只保存一个值:

int score = 80; string name = "Tom";

但“全班成绩”或“所有商品名称”不可能每一项都手写一个变量。Day 6 解决的就是这个问题:把多个同类型数据装进一个集合。

一个成绩 -> int score 多个成绩 -> List<int> scores

今天的最低通过线

第一次学习只要求做到:

  • 知道数组长度固定,List 可以继续添加。
  • 知道第一个元素的下标是 0
  • 会用 Add 添加数据,用 Count 查看数量。
  • 会用 foreach 把所有元素输出。

最高分、最低分、排序、插入和删除都可以第二遍再练。先把“保存多条数据并遍历”做通。

第一部分:先学原理和最小知识

这一部分先解决“这是什么、为什么要这样写”。每看到一个短例子就亲手敲一遍并运行,不要先背完整程序。

今天先抓住什么

前几天一个变量通常只保存一个值:

int score = 80;

但真实程序经常要保存一组数据:

多个成绩 多个商品 多条账单 多个菜单选项

这时就需要数组或 List

List<int> 这一行拆开看

初学者看到下面一行通常会被符号吓到:

var scores = new List<int>();

把它拆开:

部分人话解释
scores变量名,表示“多条成绩”
List一个长度可以变化的列表
<int>这个列表里只允许放整数
new创建一个新的列表
()调用创建列表所需的构造过程;现在先按固定写法使用
var让 C# 根据右边推断类型;这里推断成 List<int>

它也可以写成完整类型:

List<int> scores = new List<int>();

两种写法结果一样。第一次学习时可以先用完整写法,熟悉以后再用 var

下面这行会失败:

// scores.Add("Tom");

因为 scoresList<int>,只能保存 int,不能放字符串。

下标不是“第几个”的自然语言数字

假设列表里有三项:

值: 80 95 62 下标: 0 1 2

所以:

  • “第 1 个元素”写成 scores[0]
  • “第 2 个元素”写成 scores[1]
  • 最后一个元素的下标永远是 scores.Count - 1

Count 表示数量,不表示最后一个下标。数量是 3 时,最后下标是 2。

名词先讲清楚

名词直接解释例子
集合保存一组数据的结构多个成绩
数组长度固定的一组数据int[] scores = new int[3]
List长度可以变化的一组数据List<int>
元素集合里的每一项一个成绩、一个名字
下标元素的位置,从 0 开始scores[0]
Length数组长度array.Length
CountList 当前元素数量list.Count
遍历把集合里的元素一个个取出来foreach
越界访问了不存在的位置有 3 项却取 [3]

数组是什么

数组适合保存数量固定的数据。

int[] scores = new int[3]; scores[0] = 80; scores[1] = 95; scores[2] = 62; Console.WriteLine(scores[0]); Console.WriteLine(scores.Length);

输出:

80 3

数组一开始就确定长度。上面这个数组长度是 3,所以能用的下标是 012

List 是什么

List 适合保存运行过程中不断增加或减少的数据。

var scores = new List<int>(); scores.Add(80); scores.Add(95); scores.Add(62); Console.WriteLine(scores[0]); Console.WriteLine(scores.Count);

输出:

80 3

Add 会把新元素追加到列表末尾。

数组和 List 怎么选

场景推荐
数据数量一开始就固定数组
数据会不断添加List
数据会删除List
只想练基础遍历两个都可以
用户不断输入,直到回车结束List

第一周练习里,如果不确定选哪个,优先用 List,因为它更适合输入数量不确定的程序。

下标为什么从 0 开始

集合里的第一个元素下标是 0,不是 1

var names = new List<string> { "Tom", "Jerry", "Alice" }; Console.WriteLine(names[0]); Console.WriteLine(names[1]); Console.WriteLine(names[2]);

输出:

Tom Jerry Alice

如果写:

// Console.WriteLine(names[3]);

会越界,因为第 3 个下标不存在。Count 是 3,但最后一个下标是 Count - 1,也就是 2。

LengthCount

数组用 Length

int[] numbers = { 10, 20, 30 }; Console.WriteLine(numbers.Length);

ListCount

var numbers = new List<int> { 10, 20, 30 }; Console.WriteLine(numbers.Count);

两个例子都输出:

3

forforeach 怎么选

只需要拿到每个元素,用 foreach

var scores = new List<int> { 80, 95, 62 }; foreach (int score in scores) { Console.WriteLine(score); }

需要下标,用 for

var scores = new List<int> { 80, 95, 62 }; for (int i = 0; i < scores.Count; i++) { Console.WriteLine($"第 {i + 1} 个成绩: {scores[i]}"); }

注意 i < scores.Count,不要写成 i <= scores.Count

List 常用操作

var names = new List<string>(); names.Add("Tom"); names.Add("Jerry"); names.Add("Alice"); names[1] = "Jack"; names.Remove("Tom"); Console.WriteLine(names.Contains("Alice")); Console.WriteLine(names.Count);

操作解释:

写法作用
Add添加元素
names[1] = "Jack"修改下标 1 的元素
Remove删除匹配的元素
Contains判断是否包含
Count当前元素数量

更完整的常用操作表:

需求写法注意点
创建空列表new List<string>()一开始没有元素
创建时放数据new List<int> { 80, 95 }适合固定练习数据
添加到末尾list.Add(item)最常用新增
插入到指定位置list.Insert(0, item)下标不能越界
读取指定位置list[0]读取前确认 Count > 0
修改指定位置list[1] = value下标必须存在
删除指定值list.Remove(value)只删除匹配到的第一项
删除指定位置list.RemoveAt(0)下标必须存在
清空列表list.Clear()删除全部元素
判断是否包含list.Contains(value)适合简单值
查找位置list.IndexOf(value)找不到返回 -1
排序list.Sort()会改变原列表顺序
反转list.Reverse()会改变原列表顺序
转数组list.ToArray()得到固定长度数组
数量list.Count判断空列表和循环边界

完整例子:

var products = new List<string> { "Keyboard", "Mouse" }; products.Add("Monitor"); products.Insert(1, "Cable"); products[0] = "Keyboard Pro"; Console.WriteLine(products.Contains("Mouse")); Console.WriteLine(products.IndexOf("Cable")); products.Remove("Mouse"); products.Sort(); foreach (string product in products) { Console.WriteLine(product); }

输出:

True 1 Cable Keyboard Pro Monitor

这里要注意:Sort() 会直接改变 products 自己的顺序,不是返回一个新列表。

空集合为什么要先判断

下面这段有风险:

var scores = new List<int>(); // int max = scores[0];

因为列表里没有任何元素,scores[0] 不存在。

统计最高分和最低分前,要先判断:

var scores = new List<int>(); if (scores.Count == 0) { Console.WriteLine("没有数据"); return; }

今天写代码时按这个顺序

  1. 先判断要保存一条数据还是多条数据。
  2. 数量固定用数组,数量变化用 List
  3. 添加数据前先校验输入。
  4. foreach 做普通遍历。
  5. 需要显示第几个时,用 for
  6. 访问 [0] 前先判断集合不是空的。
  7. 写循环时确认下标不会超过 Count - 1

第二部分:把知识组合成完整例子

前面已经学过最小知识。现在把它们组合起来,先读懂执行顺序,再完整敲一遍。今天最终要写一个成绩统计程序:反复输入成绩,最后输出数量、总分、平均分、最高分、最低分。

这段完整程序看起来长,是因为它把前几天的知识组合起来了。第一次只找三条主线:

scores.Add(score) -> 保存一条成绩 foreach -> 逐条读取成绩 scores.Count -> 一共有多少条

其余的输入校验、whileif 都是前几天学过的旧知识。不要把整段代码误认为全是新语法。

最终你要写出这个程序

var scores = new List<int>(); while (true) { Console.Write("输入成绩,直接回车结束: "); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) { break; } if (!int.TryParse(input, out int score)) { Console.WriteLine("成绩必须是整数"); continue; } if (score < 0 || score > 100) { Console.WriteLine("成绩必须在 0 到 100 之间"); continue; } scores.Add(score); } if (scores.Count == 0) { Console.WriteLine("没有可统计的成绩"); return; } int sum = 0; int max = scores[0]; int min = scores[0]; foreach (int score in scores) { sum += score; if (score > max) { max = score; } if (score < min) { min = score; } } double average = (double)sum / scores.Count; Console.WriteLine($"数量: {scores.Count}"); Console.WriteLine($"总分: {sum}"); Console.WriteLine($"平均分: {average:F1}"); Console.WriteLine($"最高分: {max}"); Console.WriteLine($"最低分: {min}");

示例输入:

输入成绩,直接回车结束: 80 输入成绩,直接回车结束: 95 输入成绩,直接回车结束: 62 输入成绩,直接回车结束: 40 输入成绩,直接回车结束: 数量: 4 总分: 277 平均分: 69.3 最高分: 95 最低分: 40

这个程序里,List<int> 保存了所有成绩,foreach 负责一条一条统计。

第三部分:跟着敲代码

从这里开始动手。每个例子都可以直接放进 Program.cs 运行。

每个例子按三遍练:第一遍照敲;第二遍换一组集合数据;第三遍关掉答案,从创建集合开始完整重写。下标错误必须自己根据报错修一次。

动手前先做这 3 件事

  1. 打开控制台项目,确认 dotnet run 能运行。
  2. 每做一个例子,就把 Program.cs 旧内容替换掉。
  3. 每次只改一个数字或一个输入,看输出怎么变。

例子 1:数组保存固定数量成绩

int[] scores = new int[3]; scores[0] = 80; scores[1] = 95; scores[2] = 62; for (int i = 0; i < scores.Length; i++) { Console.WriteLine($"第 {i + 1} 个成绩: {scores[i]}"); }

预期输出:

第 1 个成绩: 80 第 2 个成绩: 95 第 3 个成绩: 62

例子 2:用数组简写初始化

int[] scores = { 80, 95, 62 }; foreach (int score in scores) { Console.WriteLine(score); }

这种写法适合一开始就知道所有数据。

例子 3:List 添加多个输入

var names = new List<string>(); while (true) { Console.Write("姓名,直接回车结束: "); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) { break; } names.Add(input.Trim()); } Console.WriteLine($"一共输入 {names.Count} 个姓名"); foreach (string name in names) { Console.WriteLine(name); }

输入几个名字,Count 就会变成几。

例子 4:修改和删除 List 元素

var names = new List<string> { "Tom", "Jerry", "Alice" }; names[1] = "Jack"; names.Remove("Tom"); foreach (string name in names) { Console.WriteLine(name); }

预期输出:

Jack Alice

例子 5:用 for 显示序号

var products = new List<string> { "Keyboard", "Mouse", "Cable" }; for (int i = 0; i < products.Count; i++) { Console.WriteLine($"{i + 1}. {products[i]}"); }

预期输出:

1. Keyboard 2. Mouse 3. Cable

显示给用户的序号可以从 1 开始,但访问 List 的下标仍然从 0 开始。

例子 6:统计总和和平均值

var scores = new List<int> { 80, 95, 62, 40 }; int sum = 0; foreach (int score in scores) { sum += score; } double average = (double)sum / scores.Count; Console.WriteLine($"数量: {scores.Count}"); Console.WriteLine($"总分: {sum}"); Console.WriteLine($"平均分: {average:F1}");

预期输出:

数量: 4 总分: 277 平均分: 69.3

例子 7:统计最高分和最低分

var scores = new List<int> { 80, 95, 62, 40 }; if (scores.Count == 0) { Console.WriteLine("没有成绩"); return; } int max = scores[0]; int min = scores[0]; foreach (int score in scores) { if (score > max) { max = score; } if (score < min) { min = score; } } Console.WriteLine($"最高分: {max}"); Console.WriteLine($"最低分: {min}");

先用第一个成绩当初始值,再遍历比较。

例子 8:完整输入成绩并统计

var scores = new List<int>(); while (true) { Console.Write("输入成绩,直接回车结束: "); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) { break; } if (!int.TryParse(input, out int score)) { Console.WriteLine("成绩必须是整数"); continue; } if (score < 0 || score > 100) { Console.WriteLine("成绩必须在 0 到 100 之间"); continue; } scores.Add(score); } Console.WriteLine($"有效成绩数量: {scores.Count}");

这个例子先只做输入和保存。统计放到作业完整答案里。

例子 9:商品名称列表增删改查

var products = new List<string>(); products.Add("Keyboard"); products.Add("Mouse"); products.Add("Monitor"); Console.WriteLine($"新增后数量: {products.Count}"); products[1] = "Wireless Mouse"; Console.WriteLine($"修改第 2 个商品: {products[1]}"); if (products.Contains("Keyboard")) { Console.WriteLine("列表里有 Keyboard"); } int monitorIndex = products.IndexOf("Monitor"); Console.WriteLine($"Monitor 的下标: {monitorIndex}"); products.Remove("Keyboard"); products.Sort(); Console.WriteLine("最终商品列表:"); foreach (string product in products) { Console.WriteLine(product); }

预期输出:

新增后数量: 3 修改第 2 个商品: Wireless Mouse 列表里有 Keyboard Monitor 的下标: 2 最终商品列表: Monitor Wireless Mouse

常见错误和修法

错误为什么错修法
下标从 1 开始C# 集合下标从 0 开始第一个元素是 items[0]
访问 items[Count]最后一个下标是 Count - 1先判断范围再访问
遍历时删除元素集合长度变化容易出错先收集要删的项,或倒序删除
空列表直接算平均值除以 0 会出错Count > 0 时再计算
数组和 List 混用数组长度固定,List 可增删需要动态增删时用 List<T>

每日小测

先独立回答,再看参考答案。

1. 选择数组还是 List

下面两个场景分别更适合什么?

A. 一周固定 7 天的温度 B. 用户不断添加的商品名称

2. 预测下标

var names = new List<string> { "Tom", "Jerry", "Alice" }; Console.WriteLine(names[1]);

输出什么?

3. 找越界错误

为什么下面代码会出错?

var scores = new List<int> { 80, 95, 62 }; Console.WriteLine(scores[scores.Count]);

4. 读懂泛型符号

List<decimal> 表示这个列表允许保存什么类型的数据?能不能向里面添加 "20"

5. 选择循环

只想输出每一个商品名称,不需要知道下标,优先用 for 还是 foreach

参考答案

  1. A 数量固定,可以用数组;B 数量会变化,适合 List
  2. 输出 Jerry,因为下标 1 是第二个元素。
  3. Count 是数量 3,但有效下标只有 0、1、2。最后一个元素应写 scores[scores.Count - 1]
  4. 只能保存 decimal"20" 是字符串,不能直接添加;20m 才是 decimal
  5. 优先用 foreach,因为只需要逐项读取,不需要下标。

如果第 2、3 题答错,先不要做最高分统计,重新画一遍“值和下标”的对应图。

上位机专项练习

上位机要保存通道、设备和连续采集值。数组适合固定通道,List 适合数量会变化的数据。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:数组保存固定通道名

string[] channels = { "温度", "压力", "流量" }; for (int index = 0; index < channels.Length; index++) { Console.WriteLine($"通道 {index + 1}: {channels[index]}"); }

运行结果或界面效果:

通道 1: 温度 通道 2: 压力 通道 3: 流量

改动任务: 增加“液位”通道。

专项例子 2:List 保存连续采集值

var readings = new List<double>(); readings.Add(20.1); readings.Add(20.5); readings.Add(21.0); Console.WriteLine($"采集数量: {readings.Count}");

运行结果或界面效果:

采集数量: 3

改动任务: 删除第二个值,再查看 Count。

专项例子 3:统计平均值和最大值

var readings = new List<double> { 20.1, 25.8, 22.3 }; double average = readings.Average(); double maximum = readings.Max(); Console.WriteLine($"平均值: {average:F1}"); Console.WriteLine($"最大值: {maximum:F1}");

运行结果或界面效果:

平均值: 22.7 最大值: 25.8

改动任务: 增加 30.0,观察平均值和最大值。

第四部分:作业完整答案

这一部分给出当天作业的完整答案。先按第三部分做一遍,再用这里检查。

作业要求

写一个成绩统计程序,要求:

  1. 可以反复输入成绩。
  2. 直接回车结束输入。
  3. 非整数要提示并跳过。
  4. 小于 0 或大于 100 要提示并跳过。
  5. 如果没有有效成绩,输出“没有可统计的成绩”。
  6. 有成绩时,输出数量、总分、平均分、最高分、最低分。

作业拆解

按这个顺序写:

  1. 创建 List<int> 保存成绩。
  2. while (true) 反复读取输入。
  3. 空输入时 break
  4. TryParse 转换成绩。
  5. 用范围判断拦住错误成绩。
  6. Add 保存有效成绩。
  7. 统计前判断 Count == 0
  8. foreach 统计总分、最高分、最低分。

完整代码

var scores = new List<int>(); while (true) { Console.Write("输入成绩,直接回车结束: "); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) { break; } if (!int.TryParse(input, out int score)) { Console.WriteLine("成绩必须是整数"); continue; } if (score < 0 || score > 100) { Console.WriteLine("成绩必须在 0 到 100 之间"); continue; } scores.Add(score); } if (scores.Count == 0) { Console.WriteLine("没有可统计的成绩"); return; } int sum = 0; int max = scores[0]; int min = scores[0]; foreach (int score in scores) { sum += score; if (score > max) { max = score; } if (score < min) { min = score; } } double average = (double)sum / scores.Count; Console.WriteLine("----- 成绩统计 -----"); Console.WriteLine($"数量: {scores.Count}"); Console.WriteLine($"总分: {sum}"); Console.WriteLine($"平均分: {average:F1}"); Console.WriteLine($"最高分: {max}"); Console.WriteLine($"最低分: {min}");

正确运行结果

正常输入:

输入成绩,直接回车结束: 80 输入成绩,直接回车结束: 95 输入成绩,直接回车结束: 62 输入成绩,直接回车结束: 40 输入成绩,直接回车结束: ----- 成绩统计 ----- 数量: 4 总分: 277 平均分: 69.3 最高分: 95 最低分: 40

错误输入要分别测试:

输入 abc:提示 成绩必须是整数 输入 120:提示 成绩必须在 0 到 100 之间 直接回车且没有成绩:提示 没有可统计的成绩

你要能说清楚这 7 件事

  • 数组长度固定,List 长度可以变化。
  • 数组用 LengthListCount
  • 第一个元素下标是 0
  • 最后一个元素下标是 Count - 1
  • Add 会把元素追加到 List 末尾。
  • foreach 适合逐个读取元素。
  • 访问 [0] 前要确认集合不是空的。