荡胸生层云:C语言开发修行实录
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 运算符和表达式,选择你的生活方式

璞璞

有了变量和常量就能完成我们需要的数据处理吗?当然不可能!还需要对它们进行必要的运算处理,才能实现特定的功能,这在C语言中是通过运算符和表达式来实现的。通过专用的运算符和表达式,可以实现对变量和常量的处理,以实现现实中的项目需求。

3.1 引出问题

9月11日,6:00,天气晴

今天我看了一个有趣的故事:有两个有为青年A和B,大学毕业后进入了同一家公司上班,拿着同样的工资!A头脑聪明灵活,花钱如流水,天天酒场混。B朴实木讷,不爱交朋友,下班后老老实实地待在家里。两年后A依旧骑着自行车上下班,B靠辛苦攒下的钱买了一辆捷达汽车。3年后A升任了公司副总,B还是小职员……

看完这个故事,有一种莫名的冲动扰乱了我的心扉。人天性不同,会造就不同的人生。A虽然现在贫困依旧,但是升职了,他的未来可能会海阔天空;B勤俭生活,靠自己的努力买了车。我们不能说谁成功谁失败,只能说每个人的追求不同,如图3-1所示!

图3-1 不同的生活方式

9月11日,7:00,天气晴

我:“我知道运算符和表达式是C语言中的重要组成部分,运算符是一种运算方式,表达式是由运算符和数据组成的一个式子!”

KNOWALL:“运算符和表达式也是一个方式,像你的生活方式一样,它是对程序进行处理的处理方式。通俗一点讲,运算符和表达式就是加减乘除之类的运算符号,需要使用加法时就用‘+’,需要使用减法时就用‘-’。同一个项目可能会通过多种方法来实现,例如1+1等于2,1×2也等于2,此时就需要程序员根据项目的需求来决定。”

我:“原来如此!”

KNOWALL:“要想处理复杂的问题,就必须使用运算符和表达式。我给你出一个问题:提示用户输入性别和年龄,系统输出该用户是否退休,提示时要求显示对应的女士或先生,如图3-2所示。”

图3-2 运行效果

3.2 人生有多种选择

9月11日,7:10,晴

运算符犹如生活方式,有很多种可以供我们选择。我们可以选择颓废的生活,整天醉生梦死;也可以选择积极向上的生活,随时准备用梦想的翅膀展翅高飞!但是你要实现你的最终目标,必须经过多次生活方式的洗礼后才能实现,如图3-3所示。

图3-3 多次生活方式的洗礼

我:“C语言中有很多运算符和表达式吗?”

KNOWALL:“C语言中运算符和表达式数量之多,在高级语言中是少见的。正是丰富的运算符和表达式使C语言功能十分完善,这也是C语言的主要特点之一。”

3.2.1 运算符的种类

C语言的运算符有如下10类。

(1)逻辑运算符:用于逻辑运算。包括与(&&)、或(||)、非(!)共3种;

(2)位操作运算符:参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)共6种;

(3)算术运算符:用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)共7种;

(4)关系运算符:用于比较运算。包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(!=)共6种;

(5)赋值运算符:用于赋值运算,分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)三类共11种;

(6)条件运算符:这是一个三目运算符,用于条件求值(?:);

(7)逗号运算符:把若干表达式组合成一个表达式(,);

(8)指针运算符:分为取内容(*)和取地址(&)两种运算;

(9)求字节数运算符:用于计算数据类型所占的字节数(sizeof);

(10)特殊运算符:有括号(),下标[],成员(→,.)等几种。

例如下面的都是运算符:

    a+b
    (a*4)/c
    (x+r)*9-(a+b)/6
    ++I
    sin(x)+sin(y)
    (++i)-(j++)+(k--)
    a+=5

1.强制类型转换运算符

所谓强制类型转换运算符,即把表达式的运算结果强制转换为类型说明符所表示的类型。其一般使用格式如下:

    (类型说明符)  (表达式)

例如,下面都是强制转换代码:

    (float) a                              //把a转换为实型
    (int)(x+y)                             //把x+y的结果转换为整型

2.自增、自减运算符

自增、自减是一个全自动的过程,此类运算符主要包括如下两种形式。

● 自增1运算符:标记为“++”,其功能是使变量的值自增1。

● 自减1运算符:标记为“--”,其功能是使变量值自减1。

自增1和自减1运算符均为单目运算,都具有右结合性。可有以下4种常用形式。

