单片机C语言编程实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.5 变量

2.5.1 变量的定义和初始化

在程序运行过程中,其值可以改变的数据对象称为变量。变量存在时,在内存中占据一定的存储单元,存放变量的值。变量有以下五个要素。

① 变量标识符。每个变量都必须有一个标识符,常称为变量名。

② 变量值。在程序运行过程中,变量值存储在内存中,通过变量名来引用变量的值。

③ 变量的数据类型。确定变量在存储时需要几个字节,采用哪种方式进行读、写和处理。

④ 变量的存放位置。确定变量存储在哪种存储器中,从而决定了变量的读、写速度。

⑤ 变量的存储种类。确定变量的作用时间和作用范围,存储种类有四种:自动(auto)、外部(extern)、静态(static)和寄存器(register),默认类型为自动(auto)。这些存储种类的具体含义和使用方法,将在后面进一步进行学习。

在C语言中,要求对所有用到的变量,必须先定义、后使用。只有这样,编译器才知道需分配多大的存储空间,变量的定义格式如下。

      [存储种类] [数据类型] [存放位置] 变量名;

例如:

      char data var1;                // 保存在data直接寻址区,占用1字节
      char code kk;                  // 保存在code程序存储区,占用1字节
      unsigned long xdata array;      // 保存在xdata外部数据区,占用4字节
      float idata x,y,z;             // 保存在idata间接寻址区,3个变量占用12字节
      unsigned int pdata dimension;   // 保存在pdata分页数据区,占用2字节
      unsigned char xdata vector;     // 保存在xdata外部数据区,占用1字节
      char bdata flags;              // 保存在bdata可位寻址区,占用1字节

注意

经常访问的数据应存放在data直接寻址区,它具有最快的读、写速度。

在定义变量的同时可对全部或部分变量进行赋初值的操作,称为变量初始化。

例如:

      float data k1=2.5,k2=5.23,k3=45.43;
      int xdata tt1,tt2=5,tt3;

下面举例说明变量的作用。

【例2-2】

      main()
      { int xdata x, y;                  //定义变量x、y
        x=5; y=3;                        //变量x、y赋初值
        printf("%d----- %d\n",x,y);      //打印变量x、y的值
        x=1; y=2;                        //变量x、y再次赋值
        printf("%d----- %d\n",x,y);      //打印变量x、y的值
      }

程序运行结果如图2-3所示。

图2-3 程序运行结果

2.5.2 隐含的变量数据类型和存放位置

在变量声明中,如果缺省变量的数据类型,编译器自动将其数据类型默认为int型。当默认存放位置时,编译器根据当前的存储模式,自动默认不同的存储类型。

注意

变量的数据类型和存放位置不能同时缺省。

存储模式有3种:SMALL、COMPACT、LARGE,可用编译控制命令进行设定。除了在很特殊的应用中,一般默认SMALL存储模式,可产生最快和最有效的代码。

1.SMALL模式

在本模式中,当变量存放位置默认时,变量存放在data直接寻址区,这和用data说明变量的存放位置一样,变量访问非常有效,但长度只有128B。

2.COMPACT模式

本模式所有默认存放位置的变量都放在外部数据区的第一页中,像用pdata声明的一样,可提供最多256B的变量,不如SMALL模式有效,比LARGE模式快。

3.LARGE模式

本模式所有默认存放位置的变量都放在外部数据存储区,最多可提B到64KB的变量,这和用xdata声明的一样,比SMALL或COMPACT模式产生更多的代码,且访问速度慢。

2.5.3 特殊功能寄存器(sfr)

MCS51系列单片机提供一个特别的存储区,作为特殊功能寄存器(sfr),可控制定时器、计数器、串口、并口和外围设备,sfr的地址从0x80到0xFF,能以字节和字(双字节)方式访问。另外,sfr中地址是8的整数倍的特殊功能寄存器还可以实现位寻址。

sfr和别的C语言变量一样声明,不需存放位置说明,在等号“=”后指定的地址必须是一个常数值,不允许是带操作数的表达式。sfr数据类型不能在函数内部定义,只能作为全局变量使用。

