《C语言程序设计现代方法》note-5 数组
文章目录
- 助记提要
- 8章 数组
- 8.1 一维数组
- 数组下标
- 数组初始化
- sizeof
- 8.2 多维数组
- 初始化多维数组
- 常量数组
- 8.3 变长数组
助记提要
- 数组初始化格式;
- 指示器;
- 常量数组;
- 变长数组;
8章 数组
变量包括标量和聚合变量。标量可以保存单一数据项,聚合变量可以存储成组的数值。
C语言的聚合变量有数组和结构。
8.1 一维数组
数组是含有多个数据值的数据结构,且每个数据值的数据类型都相同。数据值也称为元素。
可以按照元素在数组中的位置把它们选出来。
一维数组中的元素依次排在一行中。
声明具有n个指定类型元素的数组arr:
类型 arr[n];
n可以是任何的(整数)常量表达式。
数组下标
为了存取指定的元素,可以在数组名后边加上一个方括号围绕的整数值。这个操作称为取下标或索引。
数组元素的下标始终从0开始,最大是n-1
。n为数组长度。
注意 C语言不检查下标的范围,下标超出范围时,程序会执行预期外的行为。
下面的代码在i=10
时,会把0存到a[9]
后面。如果刚好后面的内存中是程序会用到的变量,这个变量值就会变为0。
int a[10], i;
for (i = 0; i <= 10; i++)
a[i] = 0;
数组下标可以是任何整数表达式。但是为了便于理解,尽量不要在下标中使用有副作用的表达式。
数组和for
循环搭配的惯用法:
// 清空
for (i = 0; i < n; i++)
a[i] = 0;
// 读取数据存到数组
for (i = 0; i < n; i++)
scanf("%d", &a[i]);
// 求和
for (i = 0; i < n; i++)
sum += a[i]
数组初始化
数组可以在声明时给予初始值。
- 初始化常用的格式是使用花括号包起来的常量表达式列表,逗号分隔;
- 初始化列表比数组短时,数组的剩余元素赋值为0;
- 初始化列表不能为空,也不能比数组大小长;
- 给定初始化列表时,可以不写数组长度,编译器会利用列表长度确定数组大小。
// 初始化
int a[5] = {1, 2, 3, 4, 5};
// 剩余元素为0
int a[5] = {1, 2};
// 初始化全为0的数组
int a[5] = {0};
// 省略数组大小
int a[] = {1, 2, 3, 4, 5};
有时初始化时只需要对数组中的一部分元素赋值,其他元素默认为0即可。
// 直接赋值,数组越长越麻烦
int a[10] = {0, 0, 0, 7, 0, 0, 0, 0, 0, 4};
// 初始化0数组再分别赋值,赋值项多会很麻烦
int a[10] = {0};
a[3] = 7;
a[9] = 4;
对于这个问题,C99提供了由方括号和常量表达式组成的指示器。
int a[10] = {[3] = 7, [9] = 4};
指示器赋值的顺序不影响结果。
指示器的方括号内必须是整数表达式,且值不能超过数组下标上限。如果数组长度省略,指示器可以指定任何非负整数,编译器会按照其中的最大值推断数组的长度。
可以在逐个元素初始化的同时使用指示器:
int c[10] = {2, 1, [5] = 7, 6, 3, [9]=11};
指示器使用下标对元素多次初始化是合法操作,但是注意不要这么做。
// 重复初始化元素
int a[] = {4, 9, 1, 8, [0] = 5, 7};
// 上述声明的等价声明
int a[] = {5, 7, 1, 8};
编译器初始化数组时,会记录下一个待初始化的元素的位置。
指示器会强制指定一个元素做为编译器处理的下一元素,编译器从这个元素往后依次处理。
sizeof
sizeof可以确定数组的字节数。
利用数组大小除以数组元素的大小,可获取数组长度。这种方式写的表达式在数组长度需要改变时也不用重写:
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
a[i] = 0;
有的编译器会对表达式i < sizeof(a) / sizeof(a[0])
给出警告,因为sizeof但会的类型是size_t
,不是int
,把有符号整数和无符号整数相比较是危险的,虽然这里没有问题(都是正数)。
为了避免这个警告,可以把表达式的类型强制转为有符号整数或定义宏来表示它。
// 转为有符号整数
for (i = 0; i < (int) (sizeof(a) / sizeof(a[0])); i++)
a[i] = 0;
#define SIZE ((int) (sizeof(a) / sizeof(a[0])); i++)
for (i = 0; i < SIZE; i++)
a[i] = 0;
8.2 多维数组
数组可以有任意维度。每个维度的下标都以0开始。
// 创建二维数组
int m[5][9];
// 访问二维数组的元素
m[i][j];
注意 m[i][j]
不可以写成m[i, j]
。中括号内的逗号会被当做逗号运算符,m[i, j]
等同于m[j]
。
多维数组在内存中仍然是按照行存储的。写程序时可以忽略这一细节,但是有时也会影响。
初始化多维数组
一维初始化格式嵌套可以初始化高维数组。
int m[3][4] = {{1, 1, 1, 1},
{2, 2, 2, 2},
{3, 3, 3, 3}};
- 如果嵌套的初始化列表没有填满多维数组,就把数组剩余的元素赋值为0;
- 可以省略内层的花括号,编译器填满一行后开始填下一行。但是最好不要这样做,避免多填或少填元素的行影响整个数组。
C99的指示器也可以用于多维数组:
// 使用指示器初始化2×2的单位矩阵
int a[2][2] = {[0][0] = 1, [1][1] = 1};
常量数组
在声明时前面加上const
,可以使数组称为常量数组。
const char hex_chars[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
程序不应该修改声明为const
的数组,编译器会在发现数组被修改时提示错误。
这样的数组常常包含一些在程序执行中不会改变的参考信息。
8.3 变长数组
C99中允许数组变量的长度为非常量的表达式。
变长数组的长度在程序执行时计算,而不是在编译时计算。
由程序员指定数组长度的话,长度可能过长或过短。
变长数组的好处是不需要在构造数组时给定长度,而是在执行时精确计算长度。
变长数组的限制是没有静态存储期和没有初始化器。
goto语句不能绕过变长数组的声明。这样会导致程序对未分配空间的数组中的元素进行访问。