




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第3章C语言基本知识3.1C语言基本元素3.2C的基本数据类型
3.3C的运算符及表达式
习题3
3.1C语言基本元素3.1.1C的字符集
C语言中可用的字符有以下几类:
(1)大小写的英文字母:A,B,…,Z,a,b,…,z。
(2)阿拉伯数字:0,1,2,3,4,5,6,7,8,9。
(3)特殊字符:+,-,*,/,%,=,-,(,),&,#,!,|,<,>,·,,,;,:,″,′,?,{,},~,\,[,],^,共29个。
(4)不可打印的字符:空格,换行符,制表符。3.1.2标识符标识符是某种对象的名字或标记,这些对象可以是变量名、函数名、标号等。标识符的构成成分是:字母、数字和下划线;构成规则是:以字母或下划线开头的字符序列。对象的取名最好能直观表达该对象的意义,这样能很自然地引起联想,便于阅读和理解。比如表示圆周率可取名pi,表示求和可取名sum等。在C语言中,大写、小写字母表示不同的意义,这样sum和SUM就是两个不同的名字,甚至sum和sUm也不相同。正确的标识符:abc,_ab2,_123,total等。不正确的标识符:123,abc,ab*c等。3.1.3关键字
C语言中有一种特殊的标识符,它们在程序中有特定的含义,用在特定的地方,不能随便移作它用。这些标识符是C语言本身带来的,用户在定义对象名时不能使用它们。这样的标识符称为关键字或保留字,它们都是一些英文单词或缩写。C语言的关键字如下:autobreakcasecharconstcontinuedefaultdodoubleelseenumexternfloatforgotoifintlongregisterreturnshortsignedsizeofstaticstructswitchtypedefunionunsignedvoidvolatilewhile共有32个,不需要死记,当学习过它们如何使用后,自然就会熟悉起来。而在以后的学习中,就要注意它们都用在什么地方,起什么作用,如何使用它们来构成程序。3.1.4变量和常量
1.常量常量是其值在程序执行过程中不变的量,如123,45.2,′a′,″abc″等,这样的常量也称为常量直接量,因为其含义是由其字面意义直接表达的。还有一种常量称为符号常量,是用一个名字来代表常量。定义符号常量的方法是用define命令把一个常量名和常量联系起来。如 #defineNULL0就定义了一个常量名NULL,它代表0,以后在程序中遇到NULL,就用0来代替。符号常量一般用大写字母表示,以区别于变量。当然也不一定非要大写,也可以用小写字母表示。
2.变量变量是其值在程序执行过程中可以改变的量。如定义sum是个变量,则执行语句
sum=0;后,它的值为0,再执行语句
sum=1;之后,它的值就变为1。之所以会有这些变化,是因为变量实际上是内存中的一段存储单元,里面可以存放变化的数据。要想把内存中的某段存储单元和一个变量名联系起来,必须首先对变量进行定义或声明。变量声明的一般形式是:
〈变量类型〉〈变量列表〉;其中,〈变量类型〉是C语言中可以使用的数据类型;〈变量列表〉则由一个或多个用逗号分开的标识符(即变量名)组成,最后以分号结束。如语句
intsum,i;就定义了两个变量sum和i,它们的类型是整型。这样就在内存中开辟出了两个能存放整数的存储单元。当执行语句
sum=0;i=1;之后,变量sum和i的存储单元为:0sum1i如果再执行语句
sum=i+2;则存储单元变为:对i来说,是取出它的值1去和2相加,其存储单元并未加以改变,这称为“非破坏性读出”;而变量sum的存储单元却发生了改变,这称为“破坏性读入”。当把一个值放入一个存储单元时,都会覆盖掉该单元中原有的内容。3sum1i对变量必须先声明后使用的原因在于:
(1)不同类型的数据在编译时分配的内存空间大小不同,如字符型占一个字节,整型占两个字节,而浮点型占四个字节。
(2)不同类型的数据在内存中的存储形式不同,如字符型是以ASCII码形式存储的,整型是以补码形式存储的,浮点型是以指数形式存储的。
(3)对不同类型的数据所使用的运算符不同,比如%(求余)运算符只能用于整型变量而不能用于浮点型变量。因此变量在使用前必须先作类型声明。3.1.5字符串常量字符串常量在C程序中有着广泛的使用,它是用双引号括起来的字符序列,如:“string”、“thisisabook”、“sumis%d\n”等都是字符串常量。3.1.6注解为了更好地理解程序的功能,可在适当的地方加注解。注解的内容放在“/*”和“*/”之间,编译器把其中的内容当做空白符对待而不予处理。如:
/*thisisauserdefinedfunction*/就是对一个自定义函数的注解。一个注解可以写多行,只要注意“/*”和“*/”前后配对即可,如:
/*thisis
acomment */注意:注解不能嵌套。下面的写法
/*thisis/*acomment*/infunction*/就是错误的。
【例3-1】注解的应用。/*thisisasingleprogram*/#defineTEN10#include<stdio.h>main(){intsum;sum=2*TEN+2;/*namely:sum=2*10+2;*/printf(″sum=%d\n″,sum);return0;}该程序中用到了注解和符号常量。3.2C的基本数据类型计算机可以处理多种多样的数据,这些数据有着内在的联系和差别。物以类聚,计算机中的数据也如此,C语言把具有某些共同特征的数据归为一类,以便处理。
C语言作为现代化的语言有着丰富的数据类型,它既有基本类型,又有构造类型,还有指针类型和空类型。图3-1是C的数据类型总览。图3-1C的数据类型总览3.2.1int(整数)类型
1.int类型及其扩展对不同的数据类型,编译系统会分配大小不同的内存单元以存放某一类数据,因此就决定了每一类数据必然有一定的取值范围。整型数据是整数的一个子集。在ANSIC中,基本整型占两个字节,即16位二进制位,最高位为符号位,数值位占15位,以补码表示。这样它的取值范围在机器内部是1000000000000000~0111111111111111,大小为-215~215-1,即-32768~32767。基本整型的标识符为int,由它可以定义整型变量,如
inti,j,k;则i、j、k这三个变量可以取-32768~32767之间的任何整数值。除了基本int类型之外,C语言还提供了几种int类型的扩充形式,即在类型标识符int之前添加修饰符而形成的新的整数类型。这些修饰符为:short、long、unsigned。这样可以扩大整数类型的取值范围或更准确地指明取值情况。
(1)shortint:短整型,可简写为short。其含义为其位数不比int类型长,一般和int类型一样长,数值范围也为-32768~32767,在表示较小的整数时使用。
(2)longint:长整型,可简写为long。其含义为其位数不比int类型短,其长度规定为int类型的两倍,即4个字节,取值范围为-231~231-1,可表示更大的整数。我们可以用sizeof运算符求出这两种类型的长度。
【例3-2】求int、shortint和longint类型的长度。#include<stdio.h>main(){printf(″sizeof(int)=%d\n″,sizeof(int));printf(″sizeof(short)=%d\n″,sizeof(short));printf(″sizeof(long)=%d\n″,sizeof(long));return0;}程序中的sizeof是一个运算符,不是函数名。这里是要计算数据类型在内存中所占的字节数。运行输出:sizeof(int)=2sizeof(short)=2sizeof(long)=4因此有如下关系:
sizeof(short)≤sizeof(int)≤sizeof(long)
(3)unsignedint:无符号整型,可简写为unsigned。其长度和int类型相同,但是它取消了符号位,把最高位也作为数值位使用,所以它的值全部是正的。它的取值范围为0~65535。把int类型中的0点拉到最左边即为unsignedint类型,所以其最大值即为int型中正、负最大绝对值之和,如图3-2所示。图3-2int类型和unsignedint类型的取值范围
2.整型数据的溢出上述各种整数类型都有各自的取值范围,一旦越出了这个范围,就不能正确地表示数据,这种情况称为“溢出”。我们先来看两个例子。
【例3-3】整型数据的溢出一。#include<stdio.h>main(){intmax,min;max=32767;min=max+1;printf(″max=%d,min=%d\n″,max,min);
return0;}运行输出:
max=32767,min=-32768为什么max加1以后会产生负值呢?这是因为整数在内存中是以补码形式存储的,最高位是符号位,符号位为0时表示正数,为1时表示负数,而补码运算中的符号位是参与运算的:
max:0111111111111111 + 1 min: 1000000000000000 ↑
符号位在max的最低位加1,由二进制“逢二进一”的进位规则可得到min的形式,此时符号位变为1,表示该数已变成了负数,而在补码表示的情况下,min即为十进制的-32768。
【例3-4】整型数据的溢出二。#include<stdio.h>main(){intmin,max;min=-32768;max=min-1;/*max=min+(-1);*/printf(″min=%d,max=%d\n″,min,max);return0;}运行输出: min=-32768,max=32767我们可以作同样的分析,在补码表示的情况下:
1000000000000000 -32768的补码
+ 1111111111111111 -1的补码
10111111111111111 32767因为整型只有16位,所以最高位的进位1被舍掉了,这样最高位(符号位)变成了0,于是也就变成了正数。由以上两例可以看出,在int类型范围内,正最大数加1即变为负最小,负最小减1又变成了正最大。这印证了辩证法的一个朴素真理:自然界任何事物,其发展变化都有个限度,超过这个限度就会走向反面。从上面的讨论中知道,用int类型只能表示有限的整数,如想表示更大范围的数,可选用其他类型,如longint或float等。比如要正确进行上例中的运算,可把max和min全部定义成longint类型。如果一个定义为longint类型,而另一个仍为int类,则运算结果仍然是不对的。
3.整型常量的几种表示形式在C语言中,整型常量有三种表示形式:十进制表示、八进制表示和十六进制表示。对于八进制和十六进制的整型常量,其特征还表现在数值的前缀上。
(1)十进制表示:用十进制数码表示数,不加前缀,如123、-45、0等。
(2)八进制表示:用八进制数码表示数,加前缀“0”(数字零),如0123、0275等,而0129则为错误表示形式,因为9不是八进制数码。
(3)十六进制表示:用十六进制数码表示数,加前缀0x或0X(数字零和字母x(X)),如0x1A、0X8F等。除了前缀以外,整型常量还可以带后缀,以明确指明该数属于什么整数类型。后缀可以是l(或L),u(或U)及其组合。l(或L)表示长整型,u(或U)表示无符号整型,组合形式有:ul、uL、Ul、UL、lu、lU、Lu、LU。例如:0x12u表示无符号十六进制,076UL表示无符号长整型八进制。整型常量加后缀主要用在函数调用中,当函数的形参是某种类型时,若能提供相应类型的实参,则会更快地运行。整型常量的类型如何确定呢?实际上,如果一个整型常量的值在某一个整型类型的取值范围之内,那么它就可以属于这种类型。比如123,可以认为它既是int型,又是unsignedint型,又是longint型,把它赋给这三种中任一类型的变量都是正确的;再如65330,它就不是int类型,但属于unsignedint和longint类型;而75887则肯定只能是longint类型。不同类型的整数之间可以进行混合运算,只要能保证结果不超越相应类型的取值范围即可。
【例3-5】不同类型整数之间的混合运算。#include<stdio.h>main(){inti;unsignedintu;longintl;i=123;u=123;l=123;printf(″i=%d,u=%d,l=%d\n″,i,u,l);i=u+l;u=l+i;printf(″i=%d,u=%d\n″,i,u);return0;}运行输出:i=123,u=123,l=123i=246,u=3693.2.2char(字符)类型字符类型以char作为类型标识符。用char定义的变量称为字符变量,一个字符变量只能容纳一个字符。一个字符常量是用单引号括起来的字符,如′a′、′A′、′*′等。在内存中为一个字符变量分配一个字节的存储空间。由于一个字节的8位二进制位可以有28=256种不同的组合形式,因而可以有256个不同的字符。在这256个字符中,有些是可见的,有些是不可见的,不可见的字符只起控制作用,如换行、回车等。
1.字符在内存中的存放形式字符在内存中是以编码的形式存放的。最常用的编码方式是ASCII码。存储一个字符,实际上是把它的ASCII码放入内存中。例如:charc1,c2;c1=′a′;c2=′b′;则在内存中:
c1:c2:因为′a′的ASCII编码是十进制的97,二进制的01100001;′b′的ASCII编码是十进制的98,二进制的01100010。如以十进制表示则为:c1:c2:01100001011000109798因为字符在内存中都是以二进制形式表示的,所以字符型和整型之间有相通性,这些相通性表现在:
(1)字符型数据可以用整型形式输出;反之,一定范围内的整数也可以用字符形式输出。
(2)字符型变量和整型变量之间可以进行混合运算。
(3)整型变量可以接收字符常量,字符变量也可以接收整型数值,当然这都要在一定范围之内。这一点可由以下例子说明。
【例3-6】整型变量与字符型变量的输出转换。#include<stdio.h>main(){inti,j;charc1,c2;c1=′a′;c2=′b′;printf(″c1=%c,c1=%d;c2=%c,c2=%d\n″,c1,c1,c2,c2);i=97;j=98;printf(″i=%d,i=%c;j=%d,j=%c\n″,i,i,j,j);return0;}运行输出:c1=a,c1=97;c2=b,c2=98i=97,i=a;j=98,j=b英文大小写字母之间有这样的关系:相应小写字母的ASCII码比大写字母大32。根据这一关系,我们可以将大小写字母任意转换。
【例3-7】英文大、小写字母的转换。#include<stdio.h>main(){charc1,c2;c1=′a′;c2=′B′;printf(″c1=%c,c2=%c\n″,c1,c2);c1=c1-32;c2=c2+32;printf(″c1=%c,c2=%c\n″,c1,c2);
return0;}运行输出:c1=a,c2=Bc1=A,c2=b
【例3-8】字符变量和整型变量互相接收对方类型的数值。#include<stdio.h>main(){inti;charc;i=′a′;c=98;printf(″i=%d,c=%d\n″,i,c);printf(″i=%c,c=%c\n″,i,c);return0;}运行输出:i=97,c=98i=a,c=b一个字符在内存中占一个字节,对于这个字节的最高位,不同的系统有不同的处理方法。有的系统(如TurboC)就把最高位作符号位处理,因此一个字符的值就有正有负。编码在0~127之间的字符以十进制形式输出时为正,而编码在128~255之间的字符以十进制形式输出时为负。所以定义:
charch;就相当于:
signedcharch;为了在以%d形式输出字符时不出现负值,可以把字符定义成无符号字符:
unsignedcharch;对编码在128~255之间的字符,不管是以char定义,还是以unsignedchar类型定义,它们在以字符输出格式%c输出时都是扩展的ASCII码表中的字符。
【例3-9】字符输出一。#include<stdio.h>main(){charc=199;printf(″c=%d,c=%c\n″,c,c);return0;}输出为:
c=-57,c=‖-
【例3-10】字符输出二。#include<stdio.h>main(){unsignedcharc=199;printf(″c=%d,c=%c\n″,c,c);return0;}输出为:
c=199,c=‖-
2.字符常量我们已知字符常量是用单引号括起来的字符,这对于可显示字符来说很容易表示,但对于控制字符怎样表示呢?在C语言中没有相应的直接形式显示这些字符,但可以换一种形式来表示它们,即用转义字符来表示。转义字符是以反斜杠′\′开头,后面加其他字符。反斜杠的作用是把后面的字符赋以新的含义(即转义)。以′\′开头定义的字符有三种情况:非显示字符(控制字符)、可显示字符和字符的数值表示,如表3-1所示。
【例3-11】转义字符的输出。#include<stdio.h>main(){printf(″Thisisaprogram\n″);printf(″\thisis\ba\012program\n″);printf(″\″first\\second\″″);return0;}运行输出:Thisisaprogramhisiaprogram″first\second″其中,“\t”是水平制表符,“\b”是退格符,“\012”和“\n”都是换行符。要想输出“\”,必须写成“\\”;要想输出“"”,必须写成“\"”。
3.字符和字符串前面已指出,字符串是用双引号括起来的字符序列。字符和字符串有着本质的区别:
(1)字符用单引号括起来,而字符串用双引号括起来。
(2)单引号括起来的只有一个字符,而双引号括起来的有多个字符。不管显示出来的有多少个字符,字符串都由一个看不见的字符′\0′结束,因此′a′和″a″并不相同。前者是字符,后者是字符串,″a″中有两个字符:′a′和′\0′。′\0′是ASCII码值为0的字符,是“空字符”。C语言规定,字符串必须以′\0′结束,这个字符是系统自动加上去的,不是显式输入的。系统据此可以判断字符串是否结束。因此″a″在内存中的形式是a\0它占有两个字节,长度为2,而′a′的长度为1。因此如果有定义:
charc;则
c=″a″;就是错误的。3.2.3float和double类型通常称float为浮点类型,而称double为双精度类型,它们都用来表示实数,差别只在于长短不同,因而所能表示的数的范围也不同。
1.浮点型数据的两种表示形式对于一个实型常量,可以用两种形式表示它,即十进制小数形式和指数形式。
(1)十进制小数形式,如123.45、-0.001。
(2)指数形式,其组成格式为:
〈尾数部分〉e(或E)〈指数部分〉其含义为:
〈尾数部分〉×10〈指数部分〉这几部分的书写规则是:①尾数部分不能省略,即e(或E)前必须有数据。②指数部分必须是整数。③尾数可以只有整数部分或只有小数部分。④指数、尾数都可以带符号,如省略符号则隐含为正。例如:123e2、1.23E4、.123e5、123000E-1等都是正确的书写形式,且代表同一个数12300;而3E22.5、-e-3等都是错误的指数形式。虽然同一个实数的指数形式可以有多种,但只有一种形式是规格化的,即尾数部分只有一位整数的形式,如1.23e4。浮点型数据在内存中也是按指数形式存储的。对于有4个字节32位的实型数据,一般情况是尾数部分24位,指数部分8位,其中各含一位符号位,即
2.浮点型变量的分类浮点型变量分单精度(float)、双精度(double)和长双精度(longdouble)三种,它们的长度、精度及取值范围如表3-2所示。在ANSIC中,浮点常量被默认为double类型,如果要显式地指明某浮点数为float类型,必须加上后缀f或F,如1.6f、3.7F等。在不需要太大的精度时,这种表示可以加快运算速度,节省内存空间。3.2.4变量赋初值前面已介绍了三种数据类型,用类型名可以定义相应的变量。根据先定义后使用的原则,在定义变量之后,可以对它进行处理。如:inti,j;i=3;j=3;通过赋值语句,使i、j具有数值3。我们还可以把二者结合起来,即在定义变量的同时就让它有一个值,这就是变量赋初值,如:
inti=3,j=3;程序可以对i、j的当前值进行运算,当然这些值在以后是可以改变的。注意:上面的语句中变量i和j都具有初始值3,能不能把它们连写呢?如:
inti=j=3;答案是:这样写是错误的,只能对它们分别赋初值,即使这些初值都是相同的也不能连写。要使i、j具有相同的值,还可以采用下面的方法:
inti,j; i=j=3;这种写法是合法的,但这和赋初值的含义是不同的。可以定义多个变量而对其中个别变量赋初值:
inti,j=3;则j具有初值3,而i未赋初值,即它的值是不确定的,不能直接使用。3.3C的运算符及表达式
C语言不仅数据类型丰富,运算符也十分丰富,几乎所有的操作都可作为运算符处理。由运算符加适当的运算对象可构成表达式,而表达式是C程序的重要要素之一,因此掌握好运算符的使用对编写程序是十分重要的。对于每一个运算符,要注意从两个方面去把握:运算符的优先级和结合性。优先级指多个运算符用在同一个表达式中时先进行什么运算,后进行什么运算;而结合性是指运算符所需要的数据是从其左边开始取还是从右边开始取,因而有所谓“左结合”和“右结合”之说。本章中我们介绍一些最基本的运算符,后面的章节中将陆续介绍其他运算符。3.3.1算术运算符
1.基本的算术运算符
C语言中基本的算术运算符有:+、-、*、/、%,其含义如表3-3所示。
在算术运算符的使用中有以下两点需要注意:
(1)除法运算符“/”的结果和其运算对象有关。如果两个运算对象都是符号相同的整数,则运算符“/”的功能是整除,结果为整数。整除的含义是舍去小数部分,只保留整数部分。两个运算对象中如果有一个是浮点数,则结果就是浮点数。如果两个运算对象都是整数,但其中有一个是负数,另一个是正数,则一般采用“向零取整”的原则,即按其绝对值相除后再加负号。还应注意,在做除法运算时,除数不能为0。如除数为0,则会导致致命错误而使程序以失败终止。
(2)求模运算符“%”要求其运算对象都必须是整数,但可正可负。对有负数情况的处理有以下一般性的原则:先按其绝对值求模(|r|%|s|),然后取被除数r的符号作为模的符号。
【例3-12】算术运算的输出。#include<stdio.h>main(){printf(″5/3=%d,-5/+3=%d\n″,5/3,-5/+3);printf(″3/5=%d,3./5=%f\n″,3/5,3./5);printf(″10%%3=%d,-10%%3=%d,10%%-3=%d,-10%%-3=%d\n″,10%3,-10%3,10%-3,-10%-3);return0;}运行输出:5/3=1,-5/+3=-13/5=0,3./5=0.60000010%3=1,-10%3=-1,10%-3=1,-10%-3=-1由例3-12可知,要输出一个“%”,在字符串中必须有两个“%”,即“%%”。
2.算术运算符的优先级和结合性
C语言中算术运算符的优先级和代数中的规定是一样的,并且都是从左向右结合的,如表3-4所示。例如,有代数表达式:
z=bp-r%-q+((a+b)c)÷x-y将其转变成C语言表达式为
z=b*p-r%-
q+((a+b)*c)/x-
y ①④③②⑧⑤⑥⑦⑨则运算时将按①、②、…⑧、⑨所示的顺序进行。其中,q前的“-”号为单目运算符,优先级最高,而r前的“-”是代表减运算。如何加以区分呢?凡+、-号前没有数字或只有*、/、%等级别较高的运算符时,“+”、“-”即为单目运算符,否则即为加、减运算符。3.3.2自加(++)自减(--)运算符
C语言中有两个特殊的算术运算符,即自加、自减运算符(++和--)。这两个运算符都是单目运算符,它们既可以放在运算对象之前,也可以放在运算对象之后,形成前置形式和后置形式,而运算对象也只能是整型变量。不管前置还是后置,其运算结果都是一样的,都是把运算对象的值增加1或减少1。设有整型变量i,则++i、i++都使i值增加1,--i、i--都使i值减少1。既然效果相同,那么区分前置和后置形式又有何意义呢?其意义不在于它所作用的变量本身,而在于对变量值的使用。前置形式是先把变量值加(减)1,然后用新的值参与表达式的运算;而后置形式是先用变量的原始值参与表达式的运算,然后再对变量的值加(减)1。例如有语句:
inti,j=2;则下面运算的结果是i等于3,j也等于3:
i=++j;而下面的运算结果是i等于2,而j等于3:
i=j++;
使用自加、自减运算符应注意以下几点:
(1)最常见的错误是把自加、自减运算符用在非简单整型变量的表达式上,如++(x+2)、++(-i)等都是错误的。
(2)自加自减运算符的结合性为:前置时是自右向左的,后置时是自左向右的。比如p=-x++等价于p=-(x++)。这个表达式的意思是先用x变量的当前值加负号赋给p,而后x再加1。该表达式并不等价于p=(-x)++,因为这样就使++作用在表达式-x上了,这是(1)中指出的错误。
(3)如果两个运算对象之间连续出现多个运算符,则C语言采用“最长匹配”原则,即在保证有意义的前提下,从左到右尽可能多地将字符组成一个运算符。因此i+++j就被解释成(i++)+j而不是i+(++j)。同样,i++++j被解释成(i++)++j;i+++++j被解释成((i++)++)+j。而这两种情况都是错误的。正确的写法应该是在连续的运算符中间的适当地方增加空格分隔符或加括号,使分开的部分成为有意义的运算对象。比如应把i+++++j写成i+++++j或(i++)+(++j)。
(4)对所有简单类型的变量都可以进行自加、自减运算。
【例3-13】自加运算。#include<stdio.h>main(){intp,x=3;charc=′a′;
floatf=3.2;p=-x+++1;
printf(″p=%d,x=%d\n″,p,x);printf(″c=%c\n″,++c);printf(″f=%f\n″,++f);return0;}运行输出:p=-2,x=4c=bf=4.200000
(5)ANSIC并没有规定所有运算符操作数的计算顺序。例如对于语句:i=m*n+p*q;,根据运算符的优先级和结合性可知,它等同于i=(m*n)+(p*q);,但对于m*n和p*q谁先进行计算,C并未明确规定,不同的编译系统都有不同的规定。因此在一个语句中如果要对一个变量前后使用多次,而且前后值不同,则应避免使用自增、自减运算符。比如:
i=1; m=++i*(i+1);对于这个表达式,编译系统可能:①先计算++i,再计算i+1;②先计算i+1,后计算++i。对于①,结果是m=2*(2+1)=6;而对于②,结果是m=2*(1+1)=4。为避免产生这种二义性,应当把它们分成两个表达式,明晰地表达程序员的意图。如果想得到①的结果,可写成:++i;m=i*(i+1)如果想得到②的结果,可写成:j=i+1;m=j*(i+1);i++;另外,在一个表达式中若同一个变量前后出现多次并且还被施以自加运算,则这样的表达式的运算结果还会因变量出现地点的不同而不同。如inti=5,j;j=i+++i+++i++;printf(″j=%d\n″,j);printf(″i=%d\n″,i);则结果为
j=15i=8这里,表达式中的三个i都以其初值5参与运算,相当于5+5+5。而将该表达式作为printf()函数的参数时,就会出现不同的情况:inti=5;printf(″sum=%d\n″,i+++i+++i++);printf(″i=%d\n″,i);则结果为sum=18i=8在作为函数参数的表达式中,每个参与运算的i的值都是不同的,第一个i以其初值5参与运算,同时进行++运算,使其值变为6,作为第二个i的值参与运算。同样再进行++运算后,使其值变为7,作为第三个i的值参与运算,相当于5+6+7。编写程序应当使用通用的规则,把可读性放在第一位,不要使用依赖于不同编译系统的规则去编写那些晦涩难懂、令人捉摸不定的程序,因此不要去编写(i++)+(i++)+(i++)以及printf(″%d,%d″,i,i++);这样的表达式和语句。3.3.3关系及逻辑运算符关系运算符用来比较两个运算对象的大小,比较的结果是真和假两个逻辑值,而逻辑运算符的操作对象和结果也都是逻辑值,因此这两种运算符在使用时有着密切的联系。
1.关系运算符关系运算符又分为两类:相等判断运算符和不相等判断运算符,它们分别是:
(1)不相等判断运算符:>(大于)、>=(大于等于)、<(小于)、<=(小于等于);
(2)相等判断运算符:==(等于)、!=(不等于)。关系运算符的优先级低于算术运算符,而在其内部,不相等判断又高于相等判断,但它们都高于赋值运算符。关系运算符的结合性是从左向右的。两个对象比较的结果是真或假这两个逻辑值之一,它们在C语言中是这样表示的:数字0表示假,非0表示真,比如1、4、′a′等都表示真,这似乎是不可确定的,但关系运算产生的结果都是确定的,真就是1,而不是别的值。
【例3-14】关系运算符。#include<stdio.h>main(){inta,b,c;a=b=c=10;a=b==c;printf(″a=%d,b=%d,c=%d\n″,a,b,c);printf(″a==(…):%d\n″,a==(b=c++*2));printf(″a=%d,b=%d,c=%d\n″,a,b,c);a=b++>=++b>c++;printf(″a=%d,b=%d,c=%d\n″,a,b,c);return0;}运行输出:a=1,b=10,c=10a==(…):0a=1,b=20,c=11a=0,b=22,c=12第二个printf语句输出的是一个比较结果,比较运算符右边的运算对象又是个复杂的运算,结果b、c的值都有改变,这反映在第三个printf语句中。在a=b++>=++b>c++中,按照运算符的优先级和结合性,先进行赋值号右边的运算。首先做b++和++b的大于等于(>=)比较,把比较的结果(1或0)再和c++进行大于(>)比较,最后把比较的结果赋给a。在b++>=++b中,先把第一个b的值(20)作为和后面比较的数据,同时它的值又自动加1变成了21,第二个b值是在21的基础上再进行自加运算,因而其值为22。对于由两个字符构成的比较运算符,在使用中常见的错误是:
(1)把两个字符的次序写颠倒,把>=、!=、<=写成=>、=!、=<。
(2)在两个字符之间加空格,把>=、!=、<=、==写成>=、!=、<=、==。今后凡是由两个以上字符构成的运算符,在使用中都要注意防止出现上面两种错误。
(3)把相等运算符误写成赋值运算符,即把==写成=,这也是初学者常犯的错误。
2.逻辑运算符
C语言提供了三个逻辑运算符,按由高到低的优先级次序排列如下:
! 逻辑非(把逻辑值进行翻转,相当于NOT) && 逻辑与(求两个逻辑值的与,相当于乘运算AND) ‖ 逻辑或(求两个逻辑值的或,相当于加运算OR)其中,!为单目运算符,&&、‖为双目运算符,其功能和用法可由表3-5的真值表说明。表中,e1、e2为逻辑表达式或关系表达式,具有0或非0的值。前已指出,可以用非0来表示真,不一定是1,但是在用逻辑运算符作用后,就一定是0或1了。比如3&&′a′‖0的结果为1,!5的结果为0。注意:如再对!5求非,!(!5)的值是1而不是5,因此在一般情况下,当x不是0或1时,!!x不等于x。我们可以把到目前为止我们见过的运算符放在一块,这样可以查看其在优先级中所处的位置,如图3-3所示。逻辑运算符的结合性:除!是右结合外,&&和‖均为左结合,如!!x等于!(!x),a‖b‖c等于(a‖b)‖c。程序中使用关系运算符时要注意返回结果是用0、1来表示真假值的,不要用常规的数学知识去思考和理解。图3-3运算符的优先级!(非)高算术运算符关系运算符
&&(与)‖(或)=(赋值)低
【例3-15】逻辑运算符一。#include<stdio.h>main(){inti=7,m,n;m=!!i;n=(3<i<6);printf(″i=%d,m=%d,n=%d\n″,i,m,n);return0;}运行输出:
i=7,m=1,n=1虽然经过!!i运算,但i本身的值并没有改变,它返回给m的只是个中间结果。!!i=!(!7)=!0=1,所以m值是1;3<i<6即3<7<6,从数学上看这个不等式的结果应是假的,但在C语言中却是这样处理的:
(3<i<6)=((3<i)<6)=(1<6)=1事实上,在这里不论i为何值,3<i的结果一定是0或1,它们都小于6,因此最后的结果为1,这就是n的值。根据逻辑运算符&&和‖的功能及结合性,对逻辑表达式进行求值的时候,常用所谓的“懒惰求值法”,即只要能判断出逻辑表达式的真假即停止向后进行运算。“懒惰求值法”主要指:
(1)a&&b&&c:当求得a值为假时即停止对b、c的求值,因为此时已可断定整个表达式为假了;只有当a为真时才去求b,只有当a、b全为真时才去求c。
(2)a‖b‖c:当求得a值为真时即停止对b、c的求值,因为此时已可断定整个表达式为真了;只有当a为假时才去求b,只有当a、b皆假时才去求c。
【例3-16】逻辑运算符二。#include<stdio.h>main(){inta,b,c;a=b=c=1;++a‖++b&&++c;printf(″(1)a=%d,b=%d,c=%d\n″,a,b,c);a=b=c=1;++a&&++b‖++c;printf(″(2)a=%d,b=%d,c=%d\n″,a,b,c);a=b=c=1;++a&&++b&&++c;printf(″(3)a=%d,b=%d,c=%d\n″,a,b,c);a=b=c=-1++a&&++b‖++c;printf(″(4)a=%d,b=%d,c=%d\n″,a,b,c);a=b=c=-1;++a‖++b&&++c;printf(″(5)a=%d,b=%d,c=%d\n″,a,b,c);a=b=c=-1;++a&&++b&&++c;printf(″(6)a=%d,b=%d,c=%d\n″,a,b,c);return0;}运行输出:(1)a=2,b=1,c=1(2)a=2,b=2,c=1(3)a=2,b=2,c=2(4)a=0,b=-1,c=0(5)a=0,b=0,c=-1(6)a=0,b=-1,c=-1
注意:输出(4)是求++a&&++b‖++c,++a等于0仅说明不需要求b就可知++a&&++b为假,但还不能说整个表达式为假,还必须对‖后面的++c进行运算。因为‖和&&都是严格地按从左到右的顺序进行运算的,即使它们出现在同一个表达式中也是如此,当‖出现在&&之前时,也不会因为&&的优先级高而先对&&的运算对象进行运算,因此++a‖++b&&++c等同于++a‖(++b&&++c),所以有(1)和(5)的输出结果。由关系运算符和逻辑运算符可以表示复杂的逻辑条件,这在程序设计中是经常用到的。比如要表示某一年份year是否为闰年,根据天文学知识,只要符合下列条件之一即为闰年:
(1)年份year能被4整除,但不能被100整除;
(2)年份year能被400整除。据此可以写出逻辑表达式:
(year%4==0&&year%100!=0)‖year%400==0根据运算符的优先级,式中的括号是可以不要的,加上是为了清晰。3.3.4赋值运算符要使一个变量得到值或改变它的值,除了可以用scanf函数输入外,也可以很方便地用赋值运算符实现。前面我们已介绍过赋值运算符的基本使用,现在做进一步的说明。
1.一般的赋值运算符在C语言中,赋值运算符为=,由它可以构成赋值表达式:
〈变量名〉=〈表达式〉赋值运算就是首先计算右边表达式的值,然后把它赋给左边的变量。赋值表达式的值就是左边变量最后的值。如表达式a=1和b=′a′+1,a、b的值分别是1和98。赋值运算符右边的表达式本身又可能是赋值表达式,也就是说赋值表达式可以递归定义,于是就可以出现a=b=c=10这样的写法。如何分析这种形式呢?这涉及到赋值运算符的结合性问题。赋值运算符是右结合的,因此a=b=c=10等价于a=(b=(c=10))。最右边的赋值运算使c的值为10,它也是这个小表达式的值,把这个值再赋给b,它的值10也是右边第二个赋值表达式的值,最后再把这个值赋给a,a的值为10,这也是整个表达式的值。作为过程结果,a、b、c的值都是10,就像是把最右边表达式的值连续地赋给了它左边的各个变量一样。赋值号两边的部分分别称为“左值”和“右值”,只有变量才能充当左值,非简单变量的表达式不能作左值使用。因此,a++=1;、a=b++=c++=2;等都是错误的。但++a=1;却是正确的,因为这是先对a进行加1运算,其值增加1,它本身仍然是个变量,所以可以充当左值;而a++相当于a+1,已不是个简单变量了,所以不能作左值。赋值操作也是一种破坏性输入,即不管左边变量原来具有什么样的值,结果都要变成右边表达式的值。如:inta=5,b=6;a=b+3;b=a-2;a,b的初值分别是5和6,但经过赋值运算之后,a和b的值分别变为9和7。赋值运算符的优先级是除逗号运算符之外最低的。
2.赋值运算中的类型转换如果赋值运算符两边的类型不一致,则在赋值的时候要对右边表达式的类型进行转换,使之适应左边变量的要求,或者说用左边变量的类型去剪裁改造右边的表达式。为了便于表达,我们做如下的约定: [类型1]←(类型2)表示把类型2的值赋给类型1的变量。具体地有以下几种情况:
(1)[int]←(float,double):此时去掉实数的小数部分,只把其整数部分赋给整型变量。如:
inti=9.87;结果i中只有整数9,舍掉的小数部分并不进行四舍五入处理。
(2)[float]←(int)和[double]←(int):变量中的整数部分是右边表达式的值,而小数部分是相应个数的
0。如:
floatf=15; doubled=15;则有:
f=15.000000(6个0) d=15.00…0(14个0)
(3)[float]←(double):在float类型所能容纳的范围之内,把double数值的前7位放入float型变量中,超过范围则出错。[double]←(float):在double变量中,有效位数扩展到16位,数值不变。
(4)[int]←(unsignedchar):int类型占两个字节,char类型占一个字节,赋值时把字符放入整型变量的低8位,高8位全补0,以保证是正整数。例如:
inti;unsignedcharc=′\376′;i=c;则其内存表示为:[int]←(char):是把有符号的字符赋给整型变量。这时,如字符符号位是0,则整数的高8位全补0;如字符符号位是1,则整数高8位全补1,这称为“符号扩展”,目的是为了保证赋值后的数值保持不变。再如:
inti;charc=′\376′;i=c;则赋值后的内存结果为之所以要采用符号扩展的原则,是为了保证在以十进制整数格式%d输出i和c时,它们都具有相同的值-2。
(5)[char]←(int,shortint,longint):只把整数的低8位送到字符变量中,其余全部截掉,当整数大于255(低8位的最大值)时,赋值在变量中会改变原来的值,如:
inti=256;charc;c=i; printf(″i=%d,c=%d\n″,i,c);则输出为
i=256,c=0内存表示为
(6)[long]←(int):把int类型数值送入long类型的低16位中,高16位按“符号扩展”的原则处理。 [int]←(long):把long的低16位送入int型变量,高16位截掉,如高16位中仍有数据,则赋值在变量中有可能改变原来的值。
(7)[int]←(unsignedint)、[long]←(unsignedlong)及[short]←(unsignedshort):这类赋值的特点是两边的长度相等,但取值范围不同,右边的正数范围要比左边大一倍,因此只有在左边的正数范围内赋值才正确,否则所赋的正值在变量中会得到负的结果。例如:intb;unsignedinta=65535;b=a;printf(″a=%u,b=%d\n″,a,b);结果为a=65535,b=-1内存表示为
(8)[unsignedin]←(int)、[unsignedlong]←(long)及[unsignedshort]←(short):这是和(7)相反的赋值,可以照原值全部传送,但若传送负数,则接收者会得到一个正数的结果,其原因在于把原来的符号位也当成了数值位。例如:unsignedinta;intb=-1;a=b;printf(″b=%d,a=%u\n″,b,a);则输出为
b=-1,a=65535内存表示为
3.复合的赋值运算符在普通赋值运算符的前面加上其他成分,可构成复合的赋值运算符。其一般形式是:
〈变量名〉@=〈表达式〉其中,@可以是如下运算符之一:+、-、*、/、%、、、&、|、^。前5个是算术运算符,后5个是位运算符(将在后面章节中介绍)。复合赋值运算的语义是:
〈变量名〉=〈变量名〉@(〈表达式〉)即把变量的原值和表达式的值进行由@指出的运算,再把运算结果赋给变量。如设a的初值为3,则a*=6就等价于a=a*6,即a的终值为18。因此这里要求变量一定要具有初值,如没有初值,则放入复合赋值运算表达式中就是错误的。
注意:
在把复合赋值形式转换成普通赋值形式时,对原表达式最好加括号,否则有可能出错。如:
x*=y+4;这种写法是正确的,但当把它变成普通形式而对表达式y+4不加括号时会变成:
x=x*y+4;这就和原意大相径庭了。应该写成:
x=x*(y+4);这样才正确。在有多个复合赋值的表达式中,要时刻注意变量的当前值,并且当前所考虑的复合赋值运算符两边的同一变量应该具有相同的值。例如,设a=8,求a+=a-=a*a的结果。根据赋值运算符右结合的特点,原式等价于:a+=(a=a-(a*a)),计算最右边的算术表达式后得:a+=(a=-56),此时+=两边的a应该以共同的新值-56参与运算,而不再是原来的8了。于是变为:a=a+a=-56-56=-112。这是正确的结果,而不要理解成:a=8-56=-48。当作为左值的变量有前置的++、--运算符时,则先对变量进行自加、自减运算,再将运算结果和右边的表达式进行相应运算。如a=8,则++a+=1就相当于a=(++a)+1=9+1=10。3.3.5逗号运算符逗号在C语言中主要起两个作用,一个是作为分隔符,一个是作为运算符。
1.逗号分隔符把两个对象分开可以用多种符号,如空格、反斜杠等,但多用逗号作分隔符。在变量的定义和函数的参数中都用到了逗号分隔符。如:
inti,j,k;charc1,c2,c3; result=max(a,b);这里它的作用是把同类型的变量名分开,把函数的各个参数分开。
2.逗号运算符作为运算符,它是把两个对象“连接起来”,使之成为一个逗号表达式。其一般形式是:
〈表达式1〉,〈表达式2〉,…,〈表达式n〉例如:
i=1,j=0,sum=0,m=2*i+3是一个由四个赋值表达式构成的逗号表达式。逗号运算符的优先级是最低的,它的结合性是从左向右的。因此在求表达式的值时,是从左向右进行计算的,后面表达式的计算可以利用前面表达式的计算结果,而最后那个表达式的值就作为整个表达式的值,其类型也是整个表达式的类型。因此上面的逗号表达式是先对i=1计算,求出i的值用于后面表达式的计算,即求出m=5,这也是整个逗号表达式的值。3.3.6sizeof运算符
sizeof是一个单目运算符,其作用是求运算对象所具有的字节数。它的使用形式是:
sizeof(〈运算对象〉)或sizeof〈运算对象〉其中,运算对象可以是数据类型名、变量名、常量名等。变量、常量的大小实际上是它所属类型的大小。在求字符串常量的大小时,包括了看不见的符号′\0′。若运算对象是类型名或由运算符连成的表达式,则必须用括号括起来;如果运算对象是简单常量或变量,则括与不括均可,如sizeofi++也是合法的,但最好加上括号。
sizeof运算符仅求其对象的长度,并不对运算对象进行求值计算,比如sizeof(++i)并不对i进行自加运算。
【例3-17】sizeof运算符。#include<stdio.h>main(){inti=162;floaty=0.0;printf(″sizeof(++i)=%d,i=%d\n″,sizeof(++i),i);printf(″sizeof(162)=%d\n″,sizeof(162));printf(″sizeof(\″China\″)=%d\n″,sizeof(″China″));printf(″sizeof(int)=%d\n″,sizeof(int));printf(″sizeof(float)=%d\n″,sizeof(float));printf(″sizeof(double)=%d\n″,sizeof(double));printf(″sizeof(1.1)=%d\n″,sizeof(1.1));printf(″sizeof(y=2*i+6.0)=%d\n″,sizeof(y=2*i+6.0));
printf(″y=%f\n″,y);return0;}运行输出:sizeof(++i)=2,i=162sizeof(162)=2sizeof(″China″)=6sizeof(int)=2sizeof(float)=4sizeof(double)=8sizeof(1.1)=8sizeof(y=2*i+6.0)=4y=0.000000因为考虑到了′\0′,所以sizeof(″China″)=6。编译程序总是把浮点常数作为double类型来处理,因此有sizeof(1.1)=8。
sizeof(y=2*i+6.0)=4是因为赋值运算符的左值y的类型是float,占4字节,返回的是y的类型的长度。
y=0.000000表明sizeof运算并不对运算对象求值。
虽然sizeof的使用像是一个函数调用,但它只是个运算符,不是函数,它没有函数调用方面的开销。3.3.7条件运算符条件运算符“?:”是C语言中惟一的一个三目运算符,它有三个运算对象,分别由“?”和“:”把它们连接起来,构成一个条件表达式。其语法形式是:
〈表达式1〉?〈表达式2〉:〈表达式3〉它的语义如图3-4所示。图3-4条件运算符的语义整个条件表达式的值要么是表达式2的值,要么是表达式3的值,全依赖于对表达式1的求值而定。表达式1是条件表达式,具有真假两个值,如其值为真,则计算表达式2,并把表达式2的值作为整个表达式的值返回;如其值为假,则把表达式3的求值结果返回。条件表达式可以嵌套和递归定义,在这种情况下,应先找到?和:,把它的三部分区别出来,然后按一般方法进行计算求值。条件运算符的结合性是由右向左的。例如:
a>b?a:c>d?c:d等价于
a>b?a:(c>d?c:d)条件运算符的优先级高于赋值运算符而低于关系运算符和算术运算符。因此
max=a>b?a:b+1;等价于
max=(a>b)?a:(b+1);而不等价于max=(a>b?a:b)+1;条件表达式中的三个表达式的类型可以互不相同,条件表达式的类型为表达式2和表达式3二者中较高的类型。忽视了这一点,程序就会出现错误的结果。
【例3-18】条件运算符一。#include<stdio.h>main(){intx=10;floaty=5;printf(″%d\n″,x>y?x:y);
return0;}本来预期输出应该是10,但结果却是0。这是因为在条件表达式“x>y?x:y”中,y的类型高,因此整个表达式的类型就应该是y的类型float,而对float类型数据用′%d′控制符是输出不了结果的。如果把′%d′换成′%f′,则会显示出正确的结果:10.000000。
【例3-19】条件运算符二。#include<stdio.h>main(){inta,b,c;a=b=c=1;a+=b;b+=c;c+=a;printf(″(1)%d\n″,a++>b?a:b);printf(″(2)%d\n″,a>b?c>a++:b-->a?c++:c--);(a>=b>=c)?printf(″AA\n″):printf(″BB\n″);printf(″a=%d,b=%d,c=%d\n″,a,b,c);return0;}运行输出:
(1)2 (2)0 BB a=4,b=2,c=3由复合赋值运算得:a=2,b=2,c=3,在计算a++>b?a:b中的a++>b为假之后,a就变成了3。表达式
a>b?c>a++:b-->a?c++:c--等价于
a>b?c>a++:(b-->a?c++:c--)右结合的规则使最右边的条件表达式成为一个整体,但这是否意味着先对它进行计算呢?答案是否定的。因为?:、‖、&&和,这四个运算符都严格地按从左向右的顺序进行运算,所以还是先计算a>b的值,而不是先计算b-->a的值。从上面还可以看出:条件表达式中的表达式2和表达式
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 芜湖小作坊管理制度
- 英语绘本馆管理制度
- 草原属书屋管理制度
- 胆固醇测定试剂盒分析性能评估资料
- 让人舒服的沟通技巧
- 财务会计工作职责和内容感想总结范文15篇
- 财务会计业务知识题库真题
- 试验检测师(公共基础)考试历年真题题库(含答案)
- 江苏省常州市前黄高级中学2025届高三下学期攀登行动(一)地理试卷(含答案)
- 湖南省2025年中考地理真题(含答案)
- 肠梗阻护理查房(小肠减压管的应用)
- JGT266-2011 泡沫混凝土标准规范
- 2024届辽宁省沈阳市东北育才校中考冲刺卷物理试题含解析
- 抗菌药物合理应用
- 初中体育篮球双手胸前传接球教案
- 中建盘扣式落地卸料平台施工方案
- 配电网技术标准(施工验收分册)
- 12英寸主要原辅材料消耗表
- 电力电子装置-2021复习要点
- 企业主要质量管理人员情况表
- 医院护理培训课件:《成人肠内营养支持的护理》
评论
0/150
提交评论