3.1 C的数据类型
3.2 常量与变量
3.3 整型数据
3.4 实型数据
3.5 字符型数据
3.6 变量赋初值第 3章 数据类型、运算符与表达式
3.7 各类数值型数据间的混合运算
3.8 算术运算符和算术表达式
3.9 赋值运算符和赋值表达式
3.10 逗号运算符和逗号表达式习题
3.1 C的数据类型一个程序应包括以下两方面内容:
(1) 对数据的描述。在程序中要指定数据的类型和数据的组织形式,即数据结构 (data structure)。
(2) 对操作的描述。即操作步骤,也就是算法 (algorithm)。
据是操作的对象,操作的目的是对数据进行加工处理,
以得到期望的结果。打个比方,厨师做菜肴,需要有菜谱。菜谱上一般应包括:
① 配料,指出应使用哪些原料;② 操作步骤,指出如何使用这些原料按规定的步骤加工成所需的菜肴。面对同一些原料可以加工出不同风味的菜肴。
作为程序设计人员,必须认真考虑和设计数据结构和操作步骤 (即算法 )。因此,著名计算机科学家沃思 (nikiklaus Wirth)提出一个公式数据结构 +算法 =
程序实际上,一个程序除了以上两个主要要素之外,还应当采用结构化程序设计方法进行程序设计,并且用某一种计算机语言表示。因此,可以这样表示:
程序 =算法 +数据结构 +程序设计方法 +语言工具和环境也就是说,以上 4个方面是一个程序设计人员所应具备的知识。在设计一个程序时要综合运用这几方面的知识。在本书中不可能全面介绍这些内容,
它们都属于有关的专门课程范畴。在这 4个方面中,
算法是灵魂,数据结构是加工对象,语言是工具,
编程需要采用合适的方法。算法是解决“做什么”
和“怎么做”的问题。程序中的操作语句,实际上就是算法的体现。算法处理的对象是数据,而数据是以某种特定的形式存在的 (例如整数、实数、
字符等形式 )。不同的数据之间往往还存在某些联系 (例如由若干个整数组成一个整数数组 )。所谓数据结构指的是数据的组织形式。例如,数组就是一种数据结构。不同的计算机语言所允许定义和使用的数据结构是不同的。例如,c语言提供了“结构体”这样一种数据结构,而 fortran语言就不提供这种数据结构。处理同一类问题,如果数据结构不同,算法也会不同。例如,对 10个整数排序和对由 10个整数构成的数组排序的算法是不同的。因此,在考虑算法时,必须注意数据结构。实际上,应当综合考虑算法和数据结构,选择最佳的数据结构和算法。
C语言的数据结构是以数据类型形式出现的。 c的数据类型如下,
数据类型,基本类型,整型,字符型,实型 (浮点型 )单精度型,双精度型,枚举类型,构造类型,
数组类型,结构体类型,共用体类型,指针类型,
空类型 C语言中数据有常量与变量之分,它们分别属于以上这些类型。由以上这些数据类型还可以构成更复杂的数据结构。例如利用指针和结构体类型可以构成表、树、栈等复杂的数据结构。
在程序中对用到的所有数据都必须指定其数据类型。在本章中主要介绍基本数据类型。
3.2 常量与变量
3.2.1 常量和符号常量在程序运行过程中,其值不能被改变的量称为常量。常量区分为不同的类型,如 12,0,-3为整型常量,4.6,-1.23为实型常量,‘ a?,‘ d?
为字符常量。常量一般从其字面形式即可判别。
这种常量称为字面常量或直接常量。
也可以用一个标识符代表一个常量,如,
例 3.1符号常量的使用。
#define price 30
main ( )
{
int num,total;
num=10;
total=num * price;
printf("total=%d",total);
}
程序中用 #define命令行定义 price代表常量 30,此后凡在本文件中出现的 price都代表 30,可以和常量一样进行运算,程序运行结果为
total=300
有关 #define命令行的详细用法参见第 8章。
这种用一个标识符代表一个常量的,称为符号常量,
即标识符形式的常量。请注意符号常量不同于变量,
它的值在其作用域 (在本例中为主函数 )内不能改变,
也不能再被赋值。如再用以下赋值语句给 price赋值是错误的。
price=40;
习惯上,符号常量名用大写,变量用小写,以示区别。
使用符号常量的好处是:
(1) 含义清楚。如上面的程序中,看程序时从 price就可知道它代表价格。因此定义符号常量名时应考虑
“见名知意”。 在一个规范的程序中不提倡使用很多的常数,如,sum= 15 * 30 * 23.5 * 43。在检查程序时搞不清各个常数究竟代表什么。应尽量使用
“见名知意”的变量名和符号常量。
(2) 在需要改变一个常量时能做到“一改全改”。
例如在程序中多处用到某物品的价格,如果价格用常数表示,则在价格调整时,就需要在程序中作多处修改,若用符号常量 price代表价格,只需改动一处即可。如:
#define price 35
在程序中所有以 price代表的价格就会一律自动改为 35。
3.2.2 变量其值可以改变的量称为变量。一个变量应该有一个名字,在内存中占据一定的存储单元。在该存储单元中存放变量的值。请注意区分变量名和变量值这两个不同的概念,见图 3.1。变量名实际上是一个符号地址,在对程序编译连接时由系统给每一个变量名分配一个内存地址。
在程序中从变量中取值,实际上是通过变量名找到相应的内存地址,
从其存储单元中读取数据。和其他高级语言一样,用来标识变量名、
图 3.1
符号常量名、函数名、数组名、类型名、文件名的有效字符序列称为标识符 (identifier)。简单地说,标识符就是一个名字。
C语言规定标识符只能由字母、数字和下划线三种字符组成,且第一个字符必须为字母或下划线。
下面列出的是合法的标识符,也是合法的变量名:
sum,average,-total,class,day,month,
student-name,tan,lotus-1-2-3,basic,li-ling
下面是不合法的标识符和变量名:
M,d.J ohn,y 123,# 33,3d64,a>b
注意,大写字母和小写字母被认为是两个不同的字符。因此,sum和 suM,class和 class是两个不同的变量名。一般,变量名用小写字母表示,与人们日常习惯一致,以增加可读性。
ANSI C标准没有规定标识符的长度 (字符个数 ),但各个 c编译系统都有自己的规定。有的系统 (如 ibM
PC的M s C)取 8个字符,假如程序中出现的变量名长度大于 8个字符,则只有前面 8个字符有效,后面的不被识别。例如,有两个变量:
student_name和 student_number,由于二者的前 8
个字符相同,系统认为这两个变量是一回事而不加区别。可以将它们改为 stud_name和 stud_num,
以使之区别。
Turbo C则允许 32个字符。因此,在写程序时应了解所用系统对标识符长度的规定,以免出现上面的混淆。这种错误并不反映在编译过程中 (即语法无错误 ),但运行结果显然不对。为了程序的可移植性 (即在甲机器上运行的程序可以基本上不加修改,就能移到乙机器上运行 )以及阅读程序的方便,建议变量名的长度不要超过 8个字符。
如前所述,在选择变量名和其他标识符时,应注意做到“见名知意”,即选有含意的英文单词 (或其缩写 )作标识符,如 count,name,day、
month,total,country等,除了数值计算程序外,
一般不要用代数符号 (如 a,b,c,x1,y1等 )作变量名,
以增加程序的可读性。这是结构化程序的一个特征。本书在一些简单的举例中,为方便起见,仍用单字符的变量?如 a,b,c等 ),请读者注意不要在其他所有程序中都如此。
在 c语言中,要求对所有用到的变量作强制定义,
也就是“先定义,后使用”,如例 1,2、例 1,3
那样。这样做的目的是:
(1) 凡未被事先定义的,不作为变量名,这就能保证程序中变量名使用得正确。例如,如果在定义部分写了
int student;
而在执行语句中错写成 staent。如:
staent=30;
在编译时检查出 statent未经定义,不作为变量名。
因此输出“变量 statent未经声明”的信息,便于用户发现错误,避免变量名使用时出错。
(2) 每一个变量被指定为一确定类型,在编译时就能为其分配相应的存储单元。如指定 a,b为 int型,
turbo c编译系统为 a和 b各分配两个字节,并按整数方式存储数据。
(3) 指定每一变量属于一个类型,这就便于在编译时,据此检查该变量所进行的运算是否合法。例如,整型变量 a和 b,可以进行求余运算:
a%b
%是“求余” (见 3.8节 ),得到 a/b的余数。如果将 a、
b指定为实型变量,则不允许进行“求余”运算,
在编译时会给出有关“出错信息”。
下面各节分别介绍整型、实型 (浮点型 )、字符型数据。
3.3 整型数据
3.3.1 整型常量的表示方法
整型常量即整常数。 c整常数可用以下三种形式表示:
(1) 十进制整数。如 123,-456,0。
(2) 八进制整数。以 0开头的数是八进制数。如
0123表示八进制数 123,即 (123)8,其值为:
1× 82+2× 81+3× 80,等于十进制数 8 3。 -011表示八进制数 -11,即十进制数 -9。
(3) 十六进制整数。以 0x开头的数是十六进制数。
如 0x123,代表十六进制数 123,即
(123)16=1× 162+2× 161+3× 160=256+32+3=291。
-0x12等于十进制数 -18。
3.3.2 整型变量
1,整型数据在内存中的存放形式数据在内存中是以二进制形式存放的。
如果定义了一个整型变量 i:
int i; /* 定义为整型变量 */
i=10; /* 给 i赋以整数 10 */
十进制数 10的二进制形式为 1010,在微机上使用的
c编译系统,每一个整型变量在内存中占 2个字节。
图 3.2(a)是数据存放的示意图。图 3.2(b)是数据在内存中实际存放的情况。
图 3.2
实际上,数值是以补码 (complement) 表示的。一个正数的补码和其原码的形式相同。图 3.2(b) 就是用补码形式表示的。如果数值是负的,在内存中如何用补码形式表示呢?求负数的补码的方法是:将该数的绝对值的二进制形式,按位取反再加 1。例如求 -10的补码:①取 -10的绝对值 10;②
10的绝对值的二进制形式为 1010;③对 1010取反得 1111111111110101(一个整数占 16位 );④再加 1
得 1111111111110110,见图 3.3。
图 3.3
可知整数的 16位中,最左面的一位是表示符号的,
该位为 0,表示数值为正;为 1则数值为负。
关于补码的知识不属于本书的范围,但学习 c语言的读者应该比学习其他高级语言的读者对数据在内存中的表示形式有更多的了解。这样才能理解不同类型数据间转换的规律。在本章稍后的叙述中还要接触到这方面的问题。
2,整型变量的分类整型变量的基本类型符为 int。可以根据数值的范围将变量定义为基本整型、短整型或长整型。在
int 之前可以根据需要分别加上修饰符
(modifier):short(短型 )或 long(长型 )。因此有以下三种整型变量:
(1) 基本整型,以 int表示。
(2) 短整型,以 short int表示,或以 short表示。
(3) 长整型,以 long int表示,或以 long表示。
在 turbo c中一个 int型的变量的值范围为 -215~ (215-
1),即 -32768~ 32767。在实际应用中,变量的值常常是正的 (如学号、库存量、年龄、存款额等 )。为了充分利用变量的表数范围,此时可以将变量定义为“无符号”类型。对以上三种都可以加上修饰符 unsigned,以指定是“无符号数” 。如果加上修饰符 signed,则指定是“有符号数”。 如果既不指定为 signed,也不指定为
unsigned,则隐含为有符号 (signed)。实际上
signed是完全可以不写的。归纳起来,可以用以下 6种整型变量。即:
有符号基本整型 [signed] int
无符号基本整型 unsigned int
有符号短整型 [signed] short [int]
无符号短整型 unsigned short [int]
有符号长整型 [signed] long [int]
无符号长整型 unsigned long [int]
如果不指定 unsigned或指定 signed,则存储单元中最高位代表符号 (0为正,1为负 )。如果指定 unsigned,为无符号型,存储单元中全部二进位 (bit)用作存放数本身,而不包括符号。无符号型变量只能存放不带符号的整数,如 123,4687等,而不能存放负数,如 -
123,-3。一个无符号整型变量中可以存放的正数的范围比一般整型变量中正数的范围扩大一倍。如果在程序中定义 a和 b两个变量,
int a;
unsigned int b;
则变量 a的数值范围为 -32768~ 32767。而变量 b的数值范围为 0~ 65535。
图 3.4(a) 表示有符号整型变量 a的最大值 (32767)。
图 3.4(b) 表示无符号整型变量 b的最大值 (65535)。
图 3.4
C标准没有具体规定以上各类数据所占内存字节数,
只要求 long型数据长度不短于 int型,short型不长于 int型。具体如何实现,由各计算机系统自行决定。如在微机上,int和 short都是 16位,而 long是
32位。在 Vax 750 上,short是 16位,而 int和 long
都是 32位,一般以一个机器字 (word)存乓桓鰅 nt
数据。前一阶段,微机的字长一般为 16位,故以
16位存放一个整数,但整数的范围太小,往往不够用,故将 long型定为 32位。而 Vax的字长为 32
位,以 32位存放一个整数,范围可达正负 21亿,
已足够用了,不必再将 long型定为 64位。所以将
int和 long都定为 32位。通常的做法是:把 long定为 32位,把 short定为 16位,而 int可以是 16
位,也可以是 32位。这主要取决于机器字长。在微机上用 long型可以得到大范围的整数,但同时会降低运算速度,因此除非不得已,不要随便使用 long型。
方括弧内的部分是可以省写的。例如 signed short
int与 short等价,尤其是 signed是完全多余的,
一般都不写 signed。一个整数 (以 13为例 )在存储单元中的存储情况,见图 3.5所示 (假设使用的是微机上的 c编译系统,如 turbo C,Ms C )。
图 3.5
3,整型变量的定义前面已提到,c规定在程序中所有用到的变量都必须在程序中定义,即“强制类型定义”。这是和
basic,fortran不同的,而和 pascal相类似。例如:
int a,b; (指定变量 a,b为整型 )
unsigned short c,d; (指定变量 c,d为无符号短整型 )
long e,f; (指定变量 e,f为长整型 )
对变量的定义,一般是放在一个函数的开头部分的声明部分 (也可以放在函数中某一分程序内,但作用域只限它所在的分程序,这将在第 6章介绍 )。
例 3.2整型变量的定义与使用。
main()
{int a,b,c,d; /*指定 a,b,c,d为整型变量 */
unsigned u; /*指定 u为无符号整型变量 */
a=12; b=-24; u=10;
c=a+u; d=b+u;
printf("a+u=%d,b+u=%d\n",c,d);
}
运行结果为
a+u=22,b+u=-14
可以看到不同种类的整型数据可以进行算术运算。
在本例中是 int型数据与 unsigned int型数据进行相加相减运算 (有关运算的规则在本章 3.7节中介绍 )。
4,整型数据的溢出在 turbo c中一个 int型变量的最大允许值为 32767,
如果再加 1,会出现什么情况?
例 3.3整型数据的溢出。
main()
{int a,b;
a=32767;
b=a+1;
printf("%d,%d",a,b);
}
运行结果为
32767,-32768
图 3.6
从图 3.6可以看到:变量 a的最高位为 0,后 15位全为 1。加 1后变成第 1位为 1,后面 15位全为 0。而它是 -32768的补码形式,所以输出变量 b的值为 -
32768。请注意:一个整型变量只能容纳 -
32768~ 32767范围内的数,无法表示大于 32767
的数。遇此情况就发生“溢出”,但运行时并不报错。它好像汽车的里程表一样,达到最大值以后,又从最小值开始计数。所以,32767加 1得不到 32768,而得到 -32768,这可能与程序编制者的原意不同。从这里可以看到,c的用法比较灵活,往往出现副作用,而系统又不给出“出错信息”,要靠程序员的细心和经验来保证结果的正确。将变量 b改成 long型就可得到预期的结果
32768。
3.3.3 整型常量的类型我们已知整型变量可分为 int,short int,long int和
unsigned int,unsigned short,unsigned long等类别。那么常量是否也有这些类别?在将一个整型常量赋值给上述几种类别的整型变量时如何做到类型匹配?请注意以下几点:
(1) 一个整数,如果其值在 -32768~ +32767范围内,
认为它是 int型,它可以赋值给 int型和 long int型变量。
(2) 一个整数,如果其值超过了上述范围,而在 -
2147483648~ +2147483647范围内,则认为它是长整型,可以将它赋值给一个 long int型变量。
(3) 如果某一计算机系统的 c版本 (例如 turbo c)确定
short int与 int型数据在内存中占据的长度相同,
则它的表数范围与 int型相同。因此,一个 int型的常量也同时是一个 short int型常量,可以赋给
int型或 short int型变量。
(4) 一个整常量后面加一个字母 u,认为是 unsigned
int型,如 12345u,在内存中按 unsigned int规定的方式存放 (存储单元中最高位不作为符号位,
而用来存储数据,见图 3.4(b)。如果写成 -12345u,
则先将 -12345转换成其补码 53191,然后按无符号数存储。
(5) 在一个整常量后面加一个字母 l或 l,则认为是
long int型常量。
例如 123l,432l,0l等,这往往用于函数调用中。
如果函数的形参为 long int型,则要求实参也为
long int型,此时用 123作实参不行,而要用 123l
作实参。
3.4 实型数据
3.4.1 实型常量的表示方法实数 (real number)又称浮点数 (floating-point
number)。实数有两种表示形式:
(1) 十进制小数形式。它由数字和小数点组成 (注意必须有小数点 )。,123,123.,123.0,0.0都是十进制小数形式。
(2) 指数形式。如 123e3或 123e3都代表 123× 103。但注意字母 e(或 e)之前必须有数字,且 e后面的指数必须为整数,如 e3,2.1e3.5、,e3,e等都不是合法的指数形式。
一个实数可以有多种指数表示形式。例如 123.456
可以表示为 123.456e0,12.3456e1,1.23456e2、
0.123456e3,0.0123456e4,0.00123456e5等。把其中的 1.23456e2称为“规范化的指数形式”,
即在字母 e(或 e)之前的小数部分中,小数点左边应有一位 (且只能有一位 )非零的数字。例如
2.3478e2,3.0999e5,6.46832e12都属于规范化的指数形式,而 12.908e10,0.4578e3,756e0则不属于规范化的指数形式。一个实数在用指数形式输出时,是按规范化的指数形式输出的。例如,
指定将实数 5689.65按指数形式输出,必然输出
5.68965e+003,而不会是 0.568965e+004或
56.8965e+002。
1,实型数据在内存中的存放形式在常用的微机系统中,一个实型数据在内存中占 4个字节 (32位 )。
与整型数据的存图 3.7储方式不同,实型数据是按照指数形式存储的。系统把一个实型数据分成小数部分和指数部分,分别存放。指数部分采用规范化的指数形式。实数 3.14159 在内存中的存放形式可以用图 3.7
示意。
3.4.2 实型变量图 3.7
图中是用十进制数来示意的,实际上在计算机中是用二进制数来表示小数部分以及用 2的幂次来表示指数部分的。在 4个字节 (32位 )中,究竟用多少位来表示小数部分,多少位来表示指数部分,标准 C并无具体规定,由各 C编译系统自定。不少 c编译系统以 24位表示小数部分 (包括符号 ),以 8位表示指数部分 (包括指数的符号 )。小数部分占的位 (bit)数愈多,数的有效数字愈多,精度愈高。指数部分占的位数愈多,
则能表示的数值范围愈大。
2,实型变量的分类
C实型变量分为单精度 (float型 )、双精度 (double型 )和长双精度型 (long double)三类。
ANSI C 并未具体规定每种类型数据的长度、精度和数值范围。有的系统将 double型所增加的 32位全用于存放小数部分,这样可以增加数值的有效位数,减少舍入误差。有的系统则将所增加的位
(bit)用于存放指数部分,这样可以扩大数值的范围。表 3.2列出的是微机上常用的 c编译系统 (如
turbo c,Ms c,borland c )的情况。应当了解,
不同的系统会有差异。
对每一个实型变量都应在使用前加以定义。如,
float x,y,(指定 x,y为单精度实数 )
double z; (指定 z为双精度实数 )
long double t; (指定 t为长双精度实数 )
在初学阶段,对 long double型用得较少,因此我们不准备作详细介绍。读者只要知道有此类型即可。
3,实型数据的舍入误差由于实型变量是由有限的存储单元组成的,因此能提供的有效数字总是有限的,在有效位以外的数字将被舍去。由此可能会产生一些误差。例如,a
加 20的结果显然应该比 a大。请分析下面的程序:
例 3.4实型数据的舍入误差。
main()
{float a,b;
a = 123456,789e5;
b = a + 20 ;
printf("%f",b);
}
程序内 printf函数中的,%f” 是输出一个实数时的格式符。程序运行时,输出 b的值与 a相等。原因是,a的值比 20大很多,a+20的理论值应是
12345678920,而一个实型变量只能保证的有效数字是 7位有效数字,后面的数字是无意义的,
并不准确地表示该数。
运行程序得到的 a和 b的值是 12345678848.000000,
可以看到,?位是准确的,后几位是不准确的,
把 20加在后几位上,是无意义的。应当避免将一个很大的数和一个很小的数直接相加或相减,否则就会“丢失”小的数。与此类似,用程序计算
1.0/3*3的结果并不等于 1。
3.4.3 实型常量的类型
C编译系统将实型常量作为双精度来处理。例如已定义一个实型变量 f,有如下语句:
f = 2.45678 * 4523.65
系统将 2.45678和 4523.65按双精度数据存储 (占 64位 )和运算,得到一个双精度的乘积,然后取前 7
位赋给实型变量 f。这样做可以保证计算结果更精确,
但是运算速度降低了。可以在数的后面加字母 f或
f(如 1.65f,654.87f),这样编译系统就会按单精度 (32
位 )处理。一个实型常量可以赋给一个 float型、
double型或 long double变量。根据变量的类型截取实型常量中相应的有效位数字。假如 a已指定为单精度实型变量:
float a;
a=111111,111;
由于 float型变量只能接收 7位有效数字,因此最后两位小数不起作用。如果 a改为 double型,则能全部接收上述 9位数字并存储在变量 a中。
3.5 字符型数据
3.5.1 字符常量
C的字符常量是用单引号 (即撇号 )括起来的一个字符。
如‘ a?,‘ x?,‘ d?,‘,‘’等都是字符常量。
注意,‘ a?和‘ a?是不同的字符常量。
除了以上形式的字符常量外,C还允许用一种特殊形式的字符常量,就是以一个,\”开头的字符序列。例如,前面已经遇到过的,在 printf函数中的
‘ \n?,它代表一个“换行”符。这是一种“控制字符”,在屏幕上是不能显示的。在程序中也无法用一个一般形式的字符表示,只能采用特殊形式来表示。
例 3.5转义字符的使用。
m ain()
{ printf(" ab c\t de\rf\tg\n");
printf("h\ti\b\bj k");
}
程序中没有设字符变量,用 printf函数直接输出双引号内的各个字符。
请注意其中的“转义字符”。第一个 printf函数先在第一行左端开始输出,ab c”,然后遇到,\t”,
它的作用是“跳格”,即跳到下一个“制表位置”,在我们所用系统中一个“制表区”占 8列。
“下一制表位置”从第 9列开始,故在第 9~ 11列上输出,de”。
下面遇到,\r”,它代表“回车” (不换行 ),返回到本行最左端 (第 1列 ),输出字符,f”,然后遇,\t”
再使当前输出位置移到第 9列,输出,g”。下面是,\n”,作用是“使当前位置移到下一行的开头”。第二个 printf函数先在第 1列输出字符,h”,
后面的,\t”使当前位置跳到第 9列,输出字母
,i”,然后当前位置应移到下一列 (第 10列 )准备输出下一个字符。下面遇到两个,\b”,,\b”的作用是“退一格”,因此,\b\b”的作用是使当前位置回退到第 8列,接着输出字符,j k”。
程序运行时在打印机上得到以下结果:
fab c gde
h jik
注意在显示屏上最后看到的结果与上述打印结果不同,是:
f gde
h j k
这是由于,\r”使当前位置回到本行开头,自此输出的字符 (包括空格和跳格所经过的位置 )将取代原来屏幕上该位置上显示的字符。所以原有的
,ab c,被新的字符,f g”代替,其后的
,de”未被新字符取代。换行后先输出,h i”,
退两格后再输出“j k”,j后面的,”将原有的字符,i”取而代之。因此屏幕上看不到,i”。实际上,屏幕上完全按程序要求输出了全部的字符,
只是因为在输出前面的字符后很快又输出后面的字符,在人们还未看清楚之前,新的已取代了旧的,所以误以为未输出应输出的字符。而在打印机输出时,不像显示屏那样会“抹掉”原字符,
留下了不可磨灭的痕迹,它能真正反映输出的过程和结果。
3.5.2 字符变量字符型变量用来存放字符常量,请注意只能放一个字符,不要以为在一个字符变量中可以放一个字符串 (包括若干字符 )。字符变量的定义形式如下:
char c1,c2;
它表示 c1和 c2为字符型变量,各可以放一个字符,
因此在本函数中可以用下面语句对 c1,c2赋值:
c1='a'; c2='b';
在所有的编译系统中都规定以一个字节来存放一个字符,或者说一个字符变量在内存中占一个字节。
3.5.3 字符数据在内存中的存储形式及其使用方法将一个字符常量放到一个字符变量中,实际上并不是把该字符本身放到内存单元中去,而是将该字符的相应的 ASCII代码放到存储单元中。例如字符
‘ a?的 ASCII代码为 97,‘ b?
为 98,在内存中变量 c1,c2的值如图 3.8(a)所示。实际上是以二进制形式存放的,如图
3.8(b)所示。
图 3.8
既然在内存中,字符数据以 ASCII码存储,它的存储形式就与整数的存储形式类似。这样,在字符型数据和整型数据之间的转换就比较方便了。一个字符数据既可以以字符形式输出,也可以以整数形式输出。以字符形式输出时,需要先将存储单元中的 ASCII码转换成相应字符,然后输出。
以整数形式输出时,直接将 ASCII码作为整数输出。也可以对字符数据进行算术运算,此时相当于对它们的 ASCII码进行算术运算,只是将其一个字节转化为 29字节,然后参加运算。
例 3.6向字符变量赋以整数。
main()
{char c1,c2;
c1=97;
c2=98;
printf("%c %c\n",c1,c2);/*以字符形式输出 */
printf("%d %d\n",c1,c2);/*转换为整数形式输出 */
}
c1,c2被指定为字符变量。但在第 3和第 4行中,将整数
97和 98分别赋给 c1和 c2,它的作用相当于以下两个赋值语句:
c1='a'; c2='b';
因为‘ a?和‘ b?的 ASCII
码为 97和 98。在程序的第 3和第 4行是把 97和 98
两个整数直接存放到 c1
和 c2的内存单元中。而
c1=?a?和 c2=?b?则是先将字符‘ a?和‘ b?化成 ascii
码 97和 98,然后放到内存单元中。二者的作用和结果是相同的。第 5行输出两个字符 a和 b。
,%c” 是输出字符时必须使用的格式符。程序第 6行输出两个整数 97和
98。
图 3.9
程序运行时输出如下:
ab
9798
可以看到:字符型数据和整型数据是通用的。它们既可以用字符形式输出 (用 %c),也可以用整数形式输出 (用 %d),见图 3.9。但是应注意字符数据只占一个字节,它只能存放 0~ 255 范围内的整数。
例 3.7大小写字母的转换。
main()
{char c1,c2;
c1='a';
c2='b';
c1=c1-32;
c2=c2-32;
printf("%c %c",c1,c2);
}
运行结果为
ab
程序的作用是将两个小写字母 a和 b转换成大写字母 a和
b。‘ a?的 ascii码为 97,而‘ a?为 65,‘ b?为 98,‘ b?
为 66。从 ascii代码表中可以看到每一个小写字母比它相应的大写字母的 ascii码大 32。 c语言允许字符数据与整数直接进行算术运算,即‘ a?+32会得到整数 97,
‘ a?-32会得到整数 65。
C语言对字符数据作这种处理使程序设计时增大了自由度。例如对字符作各种转换就比较方便。而在 basic语言中,为了将小写字母 a转换成大写字母 a,需要用两个字符处理函数:用 asc函数将字符转换成其相应的 ascii码,再用 chr函数将 ascii码转换为字符:
print chr(asc("a")-32)
这样来回转换,既增加程序的复杂性,又增加计算时间的开销。
字符数据与整型数据可以互相赋值。如:
int i;
char c;
i='a';
c=97;
是合法的。如果用格式符,%d” 将 i的值输出,可得到 97。用,%c” 输出 c,可得字符‘ a?。
如果在上面语句之后执行以下语句:
printf("%c,%d\n",c,c);
printf("%c,%d\n",i,i);
输出:
a,97
a,97
说明:有些系统 (如 pdp,Vax-11,turbo c)将字符变量中的最高位作为符号位,也就是将字符处理成带符号的整数,即 signed char型。它的取值范围是 -128~ 127。如果使用 ascii码为 0~ 127间的字符,由于字节中最高位为 0,因此用 %d输出时,
输出一个正整数。如果使用 ascii码为 128~ 255间的字符,由于在字节中最高位为 1,用 %d格式符输出时,就会得到一个负整数。例如,
char c=130;
printf(“%d”,c);
得到 -126。如果不想按有符号处理,可以将字符变量定义为 unsigned char类型,这时其取值范围是
0~ 255。 signed char 和 unsigned char的含义及用法与 signed int和 unsigned int相仿,但它只有一个字节。
3.5.4 字符串常量前面已提到,字符常量是由一对单引号括起来的单个字符。 c语言除了允许使用字符常量外,还允许使用字符串常量。字符串常量是一对双引号括起来的字符序列。如,
,how do you do.”,,CHINA","a",123.45"
都是字符串常量。可以输出一个字符串,如,
printf("how do you do.");
不要将字符常量与字符串常量混淆。‘ a?是字符常量,,a”是字符串常量,二者不同。假设 c被指定为字符变量:
char c;
c='a';
是正确的。而
c="a";
是错误的。
c=“CHINA"
也是错误的。不能把一个字符串赋给一个字符变量。
有人不能理解‘ a?和,a”究竟有什么区别?c规定:
在每一个字符串的结尾加一个“字符串结束标志”,以便系统据此判断字符串是否结束。 C规定以字符‘ \0?作为字符串结束标志。‘ \0?是一个
ASCII码为 0的字符,从 ascii代码表中可以看到 ascii码为 0
的字符是“空操作字符”,即它不引起任何控制动作,也不是一个可显示的字符。如果有一个字符串,CHINA”,实际上在内存中是
C H I N A \0
它的长度不是 5个字符,而是 6个字符,最后一个字符为‘ \0?。但在输出时不输出‘ \0?。例如在
printf("how do you do.")中,输出时一个一个字符输出,直到遇到最后的‘ \0?字符,就知道字符串结束,停止输出。注意,在写字符串时不必加
‘ \0?,否则会画蛇添足。‘ \0?字符是系统自动加上的。字符串,a”,实际上包含 2个字符:‘ a?
和‘ \0?,因此,把它赋给只能容纳一个字符的字符变量 c:
c="a";
显然是不行的。
在 c语言中没有专门的字符串变量 (basic中的字符串变量形式为 a,b等 ),如果想将一个字符串存放在变量中,以便保存,必须使用字符数组,即用一个字符型数组来存放一个字符串,数组中每一个元素存放一个字符。这将在第 6章中介绍。
3.6 变量赋初值程序中常需要对一些变量预先设置初值。 C语言允许在定义变量的同时使变量初始化。如:
int a=3; /* 指定 a为整型变量,初值为 3 */
float f=3.56;/* 指定 f为实型变量,初值为 3.56 */
char c='a'; /* 指定 c为字符变量,初值为 'a' */
也可以使被定义的变量的一部分赋初值。如,
int a,b,c=5;
表示指定 a,b,c为整型变量,只对 c初始化,c的值为 5。
如果对几个变量赋予初值 3,应写成
int a=3,b=3,c=3;
表示 a,b,c的初值都是 3。不能写成:
int a=b=c=3;
初始化不是在编译阶段完成的 (只有在第 7章中介绍的静态存储变量和外部变量的初始化是在编译阶段完成的 ),而是在程序运行时执行本函数时赋予初值的,相当于有一个赋值语句。例如,
int a=3;
相当于:
int a;/*指定 a为整型变量 */
a=3; /*赋值语句,将 3赋给 a */
又如
int a,b,c=5;
相当于:
int a,b,c; /*指定 a,b,c为整型变量 */
c=5; /*将 5赋给 c*/
3.7 各类数值型数据间的混合运算整型 (包括 int,short,long)、实型
(包括 float,double)可以混合运算。
前已述及,字符型数据可以与整型通用,因此,整型、实型、字符型数据间可以混合运算。例如,
10+'a'+1,5-8765,1234 *'b'
是合法的。在进行运算时,不同类型的数据要先转换成同一类型,
然后进行运算。转换的规则按图
3.10所示。
图 3.10
图中横向向左的箭头表示必定的转换,如字符数据必定先转换为整数,short型转换为 int型,float
型数据在运算时一律先转换成双精度型,以提高运算精度 (即使是两个 float型数据相加,也先都化成 double型,然后再相加 )。
纵向的箭头表示当运算对象为不同类型时转换的方向。例如 int型与 double型数据进行运算,先将 int
型的数据转换成 double型,然后在两个同类型
(double型 )数据间进行运算,结果为 double型。
注意箭头方向只表示数据类型级别的高低,由低向高转换。不要理解为 int型先转换成 unsigned
int型,再转成 long型,再转成 double型。如果一个 int型数据与一个 double型数据运算,是直接将 int型转成 double型。同理,一个 int型与一个
long型数据运算,先将 int型转换成 long型。
换言之,如果有一个数据是 float型或 double型,则另一数据要先转换为 double型,运算结果为
double型。如果参加运算的两个数据中最高级别为 long型,则另一数据先转换为 long型,运算结果为 long型。其他依此类推。
假设已指定 i为整型变量,f为 float变量,d为 double
型变量,e为 long型,有下面式子:
10+'a'+i*f-d/e
在计算机执行时从左至右扫描,运算次序为:
①进行 10+?a?的运算,先将‘ a?转换成整数 97,运算结果为 107。
②由于,*”比,+”优先,先进行 i*f的运算。先将 i
与 f都转成 double型,运算结果为 double型。
③整数 107与 i*f的积相加。先将整数 107转换成双精度数 (小数点后加若干个 0,即 107,000…00),结果为 double型。
④将变量 e化成 double型,d/e结果为 double型。⑤
将 10+'a'+i*f的结果与 d/e的商相减,结果为
double型。
上述的类型转换是由系统自动进行的。
3.8 算术运算符和算术表达式
3.8.1 C运算符简介
C语言的运算符范围很宽,把除了控制语句和输入输出以外的几乎所有的基本操作都作为运算符处理,例如将赋值符,=”作为赋值运算符,方括号作为下标运算符等。 c的运算符有以下几类:
1.算术运算符 (+-*/%)
2.关系运算符 (>< ==> =< =! =)
3.逻辑运算符 (! &&||)
4.位运算符 (<< >> ~ |∧&)
5.赋值运算符 (=及其扩展赋值运算符 )
6.条件运算符 (?,)
7.逗号运算符 (,)
8.指针运算符 (*和 &)
9.求字节数运算符 (s i z e o f)
10.强制类型转换运算符 ( (类型 ) )
11.分量运算符 (,->)
12.下标运算符 ([ ])
13.其他 (如函数调用运算符 ())
本章只介绍算术运算符和赋值运算符,在以后各章中结合有关内容将陆续介绍其他运算符。
3.8.2 算术运算符和算术表达式
1,基本的算术运算符
+(加法运算符,或正值运算符。如 3+5,+3)
-(减法运算符,或负值运算符。如 5-2,-3)
*(乘法运算符。如 3*5)
/(除法运算符。如 5/3)
%(模运算符,或称求余运算符,%两侧均应为整型数据,如 7%4的值为 3)。
需要说明,两个整数相除的结果为整数,如 5/3
的结果值为 1,舍去小数部分。但是,如果除数或被除数中有一个为负值,则舍入的方向是不固定的。例如,-5/3在有的机器上得到结果 -1,
有的机器则给出结果 -2。多数机器采取“向零取整”的方法,即 5/3=1,-5/3=-1,取整后向零靠拢。如果参加 +,-,*,/运算的两个数中有一个数为实数,则结果是 double型,因为所有实数都按
double型进行运算。
2,算术表达式和运算符的优先级与结合性用算术运算符和括号将运算对象 (也称操作数 )连接起来的、符合 c语法规则的式子,称 c算术表达式。
运算对象包括常量、变量、函数等。例如,下面是一个合法的 c
算术表达式:
a*b/c-1.5+'a'
C语言规定了运算符的优先级和结合性。在表达式求值时,先按运算符的优先级别高低次序执行,
例如先乘除后加减。如表达式 a-b*c,b的左侧为减号,右侧为乘号,而乘号优先于减号,因此,
相当于 a-(b*c)。如果在一个运算对象两侧的运算符的优先级别相同,如 a-b+c,则按规定的“结合方向”处理。
C规定了各种运算符的结合方向 (结合性 ),算术运算符的结合方向为“自左至右”,即先左后右,
因此 b先与减号结合,执行 a-b的运算,再执行加
c的运算。“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看到有些运算符的结合方向为“自右至左”,
即右结合性 (例如,赋值运算符 )。关于“结合性”
的概念在其他一些高级语言中是没有的,是 c的特点之一,希望能弄清楚。
如果一个运算符的两侧的数据类型不同,则会按
3.7节所述,先自动进行类型转换,使二者具有同一种类型,然后进行运算。
3,强制类型转换运算符可以利用强制类型转换运算符将一个表达式转换成所需类型。例如:
(double)a (将 a转换成 double类型 )
(int)(x+y) (将 x+y的值转换成整型 )
(float)(5%3) (将 5%3的值转换成 float型 )
其一般形式为 (类型名 )(表达式 )
注意,表达式应该用括号括起来。如果写成
(int)x+y
则只将 x转换成整型,然后与 y相加。
需要说明的是在强制类型转换时,得到一个所需类型的中间变量,原来变量的类型未发生变化。例如:
(int)x (不要写成 int(x))
如果 x原指定为 float型,进行强制类型运算后得到一个 int型的中间变量,它的值等于 x的整数部分,
而 x的类型不变 (仍为 float型 )。见下例。
例 3.8强制类型转换。
main()
{ float x;
int i;
x=3,6;
i=(int)x;
printf("x=%f,i=%d",x,i);
}
运行结果如下:
x=3.600000,i=3
x类型仍为 float型,值仍等于 3.6。
从上可知,有两种类型转换,一种是在运算时不必用户指定,系统自动进行的类型转换,如 3+6.5。
第二种是强制类型转换。当自动类型转换不能实现目的时,可以用强制类型转换。如,%” 运算符要求其两侧均为整型量,若 x为 float型,则
,x%3” 不合法,必须用:,(int)x % 3” 。强制类型转换运算优先于 %运算,因此先进行 (int)x
的运算,得到一个整型的中间变量,然后再对 3
求模。此外,在函数调用时,有时为了使实参与形参类型一致,可以用强制类型转换运算符得到一个所需类型的参数。
4,自增、自减运算符作用是使变量的值增 1或减 1,如:
++i,--i(在使用 i之前,先使 i的值加 (减 )1)
i++,i-- (在使用 i之后,使 i的值加 (减 )1)
粗略地看,++i和 i++的作用相当于 i=i+1。但 ++i和
i++不同之处在于 ++i是先执行 i=i+1后,再使用 i的值;而 i++是先使用 i的值后,再执行 i=i+1。如果 i
的原值等于 3,则执行下面的赋值语句:
① =++i; (i的值先变成 4,再赋给j,j的值为 4)
② j =i++; (先将 i的值 3赋给j,j的值为 3,然后 i变为 4)
又如:
i=3;
printf("%d",++i);
输出,4”。若改为
printf("%d",i++);
则输出,3”。
注意:
(1) 自增运算符 (++)和自减运算符 (--),只能用于变量,而不能用于常量或表达式,如 5++或 (a+b)++
都是不合法的。因为 5是常量,常量的值不能改变。 (a+b)++也不可能实现,假如 a+b的值为 5,
那么自增后得到的 6放在什么地方呢?无变量可供存放。
(2) ++和 --的结合方向是“自右至左”。前面已提到,算术运算符的结合方向为“自左而右”,这是大家所熟知的。如果有 -i++,i的左面是负号运算符,右面是自加运算符。如果 i的原值等于 3,
若按左结合性,相当于 (-i)++,而 (-i)++是不合法的,因为对表达式不能进行自加自减运算。负号运算符和,++”运算符同优先级,而结合方向为
“自右至左” (右结合性 ),即它相当于 -(i++),如果有 printf("%d",-i++),则先取出 i的值 3,输出 -i的值 -3,然后 i增值为 4。注意 -(i++)是先用 i的原值 3加上负号输出 -3,再对 i加 1,不要认为先加完 1后再加负号,输出 -4,这是不对的。
自增 (减 )运算符常用于循环语句中,使循环变量自动加 1,也用于指针变量,使指针指向下一个地址。这些将在以后的章节中介绍。
5,有关表达式使用中的问题说明
(1)C运算符和表达式使用灵活,利用这一点可以巧妙地处理许多在其他语言中难以处理的问题。但是应当注意,ansi c并没有具体规定表达式中的子表达式的求值顺序。允许各编译系统自己安排。
例如,对表达式 a = f1( )+f2( )并不是所有的编译系统都先调用 f1( ),然后调用 f2( )。在一般情况下,先调用 f1( ) 和先调用 f2( ) 的结果可能是相同的。但是在有的情况下结果可能不同。有时 会出现一些令人容易搞混的问题,因此务必要小心谨慎。
如果有以下表达式:
(i++)+(i++)+(i++)
设 i的原值为 3,那么,表达式的值是多少呢?有的系统按照自左而右顺序求解括弧内的运算,求完笫 1个括弧再求第 2个括弧,结果为 3+4+5,即 12。而另一些系统 (如 turbo c和M s c)设 i的原值为 3,那么作为表达式中所有 i的值,因此 3个 i相加,得 9。然后再实现自加 3次,i的值变为 6。
应该避免出现这种歧义性。如果编程者的原意是想得到 12,可以写成下列语句:
i = 3;
a = i++;
b = i++;
c = i++;
d = a + b + c;
执行完上述语句后,d的值为 12,i的值为 6。虽然语句多了,但不会引起歧义,无论程序移植到哪一种 c编译系统运行,结果都一样。
(2) c语言中有的运算符为一个字符,有的运算符由两个字符组成,在表达式中如何组合呢?如 i+++
j,是理解为 (i++)+j呢?还是 i+(++j )呢?c编译系统在处理时尽可能多地 (自左而右 )将若干个字符组成一个运算符 (在处理标识符、关键字时也按同一原则处理 ),如 i+++j,将解释为
(i++)+j,而不是 i+(++j )。为避免误解,最好采取大家都能理解的写法,不要写成 i+++j的形式,
而应写成 (i++)+j的形式。
(3) C语言中类似上述这样的问题还有一些。例如,
在调用函数时,实参数的求值顺序,C标准并无统一规定。如 i的初值为 3,如果有下面的函数调用:
printf("%d,%d",i,i++)
在有的系统中,从左至右求值,输出,3,3”。在多数系统中对函数参数的求值顺序是自右而左,
上面 printf函数中要输出两个表达式的值 (i和 i++
分别是两个表达式 ),先求出第 2个表达式 i++的值
3(i未自加时的值 ),然后求第 1个表达式的值,由于在求解第 2个表达式后,执行 i++,使 i加 1变为 4,
因此 printf函数中第一个参数 i的值为 4。所以上面
printf函数输出的是,4,3”。
以上这种写法不宜提倡,最好改写成
j = i++;
printf("%d,%d",j,i);
总之,不要写出别人看不懂的、也不知道系统会怎样执行的程序。
在看别人的程序时,应该考虑到在类似上述问题上,
不同系统的处理方法不尽相同。应当知道使用 c
语言时可能出问题的地方,以免遇到问题时不知其所以然。
使用 ++和 --时,常会出现一些人们“想不到”的副作用,初学者要慎用。
3.9 赋值运算符和赋值表达式
1,赋值运算符赋值符号,=”就是赋值运算符,它的作用是将一个数据赋给一个变量。如,a=3”的作用是执行一次赋值操作 (或称赋值运算 )。把常量 3赋给变量 a。
也可以将一个表达式的值赋给一个变量。
2,类型转换如果赋值运算符两侧的类型不一致,但都是数值型或字符型时,在赋值时要进行类型转换。
(1) 将实型数据 (包括单、双精度 )赋给整型变量时,
舍弃实数的小数部分。如 i为整型变量,执行
,i=3.56”的结果是使 i的值为 3,在内存中以整数形式存储。
(2) 将整型数据赋给单、双精度变量时,数值不变,
但以浮点数形式存储到变量中,如将 23赋给 float
变量 f,即 f=23,先将 23转换成 23,00000,再存储在 f中。如将 23赋给 double型变量 d,即 d=23,
则将 23补足有效位数字为 23,00000000000000,
然后以双精度浮点数形式存储到 d中。
(3) 将一个 double型数据赋给 float变量时,截取其前面 7位有效数字,存放到 float变量的存储单元
(32位 )中。但应注意数值范围不能溢出。如:
float f;
double d=123.456789e100;
f=d;
就出现溢出的错误。
将一个 float型数据赋给 double变量时,数值不变,
有效位数扩展到 16位,在内存中以 64位 (bit)存储。
(4) 字符型数据赋给整型变量时,由于字符只占 1个字节,
而整型变量为 2个字节,因此将字符数据 (8位 )放到整型变量低 8位中。有两种情况:
① 如果所用系统将字符处理为无符号的量或对
unsigned char型变量赋值,
则将字符的 8位放到整型变量低 8位,高 8位补零。例如:将字符‘ \376?赋给 int
型变量 i,如图 3.11(a)所示。
如图 3.11
② 如果所用系统 (如 turbo c)将字符处理为带符号的
(即 signed char),若字符最高位为 0,则整型变量高 8位补 0;若字符最高位为 1,则高 8位全补 1(图
3.11(b))。这称为“符号扩展”,这样做的目的是使数值保持不变,如变量 c(字符‘ \376?)以整数形式输出为 -2,i的值也是 -2。
(5) 将一个 int,short,long型数据赋给一个 char型变量时,只将其低 8位原封不动地送到 char型变量 (即截断 )。例如:
int i=289;
char c='a';
c=i;
赋值情况见图 3.12。 c的值为 33,如果用,%c” 输出 c,将得到字符,!” (其 ascii码为 33)。
图 3.12
(6) 将带符号的整型数据 (int型 )赋给 long型变量时,
要进行符号扩展,将整型数的 16位送到 long型低
16位中,如果 int型数据为正值 (符号位为 0),则
long型变量的高 16位补 0;如果 int型变量为负值
(符号位为 1),则 long型变量的高 16位补 1,以保持数值不改变。
反之,若将一个 long型数据赋给一个 int型变量,只将 long型数据中低 16位原封不动地送到整型变量
(即截断 )。例如:
int a;
long b=8;
a=b;
赋值情况见图 3.13。如果 b=65536(八进制数
0200000),则赋值后 a值为 0。见图 3.14。如果
b=020000000000(八进制数 ),赋值后 a也为 0。请读者自己分析。
图 3.14
图 3.13
(7) 将 unsigned int型数据赋给 long int型变量时,不存在符号扩展问题,只需将高位补 0即可。将一个 unsigned类型数据赋给一个占字节数相同的整型变量 (例如,unsigned int=>int,unsigned
long=>long,unsigned short=>short),将
unsigned型变量的内容原样送到非 unsigned型变量中,但如果数据范围超过相应整型的范围,则会出现数据错误。如:
unsigned int a=65535;
int b;
b=a;
将 a整个送到 b中 (图 3.15),由于 b是 int型,第 1位是符号位,成了负数。根据补码知识图 3.15可知,b的值为 -
1,可以用 printf(“%d”,b);来验证。
(8) 将非 unsigned型数据赋给长度相同的 unsigned型变量,也是原样照赋 (连原有的符号位也作为数值一起传送 )。如:
例 3.9有符号数据传送给无符号变量。
main()
{ unsigned a;
int b=-1;
a=b;
printf("%u",a);
}
,%u” 是输出无符号数时所用的格式符。运行结果为,65535
图 3.16
赋值情况见图 3.16。如果 b为正值,且在 0~ 32767
之间,则赋值后数值不变。以上的赋值规则看起来比较复杂,其实,不同类型的整型数据间的赋值归根到底就是一条:按存储单元中的存储形式直接传送。只要学过补码知识的,对以上规则是不难理解的。由于 c语言使用灵活,
在不同类型数据之间赋值时,常常会出现意想不到的结果,而编译系统并不提示出错,全靠程序员的经验来找出问题。这就要求编程人员对出现问题的原因有所了解,以便迅速排除故障。根据作者的经验,相当多的初学者 (甚至还有不少有一定编程经验的人 )在这方面常出错,而且不能很快地找出原因。因此本书对此做了较详细的说明,使读者在编程序和对程序排错时有所遵循。
在学习本书时不必死记,这部分内容可以通过学生自学和上机实践来掌握。
3,复合的赋值运算符在赋值符,=”之前加上其他运算符,可以构成复合的运算符。如果在,=”前加一个,+”运算符就成了复合运算符,+=”。例如,可以有:
a+=3 等价于 a=a+3
x*=y+8 等价于 x=x*(y+8)
x%=3 等价于 x=x%3
以,a+=3”为例来说明,它相当于使 a进行一次自加 (3)的操作。即先使 a加 3,再赋给 a。同样,
,x*=y+8”的作用是使 x乘以 (y+8),再赋给 x。为便于记忆,可以这样理解:
① a += b (其中 a为变量,b为表达式 )
② a +=b (将有下划线的,a+”移到,=”右侧 )
③ a = a + b (在,=”左侧补上变量名 a)
注意,如果 b是包含若干项的表达式,则相当于它有括号。如:
① x %= y+3
② x %=(y+3)
③ x = x %(y+3) (不要错写成 x=x%y+3)
凡是二元 (二目 )运算符,都可以与赋值符一起组合成复合赋值符。 c语言规定可以使用 10种复合赋值运算符。即:
+=,-=,*=,/=,%=,<<=,>>=,&=,∧ =,|=
后 5种是有关位运算的,将在第 11章介绍。
C采用这种复合运算符,一是为了简化程序,使程序精炼,二是为了提高编译效率 (这样写法与
“逆波兰”式一致,有利于编译,能产生质量较高的目标代码。学过编译原理的读者对此容易理解,其他读者可不必深究 )。
4,赋值表达式由赋值运算符将一个变量和一个表达式连接起来的式子称为“赋值表达式”。它的一般形式为
<变量 ><赋值运算符 ><表达式 >
如,a=5”是一个赋值表达式。对赋值表达式求解的过程是:将赋值运算符右侧的“表达式”的值赋给左侧的变量。赋值表达式的值就是被赋值的变量的值。例如,,a=5”这个赋值表达式的值为
5(变量 a的值也是 5)。
上述一般形式的赋值表达式中的“表达式”,又可以是一个赋值表达式。如 a=(b=5)括弧内的,b=5”
是一个赋值表达式,它的值等于 5。,a=(b=5)”相当于,b=5”和,a=b”两个赋值表达式,因此 a的值等于 5,整个赋值表达式的值也等于 5。赋值运算符按照“自右而左”的结合顺序,因此,,b=5”
外面的括弧可以不要,即,a=(b=5)”和,a=b=5”
等价,都是先求,b=5”的值 (得 5),然后再赋给 a,
下面是赋值表达式的例子:
a=b=c=5 (赋值表达式值为 5,a,b,c值均为 5)
a=5+(c=6) (表达式值为 11,a值为 11,c值为 6)
a=(b=4)+(c=6) (表达式值为 10,a值为 10,b等于 4,c等于 6)
a=(b=10)/(c=2) (表达式值为 5,a等于 5,b等于
10,c等于 2)
赋值表达式也可以包含复合的赋值运算符。如:
a+=a-=a*a
也是一个赋值表达式。
如果 a的初值为 12,此赋值表达式的求解步骤如下:
① 先进行,a-=a*a”的运算,它相当于 a=a-a*a=12-
144=-12。
②再进行,a+=-132”的运算,相当于 a=a+(-132)=-132-
132=-264。
将赋值表达式作为表达式的一种,使赋值操作不仅可以出现在赋值语句中,而且可以以表达式形式出现在其他语句 (如输出语句、循环语句等 )中,如:
printf("%d",a=b);如果 b的值为 3,则输出 a的值 (也是表达式 a=b的值 )为 3。在一个语句中完成了赋值和输出双重功能。这是 c语言灵活性的一种表现。在第
5章中将进一步看到这种应用及其优越性。在下一章介绍“语句”之后,就可以了解到赋值表达式和赋值语句之间的联系和区别了。
3.10 逗号运算符和逗号表达式
C语言提供一种特殊的运算符 ——逗号运算符。用它将两个表达式连接起来。如:
3+5,6+8
称为逗号表达式,又称为“顺序求值运算符”。逗号表达式的一般形式为表达式 1,表达式 2逗号表达式的求解过程是:先求解表达式 1,再求解表达式 2。整个逗号表达式的值是表达式 2的值。例如,上面的逗号表达式,3+5,6+8”的值为 14。
又如,逗号表达式
a=3*5,a*4
对此表达式的求解,读者可能会有两种不同的理解:
一种认为,3*5,a*4” 是一个逗号表达式,先求出此逗号表达式的值,如果 a的原值为 3,则逗号表达式的值为 12,将 12赋给 a,因此最后 a的值为
12。另一种认为:,a=3*5”是一个赋值表达式”,
,a*4”是另一个表达式,二者用逗号相连,构成一个逗号表达式。这两者哪一个对呢?赋值运算符的优先级别高于逗号运算符,因此应先求解
a=3*5(也就是把,a=3*5”作为一个表达式 )。经计算和赋值后得到 a的值为 15,然后求解 a*4,得 60。
整个逗号表达式的值为 60。
一个逗号表达式又可以与另一个表达式组成一个新的逗号表达式,如 (a=3*5,a*4),a+5 先计算出 a
的值等于 15,再进行 a*4的运算得 60(但 a值未变,
仍为 15),再进行 a+5得 20,即整个表达式的值为
20。
逗号表达式的一般形式可以扩展为表达式 1,表达式 2,表达式 3…… 表达式 n它的值为表达式 n的值。
逗号运算符是所有运算符中级别最低的。因此,下面两个表达式的作用是不同的,
① x=(a=3,6*3)
② x=a=3,6*a
第①个是一个赋值表达式,将一个逗号表达式的值赋给 x,x的值等于 18。第②个是逗号表达式,它包括一个赋值表达式和一个算术表达式,x的值为 3。
其实,逗号表达式无非是把若干个表达式“串联”
起来。在许多情况下,使用逗号表达式的目的只是想分别得到各个表达式的值,而并非一定需要得到和使用整个逗号表达式的值,逗号表达式最常用于循环语句 (for语句 )中,详见第 5章。
请注意并不是任何地方出现的逗号都是作为逗号运算符。
例如函数参数也是用逗号来间隔的。如
printf("%d,%d,%d",a,b,c);
上一行中的,a,b,c”并不是一个逗号表达式,它是 printf
函数的 3个参数,参数间用逗号间隔。有关函数的详细叙述见第 7章。如果改写为
printf("%d,%d,%d",(a,b,c),b,c);
则,(a,b,c)”是一个逗号表达式,它的值等于 c的值。括弧内的逗号不是参数间的分隔符而是逗号运算符。括弧中的内容是一个整体,作为 printf函数的一个参数。
C语言表达能力强,其中一个重要方面就在于它的表达式类型丰富,运算符功能强,因而 c使用灵活,适应性强。在后面几章中将会进一步看到这一点。
习题
3.1 请将 c语言的数据类型和其他高级语言的数据类型做比较。 c有哪些特点?
3.2 C语言为什么要规定对所有用到的变量要
“先定义,后使用”。这样做有什么好处?
3.3 请将下面各数用八进制和十六进制数 (补码 )
表示:
(1)10 (2)32 (3)75 [](4)-617
(5)-111 (6)2483 (7)-28654 (8)21003
3.4 将以下 3个整数分别赋给不同类型的变量,请画出赋值后数据在内存中的存储形式。变量的类型
25 -2 32769
int型 (16位 )
long型 (32位 )
short型 (16位 )
signed char(8位 )
unsigned int型
unsigned long型
unsigned short型
unsigned char型注:如没有学过二进制数和补码的,此题可不做。
3.5 字符常量与字符串常量有什么区别?
3.6 写出以下程序运行的结果。
main()
{char c1='a',c2='b',c3='c',c4='\101',
c5='\116';
printf("a%c b%c\tc%c\tabc\n",c1,c2,c3);
printf("\t\b%c %c",c4,c5);
}
3.7 要将,china”译成密码,密码规律是:用原来的字母后面第 4个字母代替原来的字母。例如,
字母,a”后面第 4个字母是,e”,用,e”代替
,a”。因此,,china”应译为,glm re”。请编一程序,用赋初值的方法使 c1,c2,c3,c4,c5
五个变量的值分别为‘ c?、‘ h?、‘ i?、‘ n?、
‘ a?,经过运算,使 c1,c2,c3,c4,c5分别变为‘ g?、‘ l?、‘m’、‘ r?、‘ e?,并输出。
3.8 例 3.6能否改成如下:
m ain()
{ int c1,c2; (原为 char c1,c2)
c1=97;
c2=98;
printf("%c%c\n",c1,c2);
printf("%d %d\n",c1,c2);
}
分析运行时会显示什么信息?为什么?
3.9 求下面算术表达式的值。
(1) x+a%3*(int)(x+y)%2/4 设 x=2,5,a=7,
y=4,7
(2) (float)(a+b)/2+(int)x%(int)y
设 a=2,b=3,x=3,5,y=2,5
3.10 写出程序运行结果。
main()
{ int i,j,m,n;
i=8;
j=10;
m=++i;
n=j ++;
printf("%d,%d,%d,%d",i,j,m,n);
3.11 写出下面表达式运算后 a的值,设原来 a=12。
设 a和 n已定义为整型变量。
(1) a+=a
(2) a-=2
(3) a*=2+3
(4) a/=a+a
(5) a%=(n%=2),n的值等于 5
(6) a+=a-=a*=a
3.2 常量与变量
3.3 整型数据
3.4 实型数据
3.5 字符型数据
3.6 变量赋初值第 3章 数据类型、运算符与表达式
3.7 各类数值型数据间的混合运算
3.8 算术运算符和算术表达式
3.9 赋值运算符和赋值表达式
3.10 逗号运算符和逗号表达式习题
3.1 C的数据类型一个程序应包括以下两方面内容:
(1) 对数据的描述。在程序中要指定数据的类型和数据的组织形式,即数据结构 (data structure)。
(2) 对操作的描述。即操作步骤,也就是算法 (algorithm)。
据是操作的对象,操作的目的是对数据进行加工处理,
以得到期望的结果。打个比方,厨师做菜肴,需要有菜谱。菜谱上一般应包括:
① 配料,指出应使用哪些原料;② 操作步骤,指出如何使用这些原料按规定的步骤加工成所需的菜肴。面对同一些原料可以加工出不同风味的菜肴。
作为程序设计人员,必须认真考虑和设计数据结构和操作步骤 (即算法 )。因此,著名计算机科学家沃思 (nikiklaus Wirth)提出一个公式数据结构 +算法 =
程序实际上,一个程序除了以上两个主要要素之外,还应当采用结构化程序设计方法进行程序设计,并且用某一种计算机语言表示。因此,可以这样表示:
程序 =算法 +数据结构 +程序设计方法 +语言工具和环境也就是说,以上 4个方面是一个程序设计人员所应具备的知识。在设计一个程序时要综合运用这几方面的知识。在本书中不可能全面介绍这些内容,
它们都属于有关的专门课程范畴。在这 4个方面中,
算法是灵魂,数据结构是加工对象,语言是工具,
编程需要采用合适的方法。算法是解决“做什么”
和“怎么做”的问题。程序中的操作语句,实际上就是算法的体现。算法处理的对象是数据,而数据是以某种特定的形式存在的 (例如整数、实数、
字符等形式 )。不同的数据之间往往还存在某些联系 (例如由若干个整数组成一个整数数组 )。所谓数据结构指的是数据的组织形式。例如,数组就是一种数据结构。不同的计算机语言所允许定义和使用的数据结构是不同的。例如,c语言提供了“结构体”这样一种数据结构,而 fortran语言就不提供这种数据结构。处理同一类问题,如果数据结构不同,算法也会不同。例如,对 10个整数排序和对由 10个整数构成的数组排序的算法是不同的。因此,在考虑算法时,必须注意数据结构。实际上,应当综合考虑算法和数据结构,选择最佳的数据结构和算法。
C语言的数据结构是以数据类型形式出现的。 c的数据类型如下,
数据类型,基本类型,整型,字符型,实型 (浮点型 )单精度型,双精度型,枚举类型,构造类型,
数组类型,结构体类型,共用体类型,指针类型,
空类型 C语言中数据有常量与变量之分,它们分别属于以上这些类型。由以上这些数据类型还可以构成更复杂的数据结构。例如利用指针和结构体类型可以构成表、树、栈等复杂的数据结构。
在程序中对用到的所有数据都必须指定其数据类型。在本章中主要介绍基本数据类型。
3.2 常量与变量
3.2.1 常量和符号常量在程序运行过程中,其值不能被改变的量称为常量。常量区分为不同的类型,如 12,0,-3为整型常量,4.6,-1.23为实型常量,‘ a?,‘ d?
为字符常量。常量一般从其字面形式即可判别。
这种常量称为字面常量或直接常量。
也可以用一个标识符代表一个常量,如,
例 3.1符号常量的使用。
#define price 30
main ( )
{
int num,total;
num=10;
total=num * price;
printf("total=%d",total);
}
程序中用 #define命令行定义 price代表常量 30,此后凡在本文件中出现的 price都代表 30,可以和常量一样进行运算,程序运行结果为
total=300
有关 #define命令行的详细用法参见第 8章。
这种用一个标识符代表一个常量的,称为符号常量,
即标识符形式的常量。请注意符号常量不同于变量,
它的值在其作用域 (在本例中为主函数 )内不能改变,
也不能再被赋值。如再用以下赋值语句给 price赋值是错误的。
price=40;
习惯上,符号常量名用大写,变量用小写,以示区别。
使用符号常量的好处是:
(1) 含义清楚。如上面的程序中,看程序时从 price就可知道它代表价格。因此定义符号常量名时应考虑
“见名知意”。 在一个规范的程序中不提倡使用很多的常数,如,sum= 15 * 30 * 23.5 * 43。在检查程序时搞不清各个常数究竟代表什么。应尽量使用
“见名知意”的变量名和符号常量。
(2) 在需要改变一个常量时能做到“一改全改”。
例如在程序中多处用到某物品的价格,如果价格用常数表示,则在价格调整时,就需要在程序中作多处修改,若用符号常量 price代表价格,只需改动一处即可。如:
#define price 35
在程序中所有以 price代表的价格就会一律自动改为 35。
3.2.2 变量其值可以改变的量称为变量。一个变量应该有一个名字,在内存中占据一定的存储单元。在该存储单元中存放变量的值。请注意区分变量名和变量值这两个不同的概念,见图 3.1。变量名实际上是一个符号地址,在对程序编译连接时由系统给每一个变量名分配一个内存地址。
在程序中从变量中取值,实际上是通过变量名找到相应的内存地址,
从其存储单元中读取数据。和其他高级语言一样,用来标识变量名、
图 3.1
符号常量名、函数名、数组名、类型名、文件名的有效字符序列称为标识符 (identifier)。简单地说,标识符就是一个名字。
C语言规定标识符只能由字母、数字和下划线三种字符组成,且第一个字符必须为字母或下划线。
下面列出的是合法的标识符,也是合法的变量名:
sum,average,-total,class,day,month,
student-name,tan,lotus-1-2-3,basic,li-ling
下面是不合法的标识符和变量名:
M,d.J ohn,y 123,# 33,3d64,a>b
注意,大写字母和小写字母被认为是两个不同的字符。因此,sum和 suM,class和 class是两个不同的变量名。一般,变量名用小写字母表示,与人们日常习惯一致,以增加可读性。
ANSI C标准没有规定标识符的长度 (字符个数 ),但各个 c编译系统都有自己的规定。有的系统 (如 ibM
PC的M s C)取 8个字符,假如程序中出现的变量名长度大于 8个字符,则只有前面 8个字符有效,后面的不被识别。例如,有两个变量:
student_name和 student_number,由于二者的前 8
个字符相同,系统认为这两个变量是一回事而不加区别。可以将它们改为 stud_name和 stud_num,
以使之区别。
Turbo C则允许 32个字符。因此,在写程序时应了解所用系统对标识符长度的规定,以免出现上面的混淆。这种错误并不反映在编译过程中 (即语法无错误 ),但运行结果显然不对。为了程序的可移植性 (即在甲机器上运行的程序可以基本上不加修改,就能移到乙机器上运行 )以及阅读程序的方便,建议变量名的长度不要超过 8个字符。
如前所述,在选择变量名和其他标识符时,应注意做到“见名知意”,即选有含意的英文单词 (或其缩写 )作标识符,如 count,name,day、
month,total,country等,除了数值计算程序外,
一般不要用代数符号 (如 a,b,c,x1,y1等 )作变量名,
以增加程序的可读性。这是结构化程序的一个特征。本书在一些简单的举例中,为方便起见,仍用单字符的变量?如 a,b,c等 ),请读者注意不要在其他所有程序中都如此。
在 c语言中,要求对所有用到的变量作强制定义,
也就是“先定义,后使用”,如例 1,2、例 1,3
那样。这样做的目的是:
(1) 凡未被事先定义的,不作为变量名,这就能保证程序中变量名使用得正确。例如,如果在定义部分写了
int student;
而在执行语句中错写成 staent。如:
staent=30;
在编译时检查出 statent未经定义,不作为变量名。
因此输出“变量 statent未经声明”的信息,便于用户发现错误,避免变量名使用时出错。
(2) 每一个变量被指定为一确定类型,在编译时就能为其分配相应的存储单元。如指定 a,b为 int型,
turbo c编译系统为 a和 b各分配两个字节,并按整数方式存储数据。
(3) 指定每一变量属于一个类型,这就便于在编译时,据此检查该变量所进行的运算是否合法。例如,整型变量 a和 b,可以进行求余运算:
a%b
%是“求余” (见 3.8节 ),得到 a/b的余数。如果将 a、
b指定为实型变量,则不允许进行“求余”运算,
在编译时会给出有关“出错信息”。
下面各节分别介绍整型、实型 (浮点型 )、字符型数据。
3.3 整型数据
3.3.1 整型常量的表示方法
整型常量即整常数。 c整常数可用以下三种形式表示:
(1) 十进制整数。如 123,-456,0。
(2) 八进制整数。以 0开头的数是八进制数。如
0123表示八进制数 123,即 (123)8,其值为:
1× 82+2× 81+3× 80,等于十进制数 8 3。 -011表示八进制数 -11,即十进制数 -9。
(3) 十六进制整数。以 0x开头的数是十六进制数。
如 0x123,代表十六进制数 123,即
(123)16=1× 162+2× 161+3× 160=256+32+3=291。
-0x12等于十进制数 -18。
3.3.2 整型变量
1,整型数据在内存中的存放形式数据在内存中是以二进制形式存放的。
如果定义了一个整型变量 i:
int i; /* 定义为整型变量 */
i=10; /* 给 i赋以整数 10 */
十进制数 10的二进制形式为 1010,在微机上使用的
c编译系统,每一个整型变量在内存中占 2个字节。
图 3.2(a)是数据存放的示意图。图 3.2(b)是数据在内存中实际存放的情况。
图 3.2
实际上,数值是以补码 (complement) 表示的。一个正数的补码和其原码的形式相同。图 3.2(b) 就是用补码形式表示的。如果数值是负的,在内存中如何用补码形式表示呢?求负数的补码的方法是:将该数的绝对值的二进制形式,按位取反再加 1。例如求 -10的补码:①取 -10的绝对值 10;②
10的绝对值的二进制形式为 1010;③对 1010取反得 1111111111110101(一个整数占 16位 );④再加 1
得 1111111111110110,见图 3.3。
图 3.3
可知整数的 16位中,最左面的一位是表示符号的,
该位为 0,表示数值为正;为 1则数值为负。
关于补码的知识不属于本书的范围,但学习 c语言的读者应该比学习其他高级语言的读者对数据在内存中的表示形式有更多的了解。这样才能理解不同类型数据间转换的规律。在本章稍后的叙述中还要接触到这方面的问题。
2,整型变量的分类整型变量的基本类型符为 int。可以根据数值的范围将变量定义为基本整型、短整型或长整型。在
int 之前可以根据需要分别加上修饰符
(modifier):short(短型 )或 long(长型 )。因此有以下三种整型变量:
(1) 基本整型,以 int表示。
(2) 短整型,以 short int表示,或以 short表示。
(3) 长整型,以 long int表示,或以 long表示。
在 turbo c中一个 int型的变量的值范围为 -215~ (215-
1),即 -32768~ 32767。在实际应用中,变量的值常常是正的 (如学号、库存量、年龄、存款额等 )。为了充分利用变量的表数范围,此时可以将变量定义为“无符号”类型。对以上三种都可以加上修饰符 unsigned,以指定是“无符号数” 。如果加上修饰符 signed,则指定是“有符号数”。 如果既不指定为 signed,也不指定为
unsigned,则隐含为有符号 (signed)。实际上
signed是完全可以不写的。归纳起来,可以用以下 6种整型变量。即:
有符号基本整型 [signed] int
无符号基本整型 unsigned int
有符号短整型 [signed] short [int]
无符号短整型 unsigned short [int]
有符号长整型 [signed] long [int]
无符号长整型 unsigned long [int]
如果不指定 unsigned或指定 signed,则存储单元中最高位代表符号 (0为正,1为负 )。如果指定 unsigned,为无符号型,存储单元中全部二进位 (bit)用作存放数本身,而不包括符号。无符号型变量只能存放不带符号的整数,如 123,4687等,而不能存放负数,如 -
123,-3。一个无符号整型变量中可以存放的正数的范围比一般整型变量中正数的范围扩大一倍。如果在程序中定义 a和 b两个变量,
int a;
unsigned int b;
则变量 a的数值范围为 -32768~ 32767。而变量 b的数值范围为 0~ 65535。
图 3.4(a) 表示有符号整型变量 a的最大值 (32767)。
图 3.4(b) 表示无符号整型变量 b的最大值 (65535)。
图 3.4
C标准没有具体规定以上各类数据所占内存字节数,
只要求 long型数据长度不短于 int型,short型不长于 int型。具体如何实现,由各计算机系统自行决定。如在微机上,int和 short都是 16位,而 long是
32位。在 Vax 750 上,short是 16位,而 int和 long
都是 32位,一般以一个机器字 (word)存乓桓鰅 nt
数据。前一阶段,微机的字长一般为 16位,故以
16位存放一个整数,但整数的范围太小,往往不够用,故将 long型定为 32位。而 Vax的字长为 32
位,以 32位存放一个整数,范围可达正负 21亿,
已足够用了,不必再将 long型定为 64位。所以将
int和 long都定为 32位。通常的做法是:把 long定为 32位,把 short定为 16位,而 int可以是 16
位,也可以是 32位。这主要取决于机器字长。在微机上用 long型可以得到大范围的整数,但同时会降低运算速度,因此除非不得已,不要随便使用 long型。
方括弧内的部分是可以省写的。例如 signed short
int与 short等价,尤其是 signed是完全多余的,
一般都不写 signed。一个整数 (以 13为例 )在存储单元中的存储情况,见图 3.5所示 (假设使用的是微机上的 c编译系统,如 turbo C,Ms C )。
图 3.5
3,整型变量的定义前面已提到,c规定在程序中所有用到的变量都必须在程序中定义,即“强制类型定义”。这是和
basic,fortran不同的,而和 pascal相类似。例如:
int a,b; (指定变量 a,b为整型 )
unsigned short c,d; (指定变量 c,d为无符号短整型 )
long e,f; (指定变量 e,f为长整型 )
对变量的定义,一般是放在一个函数的开头部分的声明部分 (也可以放在函数中某一分程序内,但作用域只限它所在的分程序,这将在第 6章介绍 )。
例 3.2整型变量的定义与使用。
main()
{int a,b,c,d; /*指定 a,b,c,d为整型变量 */
unsigned u; /*指定 u为无符号整型变量 */
a=12; b=-24; u=10;
c=a+u; d=b+u;
printf("a+u=%d,b+u=%d\n",c,d);
}
运行结果为
a+u=22,b+u=-14
可以看到不同种类的整型数据可以进行算术运算。
在本例中是 int型数据与 unsigned int型数据进行相加相减运算 (有关运算的规则在本章 3.7节中介绍 )。
4,整型数据的溢出在 turbo c中一个 int型变量的最大允许值为 32767,
如果再加 1,会出现什么情况?
例 3.3整型数据的溢出。
main()
{int a,b;
a=32767;
b=a+1;
printf("%d,%d",a,b);
}
运行结果为
32767,-32768
图 3.6
从图 3.6可以看到:变量 a的最高位为 0,后 15位全为 1。加 1后变成第 1位为 1,后面 15位全为 0。而它是 -32768的补码形式,所以输出变量 b的值为 -
32768。请注意:一个整型变量只能容纳 -
32768~ 32767范围内的数,无法表示大于 32767
的数。遇此情况就发生“溢出”,但运行时并不报错。它好像汽车的里程表一样,达到最大值以后,又从最小值开始计数。所以,32767加 1得不到 32768,而得到 -32768,这可能与程序编制者的原意不同。从这里可以看到,c的用法比较灵活,往往出现副作用,而系统又不给出“出错信息”,要靠程序员的细心和经验来保证结果的正确。将变量 b改成 long型就可得到预期的结果
32768。
3.3.3 整型常量的类型我们已知整型变量可分为 int,short int,long int和
unsigned int,unsigned short,unsigned long等类别。那么常量是否也有这些类别?在将一个整型常量赋值给上述几种类别的整型变量时如何做到类型匹配?请注意以下几点:
(1) 一个整数,如果其值在 -32768~ +32767范围内,
认为它是 int型,它可以赋值给 int型和 long int型变量。
(2) 一个整数,如果其值超过了上述范围,而在 -
2147483648~ +2147483647范围内,则认为它是长整型,可以将它赋值给一个 long int型变量。
(3) 如果某一计算机系统的 c版本 (例如 turbo c)确定
short int与 int型数据在内存中占据的长度相同,
则它的表数范围与 int型相同。因此,一个 int型的常量也同时是一个 short int型常量,可以赋给
int型或 short int型变量。
(4) 一个整常量后面加一个字母 u,认为是 unsigned
int型,如 12345u,在内存中按 unsigned int规定的方式存放 (存储单元中最高位不作为符号位,
而用来存储数据,见图 3.4(b)。如果写成 -12345u,
则先将 -12345转换成其补码 53191,然后按无符号数存储。
(5) 在一个整常量后面加一个字母 l或 l,则认为是
long int型常量。
例如 123l,432l,0l等,这往往用于函数调用中。
如果函数的形参为 long int型,则要求实参也为
long int型,此时用 123作实参不行,而要用 123l
作实参。
3.4 实型数据
3.4.1 实型常量的表示方法实数 (real number)又称浮点数 (floating-point
number)。实数有两种表示形式:
(1) 十进制小数形式。它由数字和小数点组成 (注意必须有小数点 )。,123,123.,123.0,0.0都是十进制小数形式。
(2) 指数形式。如 123e3或 123e3都代表 123× 103。但注意字母 e(或 e)之前必须有数字,且 e后面的指数必须为整数,如 e3,2.1e3.5、,e3,e等都不是合法的指数形式。
一个实数可以有多种指数表示形式。例如 123.456
可以表示为 123.456e0,12.3456e1,1.23456e2、
0.123456e3,0.0123456e4,0.00123456e5等。把其中的 1.23456e2称为“规范化的指数形式”,
即在字母 e(或 e)之前的小数部分中,小数点左边应有一位 (且只能有一位 )非零的数字。例如
2.3478e2,3.0999e5,6.46832e12都属于规范化的指数形式,而 12.908e10,0.4578e3,756e0则不属于规范化的指数形式。一个实数在用指数形式输出时,是按规范化的指数形式输出的。例如,
指定将实数 5689.65按指数形式输出,必然输出
5.68965e+003,而不会是 0.568965e+004或
56.8965e+002。
1,实型数据在内存中的存放形式在常用的微机系统中,一个实型数据在内存中占 4个字节 (32位 )。
与整型数据的存图 3.7储方式不同,实型数据是按照指数形式存储的。系统把一个实型数据分成小数部分和指数部分,分别存放。指数部分采用规范化的指数形式。实数 3.14159 在内存中的存放形式可以用图 3.7
示意。
3.4.2 实型变量图 3.7
图中是用十进制数来示意的,实际上在计算机中是用二进制数来表示小数部分以及用 2的幂次来表示指数部分的。在 4个字节 (32位 )中,究竟用多少位来表示小数部分,多少位来表示指数部分,标准 C并无具体规定,由各 C编译系统自定。不少 c编译系统以 24位表示小数部分 (包括符号 ),以 8位表示指数部分 (包括指数的符号 )。小数部分占的位 (bit)数愈多,数的有效数字愈多,精度愈高。指数部分占的位数愈多,
则能表示的数值范围愈大。
2,实型变量的分类
C实型变量分为单精度 (float型 )、双精度 (double型 )和长双精度型 (long double)三类。
ANSI C 并未具体规定每种类型数据的长度、精度和数值范围。有的系统将 double型所增加的 32位全用于存放小数部分,这样可以增加数值的有效位数,减少舍入误差。有的系统则将所增加的位
(bit)用于存放指数部分,这样可以扩大数值的范围。表 3.2列出的是微机上常用的 c编译系统 (如
turbo c,Ms c,borland c )的情况。应当了解,
不同的系统会有差异。
对每一个实型变量都应在使用前加以定义。如,
float x,y,(指定 x,y为单精度实数 )
double z; (指定 z为双精度实数 )
long double t; (指定 t为长双精度实数 )
在初学阶段,对 long double型用得较少,因此我们不准备作详细介绍。读者只要知道有此类型即可。
3,实型数据的舍入误差由于实型变量是由有限的存储单元组成的,因此能提供的有效数字总是有限的,在有效位以外的数字将被舍去。由此可能会产生一些误差。例如,a
加 20的结果显然应该比 a大。请分析下面的程序:
例 3.4实型数据的舍入误差。
main()
{float a,b;
a = 123456,789e5;
b = a + 20 ;
printf("%f",b);
}
程序内 printf函数中的,%f” 是输出一个实数时的格式符。程序运行时,输出 b的值与 a相等。原因是,a的值比 20大很多,a+20的理论值应是
12345678920,而一个实型变量只能保证的有效数字是 7位有效数字,后面的数字是无意义的,
并不准确地表示该数。
运行程序得到的 a和 b的值是 12345678848.000000,
可以看到,?位是准确的,后几位是不准确的,
把 20加在后几位上,是无意义的。应当避免将一个很大的数和一个很小的数直接相加或相减,否则就会“丢失”小的数。与此类似,用程序计算
1.0/3*3的结果并不等于 1。
3.4.3 实型常量的类型
C编译系统将实型常量作为双精度来处理。例如已定义一个实型变量 f,有如下语句:
f = 2.45678 * 4523.65
系统将 2.45678和 4523.65按双精度数据存储 (占 64位 )和运算,得到一个双精度的乘积,然后取前 7
位赋给实型变量 f。这样做可以保证计算结果更精确,
但是运算速度降低了。可以在数的后面加字母 f或
f(如 1.65f,654.87f),这样编译系统就会按单精度 (32
位 )处理。一个实型常量可以赋给一个 float型、
double型或 long double变量。根据变量的类型截取实型常量中相应的有效位数字。假如 a已指定为单精度实型变量:
float a;
a=111111,111;
由于 float型变量只能接收 7位有效数字,因此最后两位小数不起作用。如果 a改为 double型,则能全部接收上述 9位数字并存储在变量 a中。
3.5 字符型数据
3.5.1 字符常量
C的字符常量是用单引号 (即撇号 )括起来的一个字符。
如‘ a?,‘ x?,‘ d?,‘,‘’等都是字符常量。
注意,‘ a?和‘ a?是不同的字符常量。
除了以上形式的字符常量外,C还允许用一种特殊形式的字符常量,就是以一个,\”开头的字符序列。例如,前面已经遇到过的,在 printf函数中的
‘ \n?,它代表一个“换行”符。这是一种“控制字符”,在屏幕上是不能显示的。在程序中也无法用一个一般形式的字符表示,只能采用特殊形式来表示。
例 3.5转义字符的使用。
m ain()
{ printf(" ab c\t de\rf\tg\n");
printf("h\ti\b\bj k");
}
程序中没有设字符变量,用 printf函数直接输出双引号内的各个字符。
请注意其中的“转义字符”。第一个 printf函数先在第一行左端开始输出,ab c”,然后遇到,\t”,
它的作用是“跳格”,即跳到下一个“制表位置”,在我们所用系统中一个“制表区”占 8列。
“下一制表位置”从第 9列开始,故在第 9~ 11列上输出,de”。
下面遇到,\r”,它代表“回车” (不换行 ),返回到本行最左端 (第 1列 ),输出字符,f”,然后遇,\t”
再使当前输出位置移到第 9列,输出,g”。下面是,\n”,作用是“使当前位置移到下一行的开头”。第二个 printf函数先在第 1列输出字符,h”,
后面的,\t”使当前位置跳到第 9列,输出字母
,i”,然后当前位置应移到下一列 (第 10列 )准备输出下一个字符。下面遇到两个,\b”,,\b”的作用是“退一格”,因此,\b\b”的作用是使当前位置回退到第 8列,接着输出字符,j k”。
程序运行时在打印机上得到以下结果:
fab c gde
h jik
注意在显示屏上最后看到的结果与上述打印结果不同,是:
f gde
h j k
这是由于,\r”使当前位置回到本行开头,自此输出的字符 (包括空格和跳格所经过的位置 )将取代原来屏幕上该位置上显示的字符。所以原有的
,ab c,被新的字符,f g”代替,其后的
,de”未被新字符取代。换行后先输出,h i”,
退两格后再输出“j k”,j后面的,”将原有的字符,i”取而代之。因此屏幕上看不到,i”。实际上,屏幕上完全按程序要求输出了全部的字符,
只是因为在输出前面的字符后很快又输出后面的字符,在人们还未看清楚之前,新的已取代了旧的,所以误以为未输出应输出的字符。而在打印机输出时,不像显示屏那样会“抹掉”原字符,
留下了不可磨灭的痕迹,它能真正反映输出的过程和结果。
3.5.2 字符变量字符型变量用来存放字符常量,请注意只能放一个字符,不要以为在一个字符变量中可以放一个字符串 (包括若干字符 )。字符变量的定义形式如下:
char c1,c2;
它表示 c1和 c2为字符型变量,各可以放一个字符,
因此在本函数中可以用下面语句对 c1,c2赋值:
c1='a'; c2='b';
在所有的编译系统中都规定以一个字节来存放一个字符,或者说一个字符变量在内存中占一个字节。
3.5.3 字符数据在内存中的存储形式及其使用方法将一个字符常量放到一个字符变量中,实际上并不是把该字符本身放到内存单元中去,而是将该字符的相应的 ASCII代码放到存储单元中。例如字符
‘ a?的 ASCII代码为 97,‘ b?
为 98,在内存中变量 c1,c2的值如图 3.8(a)所示。实际上是以二进制形式存放的,如图
3.8(b)所示。
图 3.8
既然在内存中,字符数据以 ASCII码存储,它的存储形式就与整数的存储形式类似。这样,在字符型数据和整型数据之间的转换就比较方便了。一个字符数据既可以以字符形式输出,也可以以整数形式输出。以字符形式输出时,需要先将存储单元中的 ASCII码转换成相应字符,然后输出。
以整数形式输出时,直接将 ASCII码作为整数输出。也可以对字符数据进行算术运算,此时相当于对它们的 ASCII码进行算术运算,只是将其一个字节转化为 29字节,然后参加运算。
例 3.6向字符变量赋以整数。
main()
{char c1,c2;
c1=97;
c2=98;
printf("%c %c\n",c1,c2);/*以字符形式输出 */
printf("%d %d\n",c1,c2);/*转换为整数形式输出 */
}
c1,c2被指定为字符变量。但在第 3和第 4行中,将整数
97和 98分别赋给 c1和 c2,它的作用相当于以下两个赋值语句:
c1='a'; c2='b';
因为‘ a?和‘ b?的 ASCII
码为 97和 98。在程序的第 3和第 4行是把 97和 98
两个整数直接存放到 c1
和 c2的内存单元中。而
c1=?a?和 c2=?b?则是先将字符‘ a?和‘ b?化成 ascii
码 97和 98,然后放到内存单元中。二者的作用和结果是相同的。第 5行输出两个字符 a和 b。
,%c” 是输出字符时必须使用的格式符。程序第 6行输出两个整数 97和
98。
图 3.9
程序运行时输出如下:
ab
9798
可以看到:字符型数据和整型数据是通用的。它们既可以用字符形式输出 (用 %c),也可以用整数形式输出 (用 %d),见图 3.9。但是应注意字符数据只占一个字节,它只能存放 0~ 255 范围内的整数。
例 3.7大小写字母的转换。
main()
{char c1,c2;
c1='a';
c2='b';
c1=c1-32;
c2=c2-32;
printf("%c %c",c1,c2);
}
运行结果为
ab
程序的作用是将两个小写字母 a和 b转换成大写字母 a和
b。‘ a?的 ascii码为 97,而‘ a?为 65,‘ b?为 98,‘ b?
为 66。从 ascii代码表中可以看到每一个小写字母比它相应的大写字母的 ascii码大 32。 c语言允许字符数据与整数直接进行算术运算,即‘ a?+32会得到整数 97,
‘ a?-32会得到整数 65。
C语言对字符数据作这种处理使程序设计时增大了自由度。例如对字符作各种转换就比较方便。而在 basic语言中,为了将小写字母 a转换成大写字母 a,需要用两个字符处理函数:用 asc函数将字符转换成其相应的 ascii码,再用 chr函数将 ascii码转换为字符:
print chr(asc("a")-32)
这样来回转换,既增加程序的复杂性,又增加计算时间的开销。
字符数据与整型数据可以互相赋值。如:
int i;
char c;
i='a';
c=97;
是合法的。如果用格式符,%d” 将 i的值输出,可得到 97。用,%c” 输出 c,可得字符‘ a?。
如果在上面语句之后执行以下语句:
printf("%c,%d\n",c,c);
printf("%c,%d\n",i,i);
输出:
a,97
a,97
说明:有些系统 (如 pdp,Vax-11,turbo c)将字符变量中的最高位作为符号位,也就是将字符处理成带符号的整数,即 signed char型。它的取值范围是 -128~ 127。如果使用 ascii码为 0~ 127间的字符,由于字节中最高位为 0,因此用 %d输出时,
输出一个正整数。如果使用 ascii码为 128~ 255间的字符,由于在字节中最高位为 1,用 %d格式符输出时,就会得到一个负整数。例如,
char c=130;
printf(“%d”,c);
得到 -126。如果不想按有符号处理,可以将字符变量定义为 unsigned char类型,这时其取值范围是
0~ 255。 signed char 和 unsigned char的含义及用法与 signed int和 unsigned int相仿,但它只有一个字节。
3.5.4 字符串常量前面已提到,字符常量是由一对单引号括起来的单个字符。 c语言除了允许使用字符常量外,还允许使用字符串常量。字符串常量是一对双引号括起来的字符序列。如,
,how do you do.”,,CHINA","a",123.45"
都是字符串常量。可以输出一个字符串,如,
printf("how do you do.");
不要将字符常量与字符串常量混淆。‘ a?是字符常量,,a”是字符串常量,二者不同。假设 c被指定为字符变量:
char c;
c='a';
是正确的。而
c="a";
是错误的。
c=“CHINA"
也是错误的。不能把一个字符串赋给一个字符变量。
有人不能理解‘ a?和,a”究竟有什么区别?c规定:
在每一个字符串的结尾加一个“字符串结束标志”,以便系统据此判断字符串是否结束。 C规定以字符‘ \0?作为字符串结束标志。‘ \0?是一个
ASCII码为 0的字符,从 ascii代码表中可以看到 ascii码为 0
的字符是“空操作字符”,即它不引起任何控制动作,也不是一个可显示的字符。如果有一个字符串,CHINA”,实际上在内存中是
C H I N A \0
它的长度不是 5个字符,而是 6个字符,最后一个字符为‘ \0?。但在输出时不输出‘ \0?。例如在
printf("how do you do.")中,输出时一个一个字符输出,直到遇到最后的‘ \0?字符,就知道字符串结束,停止输出。注意,在写字符串时不必加
‘ \0?,否则会画蛇添足。‘ \0?字符是系统自动加上的。字符串,a”,实际上包含 2个字符:‘ a?
和‘ \0?,因此,把它赋给只能容纳一个字符的字符变量 c:
c="a";
显然是不行的。
在 c语言中没有专门的字符串变量 (basic中的字符串变量形式为 a,b等 ),如果想将一个字符串存放在变量中,以便保存,必须使用字符数组,即用一个字符型数组来存放一个字符串,数组中每一个元素存放一个字符。这将在第 6章中介绍。
3.6 变量赋初值程序中常需要对一些变量预先设置初值。 C语言允许在定义变量的同时使变量初始化。如:
int a=3; /* 指定 a为整型变量,初值为 3 */
float f=3.56;/* 指定 f为实型变量,初值为 3.56 */
char c='a'; /* 指定 c为字符变量,初值为 'a' */
也可以使被定义的变量的一部分赋初值。如,
int a,b,c=5;
表示指定 a,b,c为整型变量,只对 c初始化,c的值为 5。
如果对几个变量赋予初值 3,应写成
int a=3,b=3,c=3;
表示 a,b,c的初值都是 3。不能写成:
int a=b=c=3;
初始化不是在编译阶段完成的 (只有在第 7章中介绍的静态存储变量和外部变量的初始化是在编译阶段完成的 ),而是在程序运行时执行本函数时赋予初值的,相当于有一个赋值语句。例如,
int a=3;
相当于:
int a;/*指定 a为整型变量 */
a=3; /*赋值语句,将 3赋给 a */
又如
int a,b,c=5;
相当于:
int a,b,c; /*指定 a,b,c为整型变量 */
c=5; /*将 5赋给 c*/
3.7 各类数值型数据间的混合运算整型 (包括 int,short,long)、实型
(包括 float,double)可以混合运算。
前已述及,字符型数据可以与整型通用,因此,整型、实型、字符型数据间可以混合运算。例如,
10+'a'+1,5-8765,1234 *'b'
是合法的。在进行运算时,不同类型的数据要先转换成同一类型,
然后进行运算。转换的规则按图
3.10所示。
图 3.10
图中横向向左的箭头表示必定的转换,如字符数据必定先转换为整数,short型转换为 int型,float
型数据在运算时一律先转换成双精度型,以提高运算精度 (即使是两个 float型数据相加,也先都化成 double型,然后再相加 )。
纵向的箭头表示当运算对象为不同类型时转换的方向。例如 int型与 double型数据进行运算,先将 int
型的数据转换成 double型,然后在两个同类型
(double型 )数据间进行运算,结果为 double型。
注意箭头方向只表示数据类型级别的高低,由低向高转换。不要理解为 int型先转换成 unsigned
int型,再转成 long型,再转成 double型。如果一个 int型数据与一个 double型数据运算,是直接将 int型转成 double型。同理,一个 int型与一个
long型数据运算,先将 int型转换成 long型。
换言之,如果有一个数据是 float型或 double型,则另一数据要先转换为 double型,运算结果为
double型。如果参加运算的两个数据中最高级别为 long型,则另一数据先转换为 long型,运算结果为 long型。其他依此类推。
假设已指定 i为整型变量,f为 float变量,d为 double
型变量,e为 long型,有下面式子:
10+'a'+i*f-d/e
在计算机执行时从左至右扫描,运算次序为:
①进行 10+?a?的运算,先将‘ a?转换成整数 97,运算结果为 107。
②由于,*”比,+”优先,先进行 i*f的运算。先将 i
与 f都转成 double型,运算结果为 double型。
③整数 107与 i*f的积相加。先将整数 107转换成双精度数 (小数点后加若干个 0,即 107,000…00),结果为 double型。
④将变量 e化成 double型,d/e结果为 double型。⑤
将 10+'a'+i*f的结果与 d/e的商相减,结果为
double型。
上述的类型转换是由系统自动进行的。
3.8 算术运算符和算术表达式
3.8.1 C运算符简介
C语言的运算符范围很宽,把除了控制语句和输入输出以外的几乎所有的基本操作都作为运算符处理,例如将赋值符,=”作为赋值运算符,方括号作为下标运算符等。 c的运算符有以下几类:
1.算术运算符 (+-*/%)
2.关系运算符 (>< ==> =< =! =)
3.逻辑运算符 (! &&||)
4.位运算符 (<< >> ~ |∧&)
5.赋值运算符 (=及其扩展赋值运算符 )
6.条件运算符 (?,)
7.逗号运算符 (,)
8.指针运算符 (*和 &)
9.求字节数运算符 (s i z e o f)
10.强制类型转换运算符 ( (类型 ) )
11.分量运算符 (,->)
12.下标运算符 ([ ])
13.其他 (如函数调用运算符 ())
本章只介绍算术运算符和赋值运算符,在以后各章中结合有关内容将陆续介绍其他运算符。
3.8.2 算术运算符和算术表达式
1,基本的算术运算符
+(加法运算符,或正值运算符。如 3+5,+3)
-(减法运算符,或负值运算符。如 5-2,-3)
*(乘法运算符。如 3*5)
/(除法运算符。如 5/3)
%(模运算符,或称求余运算符,%两侧均应为整型数据,如 7%4的值为 3)。
需要说明,两个整数相除的结果为整数,如 5/3
的结果值为 1,舍去小数部分。但是,如果除数或被除数中有一个为负值,则舍入的方向是不固定的。例如,-5/3在有的机器上得到结果 -1,
有的机器则给出结果 -2。多数机器采取“向零取整”的方法,即 5/3=1,-5/3=-1,取整后向零靠拢。如果参加 +,-,*,/运算的两个数中有一个数为实数,则结果是 double型,因为所有实数都按
double型进行运算。
2,算术表达式和运算符的优先级与结合性用算术运算符和括号将运算对象 (也称操作数 )连接起来的、符合 c语法规则的式子,称 c算术表达式。
运算对象包括常量、变量、函数等。例如,下面是一个合法的 c
算术表达式:
a*b/c-1.5+'a'
C语言规定了运算符的优先级和结合性。在表达式求值时,先按运算符的优先级别高低次序执行,
例如先乘除后加减。如表达式 a-b*c,b的左侧为减号,右侧为乘号,而乘号优先于减号,因此,
相当于 a-(b*c)。如果在一个运算对象两侧的运算符的优先级别相同,如 a-b+c,则按规定的“结合方向”处理。
C规定了各种运算符的结合方向 (结合性 ),算术运算符的结合方向为“自左至右”,即先左后右,
因此 b先与减号结合,执行 a-b的运算,再执行加
c的运算。“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看到有些运算符的结合方向为“自右至左”,
即右结合性 (例如,赋值运算符 )。关于“结合性”
的概念在其他一些高级语言中是没有的,是 c的特点之一,希望能弄清楚。
如果一个运算符的两侧的数据类型不同,则会按
3.7节所述,先自动进行类型转换,使二者具有同一种类型,然后进行运算。
3,强制类型转换运算符可以利用强制类型转换运算符将一个表达式转换成所需类型。例如:
(double)a (将 a转换成 double类型 )
(int)(x+y) (将 x+y的值转换成整型 )
(float)(5%3) (将 5%3的值转换成 float型 )
其一般形式为 (类型名 )(表达式 )
注意,表达式应该用括号括起来。如果写成
(int)x+y
则只将 x转换成整型,然后与 y相加。
需要说明的是在强制类型转换时,得到一个所需类型的中间变量,原来变量的类型未发生变化。例如:
(int)x (不要写成 int(x))
如果 x原指定为 float型,进行强制类型运算后得到一个 int型的中间变量,它的值等于 x的整数部分,
而 x的类型不变 (仍为 float型 )。见下例。
例 3.8强制类型转换。
main()
{ float x;
int i;
x=3,6;
i=(int)x;
printf("x=%f,i=%d",x,i);
}
运行结果如下:
x=3.600000,i=3
x类型仍为 float型,值仍等于 3.6。
从上可知,有两种类型转换,一种是在运算时不必用户指定,系统自动进行的类型转换,如 3+6.5。
第二种是强制类型转换。当自动类型转换不能实现目的时,可以用强制类型转换。如,%” 运算符要求其两侧均为整型量,若 x为 float型,则
,x%3” 不合法,必须用:,(int)x % 3” 。强制类型转换运算优先于 %运算,因此先进行 (int)x
的运算,得到一个整型的中间变量,然后再对 3
求模。此外,在函数调用时,有时为了使实参与形参类型一致,可以用强制类型转换运算符得到一个所需类型的参数。
4,自增、自减运算符作用是使变量的值增 1或减 1,如:
++i,--i(在使用 i之前,先使 i的值加 (减 )1)
i++,i-- (在使用 i之后,使 i的值加 (减 )1)
粗略地看,++i和 i++的作用相当于 i=i+1。但 ++i和
i++不同之处在于 ++i是先执行 i=i+1后,再使用 i的值;而 i++是先使用 i的值后,再执行 i=i+1。如果 i
的原值等于 3,则执行下面的赋值语句:
① =++i; (i的值先变成 4,再赋给j,j的值为 4)
② j =i++; (先将 i的值 3赋给j,j的值为 3,然后 i变为 4)
又如:
i=3;
printf("%d",++i);
输出,4”。若改为
printf("%d",i++);
则输出,3”。
注意:
(1) 自增运算符 (++)和自减运算符 (--),只能用于变量,而不能用于常量或表达式,如 5++或 (a+b)++
都是不合法的。因为 5是常量,常量的值不能改变。 (a+b)++也不可能实现,假如 a+b的值为 5,
那么自增后得到的 6放在什么地方呢?无变量可供存放。
(2) ++和 --的结合方向是“自右至左”。前面已提到,算术运算符的结合方向为“自左而右”,这是大家所熟知的。如果有 -i++,i的左面是负号运算符,右面是自加运算符。如果 i的原值等于 3,
若按左结合性,相当于 (-i)++,而 (-i)++是不合法的,因为对表达式不能进行自加自减运算。负号运算符和,++”运算符同优先级,而结合方向为
“自右至左” (右结合性 ),即它相当于 -(i++),如果有 printf("%d",-i++),则先取出 i的值 3,输出 -i的值 -3,然后 i增值为 4。注意 -(i++)是先用 i的原值 3加上负号输出 -3,再对 i加 1,不要认为先加完 1后再加负号,输出 -4,这是不对的。
自增 (减 )运算符常用于循环语句中,使循环变量自动加 1,也用于指针变量,使指针指向下一个地址。这些将在以后的章节中介绍。
5,有关表达式使用中的问题说明
(1)C运算符和表达式使用灵活,利用这一点可以巧妙地处理许多在其他语言中难以处理的问题。但是应当注意,ansi c并没有具体规定表达式中的子表达式的求值顺序。允许各编译系统自己安排。
例如,对表达式 a = f1( )+f2( )并不是所有的编译系统都先调用 f1( ),然后调用 f2( )。在一般情况下,先调用 f1( ) 和先调用 f2( ) 的结果可能是相同的。但是在有的情况下结果可能不同。有时 会出现一些令人容易搞混的问题,因此务必要小心谨慎。
如果有以下表达式:
(i++)+(i++)+(i++)
设 i的原值为 3,那么,表达式的值是多少呢?有的系统按照自左而右顺序求解括弧内的运算,求完笫 1个括弧再求第 2个括弧,结果为 3+4+5,即 12。而另一些系统 (如 turbo c和M s c)设 i的原值为 3,那么作为表达式中所有 i的值,因此 3个 i相加,得 9。然后再实现自加 3次,i的值变为 6。
应该避免出现这种歧义性。如果编程者的原意是想得到 12,可以写成下列语句:
i = 3;
a = i++;
b = i++;
c = i++;
d = a + b + c;
执行完上述语句后,d的值为 12,i的值为 6。虽然语句多了,但不会引起歧义,无论程序移植到哪一种 c编译系统运行,结果都一样。
(2) c语言中有的运算符为一个字符,有的运算符由两个字符组成,在表达式中如何组合呢?如 i+++
j,是理解为 (i++)+j呢?还是 i+(++j )呢?c编译系统在处理时尽可能多地 (自左而右 )将若干个字符组成一个运算符 (在处理标识符、关键字时也按同一原则处理 ),如 i+++j,将解释为
(i++)+j,而不是 i+(++j )。为避免误解,最好采取大家都能理解的写法,不要写成 i+++j的形式,
而应写成 (i++)+j的形式。
(3) C语言中类似上述这样的问题还有一些。例如,
在调用函数时,实参数的求值顺序,C标准并无统一规定。如 i的初值为 3,如果有下面的函数调用:
printf("%d,%d",i,i++)
在有的系统中,从左至右求值,输出,3,3”。在多数系统中对函数参数的求值顺序是自右而左,
上面 printf函数中要输出两个表达式的值 (i和 i++
分别是两个表达式 ),先求出第 2个表达式 i++的值
3(i未自加时的值 ),然后求第 1个表达式的值,由于在求解第 2个表达式后,执行 i++,使 i加 1变为 4,
因此 printf函数中第一个参数 i的值为 4。所以上面
printf函数输出的是,4,3”。
以上这种写法不宜提倡,最好改写成
j = i++;
printf("%d,%d",j,i);
总之,不要写出别人看不懂的、也不知道系统会怎样执行的程序。
在看别人的程序时,应该考虑到在类似上述问题上,
不同系统的处理方法不尽相同。应当知道使用 c
语言时可能出问题的地方,以免遇到问题时不知其所以然。
使用 ++和 --时,常会出现一些人们“想不到”的副作用,初学者要慎用。
3.9 赋值运算符和赋值表达式
1,赋值运算符赋值符号,=”就是赋值运算符,它的作用是将一个数据赋给一个变量。如,a=3”的作用是执行一次赋值操作 (或称赋值运算 )。把常量 3赋给变量 a。
也可以将一个表达式的值赋给一个变量。
2,类型转换如果赋值运算符两侧的类型不一致,但都是数值型或字符型时,在赋值时要进行类型转换。
(1) 将实型数据 (包括单、双精度 )赋给整型变量时,
舍弃实数的小数部分。如 i为整型变量,执行
,i=3.56”的结果是使 i的值为 3,在内存中以整数形式存储。
(2) 将整型数据赋给单、双精度变量时,数值不变,
但以浮点数形式存储到变量中,如将 23赋给 float
变量 f,即 f=23,先将 23转换成 23,00000,再存储在 f中。如将 23赋给 double型变量 d,即 d=23,
则将 23补足有效位数字为 23,00000000000000,
然后以双精度浮点数形式存储到 d中。
(3) 将一个 double型数据赋给 float变量时,截取其前面 7位有效数字,存放到 float变量的存储单元
(32位 )中。但应注意数值范围不能溢出。如:
float f;
double d=123.456789e100;
f=d;
就出现溢出的错误。
将一个 float型数据赋给 double变量时,数值不变,
有效位数扩展到 16位,在内存中以 64位 (bit)存储。
(4) 字符型数据赋给整型变量时,由于字符只占 1个字节,
而整型变量为 2个字节,因此将字符数据 (8位 )放到整型变量低 8位中。有两种情况:
① 如果所用系统将字符处理为无符号的量或对
unsigned char型变量赋值,
则将字符的 8位放到整型变量低 8位,高 8位补零。例如:将字符‘ \376?赋给 int
型变量 i,如图 3.11(a)所示。
如图 3.11
② 如果所用系统 (如 turbo c)将字符处理为带符号的
(即 signed char),若字符最高位为 0,则整型变量高 8位补 0;若字符最高位为 1,则高 8位全补 1(图
3.11(b))。这称为“符号扩展”,这样做的目的是使数值保持不变,如变量 c(字符‘ \376?)以整数形式输出为 -2,i的值也是 -2。
(5) 将一个 int,short,long型数据赋给一个 char型变量时,只将其低 8位原封不动地送到 char型变量 (即截断 )。例如:
int i=289;
char c='a';
c=i;
赋值情况见图 3.12。 c的值为 33,如果用,%c” 输出 c,将得到字符,!” (其 ascii码为 33)。
图 3.12
(6) 将带符号的整型数据 (int型 )赋给 long型变量时,
要进行符号扩展,将整型数的 16位送到 long型低
16位中,如果 int型数据为正值 (符号位为 0),则
long型变量的高 16位补 0;如果 int型变量为负值
(符号位为 1),则 long型变量的高 16位补 1,以保持数值不改变。
反之,若将一个 long型数据赋给一个 int型变量,只将 long型数据中低 16位原封不动地送到整型变量
(即截断 )。例如:
int a;
long b=8;
a=b;
赋值情况见图 3.13。如果 b=65536(八进制数
0200000),则赋值后 a值为 0。见图 3.14。如果
b=020000000000(八进制数 ),赋值后 a也为 0。请读者自己分析。
图 3.14
图 3.13
(7) 将 unsigned int型数据赋给 long int型变量时,不存在符号扩展问题,只需将高位补 0即可。将一个 unsigned类型数据赋给一个占字节数相同的整型变量 (例如,unsigned int=>int,unsigned
long=>long,unsigned short=>short),将
unsigned型变量的内容原样送到非 unsigned型变量中,但如果数据范围超过相应整型的范围,则会出现数据错误。如:
unsigned int a=65535;
int b;
b=a;
将 a整个送到 b中 (图 3.15),由于 b是 int型,第 1位是符号位,成了负数。根据补码知识图 3.15可知,b的值为 -
1,可以用 printf(“%d”,b);来验证。
(8) 将非 unsigned型数据赋给长度相同的 unsigned型变量,也是原样照赋 (连原有的符号位也作为数值一起传送 )。如:
例 3.9有符号数据传送给无符号变量。
main()
{ unsigned a;
int b=-1;
a=b;
printf("%u",a);
}
,%u” 是输出无符号数时所用的格式符。运行结果为,65535
图 3.16
赋值情况见图 3.16。如果 b为正值,且在 0~ 32767
之间,则赋值后数值不变。以上的赋值规则看起来比较复杂,其实,不同类型的整型数据间的赋值归根到底就是一条:按存储单元中的存储形式直接传送。只要学过补码知识的,对以上规则是不难理解的。由于 c语言使用灵活,
在不同类型数据之间赋值时,常常会出现意想不到的结果,而编译系统并不提示出错,全靠程序员的经验来找出问题。这就要求编程人员对出现问题的原因有所了解,以便迅速排除故障。根据作者的经验,相当多的初学者 (甚至还有不少有一定编程经验的人 )在这方面常出错,而且不能很快地找出原因。因此本书对此做了较详细的说明,使读者在编程序和对程序排错时有所遵循。
在学习本书时不必死记,这部分内容可以通过学生自学和上机实践来掌握。
3,复合的赋值运算符在赋值符,=”之前加上其他运算符,可以构成复合的运算符。如果在,=”前加一个,+”运算符就成了复合运算符,+=”。例如,可以有:
a+=3 等价于 a=a+3
x*=y+8 等价于 x=x*(y+8)
x%=3 等价于 x=x%3
以,a+=3”为例来说明,它相当于使 a进行一次自加 (3)的操作。即先使 a加 3,再赋给 a。同样,
,x*=y+8”的作用是使 x乘以 (y+8),再赋给 x。为便于记忆,可以这样理解:
① a += b (其中 a为变量,b为表达式 )
② a +=b (将有下划线的,a+”移到,=”右侧 )
③ a = a + b (在,=”左侧补上变量名 a)
注意,如果 b是包含若干项的表达式,则相当于它有括号。如:
① x %= y+3
② x %=(y+3)
③ x = x %(y+3) (不要错写成 x=x%y+3)
凡是二元 (二目 )运算符,都可以与赋值符一起组合成复合赋值符。 c语言规定可以使用 10种复合赋值运算符。即:
+=,-=,*=,/=,%=,<<=,>>=,&=,∧ =,|=
后 5种是有关位运算的,将在第 11章介绍。
C采用这种复合运算符,一是为了简化程序,使程序精炼,二是为了提高编译效率 (这样写法与
“逆波兰”式一致,有利于编译,能产生质量较高的目标代码。学过编译原理的读者对此容易理解,其他读者可不必深究 )。
4,赋值表达式由赋值运算符将一个变量和一个表达式连接起来的式子称为“赋值表达式”。它的一般形式为
<变量 ><赋值运算符 ><表达式 >
如,a=5”是一个赋值表达式。对赋值表达式求解的过程是:将赋值运算符右侧的“表达式”的值赋给左侧的变量。赋值表达式的值就是被赋值的变量的值。例如,,a=5”这个赋值表达式的值为
5(变量 a的值也是 5)。
上述一般形式的赋值表达式中的“表达式”,又可以是一个赋值表达式。如 a=(b=5)括弧内的,b=5”
是一个赋值表达式,它的值等于 5。,a=(b=5)”相当于,b=5”和,a=b”两个赋值表达式,因此 a的值等于 5,整个赋值表达式的值也等于 5。赋值运算符按照“自右而左”的结合顺序,因此,,b=5”
外面的括弧可以不要,即,a=(b=5)”和,a=b=5”
等价,都是先求,b=5”的值 (得 5),然后再赋给 a,
下面是赋值表达式的例子:
a=b=c=5 (赋值表达式值为 5,a,b,c值均为 5)
a=5+(c=6) (表达式值为 11,a值为 11,c值为 6)
a=(b=4)+(c=6) (表达式值为 10,a值为 10,b等于 4,c等于 6)
a=(b=10)/(c=2) (表达式值为 5,a等于 5,b等于
10,c等于 2)
赋值表达式也可以包含复合的赋值运算符。如:
a+=a-=a*a
也是一个赋值表达式。
如果 a的初值为 12,此赋值表达式的求解步骤如下:
① 先进行,a-=a*a”的运算,它相当于 a=a-a*a=12-
144=-12。
②再进行,a+=-132”的运算,相当于 a=a+(-132)=-132-
132=-264。
将赋值表达式作为表达式的一种,使赋值操作不仅可以出现在赋值语句中,而且可以以表达式形式出现在其他语句 (如输出语句、循环语句等 )中,如:
printf("%d",a=b);如果 b的值为 3,则输出 a的值 (也是表达式 a=b的值 )为 3。在一个语句中完成了赋值和输出双重功能。这是 c语言灵活性的一种表现。在第
5章中将进一步看到这种应用及其优越性。在下一章介绍“语句”之后,就可以了解到赋值表达式和赋值语句之间的联系和区别了。
3.10 逗号运算符和逗号表达式
C语言提供一种特殊的运算符 ——逗号运算符。用它将两个表达式连接起来。如:
3+5,6+8
称为逗号表达式,又称为“顺序求值运算符”。逗号表达式的一般形式为表达式 1,表达式 2逗号表达式的求解过程是:先求解表达式 1,再求解表达式 2。整个逗号表达式的值是表达式 2的值。例如,上面的逗号表达式,3+5,6+8”的值为 14。
又如,逗号表达式
a=3*5,a*4
对此表达式的求解,读者可能会有两种不同的理解:
一种认为,3*5,a*4” 是一个逗号表达式,先求出此逗号表达式的值,如果 a的原值为 3,则逗号表达式的值为 12,将 12赋给 a,因此最后 a的值为
12。另一种认为:,a=3*5”是一个赋值表达式”,
,a*4”是另一个表达式,二者用逗号相连,构成一个逗号表达式。这两者哪一个对呢?赋值运算符的优先级别高于逗号运算符,因此应先求解
a=3*5(也就是把,a=3*5”作为一个表达式 )。经计算和赋值后得到 a的值为 15,然后求解 a*4,得 60。
整个逗号表达式的值为 60。
一个逗号表达式又可以与另一个表达式组成一个新的逗号表达式,如 (a=3*5,a*4),a+5 先计算出 a
的值等于 15,再进行 a*4的运算得 60(但 a值未变,
仍为 15),再进行 a+5得 20,即整个表达式的值为
20。
逗号表达式的一般形式可以扩展为表达式 1,表达式 2,表达式 3…… 表达式 n它的值为表达式 n的值。
逗号运算符是所有运算符中级别最低的。因此,下面两个表达式的作用是不同的,
① x=(a=3,6*3)
② x=a=3,6*a
第①个是一个赋值表达式,将一个逗号表达式的值赋给 x,x的值等于 18。第②个是逗号表达式,它包括一个赋值表达式和一个算术表达式,x的值为 3。
其实,逗号表达式无非是把若干个表达式“串联”
起来。在许多情况下,使用逗号表达式的目的只是想分别得到各个表达式的值,而并非一定需要得到和使用整个逗号表达式的值,逗号表达式最常用于循环语句 (for语句 )中,详见第 5章。
请注意并不是任何地方出现的逗号都是作为逗号运算符。
例如函数参数也是用逗号来间隔的。如
printf("%d,%d,%d",a,b,c);
上一行中的,a,b,c”并不是一个逗号表达式,它是 printf
函数的 3个参数,参数间用逗号间隔。有关函数的详细叙述见第 7章。如果改写为
printf("%d,%d,%d",(a,b,c),b,c);
则,(a,b,c)”是一个逗号表达式,它的值等于 c的值。括弧内的逗号不是参数间的分隔符而是逗号运算符。括弧中的内容是一个整体,作为 printf函数的一个参数。
C语言表达能力强,其中一个重要方面就在于它的表达式类型丰富,运算符功能强,因而 c使用灵活,适应性强。在后面几章中将会进一步看到这一点。
习题
3.1 请将 c语言的数据类型和其他高级语言的数据类型做比较。 c有哪些特点?
3.2 C语言为什么要规定对所有用到的变量要
“先定义,后使用”。这样做有什么好处?
3.3 请将下面各数用八进制和十六进制数 (补码 )
表示:
(1)10 (2)32 (3)75 [](4)-617
(5)-111 (6)2483 (7)-28654 (8)21003
3.4 将以下 3个整数分别赋给不同类型的变量,请画出赋值后数据在内存中的存储形式。变量的类型
25 -2 32769
int型 (16位 )
long型 (32位 )
short型 (16位 )
signed char(8位 )
unsigned int型
unsigned long型
unsigned short型
unsigned char型注:如没有学过二进制数和补码的,此题可不做。
3.5 字符常量与字符串常量有什么区别?
3.6 写出以下程序运行的结果。
main()
{char c1='a',c2='b',c3='c',c4='\101',
c5='\116';
printf("a%c b%c\tc%c\tabc\n",c1,c2,c3);
printf("\t\b%c %c",c4,c5);
}
3.7 要将,china”译成密码,密码规律是:用原来的字母后面第 4个字母代替原来的字母。例如,
字母,a”后面第 4个字母是,e”,用,e”代替
,a”。因此,,china”应译为,glm re”。请编一程序,用赋初值的方法使 c1,c2,c3,c4,c5
五个变量的值分别为‘ c?、‘ h?、‘ i?、‘ n?、
‘ a?,经过运算,使 c1,c2,c3,c4,c5分别变为‘ g?、‘ l?、‘m’、‘ r?、‘ e?,并输出。
3.8 例 3.6能否改成如下:
m ain()
{ int c1,c2; (原为 char c1,c2)
c1=97;
c2=98;
printf("%c%c\n",c1,c2);
printf("%d %d\n",c1,c2);
}
分析运行时会显示什么信息?为什么?
3.9 求下面算术表达式的值。
(1) x+a%3*(int)(x+y)%2/4 设 x=2,5,a=7,
y=4,7
(2) (float)(a+b)/2+(int)x%(int)y
设 a=2,b=3,x=3,5,y=2,5
3.10 写出程序运行结果。
main()
{ int i,j,m,n;
i=8;
j=10;
m=++i;
n=j ++;
printf("%d,%d,%d,%d",i,j,m,n);
3.11 写出下面表达式运算后 a的值,设原来 a=12。
设 a和 n已定义为整型变量。
(1) a+=a
(2) a-=2
(3) a*=2+3
(4) a/=a+a
(5) a%=(n%=2),n的值等于 5
(6) a+=a-=a*=a