重庆熊猫 Loading

C#教程 - 数组类型(Array Type)

更新记录
转载请注明出处。
2022年9月11日 发布。
2022年9月10日 从笔记迁移到博客。

说明

数组是最为常见的一种数据结构

数组是相同类型的、用一个标识符封装到一起的基本类型数据序列或对象序列

可以用一个统一的数组名和下标来唯一确定数组中的元素

实质上数组是一个线性序列,因此数组访问起来很快

数组本质上是用一个变量名称表示一组同类型的数据元素集合

每个元素通过变量名称和一个或多个方括号中的索引来访问

数组名[索引]
数组名[索引,索引]
数组名[索引][索引]

重要概念

元素:数组中每个独立的数据元素,数组中的元素类型必须相同或继承自同一个类型

秩/维度:数组可有任何正数的维度数,数组的维度也称为秩(rank)

维度长度:数组每个维度有一个长度,就是这个方向位置个数

数组长度:数组中所有维度中所有元素的总和称为数组的长度

image

注意:

​ 不给数组赋值,编译器会初始化元素为该类型的默认值

​ 交错数组的定义不能定义最后一个秩的大小

​ 一维数组、矩阵数组的长度是固定的,交错数组的长度不是固定的

​ 数组的下标从0开始

数组的类型

数组分为:一维数组 和 多维数组

一维数组可以认为是单行元素或元素向量

多维数组由主向量中的位置组成的,每一个位置本身又是一个数组

多维数组分类:矩阵数组 和 交错数组

矩阵数组:

数组每个维度的长度都是相同的

不管有多少个维度都是用一个[]

pandaArray[4,6,1];  //只有单个大括号

交错数组:

每一个子数组都是独立数组

子数组长度可以不同

使用多个[]

pandaArray[2][4][9];         //3组方括号

image

数组本质是对象

数组本质是对象,内部继承自System.Array

数组继承了许多有用的成员:
image

详细数组方法请看:数组笔记 和 官方文档

数组类型的结构在内存中示意图:
image

数组类型是引用类型,但是数组类型的元素可以是值类型也可以是引用类型

如果存储的元素都是值类型,数组被称为 值类型数组

如果存储的元素都是引用类型,数组被称为 引用类型数组

image

声明和实例一维数组和矩阵数组(Rectangular Array)

声明一维数组或矩阵数组

int[] arr1;         //数组类型:一维整型数组
int[,] arr2;        //数组类型:二维整型数组
int[,,] arr3;       //数组类型:三维整型数组
long[,,,] arr4;     //数组类型:四维long数组

注意:

只能用一个[]方括号,有多少维度就添n-1个,逗号

​ 数组声明后数组的秩就确定了,但长度可以在实例的时候再添加

实例化一维数组:

int[] arr2 = new int[4];
PandaClass[] arr3 = new PandaClass[4];

实例化矩阵数组:

int[,,] arr = new int[3,5,2];

说明:使用数组创建表达式实例化数组

声明和实例化一维数组和矩阵数组内存示意图:
image

声明和实例交错数组(Jagged Array)

声明交错数组:

int[][] SomeArr1;    //声明数组,秩等于2
int[][][] SomeArr2;    //声明数组,秩等于3

示意图:
image

实例化交错数组:

int[][] arr = new int[3][];               //声明并实例化交错数组
arr[0] = new int[6] {1,2,3,4,5,6 };      //实例化子数组
arr[1] = new int[2] {66,666 };           //实例化子数组
arr[2] = new int[3] { 3,33,333 };        //实例化子数组

示意图:
image

声明和实例化混合数组

混合数组:交错数组 和 矩阵数组 的混合体

注意:混合数组只可以是矩阵数组下的交错数组

int[][,] arr = new int[3][,];   //声明并实例化一个混合数组
arr[0] = new int[1, 3];         //实例化子混合数组
arr[1] = new int[3, 4];         //实例化子混合数组
arr[2] = new int[3, 1];         //实例化子混合数组

内存结构示意图:
image

比较交错数组和矩阵数组

image

初始化数组

说明

一定要初始化数组后,再使用数组

在使用数组之前,一定要判断一下是否为null

默认初始化

如果声明和实例化数组后没有初始化,编译器会进行默认初始化
image

显式初始化一维数组

在实例化语句后添加一个初始化列表即可:

int[] pandaArray = new int[] { 10, 20, 30, 40 };