例如:

      sfr P0=0x80;              // 定义特殊功能寄存器P0的地址
      sfr T2CON=0xc8;           // 定义特殊功能寄存器T2CON的地址
      sfr SCON=0x98;            // 定义特殊功能寄存器SCON的地址
      sfr RCAP2H=0xcb;          // 定义特殊功能寄存器RCAP2H的地址

许多新的MCS51派生系列单片机可用两个连续地址的sfr来指定一个16位的特殊功能寄存器,Cx51编译器提供sfr16数据类型访问两个sfr作为一个16位的sfr。访问一个16位sfr只能低字节跟着高字节,低字节用做sfr16声明的地址。在下面例子中,T2和RCAP2被声明为16位的sfr。

      sfr16 T2 = 0xCC;           /* T2L——0cch,   T2H——0CDh   */
      sfr16 RCAP2 = 0xCA;        /* RCAP2L——0Cah, RCAP2H——0CBh */

2.5.4 bit、sbit数据类型变量

为了方便控制、提高效率,Cx51编译器提供一个bit数据类型,bit变量和别的C语言变量的声明相似。所有的bit变量放在bdata可位寻址区,因为这区域只有16字节长,所以在该范围内只能声明最多128个位变量。

例如:

      bit done_flag = 0;             /* bit变量*/
      bit flag1=0, flag2=1;          /* bit变量*/

用sbit关键词声明新的变量可访问用bdata声明的变量的位。

例如:

      int bdata ibase;                 //可位寻址的int
      sfr P1=0x90;                    //定义特殊功能寄存器P1的地址
      sfr P4=0xe8;                    //定义特殊功能寄存器P4的地址
      sbit mybit0 = ibase^0;          //mybit0为ibase的最低位
      sbit mybit15 = ibase^15;        //mybit15为ibase的最高位
      sbit kg=P4^3;                  //定义开关的位地址为P4的第3位
      sbit fmq=P1^5;                 //定义蜂鸣器的位地址为P1的第5位

上面sbit声明的位变量并不独立存在,只是提供了上面声明的变量ibase、P1、P4的一种位寻址方式。例子中“^”符号后的表达式指定位的位置,这个表达式必须是常数,范围由声明中的基变量决定。例如:执行语句“mybit15=1;”,实际上使变量ibase的最高位为1。

注意

sbit数据类型不能在函数内部定义,只能作为全局变量使用。

2.5.5 硬件定义文件

为了编程方便,常将单片机特殊功能寄存器集中定义,形成硬件定义文件,Cx51编译器提供硬件定义文件reg52.h和reg51.h。下面是在文件reg52.h基础上修改的STC89系列单片机的硬件定义文件。

2.5.6 变量的绝对定位

一般定义的变量,我们不需要知道和控制其最终的物理地址,全部由编译器自动定位,但在控制硬件时,常常需要规定其绝对存储地址,可采用以下3种方法,本节只介绍第1种方法。

① 关键字_at_方式。

② 直接存储地址的宏定义方式。

③ 指针方式。

用关键词_at_将变量定位到绝对存储地址的格式如下。

      [数据类型] [存储类型] 变量名_at_ 绝对地址;

_at_后面的绝对地址必须在可用的实际存储空间内,如果用_at_关键词声明一个变量,来访问一个xdata外围设备,应使用volatile关键词来确保编译器不进行优化,以便能访问到要访问的存储区。另外,bit类型的变量不能定位到一个绝对地址。下面的例子示范如何用_at_定位几个不同的变量类型。

      char data buf1 _at_ 0x70;
      char xdata text _at_ 0xE000;
      int xdata kk _at_ 0x8000;

2.6 数据运用

C语言有很丰富的运算符,总结见表2-11。

表2-11 C语言的运算符

2.6.1 算术运算和算术表达式

C语言的算术运算符有以下5种。

+ 双目运算时两数相加,如5+6,单目运算时取正值,如+15

- 双目运算时两数相减,如5-6,单目运算时取负值,如-19

* 双目运算符,两数相乘,如5*6

/ 双目运算符,两数相除,如15/6

% 双目运算符,取模或求余数,两个数必须都是整数,如15%6值为3

所谓单目运算符是指对一个运算对象进行操作,如“-19”;双目运算符是对两个运算对象进行操作,这两个运算对象分别放在操作符的左边和右边,如“5+6”。

