运算符和表达式(类型转换)

和其他程序设计语言一样,C语言中表示运算的符号称为运算符。运算符是告诉编译程序执行特定算术或逻辑操作的符号,运算的对象称为操作数。

对一个操作数进行运算的运算符称为单目运算符,对两个操作数进行运算的运算符称为双目运算符,三目运算符对三个操作数进行运算。用运算符和括号可以将操作数连接起来组成表达式。

C语言提供了40多个运算符,其中一部分跟其他高级语言相同(例如“+”、“−”、“*”等运算符),另外的与汇编语言类似,对计算机的底层硬件(如指定的物理地址)能进行访问。

C语言的运算符功能强大,除了控制语句和输入输出以外的几乎所有的基本操作都可以用运算符来处理,例如,将“=”作为赋值运算符,方括号“[]”作为下标运算符等。C语言的运算符类型如表4-1所示。

表4-1                                                            C语言运算符类型

运算符类型

说    明

运算符类型

说    明

算术运算符

+  −  *  /  %

指针运算符

*  &

关系运算符

>  <  = =  >=  <=  ! =

求字节数运算符

sizeof

逻辑运算符

!  &&  ||

强制类型转换运算符

(类型)

位运算符

<<  >>  ^  |  &  ~

分量运算符

. →

赋值运算符

= 及其扩展赋值运算符

下标运算符

[]

条件运算符

? :

其他

如函数调用运算符()

逗号运算符

,

 

 

 

下面主要介绍基本运算符的使用。

  运算符和表达式

4.2.1  算术运算符和表达式

1.算术运算符

算术运算符包括双目的加减乘除四则运算符和求模运算符,以及单目的正负运算符,如表4-2所示。

表4-2                                                              算术运算符列表

运  算  符

描    述

结  合  性

+

单目正

从右至左

单目负

从右至左

*

从左至右

/

除和整除

从左至右

%

取模(取余)

从左至右

+

双目加

从左至右

双目减

从左至右

 

算术运算符的使用示例如下:

int a=15, b=8,c;

double x=15, y=8, z;

c = a + b ;                    // c 赋值为23

c = a - b;                          // c 赋值为7

c = a * b;                      // c 赋值为120

c = a / b;                      // c 赋值为1

c = a % b;                     // c 赋值为7

z = x + y ;                         //z 赋值为23

z = x - y;                           // z 赋值为7

z = x * y ;                         // z 赋值为120

z = x / y ;                         // z 赋值为1.875000

z = x % y ;                         // 出错

这里有几点需要说明。

① “+”、“−”、“*”、“/”4种运算符的操作数,可以是任意基本数据类型,其中“+”、“−”、“*”与一般算术运算规则相同。

② 除法运算符“/”包括了除和整除两种运算,当除数和被除数都是整型数时,结果只保留整数部分而自动舍弃小数部分,注意0不能作为除数。除数和被除数只要有一个浮点数,进行浮点数相除。例如:

15 / 2 是15除以2商的整数部分7。

③ 运算符“−”除了用作减法运算符之外,还有另一种用法,即用作负号运算符。用作负号运算符时只要一个操作数,其运算结果是取操作数的负值。例如:

−(3+5)的结果是−8。

④ 取模运算就是求余数,取模运算要求两个操作数只能是整数,不能是浮点数,如5.8%2或5%2.0都是不正确的。例如:

15%2 是15除以2的余数部分1。

字符型数会自动地转换成整型数,  因此字符型数也可以参加双目运算。 例如:

     int main ()

     {

          char m, n;        /*定义字符型变量*/

          m='c';                 /*给m赋小写字母'c'*/

          n=m+'A'-'a';     /*将c中的小写字母变成大写字母'B'后赋给n*/

          ...

           return 0;

     }

上例中m='c'即m=99,由于字母A和a的ASCII码值分别为65和97。这样可以将小写字母变成大写字母。类似的道理,如果要将大写字母变成小写字母,则用c+ 'a' -'A'进行计算。

除了上述常见的几种运算符之外,C语言还提供了两个比较特殊的算术运算符:自增运算符“++”和自减运算符“−−”(关于这两个运算符在稍后的赋值运算符和表达式中会详细讲解)。

2.算术表达式

用算术运算符和括号可以将操作数连接起来组成算术表达式。例如:

a+2*b-5、18/3*(2.5+8)-'a'

在一个算术表达式中,允许不同的算术运算符以及不同类型的数据同时出现,在这样的混合运算中,要注意下面两个问题。

(1)运算符的优先级

C语言对每一种运算符都规定了优先级,混合运算中应按次序从高优先级的运算执行到低优先级的运算。算术运算符的优先级从高到低排列如下(自左向右)。

() ++  -(负号运算符)-- * / % +- (加减法运算符)

 

(2)类型转换

不同类型的数值数据在进行混合运算时,要先转换成同一类型之后再运算,C语言提供了两种方式的类型转换。

