C 语言基础教程(我的C之旅开始了)[十]

27. 表达式(Expression)

表达式由运算符和操作数组成。单独一个操作数是最简单的表达式。请看以下例子:

9
                    -4
                    +5
                 3 + 6
       a * ( b/c - d )
             e = 9 / 3
           f = ++e % 3

表达式中的表达式称为子表达式。例如:b/c 是 a * ( b/c - d ) 的子表达式。

每个表达式都有一个值,这是 C 语言的一个重要属性。显而易见的,9 和 -4 的值就是 9 和 -4,3 + 6 的值就是 9 。e = 9 / 3 的值是 3 ,也就是是 = 号左边的变量 e 被赋予的值。我们再来看看下面这个表达式:

8 - (a = 2 * 3)

想想看,它的值是多少?没错,就是 2 。但是,不推荐使用这种表达式,因为可读性太差。








28. 数组基础(上)
 

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 是一个运算符,具体用法我以后会说。









29. 数组基础(下)
 

1. 指派初始值(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。

2. 给数组元素赋值

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

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 访问的是第六个元素!

3. 数组界限(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 不检测下标是否越界。

4. 指定数组元素数目

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 之前不支持! */

(0)

相关推荐

  • C 语言基础教程(我的C之旅开始了)[十]

    27. 表达式(Expression) 表达式由运算符和操作数组成.单独一个操作数是最简单的表达式.请看以下例子: 9                    -4                    +5                 3 + 6       a * ( b/c - d )             e = 9 / 3           f = ++e % 3 表达式中的表达式称为子表达式.例如:b/c 是 a * ( b/c - d ) 的子表达式. 每个表达式都有一个值,

  • C 语言基础教程(我的C之旅开始了)[八]

    19. 基本数据类型:复数类型和虚数类型 C99 新增了复数类型(_Complex)和虚数类型(_Imaginary).简单来说,C99 提供了三种复数类型:float _Complex,double _Complex,和 long double _Complex.对于 float _Complex 类型的变量来说,它包含两个 float 类型的值,一个用于表示复数的实部(real part),另一个用于表示虚部(imaginary part).类似地,double _Complex 包含两个

  • C 语言基础教程(我的C之旅开始了)[六]

    14. 基本数据类型:字符型(上) 1.字符型(char)简介 字符型(char)用于储存字符(character),如英文字母或标点.严格来说,char 其实也是整数类型(integer type),因为 char 类型储存的实际上是整数,而不是字符.计算机使用特定的整数编码来表示特定的字符.美国普遍使用的编码是 ASCII(American    Standard    Code   for    Information   Interchange  美国信息交换标准编码).例如:ASCII

  • C 语言基础教程(我的C之旅开始了)[五]

    12. 基本数据类型:整型(下) 1. 输出各种整数类型的变量 输出不同类型的整数,需要使用不用的格式限定符.输出 unsigned int 类型的整数,要用 %u .输出 long ,要用 %ld:如果要以十六进制或者八进制形式输出,那就用 %lx(或者%lX)或者 %lo.注意:虽然整数常量的后缀使用大写或者小写英文字母都没关系,但是它们格式限定符必须使用小写!如果我们要输出 short 类型的整数,可以在 %d 中间加上前缀 h,也就是%hd:同理,%ho 和 %hx(或者 %hX )分别

  • C 语言基础教程(我的C之旅开始了)[四]

    10. 基本数据类型:整型(上) 1. 整型 int C 语言提供了很多整数类型(整型),这些整型的区别在于它们的取值范围的大小,以及是否可以为负.int 是整型之一,一般被称为整型.    int 代表有符号整数,也就是说,用 int 声明的变量可以是正数,可以是负数,也可以是零,但是只能是整数.标准规定 int 的最小取值范围是 -32767 到 32767.int 的取值范围因机器而异,但是一定要大于或者等于-32767到 32767.一般来说,int 占用一个字的内存空间.因此,字长为

  • C 语言基础教程(我的C之旅开始了)[三]

    7. 第三个 C 程序 首先请看下面这个小程序,试着自己编译运行一下.如果不懂怎么编译,请点击下面的超链接:      编译器使用方法    编译器Dev-C++下载&使用教程 /* circle.c  --  计算圆的面积 */ #include <stdio.h> int main( void )      {          float radius;    /* 圆的半径 */ printf( "Please enter the radius: " ); 

  • C 语言基础教程(我的C之旅开始了)[二]

    3. C 程序的结构 C 程序由一个以上的函数组成,而且必须有 main 函数.此外,C 程序一般还有一些预处理指令.例如 #include 指令.当然并不是必须要有 #include 指令.函数由函数头和函数体组成.函数头由返回值.函数名以及参数列表(可以是void)组成.函数体从 { 开始,以 } 结束.函数体里可以有一系列的语句,每个语句以分号(;)结束.例如: 预处理指令     --〉   #include <stdio.h> 函数头         --〉  int main( v

  • C 语言基础教程(我的C之旅开始了)[九]

    24. +.-.*./.= 的优先级 1. 优先级 和数学一样,C 语言规定先乘除后加减.也就是说,乘法运算符和除法运算符的优先级(Precedence)比加法运算符和减法运算符高.同时,C 语言也规定,如果两个运算符的优先级相同,并且它们之间没有被优先级比它们高或者低的运算符隔开,则它们的运算顺序根据它们在语句中出现的先后而定.大多数运算符都是从左向右进行运算的,不过也有从右向左进行运算的(例如赋值运算符).乘法运算符和除法运算符的优先级相同,加法运算符和减法运算符的优先级相同.因此,以下语句

  • C 语言基础教程(我的C之旅开始了)[七]

    17. 基本数据类型:布尔型(_Bool type) _Bool 型是 C99 添加的,用于表示布尔值,亦即是表示逻辑真(true)和逻辑假(false).因为 C 用 1 表示 true ,0 表示 false ,所以 _Bool 实际上是整数类型.理论上 _Bool 只需要 1 bit 存储单元,因为1 bit 就足以表示 0 和 1 .事实上,_Bool 是无符号整型,一般占用 1 字节.例如: _Bool flag = 1; flag = 0; 包含标准头文件 stdbool.h 后,我

  • C 语言基础教程(一)颜色加亮

    首先请看下面这个简单的 C 程序,猜猜它会做些什么.猜不出来也没关系,后面会有详细的解说.         #include <stdio.h> int main( void )            /* 一个简单的程序 */         {             int num;                  /* 定义一个名叫 num 的变量 */             num = 1;                  /* 将 1 赋值给 num         */ p

随机推荐