C语言中的运算符与数学中的运算符类似,都有优先级和结合方向。C语言的算术运算符的优先级如下(同一行上的运算符,优先级相同)。

上面所有双目算术运算符的结合方向都是“从左到右”,而单目运算符取正“+”和取负“-”的结合方向是“从右到左”。

C语言的算术表达式是由算术运算符把运算对象连接起来,构成合法的式子。运算对象包括常量、变量和函数,算术表达式的值为整数或实数,如3*x+1.0/y-10*sqrt(x)。在对算术表达式进行运算时,应注意以下几点。

① 算术表达式中可以使用多层圆括号,左、右括号必须配对。运算时先计算出内层括号表达式的值,再由内向外计算表达式的值。

② 取模运算符%两侧的运算对象必须是整数,运算结果是两数相除后所得的余数。在多数机器上,取模后值的符号与运算符左侧运算对象的符号相同。例如:5%3值为2,-5%3值为-2,5%(-3) 值为2;实数不能参与取模运算,如5%1.5是非法的算术表达式。

③ 整数除。两个整数相除后值等于商的整数部分(小数部分没有四舍五入),例如:1/2=0,3/2=1,10/3=3。

④ 实数除。两个相除的数中至少有一个是实数,相除后的值等于实数,例如:1.0/2=1/2.0=1.0/2.0=0.5。

例如:(-16/3*2+1)%6=-3,先计算圆括号内的值,单目运算符“-”优先级高于其他双目运算符,先计算整数除-16/3值为-5, 然后-5*2+1值为-9, 最后-9%6值为-3。

【例2-3】

实数运算程序

      #include<stdio.h>                // 包含输入、输出库函数
      main()
      { float x,y,z;                     // 定义浮点变量x、y、z
        x=42.67; y=12.3;                 // 变量x、y赋初值
        z=x/y;  printf("z1=%f\n", z);    // 计算变量Z
        z=y/x;  printf("z2=%f\n",z);     // 再次计算变量Z
      }

程序运行结果如图2-4所示。

图2-4 程序运行结果

2.6.2 各类数值型数据的混合运算和类型转换

在C语言中允许整型、实型、字符型数据进行混合运算,如1.23+'A'+456%'B'。不同类型的数值型数据进行混合运算时,编译器自动把低精度数据类型向高精度数据类型转换,成为同一类型后才进行运算,转换的规则如图2-5所示。

图2-5 不同类型的数值型数据进行混合运算时类型的转换规则

图中向左的横向箭头表示必须进行的转换,如两个float型的数据相加,先把这两个float数据转换成double型数据,然后再进行运算,以提高精度。向上的纵向箭头表示不同类型数据混合运算时,先要进行的数据类型转换,如123.456+543-'A',运算时先把整型数据543转换成double型数据后,与123.456相加值为666.456,然后把字符'A'转换成65.0再进行相减运算,最后结果为601.456。

【例2-4】

各种类型数据的混合运算

      #include<stdio.h>            //包含输入、输出库函数
      main()
      { int a,b,c1;
        float x,y,z;
        a=85; b=18; x=42.6; y=28.3;
        c1=a/b * 9 +a %b;
        z=x/y+(x+y)/2-1;
        printf("c1=%d\n",c1);
        printf("z1=%f\n", z);
        a=35;  x=42.63;
        printf("%f\n",a+x);
        printf("%f\n",x/a);
      }

程序运行结果如图2-6所示。

图2-6 程序运行结果

如果编译器自动进行的类型转换不能满足要求,可以通过在变量前加“(类型符号)”进行强制类型转换,如例2-5所示。

【例2-5】

      #include<stdio.h>
      main()
      { int a; float x,y;
        a=1854; x=42.6; y=19.3;
        printf("1---%f\t", (float)5/2 );          // 先转换为实数,再进行实数除
        printf("2---%f\n", (float)(5/2));         // 先进行整数除,再转换为实数
        printf("3---%d\t", a+(int)x);             // 先转换x为整数,再进行整数加
        printf("4---%d\n", (int)(x + y) );        // 先实数加,再进行为整数
        printf("5---%bd\n", (char)a );            // 取整型数低字节的数据
      }

程序运行结果如图2-7所示。