① 自动类型转换。自动转换是在源类型和目标类型兼容以及目标类型广于源类型时发生一个类型到另一类的转换。这种转换是系统自动进行的,其转换规则如图4-1所示。

 

图4-1  自动类型转换规则

其中,float型向double型的转换和char型向int型的转换是必定要进行的,即不管运算对象是否为不同的类型,这种转换都要进行。图4-1中纵向箭头表示当运算对象为不同类型时的转换方向。如int型与double型数据进行运算时,是先将int型转换为double型,再对double型数据进行运算,最后的运算结果也为double型,例如:

100-'a'+40.5

这个表达式的运算过程是这样的。

第一步,计算“100−‘a’”,先将字符数据‘a’转换为int型数据97(a的ASCII码),运算结果为3;

第二步,计算“3+40.5”,先将float型的40.5转换为double型,再将int型的3转换为double型,最后的运算结果为double型。

② 强制类型转换。利用强制类型转换运算符可以将一个表达式的运算结果转换成所需要的类型。强制类型转换的一般形式是:

(类型名)表达式

例如,(double)a将a转换成double型,(int)(x+y)将x+y的值转换成int型(注意,不能写成(int)x+y)。

当较低类型的数据转换为较高类型时,一般只是形式上有所改变, 而不影响数据的实质内容, 而较高类型的数据转换为较低类型时则可能有些数据丢失。强制类型转换一般用于自动类型转换不能达到目的的时候。例如,sum和n是两个int型变量,则sum/n的结果是一个舍去了小数部分的整型数,这个整数很可能存在较大的误差,如果想得到较为精确的结果,则可将sum/n改写为sum/(float)n或(float)sum/n。

在使用强制转换时应注意以下问题。

a.类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。   

b.无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。

示例程序如下:

#include <stdio.h>

 

int main()

{   

    float a=2.34;

    printf ("(int) a=%d, a=%f ",(int)a,a);

 

    return 0;

}

程序执行结果如下:

linux@ubuntu:~/book/ch4$ cc test.c –o test -Wall

linux@ubuntu:~/book/ch4$./test

(int) a=2, a=2.340000

将float a强制转换成int a。 float a=2.34;printf("(int)a=%d, f=%a ",(int)a, a); 本例表明,f虽强制转为int型,但只在运算中起作用,是临时的,而f本身的类型并不改变。因此,(int)f的值为2(删去了小数),而f的值仍为2.34。   

再比如我们可以(int)'A',这样转换后的结果为A的ASCII码数值,因为那块内存本来就存的那个数,只是换个形式使用而已。

字符型变量的值实质上是一个8位的整数值,因此取值范围一般是-128~127,char型变量也可以加修饰符unsigned,则unsigned char 型变量的取值范围是0~255(有些机器把char型当做unsighed char型对待, 取值范围总是0~255)。

依据上面的原则,可以对任何数据类型进行转换,但是转换的结果需要仔细分析,例如:

(int)'9'的结果为多少?

结果为'9'的ASCII值,即57。

4.2.2  赋值运算符和表达式

1.赋值运算符

(1)单纯赋值运算符“=”

在前面的讲解中,读者已多次看到了符号“=”。在C语言中,“=”不是等号,而是赋值运算符,它是个双目运算符,结合性是从右向左,其作用是将赋值号“=”右边的操作数赋给左边的操作数。

示例:

x = y;    /*将变量y的值赋给变量x(注意不是x等于y)*/

a = 28;   /* 将28赋值给变量a*/

j = j+2   /*把变量j的值加上2,并把和赋值到j中*/

 

(2)复合赋值运算符“+=”、“−=”、“*=”、“/=”

在赋值符“=”之前加上其他运算符,即构成复合的运算符。C语言规定有10种复合赋值运算符,见表4-3。“%=”、“<<=”、“>>=”、“&=”、“^=”和“|=”,这些将在后面位运算中介绍。

表4-3                                                              复合运算符列表

运  算  符

功 能 说 明

示    例

+=

加赋值复合运算符

a+=b  等价于a=a+b

-=

减赋值复合运算符

a-=b  等价于a=a-b

*=

乘法赋值复合运算符

a*=b  等价于a=a*b

/=

除法赋值复合运算符

a/=b   等价于a=a/b

%=

求余赋值复合运算符

a%=b  等价于a=a%b

&=

位与赋值复合运算符

a&=b  等价于a=a&b

|=

位或赋值复合运算符

a|=b   等价于a=a|b

^=

位异或赋值复合运算符

a^=b   等价于a=a^b

>>=

位右移赋值复合运算符

a>>=b  等价于a=a>>b

<<=

位左移赋值复合运算符

a<<=b  等价于a=a<<b

 

示例:

a += 30 等效于 a = a+30,相当于a先加30,然后再赋给a

t *= x+5 等效于 t=t*(x+5)

采用复合赋值运算符既能简化程序,又能提高编译效率。所以编写程序的时候,应尽可能地使用复合赋值运算符。在Linux内核中,也随处可见复合赋值运算符的使用,如下例就是在“/drivers/char/rtc.c”中的rtc_interrupt函数中的代码。

