第 16 节 · 第二部分 流程、方法与数据

数组(Array)

数组是一排相同类型、个数固定、挨着放的格子,用一个名字加索引(从 0 开始)访问;数组变量里放的是引用。

时长 6:46授课计划第 6 周

学完这一节,你能

讲义

跟着视频的顺序写。每行代码都有注释,代码和运行结果都是实际运行得到的。

1数组是什么

数组是一组相同类型的元素,个数固定,按顺序排好。不用声明 number0 到 number99 一百个变量,只要一个名字 numbers,再用编号区分每一个,这个编号叫索引,从 0 开始。

数组的元素在内存里挨着放、中间没有空隙:地址最低的是第一个元素,最高的是最后一个。视频里把它比成一条长托盘,一格放一个元素,格子一样大(类型相同),两头有挡板(个数固定)。

2声明数组

写法是:元素类型,加一对方括号 [],再加数组名。

C# · 第 8 行
8double[] balance;// 声明一个装 double 的数组,名字叫 balance

方括号说明这是个数组,它决定的是数组的秩,也就是维数:一对空方括号就是一维,大小要等创建时才定。

3用 new 创建数组

很多人以为声明完就能往里放东西,真跑一下会编译报错:

C# · 第 8-9 行
8double[] balance;// 只声明了变量,数组本身还不存在
9balance[0] = 4500.0;// 往 0 号元素里放值
编译报错
Program.cs(9,10): error CS0165: 使用了未赋值的局部变量“balance”

数组是引用类型,要用 new 创建实例(造出来的具体对象):

C# · 第 8 行
8double[] balance = new double[10];// 在内存里造出 10 格的数组,balance 指向它

变量 balance 里放的是指向这个数组的引用(记的是地址)。

4给元素赋值

用索引给单个元素赋值:0 号格的旧值换成 4500,其余九格不受影响。

C# · 第 9 行
9balance[0] = 4500.0;// 把 4500.0 放进 0 号元素

5声明的同时赋值

可以在声明时直接给出全部元素,有三种写法:

C# · 第 8-10 行
8double[] balance = { 2340.0, 4523.69, 3421.0};// 第一种:花括号里列 3 个值,数组就有 3 个元素
9int [] marks = new int[5] { 99, 98, 92, 97, 95};// 第二种:new int[5],后面正好 5 个值
10int [] marks2 = new int[] { 99, 98, 92, 97, 95};// 第三种:方括号里不写大小,按值的个数定为 5

打印三个数组的长度(.Length)得到 3、5、5,balance[1] 是 4523.69。

第二种写法个数必须对上,只写 3 个值就编译报错:

C# · 第 8 行
8int [] marks = new int[5] { 99, 98, 92};// 说好 5 个,只给了 3 个
编译报错
Program.cs(8,37): error CS0847: 应为一个长度为“5”的数组初始值设定项

6把数组赋给另一个数组变量

score = marks 之后,两个变量指向同一个数组,没有复制。通过 score 改 0 号元素,marks 看到的也变了。

C# · 第 8-11 行
8int [] marks = new int[] { 99, 98, 92, 97, 95};// marks 指向一个 5 个元素的数组,0 号是 99
9int[] score = marks;// score 拿到同一个引用,数组还是那一个
10score[0] = 60;// 通过 score 把 0 号改成 60
11Console.WriteLine(marks[0]);// 通过 marks 读 0 号:输出 60

7默认值

创建数组时,每个元素会自动设成类型的默认值,数字就是 0,所以 int 数组一开始也全是 0。默认值是程序运行时、创建数组的那一刻设好的。

C# · 第 11 行
11int[] marks = new int[5];// 5 个元素,还没赋值,全是 0

8访问数组元素

读取元素也是数组名加方括号写索引。