图2-7 程序运行结果

2.6.3 增1、减1运算

增1运算符“++”使运算对象的值增1,而减1运算符“--”则使运算对象的值减1。它们都是单目运算符,其运算对象必须是变量,不能是常量和表达式。例如,语句i++;相当于语句i=i+1;又如语句i--;相当于语句i=i-1;

增1与减1运算符可以作前缀运算符,这时先使运算对象值增1或减1之后,再使用运算对象。例如:i=1;j=++i;则变量i的值先增1变成2,然后把i的值赋给变量j,j=2。同样:i=1;j=--i;执行后i的值为0,j=0。

增1与减1运算符也可以作后缀运算符,这时先使用运算对象,再使运算对象值增1或减1。例如:i=1;j=i++;则先把i的值赋给变量j,j=1,然后变量i的值再增1变成2。同样:i=1;j=i--;执行后i的值为0,j=1。

增1与减1运算符如果仅仅只进行自加、自减运算,没有使用运算对象的值,作前缀运算符与作后缀运算符运算结果一样。如语句i--;与语句--i;效果一样,都等同于语句i=i-1;

“++”和“--”运算符的结合方向是“从右到左”。例如:i=1;j=-i++;由于取负运算符“-”和增1运算符“++”的优先级相同,结合方向是“从右到左”,即相当于- (i++),又由于是后缀运算符,则先取出i的值使用,把-i的值赋值给j,变量j=-1,然后使i的值增1变成2。

应尽量避免在一个表达式中多次用“++”和“--”运算符,这样写出的表达式可读性差,不同的编译系统也可能给出不同的运算结果。例如:i=1;j=++i+i++;k=i++*++i;可使用临时变量过渡一下,分解成如下五条语句。

      i=1;
      t=++i;            // 执行后,i=2,t=2
      j=t+i++;          // 执行后,j=4,i=3
      t=i++;            // 执行后,t=3,i=4;算术表达式右结合性,i++*++i中先算i++
      k= t *(++i) ;     // 执行后,k=15,i=5

变量t作为临时变量,把“++”运算符只写在一个表达式中,这样程序可读性好,不同的编译系统给出的运算结果都相同。

上述例子实际编程时很少使用,只会在计算机等级考试中出现,编程时如果弄不清楚先后次序,可使用圆括号“()”进行限定。

2.6.4 位运算

数据在内存中都以二进制形式存放,在对硬件编程时经常需要对数据的二进制位进行操作。C语言提供了六种位运算符,与汇编语言的位操作相似,其优先级、结合方向、要求运算对象的个数及作用如表2-12所示,位运算是C言的优点之一。

表2-12 C语言的位运算符

位运算的运算对象只适用于字符型和整数型数据,对其他数据类型不适用。

1.按位取反运算符“~”

按位取反运算符是单目运算符,运算对象在运算符的右边,其运算功能是把运算对象的内容按位取反。例如:int i=199;则~i值为-200,这是因为:整型十进制数199的二进制数是:0000000011000111,把它按位取反是:1111111100111000,这个数是整型十进制数-200在内存的补码表示。

2.左移运算符“<<”

左移运算符的左边是运算对象,右边是整型表达式,表示左移的位数。左移时,低位(右端)补0,高位(左端)移出部分舍弃。

例如:

      char a=5,b;
      b=a<<3;

用二进制来表示a的值为00000101,执行语句“b=a<<3”之后,b的值为00101000,运算后a的值并没有改变仍为5。

左移时,若高位(左端)移出的部分均是二进制位数0,则每左移1位,相当于乘以2,如上例b的值40=5×2×2×2。可以利用左移这一特点,代替乘法,左移运算比乘法运算快得多。若高位移出的部分包含有二进制位数1,则不能用左移代替乘法运算。

3.右移运算符“>>”

右移运算符的左边是运算对象,右边是整型表达式,表示右移的位数。右移时,低位(右端)移出的二进制位数舍弃。对于正整数和无符号整数,高位(左端)补0;对于负数,高位补1(补码表示法最高位1表示负数)。

例如:

      char a=41,b;
      b=a>>3;

用二进制来表示a的值为00101001,执行语句“b=a>>3”;之后,b的值为00000101(十进制数5=41/2/2/2,注意是整数除),运算后a的值并没有改变仍为41。