rtc_irq_data += 0x100;

上面语句中的变量rtc_irq_data在接收到RTC的中断后会更新数值。

2.赋值表达式

用赋值运算符将一个变量和一个表达式连接起来,就成了赋值表达式。一般形式如下。

<变量名><赋值运算符><表达式> 即:变量 = 表达式

对赋值表达式求解的过程是,将赋值运算符右侧的“表达式”的值赋给左侧的变量。赋值表达式的值就是被赋值的变量的值,如“a = 5”这个赋值表达式的值是5。

  • 赋值运算符的左边只能是一个变量名,而不能是一个常量或其他的表达式。例如,“13=b”、“a+b=15”、“j×2=100”,这些都是错误的赋值表达式。
  • 赋值运算符右边的表达式也可以为一个赋值表达式,例如,“a=(b=2)”或“a=b=2”表示变量a和b的值均为2,表达式的值为变量a的值2。此方法适合于给几个变量同时赋一个值时使用。

3.特殊的赋值运算——自增自减运算符

“++”是自增运算符,它的作用是使变量的值增加1。“−−”是自减运算符,其作用是使变量的值减少1,例如:

i = i+1

这个赋值表达式是把变量i的值加上1后再赋给i,即将变量i的值增加1。那么在这里就可以利用自增运算符简化这个赋值表达式为

i++ 或 ++i

又如:

i-- 或--i 等价i = i-1

自增运算符和自减运算符是两个非常有用的运算符,由于通常一条C语言的语句在经过编译器的处理后会翻译为若干条汇编语句,如赋值语句等会涉及多次寄存器的赋值等操作,而自增或自减语句能直接被翻译为“inc”和“dec”,因此它的执行效率比“i = i+1”或“i = i−1”更高,而且前者的写法使程序更精练。

这里有两点需要注意。

① 自增/自减运算符仅用于变量,不能用于常量或表达式。

② 自增和自减的结合方向是自右至左。

自增和自减运算符可用在操作数之前,也可放在其后,但在表达式中这两种用法是有区别的。自增或自减运算符在操作数之前,C语言在引用操作数之前就先执行加1或减1操作;运算符在操作数之后,C语言就先引用操作数的值,而后再进行加1或减1操作,例如:

j=i++;

其执行过程是:先将变量i的值赋值给变量j,再使变量i的值增1。结果是i的值为3,j的值为2。等价于下面两个语句:

j=i;

i=i+1;

再看以下示例:

j=++i;

其执行过程是,先将变量i的值增1,再把新i的值赋给变量j。结果是i=3,j=3。

该语句等价于下面两个语句:

i = i+1;

j=i;

下面是一些关于自增自减运算符的综合示例:

int  x=5, y=9, z;

z =  ++x ;                 // z赋值为6,x 变为6

z =  x++ ;                 // z赋值为5,x 变为6

z =  --x ;                 // z赋值为4,x 变为4

z =  x-- ;                  // z赋值为5,x 变为4

z= ++x+y++ ;           // z赋值为15,x 变为6,y 变为10

z= --x+y++ ;               // z赋值为13,x 变为4,y 变为10

z= ++x+y-- ;           // z赋值为15,x 变为6,y 变为8

4.赋值中的类型转换

前面已经介绍了类型转换,当赋值运算符两边的运算对象类型不同时,将要发生类型转换,转换的规则是:把赋值运算符右侧表达式的类型转换为左侧变量的类型。具体的转换如下:

(1)浮点型与整型

将浮点数(单双精度)转换为整数时,将舍弃浮点数的小数部分,只保留整数部分。将整型值赋给浮点型变量,数值不变,只是改为浮点形式, 即小数点后带若干个0。注意:赋值时的类型转换实际上是强制的。

(2)单、双精度浮点型

由于C语言中的浮点数总是用双精度表示的,所以float 型数据只是在尾部加0延长为double型数据参加运算,然后直接赋值。double型数据转换为float型时,通过截尾数来实现,截断前要进行四舍五入操作。

(3)char型与int型

int型数值赋给char型变量时,只保留其最低8位,高位部分舍弃。

char型数值赋给int型变量时,一些编译程序不管其值大小都作正数处理,而另一些编译程序在转换时,若char型数据值大于127,就作为负数处理。对于使用者来讲,如果原来char型数据取正值,转换后仍为正值;如果原来char型值可正可负,则转换后也仍然保持原值,只是数据的内部表示形式有所不同。

(4)int型与long型

long型数据赋给int型变量时,将低16位值送给int型变量,而将高16 位截断舍弃。(这里假定int型占两个字节)。 将int型数据送给long型变量时,其外部值保持不变,而内部形式有所改变。

(5)无符号整数

将一个unsigned型数据赋给一个占据同样长度存储单元的整型变量时(如:unsigned→int、unsigned long→long,unsigned short→short) ,原值照赋,内部的存储方式不变,但外部值却可能改变。

