四、数组
Covers:数组
一维数组
C# 也是使用下标运算符 []
来定义和访问数组的。C# 不区分堆中和栈中数组,也不存在空间释放问题,下面是语言对比:
C# | C++ | |
---|---|---|
下标起始 | 同 C++ | 0 |
内存策略 | 无区分,均用 new 关键字,不需要手动释放 | 堆区或栈区,语法不同,堆区需要手动释放 |
数组名的本质 | 引用 | 指针 |
仅声明(C++)、仅定义引用(C#) | TYPE[] ARR; | TYPE *PTR; 或 TYPE ARR[SIZE]; |
定义并用默认数据填充 | TYPE[] ARR = new TYPE[SIZE]; ,SIZE 必须指定,允许动态 | TYPE ARR[SIZE]; (SIZE 必须为静态)或 TYPE *PTR = new TYPE[SIZE]; (允许动态) |
默认数据 | 0 | 取决于编译器 |
定义并初始化 | TYPE[] ARR = {...}; = TYPE[] ARR = new TYPE[] {...}; = TYPE[] ARR = new TYPE[SIZE] {...}; ,SIZE 与初始化列表中的元素数必须相等 | TYPE ARR[] = {...}; ≠ TYPE ARR[SIZE] = {...}; ≠ TYPE *PTR = new TYPE[] {...}; ≠ TYPE *PTR = new TYPE[SIZE] {...}; ,SIZE 不小于初始化列表中的元素数 |
浅拷贝 | TYPE[] ARR2 = (TYPE[]) ARR1.Clone(); = ARR1.CopyTo(ARR2, BEGIN_INDEX); = Array.Copy(ARR1, ARR2, ARR1.Length); | 多种实现 |
补充说明:
- C# 中,如果用
new type[SIZE]
指定了数组大小,又进行了初始化,则 SIZE 无意义(不能用于分配额外的空间),因此 当需要定义并初始化数组时,一般省略 SIZE 不写,此外 TYPE 也可以不写而只写 new [],或连 new [] 都不写,这些写法都允许; - 数组对象的
Clone
方法返回Object
类型的引用,需要强转为type[]
类型,而用CopyTo
和Array.Copy
方法时都需要预先为目标数组分配空间(定义并用默认数据填充); TYPE[] ARR2 = ARR1
这样直接将数组名传递给另一个数组类型的变量,不会发生任何值的拷贝,仅制作了源数组的一个引用;网上有将其称之为浅拷贝的,这是对浅拷贝的一种误解,它根本没有发生任何拷贝,只是传递了一个引用,至于何为深拷贝,需要后续学过面向对象后再讨论。
我们写一段程序来测试一下几种数组的拷贝:
using System;
namespace ArrayCopy
{
class Program
{
public static void Main()
{
// Source array:
int[] numbers = {1, 2, 3};
// Reference made by keyword "ref":
ref int[] reference = ref numbers;
// Reference made implicitly, by passing array name:
int[] pass = numbers;
// Shallow copy by Clone():
int[] clone = (int[]) numbers.Clone();
// Shallow copy by Clone(), but called from a reference:
int[] cloneFromRef = (int[]) reference.Clone();
// Shallow copy by CopyTo():
int[] copiedTo = new int[numbers.Length];
numbers.CopyTo(copiedTo, 0);
// Shallow copy by Array.Copy():
int[] copy = new int[numbers.Length];
Array.Copy(numbers, copy, numbers.Length);
// Now the source array is modified:
numbers[0] = 0;
// See what happens:
Console.WriteLine(numbers[0]);
Console.WriteLine(reference[0]);
Console.WriteLine(pass[0]);
Console.WriteLine(clone[0]);
Console.WriteLine(cloneFromRef[0]);
Console.WriteLine(copiedTo[0]);
Console.WriteLine(copy[0]);
}
}
}
运行结果:
0
0
0
1
1
1
1
交错数组
交错数组是数组,它的元素也是数组(的引用),对应着 C++ 中的多维数组。
交错数组的定义语法:
- 仅定义引用:
TYPE[][] VAR;
; - 定义并用空引用填充:
TYPE[][] VAR = new TYPE[SIZE][];
,VAR 有 SIZE 个元素,每个元素填充为空引用 null; - 初始化语法:
TYPE[][] VAR = {new [] {...}, new [] {...}, ...};
,每个子数组的尺寸不需要一样,子数组的 new 关键字不能省略。
访问语法:
VAR[INDEX]
访问下标为 INDEX 的子数组;VAR[INDEX][SUB_INDEX]
访问下标为 INDEX 的子数组中,下标为 SUB_INDEX 的元素。
写一个例子来说明:
using System;
namespace JaggedArray
{
class Program
{
public static void Main()
{
int[][] arrays =
{
new [] {1, 2, 3, 4, 5},
new [] {4, 5, 6},
new [] {7, 8, 9, 10}
};
arrays[2][3] = 0;
foreach (var array in arrays)
{
foreach (var i in array)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
}
}
}
控制台输出:
1 2 3 4 5
4 5 6
7 8 9 0
多维数组
C# 中的多维数组与 C/C++ 完全不同,它类似于矩阵,用多维索引来访问。
定义语法:
- 仅定义引用:
TYPE [,] VAR;
,定义 VAR 是二维数组(秩为 2),TYPE [,,] VAR;
定义 VAR 是三维数组(秩为 3),以此类推,以下只介绍二维情况; - 定义并用默认值填充:
TYPE [,] VAR = new int[SIZE_0, SIZE_1];
,TYPE 和 SIZE 不能省略; - 定义并初始化:
TYPE [,] VAR = {{...}, {...}, ...};
。
访问语法只有一个:VAR[INDEX_0, INDEX_1]
,也就是某个元素的多维索引,在二维索引中,INDEX_0 是行号,INDEX_1 是列号。
多维数组常用的属性和方法:
VAR.Length
:只读属性,获得多维数组对象的总元素个数;VAR.Rank
:只读属性,获得数组的维数(秩);GetLength(DIM)
:返回第 DIM 维(多维索引 [A, B, C, …] 从左至右依次是第 0、1、2……维)的长度,例如 2x3(2 行 3 列)的二维数组,第 0 维的长度是 2,第 1 维的长度是 3。
using System;
namespace MultidimArray
{
class Program
{
public static void Main()
{
int[,] mat =
{
{1, 2, 3},
{4, 5, 6}
};
mat[0, 1] = 0;
for (int i = 0; i < mat.GetLength(0); ++i)
{
for (int j = 0; j < mat.GetLength(1); ++j)
{
Console.Write(mat[i, j] + " ");
}
Console.WriteLine();
}
}
}
}
控制台输出:
1 0 3
4 5 6
T.B.C.