C# · 第 10-15 行
10double salary = balance[9];// 取出最后一格(9 号),还没赋过值
11int[] marks = new int[5];// 新建 int 数组,元素默认是 0
12Console.WriteLine(balance[0]);// 0 号刚赋了 4500:输出 4500
13Console.WriteLine(salary);// 9 号是默认值:输出 0
14Console.WriteLine(marks[0]);// int 数组的 0 号:输出 0
15Console.WriteLine(balance.Length);// Length 是元素个数:输出 10
查看完整代码(18 行)
C#
1using System;// 引入 System 命名空间
2namespace ArrayApplication// 定义命名空间 ArrayApplication
3{// ArrayApplication 命名空间开始
4 class MyArray// 定义 MyArray 类
5 {// MyArray 类开始
6 static void Main(string[] args)// 程序入口 Main 方法
7 {// Main 开始
8 double[] balance = new double[10];// 创建 10 个元素的 double 数组,全是 0
9 balance[0] = 4500.0;// 0 号元素设为 4500
10 double salary = balance[9];// 读 9 号元素:还是 0
11 int[] marks = new int[5];// 创建 5 个元素的 int 数组,全是 0
12 Console.WriteLine(balance[0]);// 输出 4500
13 Console.WriteLine(salary);// 输出 0
14 Console.WriteLine(marks[0]);// 输出 0
15 Console.WriteLine(balance.Length);// 输出长度 10
16 }// Main 结束
17 }// MyArray 类结束
18}// ArrayApplication 命名空间结束

长度是 10,索引只能从 0 到 9。写 n[10] 呢?

C# · 第 8-10 行
8int [] n = new int[10];// 10 个元素,索引 0~9
9Console.WriteLine(n[9]);// 9 号是最后一个,输出 0
10Console.WriteLine(n[10]);// 没有 10 号,运行到这里出错
运行结果
0
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at ArrayApplication.MyArray.Main(String[] args) in 数组/out-of-range/Program.cs:line 10

编译能过,一运行就抛出异常:索引超出了数组的范围。

9完整例子:用 for 循环赋值和输出

先声明能放 10 个整数的数组 n,第一个 for 循环把每格设为 i + 100,第二个按索引逐个打印。

C# · 第 12-21 行
12for ( i = 0; i < 10; i++ )// i 从 0 走到 9
13{// 赋值 for 循环开始
14 n[ i ] = i + 100;// 第 i 格设为 i + 100:0 号是 100……9 号是 109
15}// 赋值 for 循环结束
16
17/* 输出每个数组元素的值 */// 教程自带的块注释:下面开始输出
18for (j = 0; j < 10; j++ )// j 从 0 走到 9
19{// 输出 for 循环开始
20 Console.WriteLine("Element[{0}] = {1}", j, n[j]);// {0} 换成索引 j,{1} 换成元素 n[j]
21}// 输出 for 循环结束
查看完整代码(25 行)
C#
1using System;// 引入 System 命名空间
2namespace ArrayApplication// 定义命名空间 ArrayApplication
3{// ArrayApplication 命名空间开始
4 class MyArray// 定义 MyArray 类
5 {// MyArray 类开始
6 static void Main(string[] args)// 程序入口 Main 方法
7 {// Main 开始
8 int [] n = new int[10]; /* n 是一个带有 10 个整数的数组 */// 创建 10 个元素的 int 数组
9 int i,j;// 声明两个循环变量 i、j
10
11 /* 初始化数组 n 中的元素 */// 教程自带的块注释:下面开始赋值
12 for ( i = 0; i < 10; i++ )// i 从 0 走到 9
13 {// 赋值 for 循环开始
14 n[ i ] = i + 100;// 第 i 格设为 i + 100
15 }// 赋值 for 循环结束
16
17 /* 输出每个数组元素的值 */// 教程自带的块注释:下面开始输出
18 for (j = 0; j < 10; j++ )// j 从 0 走到 9
19 {// 输出 for 循环开始
20 Console.WriteLine("Element[{0}] = {1}", j, n[j]);// 打印索引和元素值
21 }// 输出 for 循环结束
22 Console.ReadKey();// 等待按键(现在的 VS 不写也会停住)
23 }// Main 结束
24 }// MyArray 类结束
25}// ArrayApplication 命名空间结束
查看完整输出(10 行)
运行结果
Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109

10用 foreach 遍历数组

foreach 每转一圈,j 就依次拿到数组里的一个元素。注意 j 是元素的值,不是索引,所以教程用 j - 100 把编号算回来,输出和上面 for 循环完全一样。

C# · 第 18-22 行
18foreach (int j in n )// 依次取出 n 里的每个元素放进 j:100、101……109
19{// foreach 循环开始
20 int i = j-100;// j 是值不是索引,减 100 算回编号
21 Console.WriteLine("Element[{0}] = {1}", i, j);// 打印编号和元素值
22}// foreach 循环结束

foreach 里不能给 j 赋值,要按位置改元素,还是用 for 加索引:

C# · 第 9-12 行
9foreach (int j in n )// j 依次拿到每个元素
10{// foreach 循环开始
11 j = 5;// 想给 j 赋值
12}// foreach 循环结束
编译报错
Program.cs(11,13): error CS1656: 无法为“j”赋值,因为它是“foreach 迭代变量”

11数组的更多概念

教程最后列了五个和数组有关的概念,逐个点名。

多维数组

最简单的是二维,像一张表,按行和列取值。下面两行三列,第二行第三列是 6;它有两个维度,所以秩是 2。

C# · 第 8-10 行
8int[,] a = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };// [,] 表示二维:2 行 3 列
9Console.WriteLine(a[1, 2]);// 第 1 号行、第 2 号列(第二行第三列):6
10Console.WriteLine(a.Rank);// Rank 是秩(维数):2

交错数组

交错数组是数组的数组:每一行都是一个独立的数组,长短可以不一样。

C# · 第 8-13 行
8int[][] scores = new int[2][];// [][]:外层 2 行,每行先空着
9scores[0] = new int[] { 92, 93, 94 };// 第一行是 3 个元素的数组
10scores[1] = new int[] { 85, 66 };// 第二行是 2 个元素的数组
11Console.WriteLine(scores[0].Length);// 第一行长度 3
12Console.WriteLine(scores[1].Length);// 第二行长度 2
13Console.WriteLine(scores[1][0]);// 第二行的 0 号:85

传递数组给函数

调用时只写数组名,不带索引。C# 传的是数组的引用,所以方法里改 0 号元素,外面的 n 也跟着变。

C# · 第 6-14 行
6static void Change(int[] arr)// 参数 arr 是 int 数组
7{// Change 开始
8 arr[0] = 99;// 把 0 号元素改成 99
9}// Change 结束
10static void Main(string[] args)// 程序入口 Main 方法
11{// Main 开始
12 int[] n = { 10, 20, 30 };// n 的 0 号是 10
13 Change(n);// 只写数组名:arr 和 n 指向同一个数组
14 Console.WriteLine(n[0]);// 外面再看 0 号:输出 99

参数数组

参数前加 params,调用时想传几个值就传几个,方法里拿到的是一个数组。

C# · 第 6 行
6static int Sum(params int[] nums)// params:传进来的几个数装进数组 nums,方法里用 foreach 加起来
C# · 第 17-18 行
17Console.WriteLine(Sum(1, 2, 3));// 传 3 个数:1 + 2 + 3,输出 6
18Console.WriteLine(Sum(10, 20));// 传 2 个数:10 + 20,输出 30

Array 类

Array 类在 System 命名空间里,是所有数组的基类(被继承的上一级类,后面会讲)。数组的 Length 属性(后面会讲),还有排序 Sort、反转 Reverse,都来自它。

C# · 第 8-13 行
8int[] list = { 34, 72, 13, 44, 25 };// 5 个没排序的数
9Array.Sort(list);// 从小到大排序
10Console.WriteLine(string.Join(" ", list));// string.Join 用空格连成一行:13 25 34 44 72
11Array.Reverse(list);// 倒过来
12Console.WriteLine(string.Join(" ", list));// 输出 72 44 34 25 13
13Console.WriteLine(list.Length);// 长度不变:输出 5

12小结

  • 数组是一排相同类型、个数固定、挨着放的格子。
  • 先声明,再用 new 创建;新元素是默认值,索引从 0 到长度减 1。
  • 数组变量放的是引用:赋给别的变量或传给方法,指向的还是同一个数组。

下一节:一串文字,比如名字、一句话,在 C# 里又是怎么存、怎么拼接的?

检查题

5 道,选完马上告诉你对不对。

  1. 1double[] balance; 之后直接写 balance[0] = 4500.0; 会怎样?

  2. 2int[] n = new int[10]; 能用的最大索引是?

  3. 3int[] score = marks; score[0] = 60; 之后 marks[0] 是?

  4. 4int[] marks = new int[5]; 还没赋值,marks[0] 是?

  5. 5foreach (int j in n) 里的 j 是?