将一个非unsigned整型数据赋给长度相同的unsigned型变量时,内部存储形式不变,但外部表示时总是无符号的。赋值运算符举例如下:

#include <stdio.h>

 

int main()

{

   unsigned a,b;

   int i,j;

   a=65535;

   i=-1;

   j=a;

   b=i;

 

   printf("(unsigned)%u→(int)%d ",a,j);

   printf("(int)%d→(unsigned)%u ",i,b);

 

   return 0;

}

程序执行结果如下:

linux@ubuntu:~/book/ch4$ cc test.c –o test -Wall

linux@ubuntu:~/book/ch4$./test

(unsigned)65535→(int)-1

(int)-1→(unsigned)65535

计算机中数据用补码表示,int型最高位是符号位,为1时表示负值,为0时表示正值。如果一个无符号数的值小于32768则最高位为0,赋给 int型变量后、得到正值。如果无符号数大于等于32768,则最高位为1, 赋给整型变量后就得到一个负整数值。反之,当一个负整数赋给unsigned 型变量时,得到的无符号值是一个大于32768的值。

C语言赋值时,不管表达式的值怎样,系统都自动将其转为赋值运算符左部变量的类型。

4.2.3  逗号运算符和表达式

C语言中逗号“,”也是一种运算符,称为逗号运算符。其功能是把两个表达式连接起来组成一个表达式,其一般形式如下。

表达式1,表达式2

其求值过程是分别求两个表达式的值,并以表达式2的值作为整个逗号表达式的值。

例如:

y =(x=a+b), (b+c);

本例中,y等于整个逗号表达式的值,也就是表达式2的值,x是第一个表达式的值。对于逗号表达式还要说明3点。

① 逗号表达式一般形式中的表达式1和表达式2也可以是逗号表达式。例如“表达式1,(表达式2,表达式3)”。这样就形成了嵌套情形。

因此可以把逗号表达式扩展为以下形式“表达式1,表达式2,……,表达式n”,整个逗号表达式的值等于表达式n的值。

② 程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值。

③ 并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。

下面是关于逗号运算符的一些示例:

float  x=10.5,  y=1.8,  z=0;

 z = (x+=5, y = x+0.2) ;

 z 赋值为15.7, x赋值为15.5, y赋值为15.7

 

 z = (x=y=5, x +=1);

 z 赋值为6, x赋值为6,  y赋值为5 

 

 z = (x=5, y = 6, x+y);    

 z 赋值为11, x赋值为5,  y赋值为6

 

 z = (z=8, x=5, y = 3);    

 z 赋值为3, x赋值为5, y赋值为3

4.2.4  位运算符和表达式

1.位运算符

位运算符是指进行二进制位的运算。C语言中提供的位运算包括与(&)、或(|)、异或(^)、取反(~)、移位(“<<”或“>>”)这些逻辑操作。对汇编语言比较熟悉的读者对这些已经非常了解了,不过在此还是作一简单介绍。

(1)与运算符(&)

双目操作符,当两个位进行相与时,只有两者都为“1”时结果才为“1”,运算规则如下:

 

例如:

unsigned char x=0156, y=0xaf, z;

z = x & y;

x&y位逻辑与运算:

左运算量

0

1

1

0

1

1

1

0

右运算量

1

0

1

0

1

1

1

1

& 结果

0

0

1

0

1

1

1

0

z赋值结果为:0x2e

(2)或运算符(|)

双目操作符,当两个位进行相或时,两者中只要有一方为“1”,结果就为“1”,运算规则如下:

例如:

unsigned char x=027, y=0x75, z;

z = x | y;

x|y位逻辑或运算:

左运算量

0

0

1

0

0

1

1

1

右运算量

0

1

1

1

0

1

0

1

| 结果

0

1

1

1

0

1

1

1

z赋值结果为0x77

(3)异或运算符(^)

而当两个位进行异或时,只要两者相同,结果就为“0”,否则结果为“1”,运算规则如下:

 

例如:

unsigned char x=25, y=0263, z;

z = x ^ y ;

x|y位逻辑异或运算:

左运算量

0

0

0

1

1

0

0

1

右运算量

1

0

1

1

0

0

1

1

^ 结果

1

0

1

0

1

0

1

0

z赋值为0252。

(4)移位操作符(“<<”或“>>”)

位移位运算的一般形式:<运算量> <运算符><表达式>。其中:

<运算量>必须为整型结果数值;

<运算符>为左移位(<<)或右移位(>>)运算符;

<表达式>也必须为整型结果数值。

移位操作就是把一个数值左移或右移若干位。假如左移n位,原来值最左边的n位被丢掉,右边n位补0。如图4-2所示:

右移操作和左移操作的移动方向相反。但是需要考虑一个特殊问题,即对于有符号数而言,符号位如何处理。常用的方法有两个。

① 逻辑移位,不考虑符号问题,原数值右移n位后,左边空出的n个位置,用0填充。

② 算术移位,原数值进行了右移操作后,需要保证符号位不变,因此,右移n位后,左边空出的n个位置,用原数值的符号位填充。原来若是负数,则符号位为1,填充的位也是1;原来若是正数,则符号位为0,填充的位也是0,这样保证移位后的数据与原数正负相同。

比如,有数“10001001”,若将其右移两位,逻辑移位的结果是“00100010”,而算术移位的结果是“11100010”。若将其左移两位,则逻辑移位和算术移位的结果是“00100100”,由于在左移时,都是左边移出去的位丢弃,右边补0,因此,算术左移与逻辑左移的结果是一样的。

 

C语言标准说明对无符号数所进行的所有移位操作都是逻辑移位,但对于有符号数,到底是采用逻辑移位还是算术移位取决于编译器,不同的编译器所产生的结果有可能会不同。因此,一个程序若采用了有符号数的右移位操作,那么它是不可移植的。

关于位运算符有两点需要注意:

a.在这些移位运算符中,除了取反(~)是单目运算符,其余都是双目运算符,也就要求运算符两侧都有一个运算对象,位运算符的结合方向均为自左向右。

b.位运算符的对象只能为整型或字符型数据,不能是实型数据。

2.位表达式

将位运算符连接起来所构成的表达式称为位表达式。在位表达式中,依然要注意优先级的问题。在这些位运算符中,取反运算符(~)优先级最高,其次是移位运算符(<<和>>),再次是与(&)、或(|)和异或(^)。

在实际使用中,通常是用其进行赋值运算,因此,之前所提到的复合赋值操作符(“<<=”、“>>=”、“&=”、“^=”,“|=”)就相当常见了,比如:

a <<= 2;

就等价于:

a = a << 2;

读者应该注意到,在移位操作中,左移n位相当于将原数乘以2n,而右移n位则相当于将原数除以2n,因此,若读者希望操作有关乘除2n的操作时,可以使用移位操作来代替乘除操作。由于移位操作在汇编语言中直接有与此相对应的指令,如“SHL”、“SAL”等,因此其执行效率是相当高的,表4-4列举了常见操作的执行时间(单位:机器周期)。

表4-4                                                            基本运算执行时间

操    作

执 行 时 间

操    作

执 行 时 间

整数加法

1

浮点乘法

5

整数乘法

4

浮点除法

38

整数除法

36

移位

1

浮点加法

3

 

 

 

可以看到,乘除法(尤其是除法)操作都是相当慢的,因此若有以下两句语句:

a = (high + low)/2;

a = (high + low) >> 1;

这时,第二句语句会比第一句语句快很多。也正是由于位运算符的高效,在Linux内核代码中随处都可见到移位运算符的身影。如前面在赋值运算符中提到的有关RTC的例子中就有如下语句:

rtc_irq_data &= ~0xff;

rtc_irq_data |= (unsigned long)irq & 0xF0;

这两句语句看似比较复杂,但却是非常常见的程序写作方法,读者可以首先将复合赋值运算符展开,这样,第一句语句就成为以下形式:

rtc_irq_data = rtc_irq_data & ~ 0xff;

这时,由于取反运算符的优先级较高,因此,就先进行对0xff的取反操作,这就相当于为“~0xff”加上了括号,如下所示。

rtc_irq_data = rtc_irq_data & (~ 0xff);

再接下来的步骤就比较明朗了,rtc_irq_data先与0xff取反的结果0xffffff00相与,再将运算结果的值赋给rtc_irq_data变量本身。读者可以按照这种方法来分析第二条语句。

4.2.5  关系运算符和表达式

1.关系运算符

在程序中经常需要通过比较两个值的大小关系来决定程序下一步的工作。比较两个值的运算符称为关系运算符。关系运算符对两个表达式进行比较,返回一个真/假值。在C语言中的关系运算符,见表4-5。

表4-5                                                                  关系运算符

运  算  符

功 能 说 明

示    例

大于

a > b  或 a > 5

>=

大于等于

a >= b或 a .= 5

小于

a < b  或 a < 5

<=

小于等于

a <= b 或 a <= 5

==

等于

a == b或 a == 5

!=

不等于

a!=b  或 a != 5

 

关系运算符都是双目运算符,其结合性均为左结合。关系运算符的优先级低于算术运算符,高于赋值运算符。

示例如下:

int  a=5, b=6;

a>(b-1)      结果值为0 

(a +1)== b    结果值为1

a>=(b-2)     结果值为1

a<100        结果值为1

(a+3)<=b     结果值为0

a != (b-1)    结果值为0

在这6个关系运算符中,“<”、“<=”、“>”、“>=”的优先级相同,高于“= =”和“!=”,“= =”和“!=”的优先级相同。根据优先级的关系,以下表达式具有等价的关系。

c>a+b       和           c>(a+b)

a>b==c     和           (a>b)==c

a=b>c       和           a=(b>c)

严格区分“= =”和“=”。“= =”为关系运算符,判断两个数值是否相等;

