第3章 C编程基础知识 本章内容提要 (1)常量和变量。 (2)基本数据类型。 (3)基本运算符和表达式。 (4)数据的类型转换。 本章介绍的是编程用的基础知识,为后面的编程套路(如选择结构、循环结构等)做准 备,就如同学习武术,必须把基本功练好了,才能学套路。 本章主要内容有常量和变量,C语言的基本数据类型,算术运算符、赋值运算符、自增 自减运算符、逗号运算符以及由这些运算符所构成的表达式,数据的类型转换等。 3.1 常量和变量 C程序中,可以使用的数据分为两类:常量和变量。 3.1.1 常量 有些数据是“死数”,不可能变化,例如2,它在任何时候都是2;再如3.14,永远都是 3.14。类似这样的不可能发生变化的数据称为常量。C语言中的常量并非仅限于数值型 的“常数”,还包括字符、字符串、符号常量和常变量等。本书在3.2节和3.3节中会分别讲 述这些常量。 3.1.2 变量 变量的概念特别重要,因为几乎每个程序都要用到变量。能否正确地理解变量将直 接决定能否学好C语言。 1.什么是变量 程序之所以需要变量,是因为在程序运行的过程中有些数据需要记住。变量就是用 来记住这些数据的。 常量和变量 36 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 生活中,人们在求解一个问题时,通常都要记住一些数据,例如,要计算表达式1+ 2+…+100的值,在求解过程中,有两个数据必须记住:一是已经求得的和是多少;二是 下一次该加的数是几。 用计算机解题也是如此,也需要记住这些数据。人类用大脑或纸来记录数据,而计算 机则是用内存:在内存中开辟一部分空间,把数据化成二进制存放进去,这部分存储数据 的空间便是变量,即变量是内存中的一段存储区域。至于为什么叫变量,是因为这一段存 储空间所存的数据可以改变。例如下面的代码: short a; //定义变量a,a 的值不确定 a=2; //执行后,a 的值是2 a=3; //执行后,a 的值是3 变量a刚分配空间时,其内容是不确定的,执行“a=2;”后,a中的值变成2;执行“a= 3;”后,a中的值变为3。其变化过程如图3-1所示。 图3-1 变量的变化过程 图3-1中灰色的2字节是系统分配给a用来存整数的,因为其中所存的数可以改变, 所以把这2字节称为变量a。 说明:本书表示内存的图形,一律以图形上方为内存的低地址,下方为高地址, 以后不再说明。 说明:几乎所有微机的CPU 在存数据时都采用小端模式,即先存低字节,再存 高字节。 2.变量的类型 C语言中的数据类型有很多种,如整型、实型和字符型等。通常,每种类型的数据都 要用与它同类型的变量来存储,故变量的类型也分很多种。 表3-1是常用的变量类型以及它们在内存空间中所占用的字节数。 第3章 C编程基础知识 37 表3-1 常用的变量类型及相关数据 类 型类型表示 VisualC++6.0中TurboC2.0中 字节数表示范围字节数表示范围 存储方式 字符型 char 1 0~(28-1) 1 0~255 ASCII码 短整型 short 2 -215~(215-1) 2 -32768~32767 补码 整型 int 4 -231~(231-1) 2 -32768~32767 补码 长整型 long 4 -231~(231-1) 4 -231~(231-1) 补码 无符号短整型unsignedshort 2 0~(216-1) 2 0~65535 补码 无符号整型 unsignedint 4 0~(232-1) 2 0~65535 补码 无符号长整型unsignedlong 4 0~(232-1) 4 0~(232-1) 补码 浮点型 float 4 -3.4×1038~ 3.4×1038 4 -3.4×1038~ 3.4×1038 IEEE754 双精度型 double 8 -1.7×10308~ 1.7×10308 8 -1.7×10308~ 1.7×10308 IEEE754 说明: (1)ASCII码是为了在计算机中用二进制存储字符而制定的一种编码。本书附录D 中列有常用字符及其ASCII码值。 (2)unsigned是无符号的意思。C语言中有些数据不可能是负数,没有必要用最高位 表示正负,故unsigned类型的数据,其最高位不再表示负号,而是跟后面的位一样代表大小。 (3)float和double型变量的存储方式遵循IEEE754标准,详见3.2.2节。 (4)有些编译器还支持longlong、longdouble等类型,需要时请自行学习。 除了上面给出的类型,C语言中还有一种指针类型的变量,这种变量用来存储某种实 体的地址,这里所说的实体包括变量、数组和函数等。 存储地址的变量称为指针变量。 指针变量在内存中所分配的字节数一般与int型变量相同。 C语言中,可以用运算符sizeof()求得某种类型或某项数据在内存中占用多少字节, 使用时要在括号中写上类型名或变量名或表达式。例如: char c; float x; printf("%d,%d\n",sizeof(char),sizeof(c)); printf("%d,%d\n",sizeof(float), sizeof(x)); 运行结果: 试一试:自己编写程序验证一下表3-1中每种类型的变量需要多少字节的内存 空间。 38 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 3.变量的定义 1)变量定义的格式 普通变量的定义格式:用类型名开头,加一个空格之后再写变量名(由程序员命名)。 若定义多个变量,变量之间用逗号隔开。例如: int a; //定义一个变量 float x,y,z; //定义多个变量,变量之间用逗号隔开 指针变量的定义格式与普通变量定义格式类似,区别是:指针变量名字前面要多写 一个*,例如: char *p; //定义了一个指针变量p,用来存char 型变量的地址 int *p1,*p2; //定义两个指针变量p1 和p2,都用来存int 型变量的地址 float x,*p3,y,*p4; //定义了两个普通变量x、y 和两个指针变量p3、p4 变量必须先定义,然后才能使用。 定义变量的目的:一是给变量起一个名字,以便在程序中分辨它;二是把变量的类型 告诉计算机,以便让计算机给变量分配空间。因为有了类型,计算机才能知道该给变量分 配多少字节,才能知道变量的值用什么方式存储。例如,若是字符变量则分配1字节,变 量的值用ASCII码存储;若是短整型变量则分配2字节,用补码存储…… 变量定义的位置应该在同级别的执行语句之前(C99取消了这条规定)。 例如: int main() { int a,b; //变量定义 float x; //变量定义 a=1; //第一条执行语句 . if(a>b) { int s; //花括号内变量的定义 s=a+b; //花括号内第一条执行语句 … } . } 2)变量的命名 不管是变量,还是今后要学到的数组、函数和结构体等,每样东西都应该有一个名字 作为标识,其名字即为标识符。 C语言对标识符有如下要求。 (1)标识符只能由英文字母、数字和下画线组成,但不能以数字开头。 (2)C语言是区分大小写的,即大小写被认为是两个不同的字符。例如,name和 Name是两个不同的标识符。 第3章 C编程基础知识 39 (3)不允许用关键字作为标识符。关键字是指已经赋予一定含义的字符序列,如 int、float、for、if、return等。C语言有32个关键字,见附录C。 (4)标识符有长度限制,超过限制时,后面的字符不起作用。C89限制的标识符长度 是不超过31个字符。 注意:C语言中,变量名不能与函数名相同。例如,已经有函数max(),则变量名 便不能用max,反之亦然。 4.变量的属性 每个变量都有值和地址两个属性。 变量的值指的是变量在内存中所存的内容。变量的地址指的是变量在内存中所处的 位置,其起始地址称为变量的地址。 设有代码“shorta=5;”,则程序运行时需要在内存中分配2字节作为变量a的存储 区域,并且将5存放进去。设系统给a分配的空间是内存中1027和1028两个单元,如 图3-2 变量的两个属性 图3-2所示,则变量的值是5,变量的地址是1027。 把内存的哪2字节分配给变量是不可预知的,但是变量分 配在什么地方,系统是知道的。每当在内存中给一个变量分配 了空间,系统都会把变量名和它的地址、类型等信息记录下来, 以便将来找到它、存取它。 因此,在变量获取空间之后,其地址是可以被知道的,用取 地址运算符& 便可以获取变量的地址。 下面的程序可以输出整型变量a的两个属性。 #include <stdio.h> int main() { int a=5; printf("%d,%p\n",a,&a); //%p 表示用十六进制数输出地址 return 0; } 运行结果: 试一试:把代码中的“=5”去掉,运行一遍程序,看a还有没有值。 5.变量的赋值和赋初值 如前所述,定义变量的目的是为了存储数据,而赋值或赋初值都可以实现这一目的。 1)赋值 在给变量分配空间的任务完成之后,再给变量存放数据,称为赋值。例如,给普通变 量赋值: int a; //在内存中给a 分配空间 40 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) a=5*2; //向a 中存放数据,即赋值 其中,=是赋值号,不是数学里的等于号,赋值就是存储:把赋值号右边表达式的值计算 出来然后存储到左边变量的内存空间中。赋值后a的值是10。 说明:C语言中,=是赋值号,==才是等于号。 又如,给指针变量赋值: int a,*p; //在内存中给a 和p 分配空间 p=&a; //把a 的地址存到p 中,即给p 赋值 2)赋初值 在给变量分配空间的时候就向其中存放数据,称为赋初值。例如: int a=10; 赋值和赋初值的区别:开辟空间和存放数据这两件事情若是分两次完成的,是赋值; 开辟空间和存放数据这两件事情是一次就完成的,是赋初值。 定义若干变量时,可以只对一个或一部分变量赋初值,例如: int a,b=1,c,d=3,e,f; 也可以给全部变量都赋初值,例如: int a=5,b=5,c=5; 注意:变量初值相同时,不可以写成: int a=b=c=5; 3.2 基本数据类型 C语言的数据类型分为基本类型和构造类型,基本类型指的是系统固有的类型,也是 常用的类型;构造类型指的是用户自定义出来的类型。 C语言的基本数据类型如表3-1所示。本节介绍这些基本数据类型的表示方法、数 据存储方式以及输出方法。 3.2.1 整型数据 本节所说的整型数据包括short、int、long、unsignedshort、unsigned、unsignedlong 等所有整数。 1.整型常量的表示 程序中用到整型常量时,可以用3种进制表示:十进制、八进制和十六进制。例如: int a,b,c,d,e; 基本数据 类型 第3章 C编程基础知识 41 a=100; //用十进制表示整数 b=0144; //用八进制表示整数,必须用0 开头 c=-0144; d=0x64; //用十六进制表示整数,必须用0x 或0X 开头 e=-0x64; 上面5个赋值语句执行后,5个变量的值分别是100、100、-100、100、-100。 注意:程序中不允许使用二进制。 2.整型数据的存储 所有整数在计算机中都是以补码形式存放的。下面的代码定义了5个变量,5个变 量在内存中的存储状态如图3-3所示。 short a=5,b=-1,c=-32768; unsigned short d=32768; long e=65536; 图3-3 整数在内存中的存储 5的补码是0000000000000101,-1的补码是1111111111111111,-32768的补码 是1000000000000000,32768的补码也是1000000000000000,65536的补码是00000000 000000010000000000000000。 3.整型数据的输出 说明:对于数据的输出格式,本书第4章会详细介绍,这里先简单介绍一些常用 的输出格式。 (1)带符号整数输出时可以用十进制、八进制或十六进制。例如: int a=76; long b=65536; short c=26; printf("%d\n",a); // %d 表 示用十进制输出整数 printf("%o\n",a); // %o 表 示用八进制输出整数 printf("%x,%X\n",a,a); //%x 或%X 表示用十六进制输出整数 printf("%ld,%Lo,%lx\n",b,b,b); //加上L 或l 表示输出长整数 printf("%hd,%ho,%hx\n",c,c,c); //加上h 表示输出短整数 输出结果: 42 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 试一试:在TC 中输出长整数时,漏掉L 或l,会怎样? 把65536、65537、65538 用%d格式输出看看,并解释原因。在TC中输出整数时,多加了L或l,又会怎样? 解释 一下原因。 (2)无符号整数一般用%u格式输出,表示用十进制把一个数据当无符号整数来输 出。例如: unsigned int a=32768; long b=50000; printf("%u,%lu\n",a,b); //%u 表示输出无符号整数 (3)带符号整数可以当成无符号整数输出,反之亦然。例如: short a=-1; unsigned short b=65535; printf("%hd,%hd\n",a,b); //%hd,输出带符号短整数 printf("%hu,%hu\n",a,b); //%hu,输出无符号短整数 图3-4 -1和65535的存储状态 这段代码的输出结果如下: 想一想:为什么会出现这样的结果? 请根据 图3-4所示的存储状态解释其原因。 3.2.2 实型数据 1.实型常量 1)实型常量的表示 带小数点的常量称为实型常量。程序中的实型常量可以用两种形式表示。 (1)小数形式。例如,3.14、-12.5、0.38、.2、-.3等。 说明:当一个数是纯小数时,小数点前面的0可以省略。 (2)指数形式。例如,1.25E-2、12.5E-3、0.0125E0等。 指数形式相当于数学中的科学记数法。C语言用1.25E-2这种形式代表数学中的 1.25×10-2。可以看出,上面所列举的3个数,其大小是相同的。由此可见,同一个数可 以有无限种表示方式。 C语言规定:用指数形式表示实数时,E前面必须有数字,E后面必须是整数。 说明:实数只能用十进制表示,上面两种表示方法都是十进制的。 2)实型常量的类型 实型常量有单精度(float)和双精度(double)两种类型,有效数字分别是7位和15 第3章C编程基础知识43 位,最后一位是近似值。 程序中表示实型常量时,可在实型常量后面加F或f,表示它是单精度型,或者加L 或l,表示它是长双精度型。若什么都不加, 2, ouble型。 如1.则系统默认是d 编程经验:表示长双精度型时,最好用大写L而不是小写l,后者容易被看成 是1。 2.实型数据的存储 单精度和双精度型的数据,都是以浮点数的方式存储的,遵循IEEE(Instituteof ElectricalandElectronicsEnginers)754标准。本书只介绍float型数据的存储方式, double型数据的存储与float型类似。 float型的任何数据,在存储前都必须先表示为下面的格式: (符号)×M ×2n 其中, n 是指数, M 须满足条件:1.0。例如: 0≤ M <2.30.875×2 0,要先表示为+1.4 -0.要先表示为-1.-2 3925, 57×2 这之后,计算机用4字节,分成三部分分别存储符号、指数部分和小数部分。三部分 的位置及所占空间大小如表3-2所示。 表3- 2 float型数据存储空间的分配 符号位(0或1) 指数部分(n+127) 小数部分(M-1) 占1位[第31位] 占8位[第30位~第23位] 占23位[第22位~第00位] 注:表中最右边是第0位,最左边是第31位。 注意:指数部分存储的是n+127而不是n,小数部分存储的是 M -1而不是 M 。 下面分别说明这三部分怎样存储。 (1)符号位。占1位,用0表示正,用1表示负。 (2)指数部分。指数用8位存储,本来也有正负的,但是考虑到前面已经有一个正负 号了,再设一个符号位不合适,所以IEEE754标准规定:将指数部分加上127后再存储, 例如,若实际指数 n 为-2,则存储为125;若实际指数 n 为4,则存储为131 。这样规定的 目的是,指数加上127后不会是负数,故不必设指数的符号位。 (3)小数部分。用23位存储 M -1。按照规定,0≤ M <2.这样, M 的小 M 满足1.0, 数点前面将肯定有一个1,因此存储时就可以不存1(将1默认了),而只存小数点后面的 纯小数 M -1,例如, 875,只存0. 对于1.875 。这样做可以多存几位小数以提高数据精度。 综上所述,对于30.4,三部分的数据分别如下。 0=+1. (1)符号位:0(表示正)。 875×2 (2)指数部分:10000011(其值为131,表示实际指数是4)。 (3)小数部分:11100000000000000000000(0.875 )。 表示实际小数是1. 故浮点数30.0的实际存储状态如图3-5所示。 875, 44 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 图3-5 浮点数30.0的存储状态 特别注意:实数的存储不都是精确的。 例如,对实数1.2,其纯小数部分0.2化为二进制是一个无限循环小数:00110011 00110011…要想存储这个值,需要无限多的内存单元,而实际上存储纯小数部分的空间只 有23位,后面部分只能截掉或进位(0舍1入),所以实际存储的数据是近似值。 1.2的实际存储状态是00111111100110011001100110011010(有进位),它表示的小 数是1.2000000476837158203125,比1.2稍大。 由于有些实数存储不精确,所以在程序中应尽量避免比较两个实型数据是否相等。 下面的程序段的输出结果是“不相等”。 float x=0.2; if(x==0.2) printf("相等\n"); else printf("不相等\n"); 原因:x是float型变量,存储的是0.2的近似值,有7位有效数字,而常数0.2默认是 一个double型数据,有15位有效数字,它们的大小必然不同。 试一试:运行下面的程序,看运行结果是什么。请解释为什么会出现这样的结果。 #include <stdio.h> int main() { float x=-789.124; printf("%f\n",x); return 0; } 说明:上面介绍的是float型数据的存储方式,double型数据的存储方式与此类 似,只不过指数部分和小数部分的位数更多,分别是11位和52位。 3.实型数据的输出 单精度和双精度型数据都可以用%f或%e(%E)格式输出,%f格式是用小数形式输 出,%e(%E)格式是用指数形式输出。例如: float x=12345678,y=0.00314; double z=123.456789123456789; printf("%f, %f, %f\n",x,y,z); //%f: 小数形式输出 printf("%e, %e, %e\n",x,y,z); //%e: 指数形式输出 printf("%E, %E, %E\n",x,y,z); //%E: 指数形式输出,结果中E 大写 第3章 C编程基础知识 45 该代码段的输出如下: 用%f时,默认输出6位小数(不管有多少位有效数字,总是输出6位小数)。 说明:用%f格式输出时程序员可以用诸如%.3f这样的方式指定小数位数,参 见第4章printf()函数的介绍。 用%e(%E)时,按标准格式输出,即小数点前有且仅有1位非0的有效数字。而小数 点后面有几位小数以及e(E)后面的指数部分占几位,取决于编译器。 3.2.3 字符型数据 数据不仅仅指数值,还包括字符,字符也是C程序中经常要处理的数据。C语言可以 处理的字符有英文字母(大小写)、数字、标点、空格及其他一些符号,见附录D。 1.字符常量 单个的字符是字符常量。程序中要表示一个字符常量,不能直接写字符名,因为会引 起二义性。例如: int a=1; char c=a; //此处的a 是变量a 还是字符a? 为了区分变量和字符常量,C语言规定:字符必须放在一对单引号之中。例如: char c1='a',c2='A',c3=' '; / /正确 这样,C语言中,单引号就被赋予了一个含义,即它是定界符,用来表示一个字符的前 界和后界。也就是说,在C语言中,单引号(’)已经不是单引号了,而是定界符。 说明:程序中的单引号是不分左右的。 那么,C语言中若用到单引号,怎么写呢? 显然不能写成''',因为这样写编译器会把它 们都认作定界符。为了表示中间的单引号不是定界符,而是单引号,C语言又做了规定: 在中间的单引号前面加上一条反斜线(\),表示它不是定界符,而是单引号,所以,程序中 单引号应该表示成\' '',例如: char ch='\''; //给变量ch 存入一个单引号 printf("%c",ch); //%c 表示要输出一个字符 输出结果如下: 反斜线(\)的作用是把后面字符的本来含义转为另外的含义。例如,\' n',若不加反斜 线代表字符n本身,加上反斜线后就变成了换行符。 这种用\开头的字符,称为转义字符。C语言中的转义字符很多,表3-3列出的是常 46 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 用的一些转义字符及作用。 表3-3 常用的转义字符及作用 转义字符代表的含义输出该字符的结果 \' '' 一个单引号(') 输出:' \' "' 一个双引号(") 输出:" \' \' 一条反斜线(\) 输出:\ \' b' 退格键(Backspace) 光标向前移动一格(退回一格) \' n' 换行符光标移动到下行开头 \' r' 回车键(CR) 光标移动到本行开头 \' t' 水平制表符(Tab) 光标移动到下一个Tab位置 \' ooo' 如\' 101' 一个字符,该字符的ASCII码值用八进制 表示是ooo 输出该字符。注:ooo代表八进制数, 最多3位 \' xhh' 如\' x41' 一个字符,该字符的ASCII码值用十六进 制表示是hh 输出该字符。注:hh代表十六进制 数,最多2位 考考你:到目前为止,要表示字符'A' ,共有3种方法,你知道是哪3种吗? 2.字符数据的存储 计算机中只能存储0和1,任何数据都必须先化成0和1才能存储,字符也不例外。 多数计算机都是用“存字符的ASCII码值”的方法来存储字符。 基本ASCII码表(见附录D)中只有128个字符,加上后来扩充的128个,不过才256 个,所以C语言规定:字符用1字节存储。 图3-6 字符数据的存储 设有如下代码: char c1='A',c2='1'; 则c1和c2两个变量在内存中的存储状态如图3-6所示。 想一想:整数1在内存中怎样存储,与字符'1'是 一样的吗? 它们的十进制数分别是多少? 3.字符数据的大小 由于字符在计算机中实际存储的是其ASCII码值,是个整数,所以,C语言认为字符 也有大小,其ASCII码值就是它的大小。 因此,字符数据既可以用作字符,也可以用作整数。给字符变量赋值时,既可以赋字 符,也可以赋整数。例如: char c1='A',c2=65,c3=' ',c4='1'; printf("%c,%c,%c,%c\n",c1,c2,c3,c4); //%c 表示要输出一个字符 printf("%d,%d,%d,%d\n",c1,c2,c3,c4); //字符可当作整数输出 printf("%d,%d,%d\n",c1+c2,c1+1,'A'+1); //字符可参与运算 第3章 C编程基础知识 47 运行结果如下: 把整数当成字符输出也可以,只要不超过255。例如,printf("%c",65),结果是A。 4.字符数据的输出 如前面代码所示,输出字符型数据,可以用printf()函数(%c或%d格式)。除了 printf()函数之外,还可以使用putchar()函数,putchar()函数的使用方法将在第4章中 介绍。 3.2.4 字符串 程序中有时候需要用到一串字符,即字符串,而不是一个字符。 1.字符串的表示 C语言规定,字符串必须用一对双引号括起来,如"abc"、"12"、""、"A"。双引号中 可以没有字符,如"",表示一个空串。 说明:程序中的双引号也是不分左右的。 C语言中只有字符串常量,没有字符串类型的变量。 2.字符串的存储 虽然没有字符串变量,但字符串在处理时,仍要在内存中找空间存储。 图3-7 字符串的存储 存储字符串时,总是先把双引号中的每个字符按顺序存储到 内存中(连续存放),然后再在后面多存一个空字符('\0')。例如, "AB"在内存中占3字节,存储状态如图3-7所示。 空字符的ASCII码是0,表示为\' 0'。 之所以最后要多存一个空字符,是为了给字符串加一个结束 标志,不然,将来使用字符串时,不知道字符串是到哪结束的。 注意:空字符和空格并不是同一个字符,空字符的ASCII 码是0,空格的ASCII码是32,它们的存储状态不同,数值大小不同,作用也不同。 考考你:"A"和'A'是否相同? 若不同,区别是什么? 3.字符串的输出 输出字符串时,printf()函数中要使用%s格式。例如: printf("%s%s%s%s%s\n","ABCD"," ","123","","xyz"); 输出结果如下: 48 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 除了printf()函数外,还可以使用puts()函数输出字符串,puts()函数的使用方法将 在10.3节中介绍。 3.3 符号常量和常变量 除了前面已经讲述过的几种常量外,C语言中还有两种常量:符号常量和常变量。 3.3.1 符号常量 为方便编程,增加程序的可读性,程序中经常需要定义符号常量。例如: #define PI 3.141593 其中的PI称为符号常量,它代表后面的3.141593。 说明:其实这是一条编译预处理命令,称为宏定义,参见第9章。 注意:符号常量的定义是一条命令,不是语句,故后面不需要有分号。 定义符号常量之后,程序中用到圆周率时,既可以写3.141593,也可以写PI,如“s= PI*r*r;”。显然,使用后者更方便,这便是定义符号常量的第一个好处。 定义符号常量的第二个好处是便于修改程序。例如: #define NUM 60 用NUM 代表人数60。假设程序中很多地方都用到这个NUM,当人数发生变化时,例如 少了一个人,只需要把上面代码中的60改为59即可。若不用符号常量,程序中都写成了 60,当人数减少时,需要修改多处源代码。 定义符号常量还有一个好处:增加程序的可读性。若写成60,阅读程序的人看到60并 不一定把它当成人数,还可能把它当做年龄、体重、分数等,写成NUM 则不易引起误解。 符号常量只是个符号,它不是变量,内存中没有它的空间,所以不能赋值,也不能这样 定义: #define PI=3.141593 //错误的符号常量定义 符号常量名通常使用大写字母。 3.3.2 常变量 有些C编译器中允许定义常变量(有些书上称为常量),常变量的定义方法如下: const int n=60,m=50; //定义两个常变量并初始化 const float x=3.14; //定义一个常变量并初始化 常变量定义要用const开头,后面部分与变量的定义类似,只不过要初始化。 注意:常变量在定义时必须初始化。 第3章 C编程基础知识 49 说明:常变量的定义是一条语句,后面有分号。 常变量其实也是变量,也在内存中分配空间(同时要初始化),但初始化后就不允许再 变了。因为不能变,具有常量的特点,故称为常变量。 常变量只可以赋初值,不能赋值,因为赋值就等于是改写。 下面的代码有两处语法错误: const float x; //错误,常变量定义未赋初值 const int a=1; a=1; //错误,常变量不允许赋值 定义常变量之后,程序中可以随时使用它,但不能改变它。 3.4 运算符和表达式 本节介绍C语言中最基本的运算符和表达式。 3.4.1 算术运算符 算术运算是最常用的运算,C语言中的算术运算与数学中的算术运算不尽相同。 1.算术运算符 算术运算符有7个:+(正号)、-(负号)、*(乘)、/(除)、%(求余)、+(加)、-(减)。 说明:C语言中,乘号用*表示,且不可省略。例如,a*b不能写成ab。 %是求余运算符,它用来求出两个整数相除之后的余数。例如,8%3的值是2,20%7 的值是6。 说明:求余运算的结果,其符号应与%前面那个数的符号相同。例如,-5%3的 结果是-2,而5%-3的结果是+2。 求余运算符要求参与运算的两个量必须都是整数。其余运算符对此没有要求,参与 运算的数可以是整数,也可以是其他类型的数据。 2.算术运算符的目数 7个算术运算符中,+(正号)、-(负号)都是单目运算符,剩下5个都是双目运算符。 单目运算符是指它只需要一个运算量(即操作数),例如-5,只需要在负号后写一个数,前 边不需要,故它是单目运算符。双目运算符则需要两个运算量,例如,a+b,加号前后各需 要一个运算量。 3.算术运算符的优先级 算术运算符中,+(正号)、-(负号)的优先级别最高,*、/、%的优先级别次之,+(加 号)、-(减号)的优先级别最低(参看附录E)。 运算符和 表达式以 及类型转 换 50 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 根据优先级可知,表达式-5*-2+2%3与((-5)*(-2))+(2%3)运算次序 相同。算 术运算时,当两个运算符的优先级别不同时,先运算哪个取决于优先级;当两个运 算符优先级别相同时,先运算哪个取决于它们的结合性。 4.算术运算符的结合性 算术运算符中,两个单目运算符的结合性都是自右至左,简称右结合性。其余运算符 的结合性都是自左至右,称为左结合性。 左结合性是指:当两个运算符优先级别相同时,要先算左边的。例如,20*3%7,应 先算左边的乘法,相当于是(20*3)%7,而不是20*(3%7),两者结果不同。右结合性是 指:当两个运算符优先级别相同时,要先算右边的。例如,-+2,相当于是-(+2)。 考考你:既然+和*都是左结合性,那为什么a+b*c要先算b*c? 关于算术运算,需要特别注意的是,两个整数运算,最终结果还是整数。例如,9/5的 结果是1,1/2的结果是0,-5/3的结果是-1。结果为负时,多数机器采用向零取整的方 法将小数截掉,不四舍五入。 说明:向零取整是指,尽量使取整后的结果绝对值更小,离0更近。比如-9/5, 可以是-1,也可以是-2,但-1的绝对值更小,故取整后值为-1。 试一试:已知华氏温度到摄氏温度的转换公式是c=5/9*(f-32)。下面程序段 用来计算摄氏温度,其中华氏温度f是由键盘输入的,请写出完整的程序并运行之,看结 果是否正确。若不正确,找出原因。 int f,c; scanf("%d",&f); //输入整数作为华氏温度 c=5/9*(f-32); printf("%d\n",c); 提示:上面程序中,若想让5/9的结果是实数,应该写成5./9(或5/9.),让其中 一个数变成实数,其结果就是实数(带“.”的数,系统默认是double型)。 3.4.2 赋值运算符和赋值表达式 1.赋值运算符 赋值运算符就是=,表示“存储”,即把赋值号右边表达式的值存给左边的变量,3.1 节已做过一些介绍,这里再补充三点。 (1)左值的概念。 可以出现在赋值号左边的式子,称为左值(lvalue,即leftvalue)。左值必须有内存空 间且允许赋值。常用的左值是变量,但常变量不是左值。例如: int a=1; 第3章 C编程基础知识 51 const int b=2; a=2; //变量作为左值,正确 b=20; //语法错误: 常变量不是左值 (2)C语言中还有一些复合的赋值运算符,表3-4列出的是5个与算术运算有关的, 还有5个与位运算有关的,将在第15章中介绍。 表3-4 复合的赋值运算符及含义 运算符举 例相当于 += a+=2 a=a+2 -= a-=b a=a-b *= a*=b+c a=a*(b+c) /= a/=b+c a=a/(b+c) %= a%=5 a=a%5 (3)赋值运算符的结合性是自右至左。若有两个赋值号,要先执行右边的。 例如,a=b=2相当于是a=(b=2)。 2.赋值表达式 若一个表达式最后进行的是赋值运算,则该表达式是赋值表达式。 说明:C语言中,一个表达式最后执行的是“什么”运算,就把该表达式称为“什 么”表达式。例如,若最后执行的是算术运算,则它是算术表达式;若最后执行的是逻辑运 算,则它是逻辑表达式…… C语言中的所有表达式都是有值的。例如,算术表达式5-2*1的值是3,关系表达 式5>3的值是1(即“真”)等。 赋值表达式也有值。C语言规定:一个赋值表达式的值,等于赋值后左边变量的值。 例如,若赋值表达式是a=3*2,则表达式的值就是赋值后a的值,即6。 赋值表达式的值可以参与运算,如b=5+(a=3*2),执行后b的值是11。 上面表达式的运算过程是:先执行a=3*2,即把6赋给a,再计算5+(赋值表达式 的值),即5+6,得11,最后再把11存入变量b。 赋值表达式的值可以赋给另一个变量,如a=(b=2)。因赋值号是右结合性,故可以 省略括号,写成a=b=2。 还可以再复杂一点:a=b=c=2,它同a=(b=(c=2))等价。 考考你:上面a=b=c=2运算时,a、b、c3个变量哪个最先得到2? 哪个最后得 到? 若赋值表达式没有大小,还能不能像上面这样连等? 3.4.3 自增自减运算符 自增运算符是++,自减运算符是--。两个运算符都是单目运算符,都是右结合 性,运算优先级与正负号相同,参见附录E。 52 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 自增运算符用于给变量增加一个1,自减运算符用于给变量减少一个1。 自增和自减运算符都有两种用法,本书主要以++为例介绍两种用法的作用及区别。 1.自增运算符 自增运算符++分为前++和后++,写在变量前面的是前++,写在变量后面的是 后++,两者作用不同。例如下面的两段程序代码: int i=1,m; m=++i; //++写在变量前,是前++ printf("%d,%d\n",m,i); int i=1,m; m=i++; //++写在变量后,是后++ printf("%d,%d\n",m,i); 运行结果分别是: 之所以出现不同的结果,是因为++i和i++的求解过程不同。 首先需要强调的是,++i和i+ + 都是表达式,且两个表达式的值都是i。只不过, 两个i的值并不相同。 对于左侧代码中的++i来说,由于++写在前面,所以要先给i加1(变成2),再取i 的值(即2)作为表达式(++i)的值。 对于右侧代码中的i++来说,由于i写在前面,++写在后面,所以先取i的值(即 1)作为表达式(i++)的值,然后再给i加1,使i变成2。 因此,左侧的“m=++i;”相当于是以下两行代码: i=i+1; //先给i 加1 m=i; //表达式(++i)的值赋给m 而右侧的“m=i++;”相当于如下两行代码: m=i; //表达式(i++)的值赋给m i=i+1 //给i 加1 一句话:表达式++i的值是加1之后的i,而表达式i++的值是加1之前的i。无 论是i++还是++i,求解过程中都给i加了1。 若表达式i++和++i都不参与运算,则它们的作用相同,下面两条语句等价: (1)i++; (2)++i; 它们都相当于是“i=i+1;”。 说明:设开始时i=1,则上面两条语句分别相当于: (1)1; //这是表达式i++的值与分号构成的语句,此后要给i加1。 (2)2; //这是表达式++i的值与分号构成的语句,此前已给i加1。 由于求解表达式过程中都给i加了1,只不过一个在求值后,一个在求值前,因此,实 际上前面两条语句分别等价于: 第3章 C编程基础知识 53 (1)1; i= i+ 1; //后加 (2)i= i+ 1; //先加 2; 其中,“1;”和“2;”两条语句没有任何实际意义,可以去掉,所以,最初的两种写法就都 成了: (1)i=i+1; (2)i=i+1; 因此说,“i++;”和“++i;”的作用完全相同。 2.自减运算符 自减运算符--的用法与++用法类似,不再赘述。 需要指出的是,自增和自减运算都相当于赋值运算,因此它们只能作用于变量,不能 对表达式和常量进行自增或自减。下面写法都是错误的: (a+b)++; //相当于写成a+b=a+b+1; 语法错误: a+b 不是左值 2--; //相当于写成2=2-1; 语法错误: 2 不是左值 3.4.4 逗号运算符和逗号表达式 1.逗号运算符 C语言中,“,”也是一个运算符,称为逗号运算符。 逗号运算符是一个双目运算符,其结合性自左至右,其优先级在C语言的所有运算 符中最低。 2.逗号表达式 1)逗号表达式的格式 表达式1, 表达式2 即用逗号把两个式子连接起来。如“2,3”、“a=2,a*3”都是逗号表达式。 2)逗号表达式的求值方法 C语言中的表达式都是有值的。逗号表达式的值等于逗号后面那个式子的值,即表 达式2的值。例如,表达式“2,3”的值是3,表达式“a=2,a*3”的值是a*3的值,即6。 但是,要想计算a*3,必须先执行a=2。 所以,逗号表达式的求值方法是先求解表达式1,再求解表达式2,表达式2的值就是 逗号表达式的值。 3)多重逗号表达式 一个逗号表达式,可以作为另一个逗号表达式中的“表达式1”,例如: (a=1,b=2),a+b 54 C语言可以这样学(第2版·MOOC版·题库版·OnlineJudge版) 上式也是一个逗号表达式,只不过其中内嵌了一个逗号表达式。由于逗号表达式的 结合性是自左至右,上面的表达式可以去掉括号直接写成: a=1,b=2,a+b 它的求值顺序是先执行a=1,再执行b=2,最后求解a+b的值作为整个表达式 的值。还 可以继续不断嵌套,使逗号表达式成为如下模样: 式子1,式子2,式子3,式子4,… 其求值方法:自左至右按顺序求解每个式子,最后一个式子的值是整个表达式的值。 编程经验:其实,很多情况下使用逗号表达式的目的并不是求整个表达式的值, 而是让计算机按顺序去求解每个表达式以完成一个个操作,例如,a=1,b=2,c=3。这种 情况通常发生在需要用一条语句完成几条语句的功能时,但一般情况下尽量不要这样用。 考考你:m=1,2,3,4是逗号表达式还是赋值表达式? 整个表达式的值是多 少? 表达式求解之后,m 的值是多少? 3.4.5 类型转换运算符 C语言中,有时候需要人为地把某种类型的数据转换为程序需要的类型,这时候就需 要用类型转换运算符。例如,a、b是任意整数,求(a-b)/(a+b)的值。程序代码如下: int a,b; float result; scanf("%d%d",&a,&b); result=(a-b)/(a+b); printf("%f\n",result); 运行上面程序,从键盘输入6和4,输出为0,结果不正确。问题出在(a-b)/(a+b) 这个表达式上,因为分子和分母都是整型数据,相除后结果必然还是整数(0)。 要想得到实数,至少应该把分子和分母中的一个变成实数。其方法是,使用类型转换 运算符把表达式写成(float)(a-b)/(a+b)或(a-b)/(float)(a+b)。 类型转换运算符的格式如下: (类型名) (表达式) 作用是把表达式的值强制转换为指定的类型。 需要指出的是,在类型转换前,系统要先求解表达式的值,然后将该值在运算器中进 行处理、转换,被处理的是运算器中的结果,而不是表达式本身。表达式的类型和数值都 不发生改变。例如: float x=3.14; int m;