(1)++i:i自增1后再参与其他运算。

(2)--i:i自减1后再参与其他运算。

(3)i++:i参与运算后i的值再自增1。

(4)i--:i参与运算后,i的值再自减1。

在理解和使用上容易出错的是i++和i--。特别是当它们出现在较复杂的表达式或语句中时,常常很难区分,因此应仔细分析。看下面的代码:

      main(){
        int i=8;
        printf("%d\n",++i);
        printf("%d\n",--i);
        printf("%d\n",i++);
        printf("%d\n",i--);
        printf("%d\n",-i++);
        printf("%d\n",-i--);
        }

在上述代码中,i的初值为8,第3行i加1后输出应为9;第4行减1后输出应为8;第5行输出i为8之后再加1(i为9);第6行输出i为9之后再减1(i为8);第7行输出-8之后再加1(i为9),第8行输出-9之后再减1(i为8)。

3.2.2 运算符的优先级

9月11日,7:20,晴

如果决定将来要过一种舒心安乐的生活,就注定现在要开始努力奋斗,辛勤工作,为将来打好物质基础!如图3-3的生活方式洗礼就有第一步、第二步和第三步的先后顺序!

我:“C运算符也有优先级吗?”

KNOWALL:“当然有,就和数学中的加减乘除优先级的原理是一样的!C运算符优先级共分为15级:1级最高,15级最低。在表达式中,优先级较高的先于优先级较低的进行运算。而在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理。如果属于同级运算符,则按照运算符的结合性方向来处理。”

C语言中各运算符的结合性可以分为如下两种。

● 左结合性:自左至右

● 右结合性:自右至左

例如,算术运算符的结合性是自左至右,即先左后右。如有表达式x-y+z则y应先与“-”号结合,执行x-y运算,然后再执行+z的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向称为“右结合性”。最典型的右结合性运算符是赋值运算符。如x=y=z,由于“=”的右结合性,应先执行y=z再执行x=(y=z)运算。

注意

C语言运算符中有不少为右结合性,应注意区别,以避免理解错误。

C语言运算符优先级的具体说明如表3-1所示。

表3-1 C语言运算符优先级

3.3 算术运算符拨慢时钟的运转

9月11日,7:30,晴

可以将人生看成一个时钟在滴滴答答地运转,程序员的生活方式可以看做是一个个时间数字的加减。随时关注时间和自己项目的进展,加班时恨不能将时钟拨慢!

我:“算术运算符和算术表达式肯定和数学运算有关!”

KNOWALL:“算术表达式是用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子;而算术表达式是由算术运算符和括号连接起来的式子。”

C语言中的算数运算符有以下7种类型。

● +:加,一目取正

● -:减,一目取负

● *:乘

● /:除

● %:取模

● --:减1

● ++:加1

3.3.1 最简单的单目运算符

只有一个运算对象,称为单目运算符。C语言中的单目运算符有++(自增1,运算对象必须为变量),--(自减1,运算对象必须为变量),+(取正),-(取负),共对应4种运算。例如,-a是对a进行的负操作。

测试1:定义各个变量,并对变量进行各种类型的算术操作。

解决思路:我的设计思路如下所示。

(1)定义两个变量并分别赋值;

(2)分别对这两个变量实现自增、自减运算,并输出运算结果。

编写程序文件sole.c,具体代码如下:

    #include <stdio.h>
    void main()
    {
      int a=8,b;                           //声明两个整型变量
      b=a++;                               //将变量a放在自增符号前
      printf("a++=%d\n",b);                //输出结果
      a=2;                                 //还原变量a的值
      b=++a;                               //将变量a放在自增符号后
      printf("++a=%d\n",b);                //输出结果
      a=2;                                 //还原变量a的值
      b=a--;                               //将变量a放在自减符号前
      printf("a--=%d\n",b);                //输出结果
      a=2;                                 //还原变量a的值
      b=--a;                               //将变量a放在自减符号后
      printf("--a=%d\n",b);                //输出结果
        getch();
    }

编写代码完毕后,编译执行后的效果如图3-4所示。

图3-4 运行结果

注意

一般的算术运算符的结合顺序都是“从左往右”的,但是自增和自左运算符的方向却是“从右向左”的。特别是当++和--与它们同级的运算符一起运算时,一定要注意它们的运算顺序。例如-m++,因为负号-和++是属于同级运算符,所以一定要先计算++,然后再计算取负-。单目运算符在日常开发中经常用到,特别是++和--。