右移时,每右移1位,相当于除以2(整数除),可以利用右移这一特点,代替除法,右移运算比除法运算快得多。但是对于负整数,右移时高位补1,则不能用右移代替除法运算。

4.按位与运算符“&”

运算符“&” 先把两个运算对象按位对齐,再进行按位与运算,如果两个对应的位都为1,则该位的运算结果为1,否则为0。例如:int a=41&165;则a=33,运算过程用二进制表示如下。

      0000000000101001        (十进制数41)
        & 0000000010100101        (十进制数165)
      0000000000100001         (十进制数33)

按位与运算有两个特点:和二进制位数0相与则该位被清零;和二进制位数1相与则该位保留原值不变。利用这两个特点,可以指定一个数的某一位(或某几位)置0,也可以检验一个数的某一位(或某几位)是否是1。例如:a=a&3;只保留a的右端最低两位二进制位数。又如:a & 4检验变量a的右端第3位是否为1。

“按位与”运算符“&”和下节将要介绍的“逻辑与”运算符“&&”不同,对于逻辑与运算符“&&”,只要两边运算数为非0,运算结果为1。例如,41&&165=1。

5.按位异或运算符“∧”

按位异或运算符“∧”把两个运算对象按位对齐,如果对应位上的数相同,则该位的运算结果为0;如果对应位上的数不相同,运算结果为1。例如:int a=41∧165;则a=140,运算过程用二进制表示如下。

        0000000000101001       (十进制数41)
        ∧0000000010100101       (十进制数165)
        0000000010001100       (十进制数140,运算时上、下相同取0,不同取1)

按位异或运算可以把一个数的二进制位的某一位(或某几位)翻转(0变1,1变0)。例如:a=a∧3;将变量a的最右端的二位翻转。

6.按位或运算符“|”

按位或运算符“|”先把两个运算对象按位对齐,再进行按位或运算,如果两个对应的位都为0,则该位的运算结果为0,否则为1。例如:int a=41|165,则a=173,运算过程用二进制表示如下。

        0000000000101001       (十进制数41)
        |0000000010100101       (十进制数165)
        0000000010101101         (十进制数173)

利用按位或运算的特点,可以指定一个数的某一位(或某几位)置1,其他位保留原值不变。

例如:

      a=a|3;        把a的右端最低两位二进制位数置1,其他位保留原值不变
      a=a|0xff;     把a的低字节全置1,高字节保持原样
      a=a|0xff00;   把a的高字节全置1,低字节保持原样

7.不同数据类型之间的位运算

如果参加位运算的两个运算对象类型不同,如长整型(long)、整型(int)或字符型(char)数据之间的位运算。此时先将两个运算对象右端对齐,若为正数或无符号数高位补0,负数高位补1。例如:9L|-200=-199L,运算过程用二进制表示如下。

        00000000000000000000000000001001 (十进制长整型数9L)
        11111111111111111111111100111000 (十进制数-200,高位补1)
        11111111111111111111111100111001 (十进制长整型数-199L)

8.一个表达式中出现多个位运算符

如果在一个表达式中出现多个运算符时,应注意各运算符之间的优先关系。如语句a=10&5<<3;执行后a的值为8。“<<”的优先级高于“&”,先进行位移运算。

2.6.5 关系运算和逻辑运算

计算机往往需要对一个已知条件的“真”、“假”进行判断,C语言中没有专门用来表示“真”、“假”的常量,在C语言中规定非零值为“真”,其值用1表示;零为“假”,其值用0表示。

注意

关系运算和逻辑运算是用来判断一系列条件的“真”和“假”,其运算最终结果只有“1(真)”和“0(假)”两种结果。

1.关系运算

关系运算用于比较两个运算对象的大小,C语言提供6种关系运算符。

<(小于)、>(大于)、<=(小于或等于)、>=(大于或等于)、==(等于),!=(不等于)

前四个运算符优先级相同,都高于后两种关系运算符,后两个运算符优先级也相同。关系运算符是双目运算符,结合方向是“从左到右”。关系运算符的优先级低于算术运算符,但高于赋值运算符。如:表达式x>y==c<d,等价于(x>y)==(c<d)。