内存结构示意图:
image

显式初始化矩阵数组

int[,] pandaArray = new int[,] { { 1, 2 }, { 3, 4 }, { 4, 5 } };

内存结构示意图:
image

注意:

​ 每一个初始值必须放在{}大括号内

​ 每一个维度必须使用{}大括号并嵌套在外大括号内

​ 使用,逗号将初始化值和{}大括号分隔开

初始化快捷语法

省略数组创建表达式,直接给数组赋值

int[] pandaArray2 = { 1, 2, 3 };
//等价于
int[] pandaArray1 = new int[3] { 1, 2, 3 };

实例二:

int[,] arr2 = { { 1, 2 }, { 3, 4 } };
//等价于
int[,] arr1 = new int[2,2] { { 1, 2 }, { 3, 4 } };

隐式类型数组

数组初始化时,编译器自动推断数组的类型

var arr1 = new[] { 1, 2, 3, 4 };
//等价于
int[] arr2 = new int[] { 1, 2, 3, 4 };

实例二:

var arr1 = new[,] { { 1, 2 }, { 3, 4 } };
//等价于
int[,] arr2 = new int[2, 2] { { 1, 2 }, { 3, 4 } };

数组的反序索引

反序索引以开头,1表示倒数第1个元素索引,^2表示倒数第二个,以此类推

适合于处理需要使用正反序处理的数值场景

注意:没有^0

注意:Span类型 和 ReadOnlySpan类型同样支持反序索引

注意:反序索引从C# 8开始支持

实例:

char[] vowels = new char[] { 'a', 'e', 'i', 'o', 'u' };
char lastElement = vowels[^1];   // 'u'
char secondToLast = vowels[^2];   // 'o'

在C# 8中还定义了索引类型Index,可以给其复制^N值

实例:

Index first = 0;
Index last = ^1;
char firstElement = vowels [first];   // 'a'
char lastElement = vowels [last];     // 'u'

数组范围匹配(Ranges)

范围匹配类似于切片(slice),使用..运算符表示

适合于处理数组中部分元素的场景

注意:范围匹配从C#8开始支持

语法:

vowels [起始位置..结束位置]

注意:起始位置 和 结束位置 都是可选的

注意:是包含起始位置的元素,不包含结束位置的元素

实例:

char[] vowels = new char[] {'a','e','i','o','u'};
char[] firstTwo =  vowels [..2];    // 'a', 'e'
char[] lastThree = vowels [2..];    // 'i', 'o', 'u'
char[] middleOne = vowels [2..3]    // 'i'
char[] lastTwo = vowels [^2..^0];    // 'o', 'u'

实例:

char[] panda = { 'p', 'a', 'n', 'd', 'a' };
Console.WriteLine(panda[..]);  //panda
Console.WriteLine(panda[1..]); //anda
Console.WriteLine(panda[2..]); //nda
Console.WriteLine(panda[2..3]); //n
Console.WriteLine(panda[^1..]); //a
Console.WriteLine(panda[^2..]); //da

为了支持范围匹配,C#8还添加了Range类型

Range firstTwoRange = 0..2;
char[] firstTwo = vowels [firstTwoRange];   // 'a', 'e'

使用数组

一维数组和矩阵数组

int[,] arr = new int[10,10];
Console.WriteLine(arr[0, 0]);

交错数组

int[][] arr = new int[10][];
arr[0] = new int[10];
Console.WriteLine(arr[0][0]);

数组的协变

说明

​ 某个数据的类型不是数组的基类型,我们可以将这个数据赋值给数组的元素,这种情况叫做数组的协变(array covariance)

发生条件

​ 数组的元素是引用类型(即数组是引用类型数组)

​ 数组元素类型和赋值的类型间存在隐式或者显式转换

注意:

值类型数组没有协变情况

实例

using System;

namespace Test
{
    class A { }
    class B : A { }
    class Program
    { 
        static void Main()
        {
            A[] arr1 = new A[3];   //声明并实例化A类型的数组
            A[] arr2 = new A[3];   //声明并实例化A类型的数组

            //给数组arr1赋值
            arr1[0] = new A();
            arr1[1] = new A();
            arr1[2] = new A();

            //给数组arr2赋值(协变)
            arr2[0] = new B();  //注意:这里赋值的B类型实例
            arr2[1] = new B();
            arr2[2] = new B();
        }
    }
}

