C# 数组核心全面详解

数组是 C# 中最基础的集合类型,用于连续存储多个相同类型的数据 ,它具有固定长度、内存连续分配、按索引快速访问的特性,是后续学习泛型集合(如List<T>)的基础。《C# 高级编程》中对数组的讲解涵盖了从基础语法到高级应用的全量内容,以下是完整核心解析:

一、 数组的核心概念

1. 数组的定义

数组是一种引用类型 (无论存储的值类型还是引用类型,数组本身都存储在堆中),它将相同数据类型的元素按线性顺序组织,每个元素通过唯一的索引(下标)访问,索引从0开始(第一个元素索引为 0,最后一个元素索引为数组长度-1)。

2. 数组的核心特性

核心特性 具体说明
固定长度 数组实例化时必须指定长度,一旦创建长度不可修改(若需动态扩容,需手动创建新数组或使用List<T>
类型一致性 数组中所有元素必须是同一类型(或其子类型,支持多态),不允许混合存储不同类型数据
内存连续性 数组元素在内存中连续分配,这是按索引快速访问(O (1) 时间复杂度)的核心原因
引用类型本质 数组变量存储的是数组在堆中的内存地址(引用),数组元素(值类型)直接存在堆中,引用类型元素存储的是对象引用
默认初始化 数组创建时,元素会自动初始化为对应类型的默认值(int→0、bool→false、引用类型→null 等)

3. 数组与泛型集合(List<T>)的核心区别

对比维度 数组 List<T>(泛型列表)
长度特性 固定长度,不可动态扩容 动态长度,支持自动扩容
内存分配 一次性连续分配 初始分配 + 按需扩容(可能碎片化)
访问效率 索引访问效率极高 索引访问效率接近数组,略低(封装层)
功能丰富度 基础功能(增删改查需手动实现) 功能丰富(内置 Add/Remove 等方法)
适用场景 数据量固定、追求极致性能 数据量动态变化、需便捷操作

二、 数组的分类与基础语法

C# 支持多种数组类型,核心分为一维数组 (最常用)、多维数组 (矩形数组)、交错数组(数组的数组),每种数组的定义和使用语法不同。

1. 一维数组(Single-Dimensional Array)

一维数组是最基础的数组类型,呈线性结构,仅需一个索引即可访问元素。

(1) 定义语法(3 种常用方式)
cs 复制代码
using System;

public class SingleDimensionalArrayDemo
{
    public static void Main()
    {
        // 方式1:声明 + 实例化(指定长度,元素默认初始化)
        int[] array1; // 声明:数据类型[] 数组名
        array1 = new int[5]; // 实例化:new 数据类型[长度]

        // 方式2:声明 + 实例化 一步完成(指定长度)
        string[] array2 = new string[3];

        // 方式3:声明 + 实例化 + 初始化元素(无需指定长度,编译器自动推断)
        int[] array3 = new int[] { 10, 20, 30, 40, 50 }; // 完整语法
        string[] array4 = { "C#", "数组", "一维" }; // 简化语法(仅声明时初始化可用)

        // 访问元素:数组名[索引]
        array1[0] = 1; // 给第一个元素赋值
        array1[1] = 2; // 给第二个元素赋值
        Console.WriteLine("array1[0] = " + array1[0]);
        Console.WriteLine("array3[2] = " + array3[2]); // 输出30
        Console.WriteLine("array4[1] = " + array4[1]); // 输出数组

        // 获取数组长度:数组名.Length(只读属性)
        Console.WriteLine($"array1长度:{array1.Length}"); // 输出5
        Console.WriteLine($"array3长度:{array3.Length}"); // 输出5
    }
}
(2) 遍历一维数组(3 种常用方式)
cs 复制代码
public class SingleArrayTraversalDemo
{
    public static void Main()
    {
        int[] array = { 10, 20, 30, 40, 50 };

        // 方式1:for循环(可控制索引,灵活操作)
        Console.WriteLine("for循环遍历:");
        for (int i = 0; i < array.Length; i++)
        {
            Console.Write(array[i] + " ");
        }
        Console.WriteLine();

        // 方式2:foreach循环(只读遍历,无需关心索引,更简洁)
        Console.WriteLine("foreach循环遍历:");
        foreach (int item in array)
        {
            Console.Write(item + " ");
        }
        Console.WriteLine();

        // 方式3:Array类静态方法(遍历输出,快速调试)
        Console.WriteLine("Array类遍历输出:");
        Array.ForEach(array, item => Console.Write(item + " "));
    }
}

2. 多维数组(矩形数组,Multi-Dimensional Array)

多维数组是固定大小的矩形结构(如二维、三维),每个维度长度固定,需多个索引访问元素,内存中连续存储(按行优先顺序)。

(1) 二维数组(最常用,行 + 列结构)

定义与使用语法:

cs 复制代码
public class MultiDimensionalArrayDemo
{
    public static void Main()
    {
        // 方式1:声明 + 实例化(指定行数和列数,默认初始化)
        int[,] array1; // 二维数组声明:数据类型[,] 数组名
        array1 = new int[3, 2]; // 3行2列(行索引0-2,列索引0-1)

        // 方式2:声明 + 实例化 一步完成
        string[,] array2 = new string[2, 3];

        // 方式3:声明 + 实例化 + 初始化元素(编译器推断维度)
        int[,] array3 = new int[,] 
        { 
            { 1, 2 },   // 第0行
            { 3, 4 },   // 第1行
            { 5, 6 }    // 第2行
        };
        int[,] array4 = { { 10, 20 }, { 30, 40 } }; // 简化语法

        // 访问元素:数组名[行索引, 列索引]
        array1[0, 0] = 10;
        array1[2, 1] = 20;
        Console.WriteLine("array1[0,0] = " + array1[0,0]);
        Console.WriteLine("array3[1,1] = " + array3[1,1]); // 输出4

        // 获取维度信息
        int rowCount = array3.GetLength(0); // 获取行数(第0个维度)
        int colCount = array3.GetLength(1); // 获取列数(第1个维度)
        Console.WriteLine($"array3:{rowCount}行{colCount}列");
        Console.WriteLine($"array3总长度:{array3.Length}"); // 总元素数=3*2=6

        // 遍历二维数组
        Console.WriteLine("\n二维数组遍历:");
        for (int i = 0; i < array3.GetLength(0); i++) // 遍历行
        {
            for (int j = 0; j < array3.GetLength(1); j++) // 遍历列
            {
                Console.Write(array3[i, j] + " ");
            }
            Console.WriteLine(); // 每行结束换行
        }
    }
}
(2) 三维数组(扩展用法)
cs 复制代码
// 三维数组:x(深度)、y(行)、z(列)
int[,,] threeDArray = new int[2, 3, 2]; // 2个深度、3行、2列
threeDArray[0, 1, 0] = 100; // 访问元素
int depthCount = threeDArray.GetLength(0);
int rowCount = threeDArray.GetLength(1);
int colCount = threeDArray.GetLength(2);

3. 交错数组(Jagged Array,数组的数组)

交错数组是数组的数组,每个子数组的长度可以不同(非矩形结构),内存中非连续存储,本质是一维数组,其元素是另一个一维数组。

(1) 定义与使用语法
cs 复制代码
public class JaggedArrayDemo
{
    public static void Main()
    {
        // 方式1:声明 + 实例化(先指定外层数组长度,再初始化每个子数组)
        int[][] array1; // 交错数组声明:数据类型[][] 数组名
        array1 = new int[3][]; // 外层数组长度为3(3个子数组)
        array1[0] = new int[2]; // 第0个子数组长度为2
        array1[1] = new int[4]; // 第1个子数组长度为4
        array1[2] = new int[1]; // 第2个子数组长度为1

        // 方式2:声明 + 实例化 + 初始化元素(子数组长度可不同)
        int[][] array2 = new int[][]
        {
            new int[] { 1, 2 },        // 第0个子数组(长度2)
            new int[] { 3, 4, 5, 6 },  // 第1个子数组(长度4)
            new int[] { 7 }            // 第2个子数组(长度1)
        };
        int[][] array3 = { new int[] {10,20}, new int[] {30} }; // 简化语法

        // 访问元素:数组名[外层索引][内层索引]
        array1[0][1] = 10;
        array2[1][2] = 5;
        Console.WriteLine("array2[1][2] = " + array2[1][2]); // 输出5

        // 获取长度信息
        int outerLength = array2.Length; // 外层数组长度(3)
        int innerLength0 = array2[0].Length; // 第0个子数组长度(2)
        int innerLength1 = array2[1].Length; // 第1个子数组长度(4)
        Console.WriteLine($"array2外层长度:{outerLength}");
        Console.WriteLine($"array2第0个子数组长度:{innerLength0}");

        // 遍历交错数组
        Console.WriteLine("\n交错数组遍历:");
        for (int i = 0; i < array2.Length; i++) // 遍历外层数组
        {
            // 遍历第i个子数组
            for (int j = 0; j < array2[i].Length; j++)
            {
                Console.Write(array2[i][j] + " ");
            }
            Console.WriteLine();
        }
    }
}
(2) 交错数组 vs 二维数组(核心区别)
特性 交错数组(int[][] 二维数组(int[,]
结构 数组的数组(非矩形) 矩形结构(行 / 列固定)
子数组长度 每个子数组长度可不同 每行每列长度固定
内存存储 非连续存储(外层数组存子数组引用) 连续存储(按行优先排列)
访问语法 array[i][j](双层索引) array[i,j](逗号分隔索引)
灵活性 高(支持动态子数组长度) 低(固定矩形大小)
性能 略低(双层引用寻址) 略高(连续内存直接寻址)

三、 数组的核心操作

1. 数组的初始化与赋值

  • 默认初始化 :数组创建时,元素自动赋值为类型默认值(int→0bool→falsestring→null、自定义引用类型→null);
  • 显式初始化:声明时直接指定元素值(简化语法),或创建后通过索引逐个赋值;
  • 静态初始化 :使用new 类型[] { ... }语法,无需指定长度。

示例:

cs 复制代码
// 默认初始化
int[] arr1 = new int[3]; // [0, 0, 0]
bool[] arr2 = new bool[2]; // [false, false]
string[] arr3 = new string[2]; // [null, null]

// 显式初始化
int[] arr4 = { 1, 2, 3 };
arr1[0] = 10;
arr1[1] = 20; // arr1变为 [10, 20, 0]

2. 数组的遍历

除了forforeach循环,还可使用Array.ForEach静态方法(简化遍历):

cs 复制代码
string[] arr = { "C#", "数组", "遍历" };

// 1. foreach(只读,简洁)
foreach (var item in arr)
{
    Console.Write(item + " ");
}

// 2. Array.ForEach(Lambda表达式简化)
Array.ForEach(arr, item => Console.WriteLine(item));

// 3. 二维数组遍历(嵌套for)
int[,] multiArr = { {1,2}, {3,4} };
for (int i = 0; i < multiArr.GetLength(0); i++)
{
    for (int j = 0; j < multiArr.GetLength(1); j++)
    {
        Console.Write(multiArr[i,j] + " ");
    }
}

3. 数组的复制(3 种常用方式)

由于数组长度固定,复制数组是实现 "扩容" 或 "截取部分元素" 的常用手段:

cs 复制代码
public class ArrayCopyDemo
{
    public static void Main()
    {
        int[] sourceArr = { 10, 20, 30, 40, 50 };

        // 方式1:Array.Copy(静态方法,高效,支持指定起始索引和长度)
        int[] targetArr1 = new int[3];
        Array.Copy(sourceArr, 1, targetArr1, 0, 3); 
        // 源数组:sourceArr,源起始索引:1,目标数组:targetArr1,目标起始索引:0,复制长度:3
        Console.WriteLine("Array.Copy结果:");
        Array.ForEach(targetArr1, item => Console.Write(item + " ")); // [20, 30, 40]

        // 方式2:数组的Clone()方法(浅拷贝,返回object类型,需强制转换)
        int[] targetArr2 = (int[])sourceArr.Clone();
        Console.WriteLine("\nClone()结果:");
        Array.ForEach(targetArr2, item => Console.Write(item + " ")); // [10,20,30,40,50]

        // 方式3:LINQ的Take/Skip(灵活截取,返回IEnumerable<T>,需转数组)
        using System.Linq; // 需引用LINQ命名空间
        int[] targetArr3 = sourceArr.Take(3).ToArray(); // 截取前3个元素
        int[] targetArr4 = sourceArr.Skip(2).Take(2).ToArray(); // 跳过前2个,截取2个
        Console.WriteLine("\nLINQ Take(3)结果:");
        Array.ForEach(targetArr3, item => Console.Write(item + " ")); // [10,20,30]
    }
}

4. 数组的排序与查找

C# 提供Array类静态方法实现数组的排序和查找,无需手动实现算法:

cs 复制代码
public class ArraySortSearchDemo
{
    public static void Main()
    {
        int[] arr = { 3, 1, 4, 2, 5 };

        // 1. 排序:Array.Sort(默认升序,原地排序,修改原数组)
        Array.Sort(arr);
        Console.WriteLine("升序排序后:");
        Array.ForEach(arr, item => Console.Write(item + " ")); // [1,2,3,4,5]

        // 反向排序:先排序再反转
        Array.Reverse(arr);
        Console.WriteLine("\n反转后(降序):");
        Array.ForEach(arr, item => Console.Write(item + " ")); // [5,4,3,2,1]

        // 2. 查找:Array.IndexOf(查找元素首次出现的索引,未找到返回-1)
        int index = Array.IndexOf(arr, 3);
        Console.WriteLine($"\n元素3的索引:{index}"); // 输出2

        // 3. 二分查找:Array.BinarySearch(仅对已升序排序的数组有效)
        Array.Sort(arr); // 先升序排序
        int binaryIndex = Array.BinarySearch(arr, 3);
        Console.WriteLine($"二分查找元素3的索引:{binaryIndex}"); // 输出2
        int notFoundIndex = Array.BinarySearch(arr, 6);
        Console.WriteLine($"查找元素6(不存在):{notFoundIndex}"); // 返回负数(表示未找到)
    }
}

5. 数组的扩容与缩容

数组长度固定,扩容 / 缩容需手动创建新数组,再复制原数组元素:

cs 复制代码
public class ArrayResizeDemo
{
    public static void Main()
    {
        int[] oldArr = { 1, 2, 3 };
        int newLength = 5; // 扩容到5

        // 1. 扩容
        int[] newArr = new int[newLength];
        Array.Copy(oldArr, newArr, oldArr.Length); // 复制原数组元素
        newArr[3] = 4;
        newArr[4] = 5;
        Console.WriteLine("扩容后数组:");
        Array.ForEach(newArr, item => Console.Write(item + " ")); // [1,2,3,4,5]

        // 2. 缩容
        int shrinkLength = 2;
        int[] shrinkArr = new int[shrinkLength];
        Array.Copy(newArr, shrinkArr, shrinkLength);
        Console.WriteLine("\n缩容后数组:");
        Array.ForEach(shrinkArr, item => Console.Write(item + " ")); // [1,2]
    }
}

四、 System.Array类:数组的基类

C# 中所有数组都直接继承自System.Array(抽象类,属于System命名空间),Array类提供了大量静态方法和实例属性,用于操作数组,核心成员如下:

1. 核心实例属性

属性名 作用
Length 获取数组中总元素个数(一维数组 / 多维数组 / 交错数组均返回总元素数)
Rank 获取数组的维度数(一维数组→1、二维数组→2、交错数组→1,因为它是数组的数组)
GetLength(int dimension) 获取指定维度的元素个数(二维数组:GetLength(0)= 行数,GetLength(1)= 列数)

2. 核心静态方法

方法名 作用
Array.Copy(...) 高效复制数组元素,支持指定起始索引和长度
Array.Sort(Array) 对一维数组进行升序排序(原地排序)
Array.Reverse(Array) 反转数组元素的顺序(原地反转)
Array.IndexOf(Array, object) 查找元素在一维数组中首次出现的索引,未找到返回 - 1
Array.BinarySearch(Array, object) 二分查找元素(数组需先升序排序),未找到返回负数
Array.ForEach<T>(T[], Action<T>) 遍历一维数组并执行指定操作(Lambda 表达式简化)

五、 数组的高级特性

1. 数组的多态性

数组支持多态,即可以存储父类的子类对象,父类数组引用可以指向子类数组实例:

cs 复制代码
public class Animal { public string Name { get; set; } }
public class Dog : Animal { public void Bark() { Console.WriteLine("汪汪叫"); } }
public class Cat : Animal { public void Meow() { Console.WriteLine("喵喵叫"); } }

public class ArrayPolymorphismDemo
{
    public static void Main()
    {
        // 父类数组存储子类对象
        Animal[] animals = new Animal[2];
        animals[0] = new Dog() { Name = "旺财" };
        animals[1] = new Cat() { Name = "咪咪" };

        // 遍历数组,通过类型判断调用子类方法
        foreach (var animal in animals)
        {
            if (animal is Dog dog)
            {
                dog.Bark();
            }
            else if (animal is Cat cat)
            {
                cat.Meow();
            }
        }

        // 父类数组引用指向子类数组
        Dog[] dogs = new Dog[] { new Dog() { Name = "大黄" } };
        Animal[] animalArr = dogs; // 隐式转换(协变)
    }
}

2. 空数组与可空类型数组

  • 空数组 :数组变量可以为null(未引用任何数组实例),也可以创建长度为 0 的空数组;
  • 可空类型数组 :值类型数组默认不允许存储null,若需存储null,需使用可空值类型(Nullable<T>T?)。

示例:

cs 复制代码
public class NullableArrayDemo
{
    public static void Main()
    {
        // 1. 空数组相关
        int[] nullArr = null; // 数组变量为null(未实例化)
        // Console.WriteLine(nullArr.Length); // 空引用异常
        int[] emptyArr = new int[0]; // 长度为0的空数组(非null)
        Console.WriteLine($"空数组长度:{emptyArr.Length}"); // 输出0

        // 2. 可空类型数组
        int?[] nullableIntArr = new int?[3]; // 可存储int或null
        nullableIntArr[0] = 10;
        nullableIntArr[1] = null;
        nullableIntArr[2] = 20;
        Console.WriteLine("可空类型数组:");
        Array.ForEach(nullableIntArr, item => Console.Write(item + " ")); // [10, , 20]
    }
}

3. 数组作为方法参数 / 返回值

数组作为方法参数时,传递的是引用(引用类型特性),修改数组元素会影响原数组;数组也可作为方法返回值,返回新的数组实例:

cs 复制代码
public class ArrayAsParamReturnDemo
{
    // 数组作为参数:修改元素(影响原数组)
    public static void ModifyArray(int[] arr)
    {
        if (arr == null || arr.Length == 0) return;
        arr[0] = 100; // 修改原数组的第一个元素
    }

    // 数组作为返回值:返回新数组
    public static int[] CreateArray(int length)
    {
        int[] newArr = new int[length];
        for (int i = 0; i < length; i++)
        {
            newArr[i] = i * 10;
        }
        return newArr;
    }

    public static void Main()
    {
        int[] arr = { 1, 2, 3 };
        Console.WriteLine("修改前arr[0]:" + arr[0]); // 输出1
        ModifyArray(arr);
        Console.WriteLine("修改后arr[0]:" + arr[0]); // 输出100

        int[] resultArr = CreateArray(4);
        Console.WriteLine("创建的新数组:");
        Array.ForEach(resultArr, item => Console.Write(item + " ")); // [0,10,20,30]
    }
}

六、 总结

C# 数组的核心要点可概括为 5 个核心层面:

  1. 核心特性:引用类型、固定长度、类型一致、内存连续、索引从 0 开始,默认初始化为类型默认值;
  2. 核心分类:一维数组(线性结构,最常用)、二维 / 多维数组(矩形结构,固定行列)、交错数组(数组的数组,子数组长度可变);
  3. 核心操作:初始化与赋值、遍历(for/foreach/Array.ForEach)、复制(Array.Copy/Clone)、排序(Array.Sort)、查找(Array.IndexOf/BinarySearch)、扩容缩容(手动创建新数组);
  4. 核心类 :所有数组继承自System.Array,其静态方法和实例属性是操作数组的核心工具;
  5. 高级特性:支持多态(父类数组存储子类对象)、可空类型数组(存储 null)、作为方法参数 / 返回值(传递引用)。
相关推荐
MyBFuture28 分钟前
C#表格与定时器实战技巧
开发语言·windows·c#·visual studio
feifeigo1231 小时前
基于C#实现即时通讯工具
开发语言·c#
程序猿多布1 小时前
C# 密封类、密封方法、密封属性、密封事件、密封索引器
c#
张人玉1 小时前
西门子 S7 PLC 通信 WPF 应用分析笔记
笔记·c#·wpf·plc
刘97532 小时前
【第22天】22c#今日小结
开发语言·c#
张人玉2 小时前
整合 Sugar ORM 连接 SQLite 数据库到 WPF 折线图项目
数据库·sqlite·c#·wpf
SunnyDays10112 小时前
使用 C# 将 Excel 转换为 Markdown 表格(含单文件与批量转换)
c#·excel转markdown
wuk9983 小时前
C# Winform实现拼图游戏
windows·microsoft·c#
唐青枫3 小时前
深入理解 System.Lazy<T>:C#.NET 延迟初始化与线程安全
c#·.net
世洋Blog14 小时前
AStar算法基础学习总结
算法·面试·c#·astar·寻路