“=”为赋值运算符。为了防止书写错误,可以参考这样的写法:if (1 == a) {…}

2.关系表达式

用关系运算符将两个表达式(可以是各种类型的表达式)连接起来的表达式,称为关系表达式,关系表达式的一般形式如下。

表达式 关系运算符 表达式

以下表达式都是合法的关系表达式。

a+=b>c;

x>y;

'a'+1<c;

-i-5*j==k+1;

由于表达式又可以是关系表达式,因此也允许出现嵌套的情况,例如:

a>(b>c),a!=(c==d)

关系表达式的值只有两种,即“真”和“假”,分别用“1”和“0”表示。一般说,0为假,非0为真。

#inclue <stdio.h>

 

int main()

{  

   int a = 5;

   if (a)

      printf(“true ”);

   else

      printf(“false ”);

   if (a >= 6)

      printf(“>=6 ”);

   else

      printf(“<6 ”);

 

   return 0;

}

程序执行结果如下:

linux@ubuntu:~/book/ch4$ cc test.c –o test -Wall

linux@ubuntu:~/book/ch4$./test

true

<6

由于在C语言中,并不存在bool(布尔)类型的值,因此,C语言程序员已形成惯例,用“1”代表真,用“0”代表假。

另外,用户还可以通过typedet来自定义bool类型,如下所示。

typedef unsigned char bool;

#define TRUE 1

#define FALSE 0

这样,在之后的使用时就可以用bool来定义变量,用TRUE和FALSE来判断表达式的值了。

也可以引入头文件stdbool.h,示例代码如下:

#include <stdio.h>

#include <stdbool.h>

 

int main()

{

    bool a = true;

    int x = 5;

   

    a = x > 3;

    if (a)

        printf("true ");

    else

        printf("false ");

 

    return 0;

}

4.2.6  逻辑运算符和表达式

1.逻辑运算符

C语言中提供了3种逻辑运算符:与运算符(&&)、或运算符(||)和非运算符(!),其中与运算符(&&)和或运算符(||)均为双目运算符,具有左结合性;非运算符(!)为单目运算符,具有右结合性。下面具体介绍这三种运算符。

(1)逻辑与运算符(&&)

双目运算符,只有两个运算量都是1时,运算结果才为1,具体运算规律如下:

例如:

    int  x=5, y=18;

    (x >= 5) && (y < 20)                结果值为1

    ((x+1) >= 0) && (y < 17)        结果值为0

    ((x-8) >= 0) && (y == 18)      结果值为0

    ((x-5) > 0)  && (y != 18)       结果值为0

(2)逻辑或运算符(||)

当两个运算量进行或运算时,只要有一个运算量为“1”,结果就为“1”, 具体运算规律如下:

例如:

    ((x >= 5))   ||  (y < 20)        结果值为1

    ((x+1)>= 0) ||  (y < 17)         结果值为1

    ((x-8) >= 0) ||  (y == 18)      结果值为1

    ((x-5) > 0)  ||  (y != 8)        结果值为0

(3)非运算符(!)

单目运算符,当运算量进行非运算,结果会取反,具体运算规则为

读者可以看到,逻辑运算符和位运算符(尤其是与或运算符)有很大的相似性。为了使读者更好地理解逻辑运算与位运算的区别,这里对逻辑运算的概念再做解释。

逻辑运算是用来判断一件事情是“对”的还是“错”的,或者说是“成立”还是“不成立”,判断的结果是二值的,即没有“可能是”或者“可能不是”,这个“可能”的用法是一个模糊概念。

在计算机里面进行的是二进制运算,逻辑判断的结果只有两个值,称这两个值为“逻辑值”,用数的符号表示就是“1”和“0”。其中“1”表示该逻辑运算的结果是“成立”的,如果一个逻辑运算式的结果为“0”,那么这个逻辑运算式表达的内容“不成立”。

【例】

通常一个教室有两个门,这两个门是并排的。要进教室从门A进可以,从门B进教室也行,用一句话来说是“要进教室去,可以从A门进‘或者’从B门进”。

这里,可以用逻辑符号来表示这一个过程:能否进教室用符号C表示,教室门分别为A和B。C的值为“1”表示可以进教室,为“0”表示进不了教室。A和B的值为“1”时表示门是开的,为“0”表示门是关着的,那么它们之间的关系就可以用表4-6来表示。

表4-6                                                                示例逻辑关系

说    明

C

A

B

两个教室的门都关着,进不去教室

0

0

0

门B是开着的,可以进去

1

0

1

门A是开着的,可以进去

1

1

0

门A和B都是开着的,可以进去

1

1

1

 

把表中的过程写成逻辑运算就是:

C = A || B

这是一个“或”运算的逻辑表达式。这个表达式要表达的就是:如果要使得C为1,只要A“或”B其中之一为“1”即可。所以“||”运算称为“或”运算。

【例】

