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];   // ❌

几个要点:

二、多维数组(矩形数组)

多维数组是规整的矩形,用逗号表示维度。最常见的是二维(矩阵):

// 多维矩形数组:用逗号表示维度
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

常用方法速查:

五、数组与其他集合互转

// 数组 → 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 就够了,性能瓶颈处再考虑 Span。

八、Array 与 List 怎么选?

实际开发 90% 用 List,数组主要在底层 API、固定常量、性能优化时用。

九、协变与限制

数组有协变性(老式 C# 1.0 遗留):object[] objs = new string[10] 合法,但写入非 string 会运行时崩溃。现代 C# 推荐用泛型 List<T> 避免这种陷阱——泛型集合是不变的,类型安全。

十、常见陷阱

小结

数组是基础但日常用得比 List 少。记住几个关键点:固定长度(需可变用 List)、引用类型(赋值是引用复制)、多维用逗号(arr[i, j])、锯齿用双方括号(arr[i][j])。下一篇讲字符串——C# 里另一个特殊的存在。

← 上一篇 C# 方法

下一篇 C# 字符串

✈️💬