示意图:
image

数组总结图

image

值类型数组和引用类型数组性能

值类型数组将在数组实例化时,立即分配其元素的内存

引用类型数组将在数组实例化时,仅分配其元素的空引用

注意:大长度的值类型数组可能导致性能问题

边界检查

如果索引超出设定的索引长度,将引发IndexOutOfRangeException异常

在访问数组索引前,一定要判断索引是否在数组的长度以内

在C# 8版本及以上,可以考虑使用^1索引

数组长度

Length returns the total number of elements in an array.

Therefore, if you had a multidimensional array such as bool cells[,,] of size 2 × 3 × 3

Length would return the total number of elements, 18

For a jagged array, Length returns the number of elements in the first array

数组实例

使用GetLength()遍历数组实例

//定义一个二维数组
int[][] pandaArray = new int[2][];

//初始化二维数组
pandaArray[0] = new int[2] { 1, 2 };
pandaArray[1] = new int[3] { 4, 5, 6 };

//使用GetLength配合for遍历数组
for (int i = 0; i < pandaArray.GetLength(0); i++)
{
    for (int i2 = 0; i2 < pandaArray[i].GetLength(0); i2++)
    {
        Console.WriteLine(pandaArray[i][i2]);
    }
}

使用foreach遍历数组实例

int[][] pandaArray = new int[2][];
pandaArray[0] = new int[2] { 1, 2 };
pandaArray[1] = new int[3] { 4, 5, 6 };

foreach (var item in pandaArray)
{
    foreach (var item2 in item)
    {
        Console.WriteLine(item2);
    }
}

超级混合数组实例

using System;

namespace PandaNamespace
{
    class PandaClass
    {
        static void Main(string[] args)
        {
            //定义一级数组
            int[][][] panda1 = new int[3][][];
            
            for (int i = 0; i < panda1.Length; i++)
            {
                //定义二级数组
                panda1[i] = new int[3][];
                for (int i2 = 0; i2 < 3; i2++)
                {
                    //定义三级数组
                    panda1[i][i2] = new int[3] { 1+i,2 + i, 3 + i };
                }
            }

            for (int i = 0; i < panda1.Length; i++)
            {    
                for (int i2 = 0; i2 < 3; i2++)
                {
                    for (int i3 = 0; i3 < panda1[i][i2].Length; i3++)
                    {
                        Console.WriteLine(panda1[i][i2][i3]);
                    }
                }
            }
        }
    }
}

动态创建数组

使用Array.CreateInstance静态方法

//动态创建一位数组
int[] arr1 = (int[])Array.CreateInstance(typeof(int), 666);
//动态创建二维数组(矩阵数组)
int[,] arr2 = (int[,])Array.CreateInstance(typeof(int), 10, 10);
//动态创建二位数组(矩阵数组)
int[] firstDem = new int[] { 3,2};
int[,] arr3 = (int[,])Array.CreateInstance(typeof(int), firstDem);
//动态创建二维数组(稀疏数值)
int[][] arr4 = (int[][])Array.CreateInstance(typeof(int[]), 10);
arr4[0] = new int[] { 1, 2, 3, 4 };
arr4[1] = new int[] { 2, 3, 4, 5 };
arr4[2] = new int[] { 4, 5, 6, 7 };

获得和设置动态数组的元素

Array arr = Array.CreateInstance(typeof(int), 10);
//设置数组的元素的值
arr.SetValue(10, 0);
//获得数组元素的值
object v = arr.GetValue(0);

数组排序

int[] arr = new int[]{
    3,2,4,6,1,2
};
//排序
Array.Sort(arr);

数组反序

int[] arr = new int[]{
    3,2,4,6,1,2
};
//排序
Array.Sort(arr);
//反序
Array.Reverse(arr);

数组二分查找

int[] arr = new int[]{
    3,2,4,6,1,2
};
//排序
Array.Sort(arr);
//二分查找
int position = Array.BinarySearch(arr, 2);
Console.WriteLine(position);

数组复制

int[] arr1 = new int[] { 1, 2, 3, 4 };
//动态创建数组
int[] arr2 = (int[])Array.CreateInstance(typeof(int), arr1.Length);
//复制数组
Array.Copy(arr1, arr2, arr1.Length);
posted @ 2022-09-11 08:29  重庆熊猫  阅读(877)  评论(0编辑  收藏  举报