假设一个房间外面有一个阳台,那么这个房间就纵向开着两个门,要到阳台去,必须要过这两个门,很明显这两个门必须都是开着的才行,否则只要其中一个门关着就去不了阳台。

这时,同样使用逻辑符号C来表示是否能去阳台,A和B表示A、B门是否打开,那么它们之间的关系就可以用表4-7来表示。

表4-7                                                                示例逻辑关系

说    明

C

A

B

两个门都关着,去不了阳台

0

0

0

门A关着,去不了阳台

0

0

1

门B关着,去不了阳台

0

1

0

门A与门B都开着,可以去阳台

1

1

1

 

把表中的过程写成逻辑运算式就是:

C = A && B

从上面的两例中可以看出,在逻辑表达式里有参加逻辑运算的逻辑量和逻辑运算最后的结果(逻辑值),把这两个概念区分开来和记住它们是很重要的。

什么是逻辑量呢?凡是参加逻辑运算的变量、常量都是逻辑量,例如上例中的A、B。而逻辑值则是逻辑量、逻辑表达式其最后的运算结果的值。下面两条规则在逻辑表达式中是非常重要的。

① 逻辑值只有“0”和“1”两个数,其中“1”表示逻辑真(成立),“0”表示逻辑假(不成立)。

② 一切非“0”的逻辑值都为真。例如:−1的逻辑值为真(1),5的逻辑值为真(1)。

表4-8列出了逻辑运算的真值表。

表4-8                                                              逻辑运算真值表

a

b

!a

!b

a&&b

a||b

2.逻辑表达式

逻辑表达式的一般形式如下。

表达式 逻辑运算符 表达式

其中的表达式也可以是逻辑表达式,从而组成了嵌套的情形。

在这里,首先要明确的还是优先级的问题,逻辑运算符和其他运算符优先级的关系如图4-3所示。

由以上优先级的顺序可以看出:

a>b && c>d等价于(a>b) && (c>d)

!b==c||d<a等价于((!b)==c)||(d<a)

a+b>c && x+y<b等价于((a+b)>c) && ((x+y)<b)

逻辑表达式的值是式中各种逻辑运算的最后值,以“1”和“0”分别代表“真”和“假”。

例如

int  x=1, y=0, z=0;

x>0  && ! (y==3) || z>5         运算结果数值为 1

! (x+1>0) &&  y==0 || z>0       运算结果数值为 0

x<0 || y==0 && z>0               运算结果数值为 0

4.2.7  sizeof操作符

sizeof是一个单目运算符,它的运算对象是变量或数据类型,运算结果为一个整数。运算的一般形式如下:

sizeof(<类型或变量名>) 

 

它只针对数据类型,而不针对变量!

 

 

若运算对象为变量,则所求的结果是这个变量占用的内存空间字节数;若运算对象是数据类型,则所求结果是这种数据类型的变量占用的内存空间字节数。

sizeof是一个使用频率很高的操作符,经常用来获取变量或数据类型所占用的内存空间的大小,下面的程序显示了sizeof的用法。

#include <stdio.h>

 

struct Student

{

        int number;

     char name[8];

};

enum season{

     spring,s ummer, fall, winter

};

 

int main()

{

    int a = 10;

    float b = 3.5;

    struct Student s1 = {1, “zhangsan”};

    enum season myseason;

   

    printf ("the size of char is %d bytes ",sizeof(char));

    printf ("the size of short is %d bytes ",sizeof(short));

    printf ("the size of int is %d bytes ",sizeof(int));

    printf ("the size of a is %d bytes ",sizeof(a));

    printf ("the size of long is %d bytes ",sizeof(long));

    printf ("the size of long long is %d bytes ",sizeof(long long));

    printf ("the size of float is %d bytes ",sizeof(float));

    printf ("the size of b is %d bytes ",sizeof(b));

    printf ("the size of double is %d bytes ",sizeof(double));

    printf ("the size of struct Student is %d bytes ",sizeof(struct Student));

    printf ("the size of enum season is %d bytes ", sizeof (enum season));

    printf ("the size of myseason is %d bytes ", sizeof (myseason));

 

     return 0;

}

程序执行结果如下:

linux@ubuntu:~/book/ch4$ cc test.c –o test -Wall

linux@ubuntu:~/book/ch4$./test

the size of char is 1 bytes

the size of short is 2 bytes

the size of int is 4 bytes

the size of a is 4 bytes

the size of long is 4 bytes

the size of long long is 8 bytes

the size of float is 4 bytes

the size of b is 4 bytes

the size of double is 8 bytes

the size of struct Student is 12 bytes

the size of enum season is 4 bytes

the size of myseason is 4 bytes

从该结果中,可以清楚地看到不同数据类型及变量所占的字节数,读者应该熟悉这些结果。还可以看到,变量所占用的空间,由其数据类型决定,与变量的值没有关系。

4.2.8  条件运算符

条件运算符(?)是C语言中唯一一个三目运算符,它可以提供如if-then-else语句的简易操作,其运算的一般形式如下。