3.3.2 双目运算符

9月11日,7:50,晴

我:“双目运算符是指可以有两个操作数进行操作的运算符。”

KNOWALL:“对,C语言中的双目运算符有5种,分别是:+(加),-(减),*(乘),/(除),%(取模或取余)。”

测试2:使用求模运算符,获取任意小于10000正整数的个位、十位、百位和千位的数字。

解决思路:我的设计思路如下所示。

(1)提示输入一个小于10000的正整数;

(2)分别获取输入数字的千位、百位、十位、个位的值;

(3)输出获取的结果。

编写程序文件mold.c,具体代码如下:

    #include <stdio.h>
    void main() {
      unsigned int number,i,j,k,m;
      printf("请输入一个0-10000的整数 :");         //提示用户输入一个小于10000正整数
      scanf("%d",&number);                         //获取用户输入的数
      i=number/1000;                               //求该数的千位数字
      j=number%1000/100;                           //求该数的百位数字
      k=number%1000%100/10;                        //求该数的十位数字
      m=number%1000%100%10;                        //求该数的个位数字
      printf("%d,%d,%d,%d\n",i,j,k,m);            //输出结果
    getch();
    }

编写代码完毕后,编译运行后先提示在屏幕中提示输入一个小于10000的正整数,输入并按【Enter】键将分别输出输入数字的千位、百位、十位和个位对应的数字,如图3-5所示。

图3-5 输入“1234”的运行结果

3.4 赋值运算符和赋值表达式

9月11日,8:00,晴

我已经选择了计算机行业,并立志成为一名优秀的程序员。我已经做好了程序员所面对的一些问题:每天面对计算机并承受着辐射,为赶项目工期而加班到深夜,随时准备学习更新的技术……

我:“赋值就是给变量一个值吧!”

KNOWALL:“对,将某一个数值赋给某个变量的过程就称为赋值,C语言的赋值运算符包括基本赋值运算符和复合赋值运算符两种。赋值就像吃饭一样,中午决定吃什么,晚上决定吃什么,都是具体的!”

我:“你就知道吃!”

KNOWALL:“有人说程序员得具有金刚不坏之身,才能应付种种压力!其实我的秘诀是吃,我很注重三餐营养。一日三餐食物要品种多样,各品种之间的比例要均衡,不可偏废某些品种。蔬菜、禽肉、豆类、鱼类、水果、奶类等不可或缺。”

图3-6 程序员要应对各种压力

3.4.1 基本赋值运算符

C语言的基本赋值运算符记是“=”,由“=”连接的式子称为赋值表达式。其一般使用格式如下:

    变量=表达式

如下所示都是基本赋值处理:

    x=m+n
    w=sin(m)+sin(n)
    y=i+++--j

赋值表达式的功能是计算表达式的值再赋予左边的变量,赋值运算符具有右结合性。所以a=b=c=5可理解为a=(b=(c=5))。

在其他高级语言中,赋值构成了一个语句,称为赋值语句。而在C语言中,把“=”定义为运算符,从而组成赋值表达式。凡是表达式可以出现的地方均可出现赋值表达式。例如下面的式子是合法的:

    x=(a=4)+(b=2)

上述代码的功能是把4赋予a,2赋予b,再把a与b相加,将它们的和赋予x,所以x值为6。

在日常赋值处理应用中,如果赋值运算符两边的数据类型不相同,系统将自动进行类型转换,即把赋值号右边的类型换成左边的类型。具体规则如下。

(1)整型赋予实型:数值不变,但将以浮点形式存放,即增加小数部分(小数部分的值为0);

(2)实型赋予整型:要舍去小数部分;

(3)字符型赋予整型:因为字符型为一个字节,而整型为两个字节,所以要将字符的ASCII码值放到整型量的低八位中,高八位为0。整型赋予字符型,只把低八位赋予字符量。具体来说有如下两种情况。

● 如果所用系统将字符处理为无符号的量或对unsigned char型变量赋值,则将字符数据的8位放到整型变量低8位中,高8位补0;

● 如果所用系统(如Turbo C)将字符处理为带符号的(即signed char),若字符最高位为0,则整型变量高8位补0;若字符最高位为1,则高8位全补1。这称为“符号扩展”,这样做的目的是使数值保持不变,如变量C(字符'\376')以整数形式输出为-2,i的值也是-2。

