数组(array)



1. 数组

  数组实际上是由一个变量名称表示的一组同类型的数据元素,每一个元素都可以通过变量名称和一个或多个方括号中的索引名称来访问,例如:MyArray[1]

2. 定义

  ■ 元素:数组中独立的数据项被称为元素。数组的所有元素必须是相同类型的,或继承自相同的类型。   ■ 秩/维度:数组可以有任何为正数的维度数(也就是几维数组的意思,例如二维数组的秩等于2),数组的维度数称为秩(rank)。   ■ 维度长度:数组的每一个维度有一个长度,就是这个方向的位置个数。   ■ 数组长度:数组的所有维度中的元素的总和称为数组的长度。

c#数组的要点:   ■ 数组一旦被创建大小就固定了。c#不支持动态数组。   ■ 数组索引号是从0开始的。

3. 数组的类型

  c#提供了两种类型的数组:   ■ 一维数组可以认为是单行元素或元素向量。   ■ 多维数组是由主向量中的位置组成的,每一个位置本身又是一个数组,称为*子数组(sub-array)*。子数组向量中的位置本身又是一个子数组。     □ 矩形数组(rectangular array):       ● 某个维度所有的子数组有相同长度的多维数组。       ● 不管有多少维度,总是使用一组方括号。myArray[2,4,5]     □ 交错数组(jagged array):       ● 每一个子数组都是独立数组的多维度数组。       ● 可以有不同长度的子数组。       ● 为数组的每一个维度使用一对方括号。jagArray[2][4][5]

4. 数组是对象

  数组实例是从System.Array继承的对象。数组从BCL基类继承了很多有用的方法:   ■ Rank:返回数组维度数的属性。   ■ Length:返回数组的长度(数组中所有元素的个数)的属性。

  数组是引用类型,数组对象本身总是存在堆上,引用存在栈或堆上。      数组是引用类型,但是数组的元素可以是引用类型,也可以是值类型。   ■ 如果存储的元素都是值类型,数组被称为值类型数组。   ■ 如果存储在数组中的元素都是引用类型对象,数组被称为引用类型数组

5. 一维数组和矩形数组

5.1. 声明一维数组或矩形数组

  声明一维数组或矩形数组,可以在类型后使用一对方括号:int[] myIntArray;   ★ 方括号内的逗号是秩说明符,它指定了数组的维度数,没有逗号代表一维数组,有一个逗号表示二维数组……

  矩形数组:   ■ 可以使用任意多的秩说明符。   ■ 不能在数组类型区域中放数组维度长度,秩是数组类型的一部分,而维度长度不是类型的一部分。   ■ 数组声明后,维度数就固定了,但是维度长度直到数组实例化时才会被确定。

1
2
3
4
5
int[,,] arr;    //数组类型:三维整型数组
int[,] arr1;    //数组类型:二维整型数组
long[,,] arr2;  //数组类型:三维long数组

long[2,3,4] secondArray;   //编译错误,不允许指定维度长度。

5.2. 实例化一维数组或矩形数组

  使用数组创建表达式实例化数组,数组创建表达式由new运算符构成,后面是基类名称和一对方括号。方括号中以逗号分隔每一个维度的长度

1
2
3
4
int[] arr2 = new int[4];    //四个元素的整型数组
MyClass[] mcArr = new MyClass[4];   //四个元素的MyClass(自定义类型)数组

int[,,] arr3 = new int[3,6,2];   //三维数组,数组长度是:3*6*2=36

5.3. 访问数组元素

  在数组中使用整型值作为索引来访问数组元素。   ■ 每一个维度的索引从0开始。   ■ 方括号内的索引在数组名称之后。

1
2
3
4
5
6
7
int[] intArr1 = new int[15];   //声明数组长度为15的一维数组
intArr1[2] = 10;               //向数组的第3个元素赋值
int var1 = intArr1[2];         //从数组第3个元素读取值

int[,] intArr2 = new int[5,10];//声明二维数组
intArr2[2,3] = 7;              //向数组赋值
int var2 = intArr2[2,3];       //从数组读取值

5.4. 初始化数组

  当数组被创建之后,数组中的每一个元素都会被自动的初始化为类型的默认值:整型默认值是0,浮点型的默认值是0.0,布尔型的默认值为false,引用类型的默认值是null。

1
int[] intArr = new int[4];

5.5. 显式初始化一维数组

  对一维数组,我们可以在数组实例化的时候显式的设置初始值。   ■ 初始值必须以逗号分隔,并封闭在一组花括号内。   ■ 维度长度是可选的,因为编译器可以通过初始化值的个数来推断长度。

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

