数组

1. 一维数组

到目前为止,我们所见的变量都是只是标量,也就是具有保存单一数据项的能力。C语言也支持聚合变量,这类变量可以存储成组的数值。在C语言中一共有两种聚合类型:数组结构

  • 数组是含有多个数据值的数据结构,并且每个数据值具有相同的数据类型。这些数据值称为元素,可以更具元素在数组中所处的位置把他们一个一个地选出来。
  • 最简单的数组类型就是一维数组,一维数组中的元素一个接一个地编排在单独一行内。

假设有一个名为a的一维数组
先要声明数组,需要指明数组元素的类型和数量。例如,为了声明数组a有10个int类型的元素。
int a[10];
数组的元素可以是任何类型,数组的长度可以用任何(整数)常量表达式指定。因为程序以后改变时可能需要调整数组的长度,所以较好的方法是用宏来定义数组的长度。

# define N 10
……
int a[10];

1.1 数组下标

为了存取特定的数组元素,可以在写数组名的同时在后边加上一个用方括号围绕的整数值[这被称为为对数组取小标或进行索引]。数组元素始终从0开始,因此长度为n的数组元素的索引是0 ~ n-1。

  • 数组和for循环结合在一起使用,可以数组中的元素进行输出。
  • 在调用scanf函数读取数组元素时,就像对待普通变量一样,必须使用取地址符号&。
  • 数组下标可以是任何整数表达式:a[i+j*10] = 0;

1.2 数组初始化

像其他变量一样,数组也可以在声明时获得一个初始值。但是,数组初始化的规则不太好掌握,因此我们现在介绍一些。

  • 数组初始化器:最常见的格式是一个用花括号括起来的常量表达式列表,常量表达式之间用逗号进行分割:
int a[10] = {1,2,3,4,5,6,7,8,9,10};  

int a[10] = {1,2,3,4,5,6};
//如果初始化器比数组短,那么数组中剩余的元素赋值为0;
//initial value of a is {1,2,3,4,5,6,0,0,0,0}

int a[10] = {0};
//利用这一特性,很容把数组初始化全为0
  • 初始化完全为空是非法的,所以要在花括号内放一个0。
  • 初始化器要比初始化的数组长也是非法的。

如果给定了初始化器,可以省略数组长度。

int a[] = {1,2,3,4,5,6,7,8,9,10};
  • 编译器利用初始器的长度来确定数组的大小。数组仍然有固定数量的元素,这跟显式地指定长度效果一样。

1.3 指示器

  • 经常有这样的情况:数组里面只有相对少量的元素需要进行显式的初始化,而其他元素可以进行默认赋值,比如数组元素2为29,元素9为7,元素14为48,而其他元素为0。
int a[15] = {a[2] = 29,a[9] = 7;a[14] = 48};
//方括号和其中的变量表达式一起,组成一个指示器。
  • 除了可以使赋值变得更简短,更易读之外,指示器还有一个优点:赋值的顺序不再是一个问题,我们也可以将先前的例子重写为:
int a[15] = {[2] = 29, [9] = 7, [14] = 48 };
//其中方括号和其中的常量表达式在一起,组成一个指示器
  • 组成指示器的方括号里必须是整型常量表达式。如果待初始化的数组长度为n,则每个表达式的值都必须在0和n-1之间。但是,如果数组的长度是忽略的,指示器可以指定仍以非负整数;对于后一种情况,编译器将根据最大的值推断出数组的长度。
int b[] = {[5] = 10, [23] = 13, [14] = 48 };
//指示符的最大值为23,因此数组的长度为24

1.4 对数组使用sizeof运算符

运算符sizeof可以确定数组的大小(字节数)。如果数组a有10个整数,那么sizeof(a) 通常为40(假定每个整数占4字节)。
还可以用sizeof来计算数组元素(如a[0])的大小。用数组元素的大小除以元素的大小可以得到数组的长度:

  • sizeof(a) / sizeof(a[0])
    当需要数组长度时,一些程序员采用上述表达式。例如,数组a的清零操作可以写成以下形式:
for(i = 0;i < sizeof(a) / sizeof(a[0]); i++)
    a[i] = 0;
    //使用这种方法,即使数组长度发生改变,也不需要改变循环
    //利用宏来表示数组长度也有同样的好处,但上述方法不需要记忆宏的名字
  • 但有时编译器会对这种写法一条警告,这稍微有点烦人。这是因为变量i的类型可能是int(有符号类型),,而sizeof返回值的类型为size_t(无符号类型)。为了避免这一警告,可以将 sizeof(a) / sizeof(a[0]) 强制转换为有符号整数。操作如下,(int)(sizeof(a) / sizeof(a[0])),但这个表达式又有点长,写起来不太方便,此时定义一个宏来表示它常常是很有帮助的。
#define SIZE (int)(sizeof(a) / sizeof(a[0]))

for(i = 0;i < SIZE; i++)
    a[i] = 0;
  • 思考一下,返回来使用宏的话,sizeof的优势哪里去了呢?

2. 多维数组

数组可以有任意维数。例如,下面的声明产生一个二维数组(数学上称之矩阵):

  • int m[][][][][][] [5] [9];
  • 数组m有5行9列。数组的行和列下标都从0开始索引。
  • 为了访问i行j列的元素,对表达式需要写成m[i] [j]。
  • 不要把m[i] [j]写成m[i,j]。如果这样写,C语言会把逗号看成逗号运算符,因此m[i,j]就等同于m[j]。

Notice

  • 虽然我们以表格的形式显示来理解二维数组,但是实际上他们在计算机内存中不是这样存储的。C语言是按照行主序存储数组的,也就是从第0行开始,接着是第1行,以此类推。详图见《现代程序设计》P131。
  • 就像for循环和一维数组紧密结合一样,嵌套的for循环是处理多维数组的理想选择。我们需要以某种系统化的方式访问数组中的每一个元素。一对嵌套的for循环可以很好的完成这项工作——一个循环遍历每一行,另一个循环遍历每一列:
#define N 10
double ident [N][N];
int row,col;
for(row = 0;row < N;row++)
    for(col = 0;col < N;col++)
    {
        if(row == col)
            ident[row][col] = 1.0;
        else
            ident[row][col] = 0.0;
    }
  • C语言中的多维数组扮演的角色相对较弱,这主要是因为C语言为存储多维数据提供了更加灵活的方法:指数数组。

2.1 多维数组初始化

通过嵌套一维初始化器的方法可以产生二维数组的初始化器,每一个内部初始化器提供了矩阵中一行的值。为高维数组构造初始化器可采用类似的方法。

int m[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。例如,下面初始化器只填充了数组m的前三行,那么后两行将赋值为0;

int m[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 m[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 m[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
               };
    //这是因为一旦编译器发现数值足以填满一行,它就开始填充下一行

Notice

  • 在多维数组中省略内层的花括号可能是很危险的,因为额外的元素(更糟糕的情况是丢失的元素)会影响剩下的初始化器。
  • C99的指示器对多维数组也有效。例如,可以这样创建2X2的单位矩阵
    • double ident[2] [2] = {[0] [0] = 1.0, [1] [1] = 1.0};

3. 常量数组

无论是一维数组还是多维数组,都可以通过在声明的最开始处加上的单词const而成为常量:

const char hex_chars[] = {
        '0','1','2','3','4','5','6','7','8','9',
        'A','B','C','D','E','F'};
  • 程序不应该对声明为const的数组进行修改,编译器能够检测到直接修改某个元素的意图。
  • 把数组声明为const有两个主要的好处。他表示程序不会改变数组,这对以后阅读程序的人有可能是有价值的信息。他还有助于编译器发现错误——const会告诉编译器,我们不打修改数组。

4. C99中的变长数组

数组变量的长度必须用常量表达式进行定义

  • 变长数组的长度是在程序执行时计算的,而不是在程序编译时计算的。变长数组的主要优点是程序员不必在构造数组时随便给定一个长度,程序在执行时可以准确地计算出所需的元素个数。

  • 变长数组的长度不一定要用变量来指定,任意表达式(可以含有运算符)都可以。例如:

    • int a[3*i+5];
    • int b[j+k];
  • 向其他数组一样,变长数组也可以是多维的:

    • int c[m] [n];
  • 变长数组的主要限制就是它们不具有静态存储期,另一个限制是变长数组美亚由初始化器。

  • 变长数组常见于除main函数以外的其他函数。对于函数f而言,变长数组的最大优势就是每次调用f时长度可以不同。

分类: C

易碎

易碎

我看到的今夜的星空,是几万年前的光,我眼中的你是此时的你!

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注

网站ICP备案皖ICP备2024045222号-1