『C程序设计』读书笔记系列文章之第六章 数组

简介:
1. 数组简介

  数组(Array)由一系列同种数据类型的元素组成。编译器可以从数组声明中知道数组中元素的数目,以及这些元素的数据类型。例如:

       double dbl[20];        /* 包含 20 个 double 类型元素的数组 */
       int      c[12];        /* 包含 12 个 int 型元素的数组      */
       char    ch[40];        /* 包含 40 个 char 型元素的数组     */

方括号 [] 表明它们是数组,[] 里的数字表明数组包含的元素数目。

  数组中的元素是相邻的,初始化之前,元素的值可能是随机的。

      使用数组名和下标(subscript number 或 index)就可以访问特定的元素。下标始于 0,止于 n - 1。例如:c[0] 是数组 c 的第一个元素,而 c[11] 是它的最后一个元素,也就是第 12 个元素。

 


2. 初始化

        int c[12] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

  如上所示,我们使用大括号中一系列逗号分隔的值来初始化数组。我们把这个称之为初始化列表。大括号是必须的!逗号和值之间的空格可有可无。初始化后,数组 c 的第一个元素 c[0] 的值为 0,以此类推。

    下面的小程序输出数组 iarr 中所有元素的值。

        #include <stdio.h>

        int main(void)
        {
            int iarr[4] = { 0, 1, 2, 3 };
            int i = 2;

            printf("%d\n", iarr[0]);    /* 输出 0 */
            printf("%d\n", iarr[1]);    /* 输出 1 */
            printf("%d\n", iarr[i]);    /* 输出 2 */
            printf("%d\n", iarr[1+2]);  /* 输出 3 */

            return 0;
        }

  如上所示,访问数组元素时,[] 里的可以是常量,可以是变量,也可以是表达式。[] 里还可以是返回值为整型的函数调用。总之,只要 [] 里的值是整数类型都可以。

  注意,上面的程序,如果把 int iarr[4] = { 0, 1, 2, 3 }; 改成 int iarr[4];(即没有初始化),则它里面的元素的值是随机的,也就是本来就存在于那段内存空间的值。如果改成 int iarr[4]; 后再把它放在 int main(void) 之前,则它里面的元素的值都是 0。具体原因我在后续的教程会说明。

  如果初始化列表中的值的个数少于数组元素个数,则余下的元素都会被初始化为 0。例如:

        int iarr[4] = { 0, 1 };

iarr[0] 和 iarr[1] 分别为 0 和 1;iarr[2] 和 iarr[3] 都被初始化为 0。注意,初始化列表中的值的个数可以少于数组元素个数,但是超过数组元素个数却是不对的!

    初始化数组时,[] 里可以留空。例如:

        int iarr[] = { 1, 2, 3 };

  编译器会算出初始化列表中的值的个数,然后构造包含那么多个元素的数组。如上例,编译器会算出列表中一共有 3 个值,然后把 iarr 构造成包含 3 个元素的数组。例如:

        #include <stdio.h>

        int main(void)
        {
            int iarr[] = { 1, 2, 3 };

            printf("%d\n", iarr[0]);    /* 输出 1 */
            printf("%d\n", iarr[1]);    /* 输出 2 */
            printf("%d\n", iarr[2]);    /* 输出 3 */

            return 0;
        }

  我们可以用以下表达式算出 iarr 中元素的个数:

        sizeof iarr / sizeof iarr[0]

  其中,sizeof iarr 算出数组 iarr 占用的内存大小,sizeof iarr[0] 算出 iarr[0] 占用的内存大小(也就是数组 iarr 中每个元素占用的内存大小),它们相除就得出 iarr 的元素个数。sizeof 是一个运算符,具体用法我以后会说。

3. 指派初始值(Designated Initializers)

  指派初始值这个特性是 C99 增加的,它允许我们直接初始化数组中特定的元素。C99 以前,如果我们要初始化数组中的某个元素,如第三个元素,必须同时初始化它之前的元素。例如:

        int iarr[10] = { 0, 0, 300 };

而 C99 中,我们可以这样初始化特定的元素:

        int iarr[10] = { [2] = 300 };  /* 指派初始化 iarr[2] 为 300 */