例如:

      123>=456          结果=0
      1>(123>456)    结果=1
      'A'<'a'           结果=1

例如:

      n1=3,n2=4,n3=5,则:
      n1>n2            结果=0
      (n1>n2)!=n3       结果=1
      n1<n2<n3         结果=1

另外,关系表达式的值,还可以参与其他种类的运算,如(n1<n2)+n3表达式的值为6,因为n1<n2的值为1,1+5=6。

2.逻辑运算

C语言提供三种逻辑运算符: &&(逻辑与),||(逻辑或),!(逻辑非)。其中“!”是单目运算符,“&&”和“||”是双目运算符,逻辑非“!”运算符的结合方向是“从右到左”,而“&&”和“||”结合方向则是“从左到右”,逻辑运算符的优先级如图2-8所示。

图2-8 逻辑运算符的优先级

C语言中逻辑表达式的值也只能是1或0。逻辑表达式值为1,逻辑表达式为“真”,则其值为1;若为“假”,则其值为0。逻辑表达式的运算规则如表2-13所示,其中a、b为合法的C语言表达式。

表2-13 逻辑表达式的运算规则

例如:

      int n=12; int x=5;
      !n                        结果=0
      !(n==10)                  结果=1
      n>=1&&n<=31             结果=1
      (x>=0)&&(x<3)            结果=0
      n || n>31                 结果=1
      (x<-1) || (x>5)           结果=0

例如:

判断1900 年是否是闰年?判断一个年份是否是闰年的逻辑表达式:(year%4==0)&&(year%100!=0)||(year%400==0)

① 先求表达式year%4==0的值得1;

② 再求表达式year%100!=0的值得0;

③ 逻辑表达式(year%4==0)&&(year%100!=0)的值得0;

④ 还要求表达式year%400==0的值得0;

⑤ 最后求值整个表达式的值得0,因此1900年不是闰年。

逻辑运算符两侧的操作数,除可以是0和非0的整数外,也可以是其他任何类型的数据,如实型、字符型等。

例如:

      float a, b ;
      a=25.28; b=52.78;
      !a                        // 结果=0
      !('A ')                   // 结果=0
      a>11.564 && b<=157.8     // 结果=1
      ('y ') && ('x ')           // 结果=1
      a ¦¦ a>31                 // 结果=1
      (b<-19.9) || (b>5)       // 结果=1

数学上的关系式0≤x≤100,在C语言中不能用关系表达式0<=x<=100来表示,表达式0<=x<=100相当于(0<=x)<=100,无论x取何值,表达式0<=x<=100的值总是1,要正确表示数学上的关系式0≤x≤100,只能用逻辑表达式0<=x&&x<=100表示。

使用逻辑表达式时,应注意逻辑表达式的“不完全计算”。下面的例子只会在等级考试中出现:

      a=0; b=1;
      c=a++ && b++;
      d=a++|| b++;

对于上述第二条语句,“由左到右”扫描表达式,根据优先级的规定,先计算表达式a++的值为0,变量a值被加1变成1,这时系统可以确定逻辑表达式0 && b++ 的值必定是0,因此不再对表达式b++求值,变量b的值不变仍为1。第二句执行后变量a,b的值均为1。

对于上述第三条语句,“由左到右”扫描表达式,根据优先级的规定,先计算表达式a++的值为1,变量a的值被加1变成2,这时系统可以确定逻辑表达式1|| b++的值必定是1,因此不再对表达式b++求值,变量b的值不变仍为1。

因此执行上述程序段后,变量a的值为2,变量b的值仍为1,变量c的值为0,变量d的值为1。

2.6.6 条件运算

条件运算要求有三个运算对象,是C语言中唯一的一个三目运算,其一般形式如下。

      判定式? 表达式1 : 表达式2

运算时先求出“判定式”的值,若“判定式”的值是非零,条件表达式的值取“表达式1”的值,若“判定式”的值为零,条件表达式的值取“表达式2”的值。例如:执行语句min=a<b?a:b后,变量min取a,b中的小者。

条件运算符的优先级高于赋值运算符,但低于关系运算符和逻辑运算符,条件运算符的结合方向为“从右到左”。

例如:

      a=1; b=2;
      c= a<b?3:b>4?5:6;