5.6. 显式初始化矩形数组

  ■ 矩形数组中每一个初始值向量必须封闭在花括号内。   ■ 除了初始值,每一个维度的初始化列表和组成部分必须使用逗号分隔。

1
int[,] intArr2 = new int[,] {{10,1},{2,10},{11,9}};

5.7. 快捷语法

1
2
3
4
5
int[] arr1 = new int[]{10,20,30};
int[] arr1 = {10,20,30};

int[,] arr2 = new int[2,3]{{0,2,3},{10,11,12}};
int[,] arr2 = {{0,2,3},{10,11,12}};

6. 交错数组

  交错数组是数组的数组。交错数组的子数组可以有不同数目的元素。

1
2
3
4
//声明并创建顶层数组
int[][] jagArr = new int[3][];  //声明三个int数组的数组
//声明并创建子数组
...

6.1. 声明交错数组

  交错数组的声明语法要求每一个维度都有一对独立的方括号。交错数组变量声明中的方括号数决定了数组的秩。   ■ 交错数组可以有任何大于1的维度。

1
2
int[][] SomeArr;     //秩等于2
int[][][] OtherArr;  //秩等于3

  不能再声明语句中初始化最高级别数组之外的数组

1
2
int[][] jagArr = new int[3][];   //三个子数组的顶层数组
int[][] jagArr = new int[3][4];  //编译错误,不允许初始化顶层以外的数组

6.2. 实例化交错数组

  由于交错数组是独立数组的数组,所以每一个数组都必须独立创建:   ① 首先实例化顶层数组。   ② 其次分别实例化每一个子数组,把新建子数组的引用赋值给包含它们的数组的合适元素。

1
2
3
4
5
int[][] Arr = new int[3][];              //1、实例化顶层数组

Arr[0] = new int[] {10,20,30};           //2、实例化子数组
Arr[1] = new int[] {40,50,60,70};        //3、实例化子数组
Arr[2] = new int[] {80,90,100,110,120};  //4、实例化子数组

6.3. 交错数组中的子数组

  交错数组中的子数组本身就是数组,因此交错数组中也可能有矩形数组。

1
2
3
4
5
int[][,] Arr = new int[3][,];   //实例化带有三个二维数组的交错数组

Arr[0] = new int[,] {{10,20},{100,200}};
Arr[1] = new int[,] {{30,40,50},{300,400,500}};
Arr[2] = new int[,] {{60,70,80,90},{600,700,800,900}};

7. foreach语句在数组的使用

  ■ 迭代变量是临时的,只读的,并且和数组中元素的类型相同。foreach使用迭代变量来连续表示数组中的每一个元素。   ■ foreach语句的工作方式:     □ 从数组的第一个元素开始遍历并把它赋值给迭代变量。     □ 然后执行语句主体(以大括号包裹的内容),在主体中,把当前遍历到的一个数组元素使用迭代变量表示,该元素是只读的。     □ 在主体执行之后,foreach语句选择下一个数组元素,并重复这些过程。

8. 迭代变量是只读的

  因为迭代变量是只读的,所以在foreach中不能实现修改数组元素值的功能。这个只读属性对于值类型数组和引用类型数组来说效果是不一样的。   ★ 对于值类型数组和引用类型数组来说,迭代变量都是不能被改变的。但是例外的是,引用类型存储的是数据的引用,但是数据本身并没有和迭代变量有联系,所以我们可以在foreach中修改数据本身。

值类型数组

1
2
3
4
5
int[] arr1 = {10,11,12,13};
foreach(var item in arr1)
{
    item++;    //错误!!编译时会抛出错误
}

引用类型数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyClass
{
    public int MyField = 0;
}

class Program
{
    static void Main(string[] args)
    {
        MyClass[] mc = new MyClass[4];  //创建引用类型数组
        for(int i =0;i<4;i++)   //初始化数组
        {
             mc[i] = new MyClass();  //创建类对象
             mc[i].MyField = i;      
        }
        foreach(var item in mc)
        {
            item.MyFiled+=10;     //遍历修改数据本身,引用没有发生变化
        }
        foreach(var item in mc)
        {
            Console.WriteLine(item);
        }
        Console.ReadKey();
    }
}

9. 数组协变

  在下面情况下,即使某一个对象和数组的类型不相同,我们也可以把它赋值给数组元素:   ■ 数组是引用类型数组。   ■ 在赋值的对象或者类型之间有隐式转换或强制转换。

  ★ 由于派生类和基类之间默认是含有隐式的转换的,所以总是可以将一个派生类的对象赋值给基类数组元素。    👉 值类型数组没有协变。

10. Array继承的有用成员

  c#数组从System.Array类继承,有很多有用的属性和方法: