神创天陆活动专区

C语言中的数组

文章目录

数组1. 一维数组数组索引C语言为什么不做数组下标越界检查

1.1 数组初始化1.2 对数组使用sizeof运算符练习

2. 多维数组2.1 多维数组初始化2.2 常量数组练习

数组

到目前为止,我们见过的变量都只能存储单个数据项,这样的变量称为标量 (scalar)。

C 语言也支持聚合变量,这类变量可以存储多个数据项。C 语言提供两种类型的聚合变量:数组和结构体。本章主要介绍数组。

1. 一维数组

数组是含有多个数据项的数据结构,并且这些数据项都具有相同的数据类型。这些数据项称为数组的元素,我们可以根据元素在数组中的位置来选取元素。

最简单的数组就是一维数组。数组元素在内存中是依次排列的,如下图所示:

声明一个数组,我们需要指定数组元素的类型和数量。如:

int a[10];

数组元素可以是任何类型,数组的长度则必须是常量表达式 (能够在编译期间求值的表达式)。

因为程序以后可能需要调整数组的长度,所以一般情况下,我们会使用宏来定义数组的长度:

#define N 10

...

int a[N];

数组索引

为什么数组索引一般从零开始? 答:当对数组某个元素取下标时,该元素地址i_addr = base_addr + sizeof(elem_type) * i ;如果数组下标从1开始,元素地址就变成了i_addr = base_addr + sizeof(elem_type) * (i - 1)。索引从1开始的方式取地址时会多做一次减法操作,而数组索引在C语言使用非常频繁,所以如果数组索引从1开始。会增加不小的性能消耗。

我们可以用数组索引来访问数组中的元素。在 C 语言中,数组索引是从 0 开始的,所以长度为 n 的数组索引范围为 0 ~ n-1。假如 a 是含有 10 个元素的数组,那么这些元素可以依次标记为 a[0], a[1], …, a[9]。

数组和 for 循环是好伙伴,它们往往结伴而行。下面给出了在长度为 N 的数组上的一些常见操作:

/* clears a */

for (i = 0; i < N; i++)

a[i] = 0;

/* reads data into a */

for (i = 0; i < N; i++)

scanf("%d", &a[i]);

/* sums the element of a */

for (i = 0; i < N; i++)

sum += a[i];

C 语言有一个常被人诟病的问题:不检查数组索引越界 (why?)。当数组索引越界时,程序的行为是未定义的,也就是说任何情况都可能发生。

C语言为什么不做数组下标越界检查

为了提高运行效率,不检查数组下表越界,程序就可以跑得快。因为C语言并不是一个快速开发语言,它要求开发人员保证所有逻辑的正确性。所以至少到目前为止,C语言是所有高级语言中速度最快,效率最高的。几乎所有对性能有苛刻要求的场合都使用C语言。不检查下标是为了给程序员更大的空间,也为指针操作带来更多的方便。如果有这个检查的话指针的功能将会大大被削弱,C的数组标识符,里面并没有包含该数组长度的信息,只包含地址信息,所以语言本身无法检查,只能通过编译器检查,而早期的C语言编译器也不对数组越界进行检查,只能由程序员自己检查确保。

1.1 数组初始化

数组可以像其它变量一样进行初始化,不过数组的初始化需要一些技巧。

数组初始化式最常见的形式是,用大括号包含一组常量表达式,常量表达式之间用逗号分隔。

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

如果初始化式比数组短,那么剩余元素被初始化为 0:

int a[10] = {1, 2, 3, 4, 5, 6};

/* initial value of a is {1, 2, 3, 4, 5, 6, 0, 0, 0, 0} */

利用这个特性,我们很容易把数组元素全部初始化为 0:

int a[10] = {0};

注意:初始化式不能比数组长,也不能完全为空。

如果给定了初始化式,我们也可以省略数组的长度:

int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

1.2 对数组使用sizeof运算符

我们可以使用 sizeof 运算符确定数组的大小 (字节)。如果数组 a 包含 10 个整数,那么 sizeof(a) 的值通常为 40。

sizeof 运算符也可以确定数组元素的大小,两者相除即得到数组的长度:

sizeof(a) / sizeof(a[0])

有些程序员喜欢用这个表达式来表示数组的长度。例如,清空数组我们可以写成如下形式:

for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)

a[i] = 0;

这样做有个好处:即使日后数组的长度发生改变,这个 for 循环是不需要发生变化的。