其余的元素都会被初始化为 0 。下面我们来看一个小程序。

        #include <stdio.h>

        int main(void)
        {
            int iarr[5] = { 6, 3, [3] = 1, 5, [1] = 8};
     
            printf("%d\n", iarr[0]);
            printf("%d\n", iarr[1]);
            printf("%d\n", iarr[2]);
            printf("%d\n", iarr[3]);
            printf("%d\n", iarr[4]);

            return 0;
        }

输出为:

        6
        8
        0
        1
        5

从中可以看出两点:

    A. 如果指派初始值后面还有值,则后面的值会被用于初始化后续的元素。上例中,
       iarr[3] 被初始化为 1 ,它后续的元素 iarr[4] 被初始化为 5。

    B. 如果初始化列表中多次出现对某元素的初始化,则以最后一次为准。上例中,
       iarr[1] 先被初始化为 3,然后被 [1] = 8 指派初始化为 8。


4. 给数组元素赋值

  我们可以利用下标给特定的元素赋值。例如:

        int iarr[5];
        iarr[0] = 100;  /* 赋值给第一个元素 */
        iarr[4] = 120;  /* 赋值给第五个元素 */
        iarr[2] = 180;  /* 赋值给第三个元素 */

C 不允许直接使用数组对别的数组进行赋值,也不允许使用初始化列表对数组进行赋值。例如:

        int iarr_1[5] = { 1, 2, 3, 4, 5 };   /* 正确 */
        int iarr_2[5];

        iarr_2 = iarr_1;                     /* 错误! */
        iarr_2[5] = { 3, 4, 5, 6, 7 };       /* 错误! */
        iarr_2[5] = iarr_1[5];               /* 越界! */

  最后一个语句发生了越界!因为这两个数组都只有 5 个元素,而使用下标 5 访问的是第六个元素!

5. 数组界限(array bounds)

  使用下标时,我们必须确保下标没有越界。例如:

        int iarr[46];

  这个数组的下标范围是 0 到 45,确保下标没有超出这个范围是我们的责任,因为编译器不会对下标越界进行检测!

    C 标准没有定义下标越界的后果,也就是说,当我们写的程序中出现下标越界的问题,程序可能正常工作,也可能异常退出,还有可能出现其它奇怪的情况。

        #include <stdio.h>

        int main(void)
        {
            int var_1 = 20;
            int arr[5];
            int var_2 = 40;

            printf("var_1: %d, var_2: %d\n", var_1, var_2);

            arr[-1] = -1;
            arr[5]  =  5;

            printf("%d  %d\n", arr[-1], arr[5]);
            printf("var_1: %d, var_2: %d\n", var_1, var_2);

            return 0;
        }

上述程序使用 Dev-C++ 4.9.9.2 编译运行的输出为:

        var_1: 20, var_2: 40
        -1  5
        var_1: 20, var_2: -1

  可见,下标越界可能改变其它变量的值。这是因为 gcc(dev-c++ 使用的 C 编译器)把 var_2 保存于数组 arr 之前的内存空间,所以对 arr[-1] 赋值正好改变了 var_2 的值。不同的编译器编译运行该程序可能会有不同的输出,也可能会异常退出。

  C 语言的哲学是信任程序员,而且不检测越界程序运行更快。程序编译时有些下标的值仍然是不可知的,所以如果要检测下标越界的话,编译器必须在生成的目标代码中加入额外的代码用于程序运行时检测下标是否越界,这就会导致程序运行速度下降。故而,为了运行效率,C 不检测下标是否越界。


6. 指定数组元素数目

  C99 之前,声明数组时,[] 中的值必须是大于零的整数常量。C99 中,声明数组时,[] 中可以是变量。这就是所谓的变长数组(variable-length array,简称 VLA)。声明 VLA 时,不能对其进行初始化。在后续的教程中,我会对 VLA 进行详细讲解。

        int n = 99;
        double dbl_1[4];          /* 正确 */
        double dbl_2[8/2 + 4];    /* 正确 */
        int    iar_1[-5];         /* 错![] 中的值必须大于 0 */
        int    iar_2[0];          /* 错![] 中的值必须大于 0 */
        int    iar_3[9.2];        /* 错![] 中的值必须是整数类型 */
        char   ch[n];             /* C99 之前不支持! */




   本文转自loose_went博客园博客,原文链接:http://www.cnblogs.com/michaelxu/archive/2008/04/22/1165433.html,如需转载请自行联系原作者


相关文章
|
存储 Unix Java
《C语言程序设计现代方法(第2版)》读书笔记 第二章(二)
《C语言程序设计现代方法(第2版)》读书笔记 第二章(二)
《C语言程序设计现代方法(第2版)》读书笔记 第二章(二)
|
程序员 编译器 C语言
第五章 选择语句《C语言程序设计现代方法(第2版)》读书笔记(二)
第五章 选择语句《C语言程序设计现代方法(第2版)》读书笔记(二)
第五章 选择语句《C语言程序设计现代方法(第2版)》读书笔记(二)
|
存储 C语言
第五章 选择语句《C语言程序设计现代方法(第2版)》读书笔记(一)
第五章 选择语句《C语言程序设计现代方法(第2版)》读书笔记(一)
第五章 选择语句《C语言程序设计现代方法(第2版)》读书笔记(一)
|
存储 Unix 编译器
第七章 基本类型《C语言程序设计现代方法(第2版)》读书笔记(二)
第七章 基本类型《C语言程序设计现代方法(第2版)》读书笔记(二)
第七章 基本类型《C语言程序设计现代方法(第2版)》读书笔记(二)
|
存储 编译器 程序员
第八章 数组《C语言程序设计现代方法(第2版)》读书笔记
我们所见的变量都只是 标量(scalar ):标量具有保存单一数据项的能力。C语言也支持 聚合 (aggregate )变量,这类变量可以存储一组一组的数值。在 C 语言中一共有两种聚合类型: 数组 (array)和结构(structure )。
第八章 数组《C语言程序设计现代方法(第2版)》读书笔记
|
Java 数据库 算法
《Akka应用模式:分布式应用程序设计实践指南》读书笔记9
性能   这也是一个比较大的问题,因为性能不一定是Akka本身的问题,还可能是你代码写的有问题。   优化的第一步就是找出性能的瓶颈,隔离出应用程序里面比较耗时的部分,然后尝试对其优化,减少需要耗费的时间成本。
1635 0
|
缓存 运维 数据库
《Akka应用模式:分布式应用程序设计实践指南》读书笔记8
可用性   简单点来说就是系统能否正常使用。如果系统能够及时响应一个请求,则认为是可用的;如果响应时间过长或者根本不响应,则是不可用的。系统在停机或超载时是不可用的。一般用系统正常运行时长的百分比来计量系统的可用性,例如常常用N个9表示系统的可用性。
1883 0
|
存储 缓存 NoSQL
《Akka应用模式:分布式应用程序设计实践指南》读书笔记6
一致性和可扩展性   一致性是系统内比较复杂的属性,它会随着系统的变化而变化。简单来说,一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。一旦系统具有并行性(分布式只是并行的一种表现),保持一致性就变得困难了,毕竟需要协调全局状态。
1363 0
|
运维 Java
《Akka应用模式:分布式应用程序设计实践指南》读书笔记7
容错   容错绝对是分布式系统最难搞定的事儿,至少我这样认为,因为意外总是会发生。   处理故障在许多方面意味着要放弃全局一致性。Akka是基于不粗要调用方负责处理故障的想法而建立的。它主张由发生故障的actor负责处理问题,在actor不能处理的情况下,会向其“监督者”寻求帮助。
1706 0
|
设计模式 消息中间件 程序员
《Akka应用模式:分布式应用程序设计实践指南》读书笔记1
  作者属于Scala、Akka技术爱好者,但苦于Akka没有关于设计模式的文章,偶尔搜到《Akka应用模式》一书,如获至宝。现整理一些读书笔记和自己的感悟,以供参考。 Actor模型 Actor模型感觉还是很给力的,要是按我以前学习actor模型,绝对会对他嗤之以鼻,这玩儿意能干啥。
1843 0