上述条件表达式等价于a<b?3:(b>4?5:6),因此变量c的值为3。注意这里不能等价于(a<b?3:b>4=?5:6,因为这个条件表达式的值为5,与原意不符。

若条件表达式的〈表达式1〉与〈表达式2〉类型不同,此时条件表达式的值的类型为二者中精度较高者的类型。

例如:

      float f, f1;
      f=(1>0?1:5)/2;
      f1=(1>0?1:5.0)/2;

程序运行后,变量f的值为0.0,而变量f1的值为0.5,这是因为条件表达式(1?1:5)的值为1,1/2为整数除,值为0,赋值给变量f=0.0;而(1?1:5.0)的值为1.0,1.0/2为实数除。

2.6.7 逗号运算

逗号运算符“,”也称为顺序求值运算符,结合性为自左至右,从左至右计算表达式,求值结果是最后一个表达式的值,逗号表达式的一般格式如下。

      表达式1 , 表达式2 , 表达式3, …, 表达式n

例如:

      int a,b,c,n;
      n=(a=10,b=20,c=a+b)

表达式求值结果:

      n=30, a=10, b=20, c=30。

逗号运算符无非是把若干个表达式“串联”起来,特别适合程序中需执行多个表达式而语法上只允许一个表达式的地方。注意:逗号有2种作用,1种作为分隔符,如上例第1行,另作为逗号运算符,如上例第2行。

2.6.8 长度运算符

长度运算符“sizeof()”是单目运算符,用于计算变量或类型所占内存字节数的大小。sizeof()运算符的有如下两种用法。

① sizeof(数据类型)计算该数据类型在内存中所占的字节数。

② sizeof(变量名)计算该变量在内存中所占的字节数。

如sizeof(int)=2,sizeof(long)=4,若有语句double d; 则sizeof(d)=8。

2.6.9 指针运算符

假设一个变量名为dd的变量,计算机内最关心的是它的地址和值,如果另外用一个变量pp保存变量dd的地址,则称变量pp为指针变量,指针变量最基本的运算符是&和*。

&——取地址运算符。它的作用是返回后随变量的内存地址,它只能用于一个具体的变量或数组元素,不能用于表达式。

*——指针运算符。它的作用是返回其后随地址(指针变量所表达的地址值)中的变量值,即取值。它的另一作用是定义指针变量。

例如:

      int *p;                // 定义一个用于保存整型变量地址的指针
      int a,b=21;            // 假定变量a地址=0x2000,b地址=0x2002
      p=&a;                 // 取变量a地址赋给指针变量p,p=0x2000
      ++p;                  // p=0x2002,增加2字节的位置
      a=*p                  // 取指针变量p所指向地址的值赋给变量a,a=21

上述程序使用指针运算符是&和*,其结果等同赋值语句“b=a”;在此,用指针运算符多此一举!但在某些场合,指针运算符可简化程序,提高运行速度。

2.6.10 复合赋值运算

C语言为简化程序,允许在“=”之前加上其他运算符,构成复合赋值运算,其用法见表2-14。

表2-14 复合赋值运算符

例如:

      int x=3,n=4;
      x+=n%3;           // 等价为x=x+(n%3),x=3+1=4
      x*=n-1;           // 等价为x=x*(n-1),x=4*(4-1)=12

2.6.11 运算符的优先级与结合性

表达式的运算规则是由运算符的功能、优先级和结合性决定的,当一个表达式有多个运算符时,优先级较高的运算符先执行,优先级较低的运算符后执行。

处于同一优先级的运算符的运算顺序称为运算符的结合性,运算符的结合性分为“从左到右”和“从右到左”两种,绝大部分运算符是按“从左到右”顺序运算的。

C语言运算符及其优先级和结合性见表2-15,共分为15级,1级最高,15级最低,除2、13、14级是“从右到左”结合外,其余均为“从左到右” 结合。圆括号()的优先级最高,除表示函数调用外,通常用于改变运算符的优先级和结合性,以满足实际需要。例如:a>b!=c+d等效于(a>b)!=(c+d),a&&b+c||a-b!=0等效于(a&&(b+c))||((a-b)!=0)。

表2-15 运算符的优先级和结合性