(4)float型数据赋给Double型数据时:数值不变,有效位扩展到16位。

(5)将一个int、short、long型数据赋给一个char型变量:只将其低8位原封不动地送到char型变量(即截断)。例如下面的赋值:

    int i=200;
    char c='a';
    c=i;

(6)double型数据赋给float型数据:只截取其前面的7位有效数字,存放在float变量的存储单元中,但是数值不能溢出。例如下面代码将会产生溢出错误:

    float f;
    double d=123.456789e100;
    f=d;

(7)将unsigned int型数据赋给long int型变量:不存在符号扩展问题,只需将高位补0即可。

(8)将带符号的整型数据(int型)赋给long型变量时:要进行符号扩展,将整型数的16位送到long型低16位中,如果int型数据为正值(符号位为0),则long型变量的高16位补0;如果int型变量为负值(符号位为1),则long型变量的高16位补1,以保持数值不改变。

(9)将一个unsigned类型数据赋给一个占字节数相同的整型变量,例如unsigned int=>int,unsigned long=>long,unsigned short=>short;将unsigned型变量的内容原样送到非unsigned型变量中,但如果数据范围超过相应整型的范围,则会出现数据错误。例如下面的赋值代码:

    unsigned int a=65535;
    int b;
    b=a;

(10)将非unsigned型数据赋给长度相同的unsigned型变量:也是原样照赋,连原有的符号位也作为数值一起传送。例如下面的代码将有符号数据传送给无符号变量:

    main(){
      unsigned a;
      int b=-1;
      a=b;
      printf("%u",a);
    }

因为“%u”是输出无符号数时所用的格式符,所以运行后a结果为65535。但是如果是下面的赋值:

    main(){
        unsigned int a;
        int b=-1;
        a=b;
        printf("%u",a);
    }

因为unsigned int的范围是0~65535,int型数据-1超出了int型的范围,所以结果数据发生错误。

测试3:将字符型数据或数值型数据赋给不同的数值型和字符型变量。

解决思路:我的设计思路如下所示。

(1)声明变量a、b和c;

(2)声明实型变量和字符型变量;

(3)将字符型数据或数值型数据赋给不同的数值型和字符型变量,并分别输出结果。

编写实现文件ji.c”,具体代码如下:

    #include <stdio.h>
    void main(){
      int a,b,c,d=-11;                         //声明整型变量
      unsigned int e,f=12355;                  //声明无符号整型变量
      float x,y=9.1230;                        //声明实型变量
      char c1,c2='z';                          //声明字符型变量
      a=y;                                     //将实型数据赋给整型变量
      x=d;                                     //将整型数据赋给实型变量
      b=c2;                                    //将字符型数据赋给整型变量
      c1=d;                                    //整型数据赋给字符型变量
      c=f;                                     //错误,无符号整型65535赋给整型变量
      e=d;                                     //错误,负整型-10赋给无符号整型
      //显示结果
      printf("\na=%d,b=%d,c=%d,e=%u,x=%f,c1=%c",a,b,c,e,x,c1);
      getch();
    }

编译运行后将分别输出赋值处理后的变量值,如图3-7所示。

图3-7 执行效果

3.4.2 复合赋值运算符

9月11日,8:20,晴

我:“为什么要用复合赋值运算符?”

KNOWALL:“为了简化程序并提高编译效率,C语言允许在赋值运算符‘=’之前加上其他运算符,这样就构成了复合赋值运算符。”

复合赋值运算符的功能是对赋值运算符左、右两边的运算对象进行指定的算术运算符运算,再将运算结果赋予右边的变量。复合赋值运算符的格式如下:

    算术运算符=

例如下面的都是复合赋值运算符处理:

    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+1;等价于a=a*(b+1);。

3.4.3 赋值表达式

9月11日,8:30,晴

我:“为什么要用赋值表达式?”

KNOWALL:“用赋值运算符将运算对象连接而成的式子称为赋值表达式。例如:k=(j=1);由于赋值运算符的结合性是从右向左的,因此该赋值表达式等价于k=j=1。”

下面是赋值表达式的例子:

    int k,a=1,j=5;
    a+=j++;        /*a被赋值为5,j的值改变为6*/
    a=20+(j=7);    /*a被赋值为27*/
    a=(j=9)+(k=7); /*a被赋值为16*/

赋值表达式也遵循运算优先级和转换规则,例如a=2,那么a+=a-=a*a的计算过程如下:

首先,从右向左顺序计算a-=a*a,即a=a-a*a=2-2*2=-2,即此时a=-2,然后a+=-2,即a=a+(-2)=-2-2=-4。

测试4:使用基本赋值表达式实现运算处理

解决思路:我的设计思路如下所示。

(1)分别定义了3个变量a、b和c;

(2)为变量a、b和c初始赋值为20,并将a和c的和赋值为a;

(3)将变量b和c的积赋给b;

(4)通过“printf("a=%d,b=%d,c=%d\n",a,b,c)”将输出当前的3个变量值;

(5)通过“printf("a+=b*=b-c is %d\n",a+=b*=b-c)”输出a+=b*=b-c表达式的值,具体是先运算b*=b-c,再运算a+=b;

(6)通过“printf("(a=(b=4)+(c=6)) a=%d \n",a=(b=4)+(c=6))”输出a=(b=4)+(c=6)的值,具体是先进行b=4和c=6运算,然后进行b+c运算,最后进行a=b+c运算。

编写程序文件fuzhi.c,具体实现代码如下:

    #include <stdio.h>
    void main() {
      int a,b,c;
      a=b=c=20;
      a+=c;
      b*=c;
      printf("a=%d,b=%d,c=%d\n",a,b,c);
      printf("a+=b*=b-c is %d\n",a+=b*=b-c);
      printf("(a=(b=4)+(c=6)) a=%d \n",a=(b=4)+(c=6));
    }

编写代码完毕,编译执行后的效果如图3-8所示。

图3-8 编译执行后的结果

3.5 关系运算下的男女关系

9月11日,9:00,晴

自古英雄难过美人关,美人也难过英雄魂!风花雪月和唯美浪漫的故事永远是史书、小说和影视的题材主旋律。人生也必经历恋爱、结婚、生子、携手夕阳红的过程。

我:“关系运算我还是第一次听说!”

KNOWALL:“关系运算表示了一种关系,例如男女之间有朋友关系、同学关系、同事关系。关系运算也叫比较运算,是一种很重要的运算方式。”

我:“我最近比较关心女友何处寻,哈哈!”

KNOWALL:“你是情窦初开的年龄,这很正常,我在这里给你好好上一课!男女关系是一个四则运算的关系,分为4个阶段,如图3-9所示。”

图3-9 男女关系的四则运算

关系运算有两类:一类是传统的集合运算(并、差、交等),另一类是专门的关系运算(选择、投影、连接等),有些查询需要几个基本运算的组合,要经过若干步骤才能完成。

1.关系运算符

C语言提供如下6种关系运算符。

● <:小于

● <=:小于等于

● >:大于

● >=:大于等于

● = =:等于

● !=:不等于

上述关系运算符的优先级低于算数运算符,高于赋值运算符。其中<、<=、>和>=是同级的,而==和!=是同级的,并且前4种的优先级高于后两种。

2.关系表达式

关系表达式就是用关系运算符将两个表达式连接起来的式子,被连接的表达式可以是算数表达式、关系表达式、逻辑表达式、赋值表达式和字符表达式等。例如下面的表达式的都是关系表达式:

    a>b
    (a=8)<(b=4)
    a+b<c-d
    x!=y
    b*b>4*a*c

任何一个关系表达式的结果为真和假两个,其中用1代表真,用0代表假。例如假设x=1,y=2,z=3,看下面的关系表达式。

● fabs(x-y)<1.06E-06:求值顺序为先做函数运算,再做“<”运算,表达式的结果为0;

● z>y+x:求值顺序为先做“+”运算,再做“>”运算,表达式的结果为0;

● x!=y==z-2:求值顺序为先做“-”运算,再做“!=”运算,最后做==运算(同级从左向右),表达式的结果为1;

● x=y==z-1:求值顺序为先做-运算,再做“==”运算,最后做“=”运算,表达式的结果为1。

测试5:比较两个数值并返回比较的结果。

解决思路:我的设计思路如下所示。

(1)声明变量a和b,并分别赋值;

(2)获取各个表达式的结果,并分别输出结果。

编写程序文件Relations.c,具体实现代码如下:

    #include <stdio.h>
    void main(){
      int Finally ,a=7,b=3;                            //声明变量
      Finally =(a>b);                                  //获得关系表达式a>b的结果
      printf("结果 =(a>b)\ni=%d\n",Finally );          //输出结果
      Finally =(a<b);                                  //获得关系表达式a<b的结果
      printf("结果 =(a<b)\nFinally =%d\n",Finally );   //输出结果
      Finally =(a>=b);                                 //获得关系表达式a>=b的结果
      printf("结果 =(a>=b)\nFinally =%d\n",Finally );  //输出结果
      Finally =(a<=b);                                 //获得关系表达式a<=b的结果
      printf("结果 =(a<=b)\nFinally =%d\n",Finally );  //输出结果
      Finally =(a==b);                                 //获得关系表达式a==b的结果
      printf("结果 =(a==b)\nFinally =%d\n",Finally );  //输出结果
      Finally =(a!=b);                                 //获得关系表达式a!=b的结果
      printf("结果 =(a!=b)\nFinally =%d\n",Finally );  //输出结果
      getch();
    }

编译运行后将分别输出各个关系表达式的运算结果,如图3-10所示。

图3-10 输出运算结果

3.6 逻辑人生下的逻辑运算符和逻辑表达式

9月11日,9:30,晴

我最佩服的科学家是爱因斯坦!但是殊不知他最好的知己哥德尔同样也是一位巨人。美国《时代》杂志评选出对20世纪人类思想产生重大影响的100人中,哥德尔列为第四。哥德尔被认为是自亚里士多德以来最伟大的逻辑学家。对于人类来说,不了解哥德尔就不了解人类已达到的智力水平与人类智力奋斗的历程,也就无法了解我们这个世界在思想观念上已经发生或正在发生的深刻变化。

今天拜读了描述哥德尔的著作《逻辑人生》,我感慨良多……

我:“逻辑运算也是第一次听说,应该是新的吧!”

KNOWALL:“逻辑运算是大师布尔的发明,所以也叫布尔运算。布尔用数学方法研究逻辑问题,成功地建立了逻辑演算。他用等式表示判断,把推理看做等式的变换。这种变换的有效性不依赖人们对符号的解释,只依赖于符号的组合规律。这一逻辑理论人们常称它为布尔代数。20世纪30年代,逻辑代数在电路系统上获得应用,随后,由于电子技术与计算机的发展,出现各种复杂的大系统,它们的变换规律也遵守布尔所揭示的规律。”

逻辑运算就是将关系表达式用逻辑运算符连接起来,并对其求值的一个运算过程。

1.逻辑运算符

C语言提供3种逻辑运算符,分别如下所示。

● &&:逻辑与

● ||:逻辑或

● !:逻辑非

其中,“逻辑与”和“逻辑或”是双目运算符,要求有两个运算量,例如 (A>B) && (X>Y)。“逻辑非”是单目运算符,只要求有一个运算量,例如 !(A>B)。

2.逻辑表达式

“逻辑与”相当于生活中说的“并且”,就是当两个条件都成立的情况下“逻辑与”的运算结果才为“真”。例如,“明天又下雨并且又闪电”这是一个预言,到底这个预言对不对呢?如果明天只下雨或者只闪电或者干脆就是大晴天,那么这个预言就错,或者说是假的。只有明天确实是又下雨又闪电,这个预言才是对的,或者是真的。

“逻辑或”相当于“或者”,当两个条件中有任何一个条件满足,“逻辑或”的运算结果就为“真”。“逻辑非”相当于生活中的“不”,当一个条件为真时,“逻辑非”的运算结果为“假”。例如,“明天要下雨”这个意思我们可以用另外一种方式来描述:“明天不下雨是不可能的”我们将“明天不下雨”用A来表示,那么!A就表示“明天不下雨是不可能的”或者是“明天要下雨”。

看下面表3-2中a和b之间的逻辑运算,在此假设a=6,b=2。

表3-2 逻辑运算

从表3-2中的运算结果可以得出如下两个规律:

(1)进行与运算时,只要参与运算中的两个对象有一个是假,则结果就为假。

(2)进行或运算时,只要参与运算中的两个对象有一个是真,则结果就为真。

测试6:尝试对变量进行逻辑运算处理,并输出运算后的结果。

解决思路:我的设计思路如下所示。

(1)分别定义了类型为int的变量a、b和c的初始值;

(2)定义了类型为float的变量x和y的初始值;

(3)设置char类型变量ch值为x的值;

(4)计算表达式x*!y和!!!x,并输出结果;

(5)计算表达式x||a&&b<c,a+3>b&&x<y,并输出结果;

(6)计算表达式a==4&&!ch&&(b=9),x+y||a+b||c,并输出结果。

编写实现文件为Logical.c,具体实现代码如下:

    #include <stdio.h>
    void main(){
      //声明变量并定义初值
      int a=3,b=4,c=5;
      float x=12.345,y=0.123;
      char ch='x';
      //将各变量进行逻辑运算,并将结果输出
      printf("%d,%d\n",x*!y,!!!x);
      printf("%d,%d\n",x||a&&b<c,a+3>b&&x<y);
      printf("%d,%d\n",a==4&&!ch&&(b=9),x+y||a+b||c);
      getch();
    }

编译运行后将分别输出各个关系表达式的运算结果,如图3-11所示。

图3-11 输出运算结果

3.7 人生需要停顿的逗号运算

9月11日,10:00,晴

人不能在路上一直走下去,必要时也需要停下来歇一歇。不管是驻足观望,还是驻足思索,都可以看做一次次的积累和沉淀。利用少量的闲暇时间好好沉思一下自己的路,做一个合理的决定,然后再重新上路……

图3-12 人生中的逗号

我:“逗号也是一种运算符吗?”

KNOWALL:“对,在C语言中逗号‘,’也是一种运算符,称为逗号运算符。其功能是把两个表达式连接起来组成一个表达式,这个表达式称为逗号表达式。”

1.逗号运算符

在C语言中,逗号“,”的用法有两种:一种是用作分隔符,另一种是用作运算符。在变量声明语句、函数调用语句等场合,逗号是作为分隔符使用的。例如:

    int a,b,c;
    scanf('%f%f%f',&f1,&f2,&f3);

C语言还允许用逗号连接表达式。例如x=5.1,y=2.3,10+x,x+y用三个逗号运算符将4个算术表达式连接成一个逗号表达式。

2.逗号表达式

逗号表达式的一般格式如下:

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

例如下面就是一个逗号表达式:

    a=2*6,a-4,a+15;

当逗号作为运算符使用时是一个双目运算符,其运算优先级是所有运算符中最低的。逗号运算符的运算顺序是自左向右的,因此上述赋值语句的求值顺序为:先计算2*6的值并赋予a(结果是a=12),再计算a-4的值(只计算,不赋值),最后计算a+15的值(只计算,不赋值),最终以27作为整个逗号表达式的值。但是需要注意的是,后面两个表达式的值仅作了计算,而并没有赋给a,所以a的值仍然为12。

有时候使用逗号表达式的目的仅仅是为了得到各个表达式的值,而并非要得到整个逗号表达式的值。看下面的代码:

    t=a,a=b,b=t;

上述逗号表达式的目的是实现变量a和b值互换,而不是使用整个表达式的值。

再看下面的代码:

    int j=5;
    a=(a=j+1,a+2,a+3);

上述赋值语句的执行顺序为:先对a变量赋值6,再计算a+2得8,再计算a+3得9,最后将9作为整个逗号表达式的值赋给变量a,使a重新赋值为9。如果将一对括号“( )”去掉,a的值为6。

再看一下下面的代码:

    int x ,y;
    y=(x=1,++x,x+2);

上述赋值语句的执行顺序为:x被赋值为1,x自增1得2,再计算x+2得4,4作为整个逗号表达式的值付给变量y,因此y被赋值为4。

测试7:通过用逗号将两个表达式连接起来,并输出运算后的结果。

解决思路:我的设计思路如下所示。

(1)分别定义了类型为int的变量a、b和c的初始值,没有赋值给x和y;

(2)定义逗号表达式x=a+b,b+c,因为赋值运算符的优先级大于逗号运算符,所以先执行x=a+b,再执行此逗号表达式;

(3)定义逗号表达式y=(a+b,b+c),因为有小括号“( )”,所以首先执行a+b,b+c这个逗号表达式,再将结果赋给变量y;

(4)最后通过printf函数输出结果。

编写实现文件Comma.c,具体实现代码如下:

    #include <stdio.h>
    void main(){
      int a=3,b=4,c=5,x,y;        //声明变量
      x=a+b,b+c;                   //定义逗号表达式
      y=(a+b,b+c);
      printf("%d,%d",x,y);        //输出结果
      getch();
    }

编译运行后将分别输出逗号表达式的运算结果,如图3-13所示。

图3-13 输出运算结果

3.8 花落知多少,求字节数运算

9月11日,10:30,晴