为了可读性和通用性,我们往往把 sizeof(a) / sizeof(a[0]) 定义为带参数的宏:

#define SIZE(a) (sizeof(a) / sizeof(a[0]))

...

for (i = 0; i < SIZE(a); i++)

a[i] = 0;

练习

用户输入初始金额,利率和投资年数,程序将打印一张表格。表格将显示输入的利率 以及紧随其后 4 个更高利率下的总金额。程序的会话如下:

2. 多维数组

数组可以有任意维数。其中最常用的是一维数组,其次是二维数组,一般很少见到更高维的数组。如下,我们声明了一个二维数组 (类似数学上的矩阵):

int matrix[5][9]; // 数组 matrix 有 5 行 9 列,且行和列的索引都是从 0 开始的。

访问 i 行 j 列的元素,需要写成 matrix[i][j]。matrix[i] 指定了二维数组 matrix 的第 i 行,它是一个一维数组;matrix[i][j] 指定了这一行的第 j 个元素。

虽然我们经常把二维数组看成矩阵,但实际上它们在内存中是连续存储的。C 语言是按照行主序的方式存储二维数组的,也就是先存储第 0 行,再存储第 1 行,依次类推。如下图所示:

二维数组的本质:元素是一维数组的一维数组。

就像一维数组和 for 循环是好伙伴一样,二维数组和嵌套的 for 循环是好伙伴。例如,我们可以通过下面的方式创建一个维度为 10 的单位矩阵:

#define N 10

double identity[N][N];

int row, col;

for (row = 0; row < N; row++)

for (col = 0; col < N; col++)

if (row == col)

identity[row][col] = 1.0;

else

identity[row][col] = 0.0;

2.1 多维数组初始化

通过嵌套一维数组初始化式,我们可以构建二维数组的初始化式:

int matrix[5][9] = {{1, 1, 1, 1, 1, 0, 1, 1, 1},

{0, 1, 0, 1, 0, 1, 0, 1, 0},

{0, 1, 0, 1, 1, 0, 0, 1, 0},

{1, 1, 0, 1, 0, 0, 0, 1, 0},

{1, 1, 0, 1, 0, 0, 1, 1, 1}};

更高维数组的初始化式可以采取类似的方法构建。

C 语言提供了多种方法来简化多维数组的初始化。

如果初始化式的长度不够,那么剩余元素被初始化为 0。如,下面的初始化式只填充了数组的前 3 行,后面 2 行将被初始化为 0。

int matrix[5][9] = {{1, 1, 1, 1, 1, 0, 1, 1, 1},

{0, 1, 0, 1, 0, 1, 0, 1, 0},

{0, 1, 0, 1, 1, 0, 0, 1, 0}};

如果内层初始化式不足以填满数组的一行,那么这一行剩余的元素会被初始化为 0。

int matrix[5][9] = {{1, 1, 1, 1, 1, 0, 1, 1, 1},

{0, 1, 0, 1, 0, 1, 0, 1},

{0, 1, 0, 1, 1, 0, 0, 1},

{1, 1, 0, 1, 0, 0, 0, 1},

{1, 1, 0, 1, 0, 0, 1, 1, 1}};

甚至,我们可以省略内存的大括号 (不推荐)。

int matrix[5][9] = {1, 1, 1, 1, 1, 0, 1, 1, 1,

0, 1, 0, 1, 0, 1, 0, 1, 0,

0, 1, 0, 1, 1, 0, 0, 1, 0,

1, 1, 0, 1, 0, 0, 0, 1, 0,

1, 1, 0, 1, 0, 0, 1, 1, 1};

​ 编译器一旦发现值足以填满一行,它就开始填充下一行。

​ 注意:在初始化式中省略内层大括号是非常危险的,因为不小心多写或者少写一个值都会影响后面元素的初始化。

2.2 常量数组

无论是一维数组还是二维数组,我们都可以在声明时加上 const 修饰符而变成 “常量”:

const char hex_chars[] =

{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',

'A', 'B', 'C', 'D', 'E', 'F'};

程序在运行期间不会对数组进行修改。 const 不仅仅可以修饰数组,它可以修饰任意变量。但是 const 在数组声明中特别有用,因为数组经常包含一些不会发生改变的信息。

常量数组的优势:

安全(存储静态数组)效率更高,因为编译器可以对常量数组做一些优化

练习

写一个随机发牌的程序。用户指定发几张牌,程序打印手牌。