数组(一维/多维/交错)
本教程共 100 篇 · 第 20 篇 · 更新于 2026-07-31 · 约 12 分钟阅读
20. 数组(一维/多维/交错)
本节目标:学完能声明并初始化一维数组、多维数组和交错数组,用索引读写元素,并用循环把它们遍历出来。
上一章讲了循环,循环最常被用来”对每个元素做一遍”。可元素放哪?总不能起 100 个变量名。数组(array)就是干这个的:它把同一类型的一批数据装进一个变量,用编号去取。
数组有三个关键特征,先记牢:
- 所有元素类型相同,比如全都是
int或全是string。 - 长度(length)在创建时就固定了,之后不能直接伸缩。
- 编号从 0 开始,最后一个元素的编号是「长度减一」。
C# 里数组是引用类型,所以创建它要用 new。下面分三种来看:一维、多维、交错。
一、一维数组:最常用
一维数组(single-dimensional array)就是一条直线排开的一串元素。int[] 表示一个”装整数的数组”。
声明与初始化
只声明还不够,得用 new 真正在内存里开辟空间:
int[] numbers = new int[5]; // 长度为 5,元素默认都是 0
更省事的是直接把值列出来,让编译器自己数长度。C# 12 起的集合表达式(collection expression)写法最干净:
int[] numbers = [10, 20, 30, 40]; // 长度 4,依次赋值
也可以写成传统的数组初始化器(initializer),效果一样:
int[] numbers = new int[] { 10, 20, 30, 40 };
Tip现代写法优先用
[...]集合表达式。它不光能用于数组,下一章的集合List<T>也能用同一套语法,学一次处处通用。
索引访问
方括号里的数字叫索引(index)。第一个是 [0],第二个是 [1]:
int[] numbers = [10, 20, 30];
Console.WriteLine(numbers[0]); // 输出 10
numbers[1] = 99; // 把第二个元素改成 99
Console.WriteLine(numbers[1]); // 输出 99
访问时索引不能越界。若写了 numbers[3](这里只有 3 个元素,编号最大是 2),运行时会抛出 IndexOutOfRangeException。新手不少 bug 就出在这。
想知道数组多长,用 Length 属性:
int[] numbers = [10, 20, 30];
Console.WriteLine(numbers.Length); // 输出 3
遍历
遍历(traverse)就是把每个元素都访问一次。最常见两种:
int[] numbers = [10, 20, 30];
// 方法一:for 循环,需要下标时用
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"第 {i} 个是 {numbers[i]}");
}
// 方法二:foreach,只关心值、不关心位置时用
foreach (int n in numbers)
{
Console.WriteLine(n * 2); // 输出 20、40、60
}
Note
foreach的特点是”只读地挨个过一遍”,它不会越界,也拿不到下标。需要按位置改元素或倒序遍历,就用for。
二、多维数组:整齐的表格
多维数组(multidimensional array,也叫矩形数组 rectangular array)可以理解成一张表。int[,] 是二维,像一个行×列的矩阵。
声明与初始化
int[,] matrix = new int[2, 3]; // 2 行 3 列,共 6 个元素,默认 0
直接填值也更直观,每行用一层花括号:
int[,] matrix =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
C# 12 的集合表达式同样适用,内层用 [...] 表示每行:
int[,] matrix = [[1, 2, 3], [4, 5, 6]];
方括号里逗号的数量 = 维数减一:[,] 是二维,[,,] 就是三维,依此类推。
索引与遍历
访问元素要写两个索引:行和列,中间用逗号:
int[,] matrix = [[1, 2, 3], [4, 5, 6]];
Console.WriteLine(matrix[0, 1]); // 输出 2(第 0 行第 1 列)
matrix[1, 2] = 99; // 把第 1 行第 2 列改成 99
多维数组遍历要用嵌套循环,每个维度一层。取某一维的长度得用 GetLength(维度编号),而不是 Length:
int[,] matrix = [[1, 2, 3], [4, 5, 6]];
for (int i = 0; i < matrix.GetLength(0); i++) // 行
{
for (int j = 0; j < matrix.GetLength(1); j++) // 列
{
Console.Write($"{matrix[i, j]} ");
}
Console.WriteLine(); // 一行结束换行
}
// 输出:
// 1 2 3
// 4 5 6
Note对多维数组用
Length拿到的是”所有元素总数”(这里是 6),不是行数。要分行取长度,必须用GetLength(0)取行、GetLength(1)取列。
三、交错数组:每行长度可以不同
交错数组(jagged array)是”数组的数组”——外层数组的每一项,本身又是一个一维数组,而且每一行长度可以不一样。int[][] 就是代表。
声明与初始化
先定好有几行,每行再单独创建:
int[][] jagged = new int[3][]; // 3 行,每行还是 null
jagged[0] = [1, 2, 3]; // 第 0 行 3 个元素
jagged[1] = [4, 5]; // 第 1 行 2 个元素
jagged[2] = [6, 7, 8, 9]; // 第 2 行 4 个元素
也可以一步到位,用集合表达式把每行直接列出来:
int[][] jagged = [[1, 2, 3], [4, 5], [6, 7, 8, 9]];
注意每一行用两个方括号取值:第一个取”第几行”,第二个取”该行里的第几个”。
索引与遍历
int[][] jagged = [[1, 2, 3], [4, 5], [6, 7, 8, 9]];
Console.WriteLine(jagged[0][1]); // 输出 2(第 0 行第 1 个)
jagged[1][0] = 40; // 把第 1 行第 0 个改成 40
// 外层循环走行,内层用 foreach 走该行元素
foreach (int[] row in jagged)
{
foreach (int value in row)
{
Console.Write($"{value} ");
}
Console.WriteLine();
}
// 输出:
// 1 2 3
// 40 5
// 6 7 8 9
取行数用 jagged.Length(外层的行数),取某一行的元素数用 jagged[i].Length。
四、多维 vs 交错,怎么挑
| 对比点 | 多维数组 int[,] | 交错数组 int[][] |
|---|---|---|
| 形状 | 矩形,每行等长 | 锯齿,每行可不等长 |
| 内存 | 一块连续空间 | 每行各是独立数组 |
| 索引 | matrix[i, j] 一个逗号 | jagged[i][j] 两层括号 |
| 适合场景 | 规整的表格数据(如棋盘) | 不规则数据(如每个学生的选课数不同) |
简单记:数据是整齐表格 → 用多维数组;每行长短不一 → 用交错数组。两者都能用嵌套循环遍历。
Tip性能上,多维数组在连续内存里,缓存更友好;交错数组每行独立,灵活但稍多开销。初学阶段按”数据形状”选即可,别为微小性能提前优化。
五、新手常见坑
- 索引从 0 开始:长度为
n的数组,最大合法索引是n - 1。写numbers[numbers.Length]必然越界。 - 长度固定:数组创建后不能加长。想动态增删,学完本章后请看下一个章节的集合
List<T>。 - 交错数组每行要先
new:int[][] j只建了外层,里层每行默认是null,没初始化就访问会报空引用。 - 多维数组用
GetLength而非Length:前面强调过,遍历维度长度务必用对方法。
六、为什么数组长度固定(设计动机)
你也许会嘀咕:长度不能变的数组,用着多憋屈?这其实是用「灵活」换「速度」的取舍。
把数组想成银行墙里一排编号储物柜:柜子数量在装修时就钉死了,但好处是——你报一个号,管理员一步就能走到对应柜子,不用挨个找。数组在内存里就是「一块连续、等距排列」的空间,numbers[3] 的速度来自简单加法:起始地址 + 3 × 单个元素大小,一步直达(这种访问叫 O(1) 随机访问)。
一旦允许「中途加长」,这整齐的连续布局就被打破,要么整体搬家、要么碎片化。所以语言让数组保持固定,把「能伸缩」的任务交给下一章的 List<T>(它底层用数组,满了就自动扩容搬家)。一句话:数组 = 固定、快、简单;List = 灵活、自动扩容。按需求选。
七、默认值与引用类型数组
new 出来的数组,元素会被自动赋「类型的默认值(default value)」:数值类型是 0,bool 是 false,而引用类型(如 string、自定义类)是 null。这点特别容易踩坑:
string[] names = new string[3]; // 三个元素目前都是 null
Console.WriteLine(names.Length); // 输出 3(数组本身非空)
// Console.WriteLine(names[0].Length); // 危险!names[0] 是 null,会抛 NullReferenceException
names[0] = "小明"; // 用之前先赋值
Console.WriteLine(names[0].Length); // 输出 2
引用类型数组装的其实是「引用(地址)」,不是对象本身。Person[] people = new Person[2]; 只是建了一个能放两个引用的槽,槽里还是 null,得 people[0] = new Person(); 才算真正创建对象。
Warning数组创建后,未赋值的引用类型元素全是
null。在访问它们的属性或方法前,务必先判断或先赋值,否则运行时会炸NullReferenceException。这正是第 16 章??和第 90 章可空引用类型要帮你兜住的场景。
八、复制数组:别直接赋值
数组是引用类型,int[] b = a; 不是复制,而是让 b 和 a 指向同一块内存。改了 b[0],a[0] 也跟着变:
int[] a = [1, 2, 3];
int[] b = a; // b 和 a 指向同一个数组
b[0] = 99;
Console.WriteLine(a[0]); // 输出 99,原数组也被改了
想要「一份独立副本」,用 C# 的范围运算符(range operator)[..] 最简洁,或用 Array.Copy:
int[] a = [1, 2, 3];
int[] copy = a[..]; // 复制出一份独立的新数组
copy[0] = 99;
Console.WriteLine(a[0]); // 仍是 1,原数组不受影响
Note
a[..]表示「从开头到结尾的全部元素」,生成一个新数组。范围语法还能截取片段,如a[1..3]取第 1、2 个元素。需要部分复制时它比手写循环方便。
要点速记
- 一维
int[]:直线排布,numbers[0]取值,Length取长度。 - 多维
int[,]:矩形表格,matrix[i, j]取值,遍历用GetLength(维度)。 - 交错
int[][]:每行不等长,jagged[i][j]取值,每行是独立一维数组。 - 声明初始化优先用集合表达式
[...];遍历一维用for/foreach,多维和交错用嵌套循环。 - 数组长度固定、元素是默认值;复制要用
[..]或Array.Copy,别直接赋值。
动手练一练
- 用集合表达式建一个
int[],存 5 个成绩,用foreach算出总分并打印。 - 建一个 3×3 的
int[,]矩阵,用嵌套for把主对角线(行号等于列号)上的元素都改成 1,其余为 0,并打印整张表。 - 建一个
int[][]交错数组,三行分别有 2、4、3 个元素,遍历打印出”第几行有几个元素、分别是什么”。
下一章看数组的常用操作:排序、查找、复制、求最值,以及 Array 类上那些现成好用的方法。