人生是由很多个片段组成的,一个个的片段组成了一个绚丽多姿的一生。今天这个片段中你还和他在一起,明天就可能各奔东西,又开始一个新的片段!人这一辈子的一个个片段漫无目的,过了就怀念,怀念后就遗忘,遗忘后又开始,开始完又是过了……仅此循环,或许精彩,或许无奈!

我:“求字节数运算符是获取所占的字节数吗?”

KNOWALL:“C语言中的求字节数运算符是sizeof,用于计算数据类型所占的字节数。sizeof将以字节形式给出其操作数的存储大小。被操作数可以是一个表达式或在小括号内( )的类型名。”

sizeof可以用于数据类型,其使用格式如下:

    sizeof  (type)

其中,“type”是数据类型,它必须放在小括号“( )”内。sizeof也可以用于变量,其使用格式可以是下面的一种:

    sizeof  (var_name)
    sizeof  var_name

求字节数运算符sizeof主要有如下两个方面的用途。

● 和存储分配或I/O系统等例程进行通信。例如下面的代码:

    void * malloc(size_t size),
    size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)

● 计算数组中的元素个数。例如下面的代码:

    void * memset(void * s,int c,sizeof(s))

需要读者注意的是,sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。

测试8:通过使用sizeof运算符获取当前系统各基本数据类型所在内存中占用的空间大小。

解决思路:我的设计思路如下所示。

(1)显示整型数据所在内存的字节数;

(2)显示实型数据所在内存的字节数;

(3)显示字符型数据所在内存的字节数。

编写实现文件size.c,具体实现代码如下:

    #include <stdio.h>
    void main(){
      //显示整型数据所在内存的字节数
      printf("一个int类型占用%d bytes\n",sizeof(int));
      printf("一个short类型占用 %d字节\n",sizeof(short));
      printf("一个long类型占用 %d字节\n",sizeof(long));
      printf("一个unsigned int类型占用 %d字节\n",sizeof(unsigned int));
      printf("一个unsigned short类型占用 %d字节\n",sizeof(unsigned short));
      printf("一个unsigned long类型占用 %d字节\n\n",sizeof(unsigned long));
      //显示实型数据所在内存的字节数
      printf("一个float类型占用 %d字节\n",sizeof(float));
      printf("一个double类型占用 %d字节\n\n",sizeof(double));
      //显示字符型数据所在内存的字节数
      printf("一个char类型占用 %d字节\n",sizeof(char));
      printf("一个unsigned char类型占用 %d字节\n",sizeof(unsigned char));
      getch();
    }

编译执行后将分别输出逗号表达式的运算结果,如图3-14所示。

图3-14 输出运算结果

注意

因为sizeof可以用于数据类型,所以可以通过“sizeof(type)”来获取各个类型在内存中的占用存储单元。

3.9 解决问题——程序员的7种生活方式

9月11日,11:00,多云

我:“生活方式多种多样,作为程序员,我需要遵循什么样的生活方式?”

KNOWALL:“针对IT从业人员经常要面对的计算机辐射、熬夜加班、腰酸背痛、睡眠缺乏、饮食结构不合理等现状,我建议你学会7种生活方式,见图3-15!”

图3-15 程序员应学会的7种生活方式

我:“我知道了,谢谢指点!”

KNOWALL:“不要油嘴滑舌!转回本章开始的问题:提示用户输入性别和年龄,系统输出该用户是否退休,提示时要求显示对应的女士或先生信息。”

我:“哈哈,现在看来问题就简单多了!看我的解决思路!”

(1)定义两个变量,分别表示性别和年龄;

(2)获取用户输入的性别和年龄;

(3)使用两个if语句进行判断,并输出对应的提示。

编写实现文件question.c,具体代码如下所示:

    #include "stdio.h"
    main(){
    char sex;
    int age;
    printf("请输入性别,年龄\n");
    printf("男士用m表示,女士用w表示");
    scanf("%c,%d",&sex,&age);
    if(sex==109)
        if(age<60)
          printf("该男士尚未退休");
        else
          printf("该男士已退休");
    else
        if(age<=55)
          printf("该女士尚未退休");
        else
          printf("该女士已退休");
          getch();
          return 0;
    }

编译执行后,先提示输入性别和年龄,输入后按【Enter】键,将输出图3-16对应的提示。

图3-16 执行效果

3.10 我的总结

本章详细讲解了C语言运算符和表达式的基本知识。表3-3是我的学习时间进度表。

表3-3 时间进度表