C# 数组
数组是最基础的数据结构,把一组相同类型的数据连续存放在内存。C# 数组是引用类型(尽管元素是值类型),长度固定(创建后不能改变)。这一篇我们看一维、多维、锯齿三种,以及 Array 类的实用方法。
一、一维数组
最常用的数组形式:
// 几种声明 + 初始化方式
int[] a1 = new int[5]; // 长度 5,默认值 0
int[] a2 = new int[] { 1, 2, 3 }; // 显式
int[] a3 = { 1, 2, 3 }; // 简写(最常用)
int[] a4 = [1, 2, 3]; // C# 12+ 集合表达式(新)
// 访问与修改(索引从 0 开始)
Console.WriteLine(a3[0]); // 1
a3[0] = 99;
Console.WriteLine(a3.Length); // 3
// 遍历
foreach (int n in a3)
Console.WriteLine(n);
// 用 for + Length
for (int i = 0; i < a3.Length; i++)
Console.WriteLine($"a3[{i}] = {a3[i]}");
// 越界会抛 IndexOutOfRangeException
// int bad = a3[10]; // ❌几个要点:
- 声明语法:
T[]而不是T[]的其他形式(和 Java/C 不同,C# 中[]紧跟类型)。 - 索引从 0 开始,越界抛
IndexOutOfRangeException。 - 长度固定,需要动态增删用
List<T>(下一篇会讲)。 - C# 12+ 集合表达式:
[1, 2, 3]是新语法,可隐式推断目标类型。
二、多维数组(矩形数组)
多维数组是规整的矩形,用逗号表示维度。最常见的是二维(矩阵):
// 多维矩形数组:用逗号表示维度
int[,] grid = new int[3, 4]; // 3 行 4 列
int[,] grid2 = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
// 访问
Console.WriteLine(grid2[1, 2]); // 7
grid2[0, 0] = 100;
// 维度信息
Console.WriteLine(grid2.Rank); // 2(维度数)
Console.WriteLine(grid2.GetLength(0)); // 3(第 0 维长度,行数)
Console.WriteLine(grid2.GetLength(1)); // 4(第 1 维长度,列数)
Console.WriteLine(grid2.Length); // 12(总元素数)
// 嵌套循环遍历
for (int i = 0; i < grid2.GetLength(0); i++)
{
for (int j = 0; j < grid2.GetLength(1); j++)
Console.Write(grid2[i, j] + " ");
Console.WriteLine();
}访问用 arr[i, j](单方括号内逗号分隔),和锯齿数组不同。三维、四维理论上都支持,但日常很少超过二维。
三、锯齿数组(Jagged Array)
锯齿数组是数组的数组,每行可以变长:
// 锯齿数组:数组的数组(每行长度可不同)
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2, 3 };
jagged[1] = new int[] { 4, 5 };
jagged[2] = new int[] { 6, 7, 8, 9 };
// 简化初始化
int[][] jagged2 = {
new[] { 1, 2, 3 },
new[] { 4, 5 },
new[] { 6, 7, 8, 9 }
};
// 访问(两次索引)
Console.WriteLine(jagged2[0][1]); // 2
Console.WriteLine(jagged2[1].Length); // 2
// 嵌套遍历
foreach (var row in jagged2)
foreach (var n in row)
Console.WriteLine(n);
// 锯齿数组 vs 多维数组
// 锯齿:每行可变长,灵活,内部是多个独立数组
// 多维:规整矩形,内存连续,适合网格/矩阵锯齿 vs 多维的选择:
- 多维:行数 × 列数固定,内存连续,适合规整网格(棋盘、矩阵)。
- 锯齿:每行长度可变,内部是多个独立数组,适合不规则数据(学生选课表、稀疏矩阵)。
日常使用:大部分场景用 List<List<T>> 更方便,锯齿数组用得较少。
四、Array 静态类的实用方法
C# 提供了 Array 类(注意是类,不是命名空间),含大量静态方法:
// Array 静态类的实用方法
int[] nums = { 5, 2, 8, 1, 9 };
Array.Sort(nums); // 原地排序:1 2 5 8 9
Array.Reverse(nums); // 翻转:9 8 5 2 1
int idx = Array.IndexOf(nums, 5); // 查找索引
bool found = Array.Exists(nums, n => n > 5);
int found_item = Array.Find(nums, n => n > 5);
int[] all = Array.FindAll(nums, n => n > 5);
Array.Clear(nums, 0, nums.Length); // 全部置零
Array.Resize(ref nums, 10); // 调整大小
Array.Copy(src, dest, length); // 复制
// 二分查找(必须先排序)
int[] sorted = { 1, 3, 5, 7, 9 };
int i = Array.BinarySearch(sorted, 5); // 2常用方法速查:
Array.Sort:原地排序。引用类型用快速排序,值类型用内省排序。Array.Reverse:翻转数组。Array.IndexOf:线性查找,返回第一个匹配的索引(-1 表示没找到)。Array.Find / FindAll / FindIndex:基于谓词查找,比 IndexOf 灵活。Array.BinarySearch:二分查找(必须先排序),O(log n)。Array.Copy:复制数组。也可用Clone()浅拷贝。Array.Resize:调整大小(本质是新建一个数组并复制)。
五、数组与其他集合互转
// 数组 → List
int[] arr = { 1, 2, 3 };
List<int> list = arr.ToList();
// List → 数组
int[] back = list.ToArray();
// 字符串 → 字符数组 → 字符串
string s = "Hello";
char[] chars = s.ToCharArray();
Array.Reverse(chars);
string reversed = new string(chars); // "olleH"
// 字符串数组 → 单字符串
string[] parts = { "Hello", "World" };
string joined = string.Join(", ", parts); // "Hello, World"
// LINQ 操作
int[] nums = { 1, 2, 3, 4, 5 };
int sum = nums.Sum(); // 15
int max = nums.Max(); // 5
double avg = nums.Average(); // 3
var doubled = nums.Select(n => n * 2).ToArray();ToArray()、ToList()、ToDictionary() 是 LINQ 提供的扩展方法,集合互转一行搞定。
六、数组是引用类型
虽然元素是 int 这种值类型,但数组本身是引用类型。赋值时复制的是引用:
int[] a = { 1, 2, 3 };
int[] b = a; // 复制引用,b 和 a 指向同一个数组
b[0] = 99;
Console.WriteLine(a[0]); // 99(被改了!)
// 要真正复制用 Copy 或 ToArray
int[] c = (int[])a.Clone(); // 浅拷贝
int[] d = a.ToArray(); // LINQ 写法
d[0] = 0;
Console.WriteLine(a[0]); // 99(没被改)七、数组遍历的性能
- foreach:语法简洁,适用于所有 IEnumerable。性能略低于 for(但 JIT 通常能优化)。
- for + Length:数值数组最高效,可避免迭代器分配。
- Span<T>:C# 7+ 引入的高性能切片,无分配,极致性能场景用。
日常业务用 foreach 就够了,性能瓶颈处再考虑 Span。
八、Array 与 List 怎么选?
- 用数组:长度确定不变、性能敏感、API 要求(如 params)。
- 用 List<T>:长度可变、需要 Add/Remove、绝大多数业务场景。
- 用 IEnumerable<T>:作为方法参数/返回值,接受任何集合,延迟计算。
实际开发 90% 用 List,数组主要在底层 API、固定常量、性能优化时用。
九、协变与限制
数组有协变性(老式 C# 1.0 遗留):object[] objs = new string[10] 合法,但写入非 string 会运行时崩溃。现代 C# 推荐用泛型 List<T> 避免这种陷阱——泛型集合是不变的,类型安全。
十、常见陷阱
- 越界:IndexOutOfRangeException。检查
index < arr.Length。 - 负索引:C# 数组不支持负索引(用 Python 习惯了注意)。新 C# 引入
^1索引(arr[^1]表示倒数第一个)。 - 数组为 null vs 空数组:注意区分,空数组用
Array.Empty<T>()(无分配)。 - foreach 修改:遍历中修改集合会抛异常。
小结
数组是基础但日常用得比 List 少。记住几个关键点:固定长度(需可变用 List)、引用类型(赋值是引用复制)、多维用逗号(arr[i, j])、锯齿用双方括号(arr[i][j])。下一篇讲字符串——C# 里另一个特殊的存在。
← 上一篇 C# 方法
下一篇 C# 字符串 →