<表达式1>  ?  <表达式2>  :  <表达式3>

操作符“?”作用是这样的:先计算表达式1的逻辑值,如果其值为真,则计算表达式2,并将数值结果作为整个表达式的数值;如果表达式1的逻辑值为假,则计算表达式3,并以它的结果作为整个表达式的值,其执行过程如图4-4所示。

 

图4-4  条件操作符的执行过程

条件运算符的优先级高于赋值运算符,读者可以自行分析一下以下语句的含义。

max = (a>b)?a:b

由于条件运算符的优先级高于赋值运算符,因此,先计算赋值语句的右边部分。

当a大于b为真(即a大于b)时,条件表达式的值为a;当a大于b为假(即a大于b不成立)时,条件表达式的值为b。因此,max变量的值就是a和b中较大的值(若a与b相等时取b)。

相当于下面的语句:

if (a > b)

  max = a; 

else

  max = b;

示例代码如下:

   int  x=82, y=101, z;

   z = x >= y  ? x+18 : y-100      //z为1

   z = x < (y-11) ? x-22 : y-1    //z为60

4.2.9  运算符优先级总结

C语言中的优先级一共分为15级,1级最高,15级最低。在有多个不同级别的运算符出现的表达式中,优先级较高的运算符将会先进行运算,优先级较低的运算符后运算。另外,如果在一个运算对象两侧的运算符的优先级相同时,则按运算符的结合性所规定的结合方向来进行处理。

C语言的结合性有两种,即左结合性和右结合性。若为左结合性,则该操作数先与其左边的运算符相结合;若为右结合性,则该操作数先与其右边的运算符相结合。

因此,对于表达式“x−y+z”,读者可以看到y的左右两边的操作符“−”和“+”都为同一级别的优先级,而它们也都具有左结合性,因此,y就先与“−”相结合,故在该表达式中先计算“x−y”。

表4-9列举了C语言中的运算符的优先级和结合性。

表4-9                                                       运算符的优先级和结合性

优  先  级

运  算  符

含    义

运算对象个数

结 合 方 向

1

()

圆括号

 

自左向右

[]

下标运算符

−>

指向结构体成员运算符

.

结构体成员运算符

2

!

逻辑非运算

1(单目)

自右向左

按位取反运算

++

自增运算符

− −

自减运算符

负号运算符

(类型)

类型转换运算符

*

指针运算符

&

地址运算符

sizeof

长度运算符

续表   

优  先  级

运  算  符

含    义

运算对象个数

结 合 方 向

3

*

乘法运算符

2(双目)

自左向右

/

除法运算符

%

求余运算符

4

+

加法运算符

减法运算符

2(双目)

自左向右

5

<< 

左移运算符

2(双目)

自左向右

>> 

右移运算符

6

关系运算符

2(双目)

自左向右

<=

>=

7

= =

等于运算符

不等于运算符

2(双目)

自左向右

!=

8

&

按位与运算符

2(双目)

自左向右

9

^

按位异或运算符

2(双目)

自左向右

10

|

按位或运算符

2(双目)

自左向右

11

&&

逻辑与运算符

2(双目)

自左向右

12

||

逻辑或运算符

2(双目)

自左向右

13

?:

条件运算符

3(三目)

自右向左

14

=

+=

−=

*=

/=

%=

>>=

<<=

&=

^=

|=

赋值运算符

2(双目)

自右向左

15

逗号运算符

 

自左向右

 

这些运算符的优先级看起来比较凌乱,表4-10所示为一个简单易记的口诀,可以帮助记忆。

表4-10                                                        运算符的优先级口诀

口    诀

含    义

括号成员第一

括号运算符[]、()成员运算符.、−>

全体单目第二

所有的单目运算符,比如++、−−、+(正)、−(负)等

乘除余三,加减四

这个“余”是指取余运算即%

移位五,关系六

移位运算符:<<、>>,关系:>、<、>=、<=等

等于(与)不等排第七

即= =、!=

位与异或和位或“三分天下”八九十

这几个都是位运算: 位与(&)异或(^)位或(|)

逻辑或跟与

十二和十一

逻辑运算符:||和&&

注意顺序:优先级(||)低于优先级(&&)

续表   

口    诀

含    义

条件高于赋值

三目运算符优先级排到14位只比赋值运算符和“,”高,需要注意的是赋值运算符很多

逗号运算级最低

逗号运算符优先级最低

 

堆于结合性的记忆比较简单,可以注意到,大多数运算符的结合性都是自左向右的,唯独单目运算符、条件运算符和赋值运算符是自右向左。

int  x=1, y=0, z=0

 

x>0  &&  ! (y==3)  ||  z>5

运算结果数值为 1

 

! (x+1>0)  &&  y==0  ||  z>0

运算结果数值为 0

 

x<0  ||  y==0  &&  z>0

运算结果数值为 0

 

x += y==z, y=x+2, z=x+y+x >0

x赋值位2,y赋值为4 ,z赋值为1