第3章 数据类型和表达式 主要知识点 标识符 常量和变量 基本数据类型 常用的输入输出函数 运算符和表达式 数据类型转换 在程序中使用的数据都要指定其数据类型。数据类型决定数据所占内存空间的大小、数据的合法取值范围和可以进行的运算。 C语言提供了以下几种数据类型。 数据类型基本类型整型短整型 基本整型 长整型 实型单精度型 双精度型 字符型 枚举类型 构造类型数组类型 结构体类型 共用体类型 指针类型 空类型 本章主要介绍C语言程序中常用的整型、实型和字符型等数据类型。不同的C编译系统对各种类型数据分配的存储空间大小不同。在Visual C++6.0编译系统中,整型、实型和字符型数据所占内存空间(字节数)和取值范围如表31所示。 表31常用基本数据类型的字节数 数 据 类 型类型标识符字节数取 值 范 围 整型 短整型short int(或short)2-32768(-215)~ 32767(215-1) 基本整型int4-231 ~ 231-1 长整型long int(或long)4-231 ~ 231-1 续表 数 据 类 型类型标识符字节数取 值 范 围 实型 单精度float4-3.4×10-38 ~ 3.4×1038 双精度double8-1.7×10-308 ~ 1.7×10308 字符型char1-128(-27)~ 127(27-1) 3.1标识符 如同人们的名字一样,C语言中用标识符区分变量、符号常量、数组、函数等对象。标识符是由数字、字母和下画线3种字符组成的,且第一个字符必须为字母或下画线的字符序列。 例如,num、i、_io3等均为合法的标识符。M.D.Y、5b、wang1等均为不合法的标识符。 在使用标识符时需要注意以下问题。 (1) 标识符应尽量做到见名知义,以增加程序的可读性。如用year表示“年”,用month表示“月”。 (2) 标识符中的字母严格区分大小写。如Sum和sum是两个不同的标识符。 (3) 用户定义的标识符不要以下画线开头,因为库程序中经常用到以下画线开头的标识符。 (4) ANSI C标准没有规定标识符的长度,但不同的C编译系统对标识符的长度限制都有各自规定。因此,编写程序时需要了解所用系统对标识符长度的规定,以避免标识符出现混淆。 C语言预定义了32个标识符,如auto、double、if、for等(详见附录B),它们被赋予了特定的含义,不能另作他用,这些标识符称为关键字或保留字。 3.2常量与变量 C语言程序中的数据一般以常量或变量的形式出现。 3.2.1常量 常量是指在程序执行过程中,其值始终保持不变的量。C语言中的常量有两种: 直接常量和符号常量。 1. 直接常量 直接常量一般从其字面形式即可判别,有不同类型的常量。例如,5、0、-12为整型常量,3.14、-2.45为实型常量,'1'、'a'、'#'为字符常量,"Beijing"、"a"、"123"为字符串常量。 2. 符号常量 C语言中可以用一个标识符代表一个常量,称为符号常量。使用符号常量的优点是能够见名知义,提高程序的可读性,便于程序的修改和维护。 在C语言中,通过预编译命令#define定义符号常量,其一般格式如下: #define符号常量名 直接常量 在C程序中,一般符号常量名用大写字母,变量名用小写字母,以示区别。 【实例31】已知长方形的长和宽,计算并输出其面积。 /*实例31*/ #include <stdio.h> #define LENGTH 12 #define WIDTH 6 int main() { int s; s = LENGTH * WIDTH; /*计算面积*/ printf("s=%d\n",s); return 0; } 说明: (1) 程序中用LENGTH表示长方形的长度为12,用WIDTH表示其宽度为6。比起12和6,LENGTH和WIDTH可读性明显要好。 (2) “/*…*/”括起来的内容为注释,起到解释说明的作用,目的是便于阅读程序。将其删除不会影响程序运行结果。另一种注释语句的方法是用双斜杠“//”,一般用于一行的注释,例如,“/*实例31*/”可以写成“//实例31”。 程序运行结果如图31所示。 图31求长方形的面积 思考: 如何定义符号常量PI为3.14159? 3.2.2变量 在程序执行过程中,其值可以变化的量称为变量。变量的实质是内存中的一块存储单元,其大小由变量的类型决定。为便于引用,每个变量都有一个名字,通过变量名对其存储空间中的数据进行操作。变量名为合法的标识符,其命名规则依照3.1节标识符中的规定。 这里要注意区分两个概念: 变量名和变量的值。变量名实际上是存储单元的符号地址,而变量的值是指存储单元中的数据,两者之间的关系如图32所示。 图32变量名和变量的值 在C程序中,变量的使用遵循“先定义,后使用”的原则。定义变量的实质是程序申请内存空间,用于存储数据。 1. 变量的定义 定义变量的一般格式如下: 类型标识符变量名表列; 其中,类型标识符为int、float、char等数据类型名。变量名表列可以是一个变量名,也可以是多个变量名,多个变量名之间用逗号“,”分隔。 例如: int step;/*定义了整型变量step,分配4字节的存储单元,用于存储整型数据*/ float s,v;/*定义了两个单精度类型变量s和v,各分配4字节的存储单元,用于存储单精度实型数据*/ 可以使用一个特殊的运算符sizeof测试不同数据类型的变量所占的字节数。 【实例32】定义变量并测试变量所占字节数。 /*实例32*/ #include <stdio.h> int main() { short int a; int b; char c; float f; printf("%d,%d,%d,%d\n",sizeof(a),sizeof(b),sizeof(c),sizeof(f)); return 0; } 图33不同类型的变量所占 字节数不同 说明: 程序定义了4个不同类型的变量,并用printf()函数输出这4个变量在内存中所占的字节数。 程序执行结果如图33所示。 2. 变量赋值 可以用赋值运算符“=”为变量赋值。 (1) 定义变量的同时为变量赋初值,也称为变量的初始化。 一般格式为: 类型标识符变量名1=值1,变量名2=值2,…,变量名n=值n; 例如: int step=1;/*定义整型变量step,其初值为1*/ int sum=0,cnt=0;/*定义整型变量sum和cnt,它们的初值均为0*/ (2) 先定义变量,然后为其赋值。 例如: int step,sum=0;/*定义变量step和sum,并且只为变量sum赋初值0*/ step=1;/*为变量step赋值1*/ 点拨: 一般定义自动变量(涉及变量的存储属性,详见6.5.2节变量的生存期)后,如果不对其赋值,其值不确定。如没有特殊说明,一般函数内定义的变量都是自动变量。 【实例33】变量赋初值和引用。 /*实例33*/ #include <stdio.h> int main() { int step=1,i; i=i+step; 图34变量赋初值 printf("%d\n%d\n ",step,i); return 0; } 程序执行结果如图34所示。 说明: 因为在程序中没有给变量i赋初值,导致输出结果异常。如果将定义语句改成 int step=1,i=1; 或在定义语句后加一个赋值语句“i=1;”,再运行程序,即可得到正常的结果。 3. 声明只读变量 定义变量时,如果在类型标识符前加关键字const,则说明该变量为只读变量,其值不允许被修改。 【实例34】声明只读变量。 /*实例34*/ #include <stdio.h> int main() { const int step=1; printf("%d\n",step); step=2; printf("%d\n",step); return 0; } 该程序在编译时出现错误信息,如图35所示。 说明: 变量step为只读变量,只能被引用,程序中不能修改它的值。 图35只读变量 4. 指针变量 由于变量常常会占用多个内存单元,对应多个存储单元的地址,通常把起始地址作为该变量的地址,在程序中变量的地址表示为“&变量名”,其中&为取地址运算符。 一般变量的访问方式有两种,第一种是通过变量名(变量的符号地址)存取变量的值,称为“直接访问”方式; 第二种是先把变量的地址存放在另一个变量中,通过该变量找到要访问的变量的地址,然后通过地址存取变量的值,称为“间接访问”方式。 一个变量的地址称为该变量的“指针”。存放变量的地址的变量,称为“指针变量”。 定义指针变量的一般形式为: 类型标识符*变量名; 例如: int *ip;/*定义指针变量ip,它可以保存一个整型变量的地址*/ float *fp/*定义指针变量fp,它可以保存一个单精度类型变量的地址*/ 【实例35】定义整型变量i和指针变量ip,ip中保存变量i的地址。用直接访问的方式对变量i赋值为3,用间接访问的方式对变量i赋值为5,分别输出赋值后变量i的值。 /*实例35*/ #include <stdio.h> int main() { int i,*ip; ip=&i; /*将变量i的地址保存在指针变量ip中*/ printf("%x,%x\n",&i,&ip); /*以十六进制整数格式输出i和ip的地址*/ i=3; /*直接访问变量i*/ printf("i=%d\n",i); *ip=5; /*间接访问变量i*/ printf("i=%d\n",i); return 0; } 程序执行结果如图36所示。 说明: (1) 程序中有两个变量: 变量i和指针变量ip,从地址的输出结果可以看出它们各自占据不同的存储单元。 (2) 将变量i的地址保存在指针变量ip中,则可以形象地称为指针变量ip“指向”变量i。变量i与指针变量的关系如图37所示。 图36变量的两种访问方式 图37指针变量ip指向变量i (3) 变量的定义语句“int i,*ip;”中,变量名ip前的星号“*”仅仅是一个标识符号,表示变量ip是一个指针变量,与普通的整型变量i不同,以示区别。 (4) 与定义行中的不同,语句“*ip=5;”中的星号“*”为指针运算符,其运算对象是指针变量,*ip表示指针变量ip所指的对象,即变量i。“*ip=5;”与“i=5;”等价,从程序的执行结果也可以看出,执行该赋值语句后,变量i的值发生了变化。 点拨: 一个指针变量只能指向一个同类型的变量,因为不同类型的变量在内存中所占的字节数不同,指针移动时的“跨度”就不同。指针的移动和指针的运算详见5.5节数组与指针。 3.3基本数据类型 数据类型决定数据在内存中所占的字节数和存储方式,进而决定数据的取值范围和精度,数据类型还决定数据运算(操作)的规则。本节将介绍整型、实型和字符型等几种常用的基本数据类型。 3.3.1整型 C语言提供了短整型(short int)、基本整型(int)和长整型(long int)三种类型的整型,并且规定short int型所占的字节数不能大于int型,int型所占的字节数不能大于long int型。具体某个整型数据所占的字节数取决于机器CPU的类型和编译器。例如,一般基本整型(int)占用一个字的存储空间。如果字长为16位,则基本整型用16位来表示; 如果字长为32位,则基本整型用32位表示。 整型又分为有符号(signed)整型和无符号(unsigned)整型,其区别是取值范围不同,无符号整型所占字节数和取值范围见表32。定义整型变量时,只要不指定为无符号型(类型标识符前面加unsigned),其隐含的类型就是有符号型(即signed可以省略)。 表32无符号整型所占字节数和取值范围 数 据 类 型类型标识符字节数取 值 范 围 无符号短整型unsigned short 20~65535(216-1) 无符号基本整型unsigned int40~232-1 无符号长整型unsigned long 40~232-1 1. 整型常量 在C语言中,整型常量有三种形式。 (1) 十进制整数,如125、-23等。 (2) 八进制整数,以数字0开头,由0~7组成的数字序列,如0125表示(125)8,相当于十进制数85(1×82+2×81+5×80)。 (3) 十六进制整数,以数字0和字母x(或X)开头,由0~9,a~f或A~F组成的序列,如0x125表示(125)16,相当于十进制数293(1×162+2×161+5×160)。-0x3c相当于十进制数-60(3×161+12×160)。 【实例36】整型常量的三种形式。 /*实例36*/ #include <stdio.h> int main() { /*分别以十进制、八进制和十六进制整型格式输出十进制整数10*/ printf("%d,%o,%x\n", 10, 10, 10); /*以十进制整型格式输出(10)10、(10)8和(10)16*/ printf("%d,%d,%d\n", 10, 010, 0x10); return 0; } 图38整型常量的输出 程序执行结果如图38所示。 点拨: (1) 若整型常量后面加字母l或L,则认为是长整型常量,如-45l、125L等。 (2) 若整型常量后面加字母u或U,则认为是无符号整型常量,如23u、125U等。 2. 整型变量 (1) 整型数据在内存中的表示。 数据在内存中是以二进制形式存放的。整数分为有符号整数与无符号整数。无符号整数的表示是基于二进制表示数据的方法。给定n位二进制序列,它所能表示的数值范围是[0,2n-1],如16位二进制数表示的无符号整数的范围是[0,216-1],即[0, 65535]。 在计算机中,使用补码表示有符号整数。补码就是将位序列的最高位解释为负权。例如,给定16位二进制序列1000 0000 0000 0001,如果它表示无符号整数,则它的值是1×215+1×20=32769,如果用补码理解它,则最高位是负权,也就是-1×215+1=-32767。补码会将最高位理解为一个负数,直观看,就是当最高位是1的时候,是一个非常大的负数加上后面的正整数,后面的正整数越大,此数越接近0,当最高位是0时,负权整个都是0,剩下的几位如同无符号整数一样表示正整数。因此,16位二进制数表示的有符号整数的范围是 [-215, 215-1],即[-32768,32767]。n位二进制数能表示的有符号整数范围是[-2n-1,2n-1-1]。 下面以unsigned short和short为例,说明整型数据在内存中的存储方式。 unsigned short类型数据占2字节,若16位二进制数如下: 1000000000000011 则表示1×215+1×21+1×20=32768+2+1=32771。 short类型数据占2字节,但数据以补码形式存放,最高位(最左侧)是符号位,符号位为0表示正数,符号位为1表示负数。若16位二进制数如下: 1000000000000011 则表示-1×215+1×21+1×20=-32768+2+1=-32765。 若16位二进制数如下: 0100000000000011 则表示1×214+1×21+1×20=16384+2+1=16387。 点拨: 正数的补码和原码相同。 求负数的补码方法: 先求该数的绝对值的原码,原码各位数取反后再加1得补码。 例如,求-32765的补码步骤如下。 ① 求32765的原码(32765=215-3)。 0111111111111101 ② 各位数取反。 1000000000000010 ③ 再加1得补码。 1000000000000011 (2) 整型变量的定义和使用。 遵循变量定义的格式,整型变量可以定义如下: int sum;/*定义整型变量sum*/ short cnt;/*定义短整型变量cnt*/ long f;/*定义长整型变量f*/ unsigned int age;/*定义无符号整型变量age*/ 在计算机中,当要表示的数据超出计算机所使用数据的表示范围时,就会产生数据的溢出。整型变量在使用时要注意“数据溢出”问题。 【实例37】整型数据的溢出。 /*实例37*/ #include <stdio.h> int main() { short a,b; a=32767; b=a+1; printf("a=%d\n",a); printf("b=%d\n",b); return 0; } 图39整型数据的溢出 程序执行结果如图39所示。 说明: 从程序的运行结果看,变量b的值出现“异常”状况。这是因为一个有符号的短整型变量占据2字节的内存空间,能表示的数据范围是[-215, 215-1],即[-32768,32767]。十进制整数32767的二进制数表示为0111 1111 1111 1111,它加上1后得到二进制数1000 0000 0000 0000,此二进制数刚好是十进制整数-32768的补码。 3.3.2实型 实型也称浮点类型。C语言中实型分为单精度类型(float)、双精度类型(double)和长双精度类型(long double)三种形式,本章主要介绍常用的单精度和双精度两种类型。 1. 实型常量 实型常量又称浮点数(floating point number),即实数。浮点数有两种表示形式。 (1) 小数形式。由数字和小数点组成,如3.14、-0.268、326.4等。实数3.0可以写成3.,而0.3可以写成.3。 (2) 指数形式。由尾数部分、字母e(或E)、指数部分组成,如48.6235e12,其中的48.6235为尾数,12为指数,e为基数10,表示48.6235×1012。48.6235e12还可以表示为4.86235e13、4862.35e10、0.4862355e14等,其中的4.86235e13称为“规范化的指数形式”,即尾数部分小数点左边有且只有一位非零的数字。例如,2.312e6、3.4256e-5、1.5673E-2等都属于规范化的指数形式。 指数形式的实数书写必须遵循如下规则。 ① 有且仅有一个字母e或E,且字母e(或E)的左右两侧不能包含空格。例如,3.25□E3、3.25E□3都不是合法的实数(□表示空格)。 ② 指数与尾数部分都不可缺省,且指数部分只能是整数。例如,E-3、1.2E、2.3E1.2都不是合法的实数。 点拨: C语言编译系统一般将实数作为双精度数来处理,这样虽然使得计算结果更精确,但却降低了运算速度。若要明确某个实数为单精度数,则在该数最后加字母f或F,例如,-0.26f、326.4F等。长双精度实数的表示方法是在数值最后加字母l或L,例如,256.33l、-6.4L等。 2. 实型变量 (1) 实型数据在内存中的表示与整型数据的存储形式不同,实型数据在内存中是按指数形式存储的,分为以下三部分。 ① 数符: 符号位,0代表“正”,1代表“负”。 ② 尾数: 一般采用纯小数形式存储。 ③ 阶码: 存储指数,包括符号位。 具体尾数部分和指数部分所占位数的多少则由各C语言编译系统自定。对于占4字节的单精度数来说,不少编译系统用24位存储尾数(包括符号位),用8位存储阶码(包括符号位)。尾数部分所占的位数决定实数的精度,位数越多,精度也就越高。阶码部分所占的位数决定实数的取值范围,位数越多,能够表示的数值范围越大。表33为常用实型数据的取值范围及精度。 表33常用实型数据的取值范围及精度 数 据 类 型字节数有 效 数 字取 值 范 围 单精度类型(float)46~7-3.4×10-38 ~3.4×1038 双精度类型(double)815~16-1.7×10-308 ~1.7×10308 (2) 实型变量的定义和使用。 实型变量可以定义如下: float ave;/*定义单精度型变量ave*/ double pi;/*定义双精度型变量pi*/ 【实例38】在两个不同的实型变量中保存同一个实数,并输出这两个变量的值。 /*实例38*/ #include <stdio.h> int main() { float fx; double fy; fx=123456.789e6; fy=123456.789e6; printf("fx=%f\n",fx); 图310实型数据的精度 printf("fy=%f\n",fy); return 0; } 程序执行结果如图310所示。 说明: (1) 程序编译时出现如下警告信息: warning C4305: '=' : truncation from 'const double' to 'float' 这是因为在执行赋值语句“fx=123456.789e6;”时,赋值号“=”右侧的123456.789e6是double型常量,左侧的fx 是float型变量,类型不一致,系统预警此次操作可能出现截断误差。 (2) printf函数中的“%f”表示以小数形式输出实型数据,默认保留6位小数。 (3) 从程序运行结果可以看出,不同类型的实型变量,保存实数的有效数字不同。超过有效数字范围的数字是不精确的,所以,实型变量在使用时要注意“舍入误差”问题。 【实例39】实型数据的舍入误差。 /*实例39*/ #include <stdio.h> int main() { float a,b; 图311实型数据的舍入误差 a=123456.789e4; b=a+30; printf("a=%f\n",a); printf("b=%f\n",b); return 0; } 程序执行结果如图311所示。 说明: 从理论上来说,变量a的值应该是1234567890.0,变量b的值应该是1234567920.0,但程序输出两个变量的值却是另外的数。原因是变量a和b都是float类型,只有7个有效数字,不能把实数123456.789e4的每个数字都精准地保存起来,后面的数字是不精确的。因此,实数计算中应避免将一个很大的数和一个很小的数相加或相减,以免造成误差。 3.3.3字符型 字符型数据占1字节,且内存中存储的是字符的ASCII码。 1. 字符常量 字符常量一般是用单引号“'”括起来的一个字符,例如,'3'、'a'、'A'、'#'等。除了这些普通字符常量外,C语言还提供一种特殊形式的字符常量,是用单引号“' ”括起来并且以斜杠“\”开头的字符序列,例如,'\n'、'\t'、'\b'等,常用于表示一些转义字符。常用的转义字符如表34所示。 表34常用的转义字符 转义字符意义ASCII码转义字符意义ASCII码 \n换行,移到下行行首10\\反斜杠92 \t水平制表9\?问号63 \v垂直制表11\'单引号39 \b退格8\"双引号34 \r回车,移到本行行首13\ooo1~3位八进制数 \f换页12\xhh1~2位十六进制数 \a振铃7\0空操作字符0 说明: (1) 注意'\n'和'\r'的区别,前者是换行,当前位置移到下一行的行首; 后者是回车不换行,当前位置移到本行的行首。 (2) '\ooo'表示1~3位八进制数表示的字符,例如,'\102'表示八进制数102,即十进制数66,表示大写字母B(见附录C常用字符的ASCII码)。'\0'是一种特殊情况,表示空操作,常用于字符串中,是字符串的结束标记。 (3) '\xhh'表示1~2位十六进制数表示的字符,例如,'\x41'表示十六进制数41,即十进制数65,表示大写字母A。 2. 字符串常量 字符串常量是用双引号括起来的字符序列,例如,"Zhejiang"、"$123.5"、"789#"等。若字符串中没有字符,即"",表示空字符串(简称“空串”)。 从直观上区分,字符常量与字符串常量有以下两个不同。 (1) 字符常量用单引号,字符串常量用双引号。 (2) 字符常量的单引号中只有一个字符(转义字符除外),字符串常量的双引号中可以有多个字符。 字符常量和字符串常量本质的区别是它们的存储形式不同。每个字符串常量结尾会存储一个字符串结束标记'\0'(即空操作字符)。例如,字符'a'和字符串"a"在内存中的存储如图312所示,保存'a'只要1字节,而保存"a"需要2字节的内存空间,除了保存字母a之外,还需要保存字符串结束标记'\0'。 图312字符和字符串的存储形式不同 3. 字符变量 (1) 字符数据在内存中的表示。 在计算机中,非数值的数据,如字母、文字或其他符号是用二进制编码表示的。西文字符最常用的是ASCII编码。对于汉字,我国也制定了相应的编码方案。本节主要介绍西文字符在计算机中的表示。 标准ASCII 码使用7 位二进制数来表示所有的大、小写字母,数字字符0~9,标点符号以及一些特殊转义字符。为了便于处理,在ASCII码的最高位增加一位0,用1字节存储一个ASCII码,也即用1字节存储一个西文字符。例如,字母a的ASCII码为十进制数97,在内存中的存储如下: 01100001 (2) 字符变量的定义和使用。 字符变量可以定义如下: char c1,c2;/*定义字符变量c1和c2*/ 一个字符变量只能存放一个字符,可以先定义变量再赋值,也可以在定义时为变量赋初值。 例如: char c1='a',c2; /*定义字符变量c1和c2,并为c1赋初值*/ c2='b'; /*为变量c2赋值为字符常量'b'*/ 既然字符在计算机内存中存储的是它的ASCII 码,其存储形式与整型数据的存储类似,如此,字符数据与整型数据就可以通用。 【实例310】字符数据与整型数据通用。 /*实例310*/ #include <stdio.h> int main() { int i; /*定义整型变量i*/ char c; /*定义字符变量c*/ c=97; /*为变量c赋值为整数97*/ i='a'; /*为变量i赋值为字符常量'a'*/ printf("%c, %d\n", c, c); /*分别以字符和十进制整数格式输出变量c的值*/ printf("%c, %d\n", i, i); /*分别以字符和十进制整数格式输出变量i的值*/ return 0; } 图313字符数据与整型数据通用 说明: 程序中将一个整数赋值给变量c,将一个字符常量赋值给变量i,分别用%c(字符格式)和%d(十进制整数格式)输出变量的值。因为字符数据与整型数据通用,所以输出结果完全相同。 程序执行结果如图313所示。 利用字符数据与整型数据通用的特点,在C语言程序中,要实现英文字母的大小写转换就很容易。 【实例311】小写字母转换成大写字母。 /*实例311*/ #include <stdio.h> int main() { char ch; /*定义字符变量ch*/ ch='a'; /*为变量ch赋值为字符常量'a'*/ printf("%c\n", ch); /*输出变量ch中保存的字符*/ ch=ch-32; /*将小写字母转换成大写字母*/ printf("%c\n",ch); /*输出转换后的结果*/ return 0; } 图314小写字母转换成大写字母 说明: 因为小写字母的ASCII码值比大写字母大32,所以小写字母转换成大写字母用语句“ch=ch-32;”实现。反之,若要将大写字母转换成小写字母,则用语句“ch=ch+32;”实现。 程序执行结果如图314所示。 点拨: 字符数据和整型数据通用有一个前提,那就是在1字节存储空间能够表示的数值范围内,否则可能发生溢出问题,例如: char ch=180; printf("%d",ch); 输出结果为-76。因为char型即signed char型,占1字节的内存空间,能够表示的有符号数据范围是[-128(-27)~127(27-1)],显然180超过了正数的范围。如果将语句“char ch=180;”改为“unsigned char ch=180;”后,输出结果为180,因为unsigned char的取值范围是[0~255]。 3.4常用的输入输出函数 C语言的输入输出函数有很多种,本节主要介绍四个最常用的输入输出函数: printf()、scanf()、putchar()和getchar()。特别强调,如果程序中需要使用这几个函数,必须在程序的开始位置加上预处理命令: #include <stdio.h> 3.4.1格式化输入输出函数 C语言标准库提供了格式化输出函数printf()和格式化输入函数scanf()。printf()函数用来向标准输出设备(屏幕)输出数据,scanf()函数用来从标准输入设备(键盘)读取输入数据。下面详细介绍这两个函数的用法。 1. 输出函数printf() printf()函数是格式化输出函数,一般用于向标准输出设备(屏幕)按规定格式输出信息。 printf()函数的调用格式为: printf(格式控制, 输出表列); 其中“格式控制”是用双引号括起来的一个字符串,也称“格式控制字符串”。格式控制字符串分为格式字符串和非格式字符串两种。格式字符串是以%开头的字符串,在%后面跟各种格式字符,用来说明输出数据的类型、长度、小数位等。非格式字符串是指普通字符,在输出时原样输出。 例如: printf("%d,%d",a,b); 其中“%d”是格式字符串,“,”是非格式字符串,也即普通字符,a和b是输出表列。 2. 格式控制 对不同类型的数据,使用不同的格式字符。常用的格式字符有以下几种。 (1) d格式符,输出一个有符号的十进制数。 %d: 按整型数据的实际长度输出。 %md: m是指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格; 若大于m,则按实际位数输出。 %ld: 输出长整型数据,其中的l是long的意思。 例如: printf("%5d\n%5d\n",23,-567); 输出结果为: □□□23(□表示1个空格) □ -567 (2) c格式符,输出一个字符。 例如: char ch='y' printf("%c",ch); 运行时输出: y 一个大小在0~127的整数,也可以用“%c”使之按字符形式输出,在输出前,系统会将该整数作为ASCII码转换成相应的字符。例如: int a=65; printf("%c",a); 运行时输出: A 输出时也可以指定域宽,例如: printf("%6c",ch); 运行时输出: □□□□□A(□表示一个空格) 如果整数比较大,则把它的最后1字节的信息以字符形式输出。同理,一个字符数据也可以按整型数据输出。 【实例312】输出字符数据。 /*实例312*/ #include <stdio.h> int main() { int i=99; char ch='b'; printf("%d,%5c\n",ch,i); /*以十进制和字符形式输出*/ return 0; } 程序运行结果如图315所示。 图315输出字符数据 (3) f格式符,以小数形式输出实型数据。 %f: 不指定输出数据的宽度,整数部分全部输出,小数部分输出6位。 %m.nf: 输出的数据占m列(小数点“.”也占1列),其中有n位小数,如果数值长度小于m,则左端补空格。 %-m.nf: 输出的数据占m列(小数点“.”也占1列),其中有n位小数,如果数值长度小于m,则右端补空格。 注意: 在用%f输出时要注意数据有效数字的位数。 【实例313】输出实型数据。 /*实例313*/ #include <stdio.h> int main() { float i=1234.567856789; double j=1234.567856789; printf("%f\n%15.2f\n%-10.3f\n%f\n",i,i,i,j);/*以指定格式输出*/ return 0; } 图316输出实型数据 程序运行结果如图316所示。 说明: 图316第1行的输出结果,理论上应该输出1234.567856,而实际输出1234.567871,这是因为float型数据只能保证6位或7位有效数字,左边第7位数字(也就是第3位小数)以后的数字并不能保证是绝对精确的。 其他常用的格式字符还有s、e(E)、g(G)、o、x(X)、u等,其格式字符、格式字符意义、举例和输出结果如表35所示。 表35常用的格式字符及意义 格式字符格式字符意义举例输出结果 s输出字符串char ch[]="helloworld!"; printf("%s\n%10.6s\n%- 10.3s\n%1.2s\n",ch,ch,ch,ch);helloworld □□□□hellow hel he e(E)以指数形式输出单、双精度实数float i=1234.567856789; double j=1234.567856789; printf("%e\n%15.2e\n%- 10.3e\n%e\n",i,i,i,j);1.234568e+003 □□□□□□1.23e+003 1.235e+003 1.234568e+003 g(G)以%f、%e中较短的输出宽度输出单、双精度实数double i=12345678954321; printf("%f\n%e\n%g\n",i,i,i);12345678954321.000000 1.234568e+013 1.23457e+013 o以八进制形式输出整数(不输出前缀0)int a=10; printf("%o",a);12 x(X)以十六进制形式输出整数(不输出前缀0x)int a=10; printf("%x\n%X\n",a,a);a A u以十进制形式输出无符号整数(正整数) int m,n; m=-1; n=32767; printf("%d,%d\n",m,n); printf("%u,%u\n",m,n); -1, 32767 4294967295, 32767 说明: (1) 除了格式字符e、g、x在使用时可以大写外,其他格式字符必须小写。 (2) 可以在printf()函数的格式控制字符串中使用转义字符,如\n、\t、\b、\r等。 (3) 在输出字符“%”时要注意,必须在格式控制中连写两个“%”才能得到预期的结果。例如: printf("%d%%",100); 输出结果为: 100% 下面再举一个综合使用printf()函数的例子。 【实例314】printf()函数的综合应用。 /*实例314*/ #include <stdio.h> int main() { int a=123; float b=123.456789; double c=123.456789; char d='A'; printf("%d,%5d\n",a,a+1); printf("%f,%lf,%6.4f\n",b,b,b); printf("%lf,%f,%6.4lf\n",c,c,c); printf("%c,%8c\n",d,d); printf("%.4s\n","this is my test!"); printf("%8.4s\n","this is my test!"); return 0; } 图317printf()函数的综合应用 程序运行结果如图317所示。 读者可以自行分析图317的输出结果。 点拨: 使用printf()函数时还要注意输出表项中的求值顺序。不同的编译系统不一定相同,可能从左到右,也可能右到左。VC++ 6.0是按从右到左的顺序进行求值的。 3. 输入函数scanf() (1) scanf()函数的一般形式。 scanf()函数是格式化输入函数,它从标准输入设备(键盘)读取输入的信息。一般格式为: scanf(格式控制,地址表列); 其中,“格式控制”的含义与printf()函数相同,“地址表列”是由若干地址组成的表列,可以是变量的地址,也可以是字符串的首地址,中间用逗号隔开。地址由地址运算符“&”后面跟变量名组成,例如,&a和&b分别表示变量a和变量b的地址。下面先看一个数据输入的例子。 【实例315】scanf()函数的使用。 /*实例315*/ #include <stdio.h> int main() { int a,b,m,n; scanf("%d,%d",&a,&b); scanf("%d%d",&m,&n); printf("a is %d,b is %d\n",a,b); printf("m is %d,n is %d\n",m,n); return 0; } 程序执行结果如图318所示。 图318scanf()函数的使用 为什么会出现这样的结果呢?当格式控制中出现“%d,%d”,即两个%d之间用逗号隔开时,输入的数据之间也必须用逗号隔开。当格式控制中出现“%d%d”,即两个%d之间没有符号时,输入的两个数据之间可以用一个或多个空格隔开,也可以用回车键、Tab键隔开。 (2) 格式控制。 格式控制的一般形式为: %[*][域宽][长度]类型 其中有方括号[]的项为可选项。 ① “类型”是scanf()函数的格式字符,与printf()函数中的格式符相同。 ② “*”用来表示该输入项读入后不赋予对应的变量,即跳过该输入值。例如: scanf("%d%*d%d",&a,&b); 当输入12 34 56时,把12赋给变量a,34被跳过,56赋给变量b。 ③ “域宽”是输入数据的宽度,应该是正整数。例如: scanf("%6d",&x); 当输入123456789时,将123456赋给变量x,其余部分截去。又如: scanf("%3d%4d",&x,&y); 当输入123456789时,将123赋给变量x,将4567赋给变量y,其余部分截去。 ④ “长度”格式符为l和h,l表示输入长整型数据(可以用%ld,%lo,%lx,%lu)和double型数据(可以用%lf,%le),h表示输入短整型数据(可以用%hd,%ho,%hx)。 (3) 使用scanf()函数要注意的几个问题。 ① scanf()函数中要求给出变量地址,所以“&”符号必不可少。例如,下面的语句是错误的: scanf("%d,%d,%d",x,y,z); 正确的语句是: scanf("%d,%d,%d",&x,&y,&z); ② 如果在格式控制字符串中除了格式声明外还有其他字符,那么,在输入数据时在对应的位置上必须输入与这些字符一样的字符。例如: scanf("x=%d,y=%d,z=%d",&x,&y,&z); 那么在输入时必须输入: x=1,y=2,z=3 如果输入: 1,2,3 就会产生错误,系统不会把1,2,3分别赋给变量x,y,z。 ③ scanf()函数中没有精度控制。例如,下面的语句是错误的: scanf("%10.2f",&m); ④ 在scanf()函数中用"%c"为字符类型的变量输入值时,空格和转义字符均作为有效字符。例如,执行语句“scanf("%c%c%c",&c1,&c2,&c3);”,输入: a□b□c,则c1得到字符'a',c2得到字符'□',c3得到字符'b',其余的输入被丢弃(□表示1个空格)。 表36是scanf()函数的各种输入形式汇总。 表36scanf()函数的各种输入形式汇总 输 入 语 句输 入 形 式 scanf("%d,%d",&a,&b);5,6 scanf("%d%d",&a,&b);5□6或5□□□6或5(Tab键)6或5(回车键)6 scanf("%d:%d",&a,&b);5:6 scanf("a=%d,b=%d",&a,&b);a=5,b=6 前两种是比较常用的输入形式。 3.4.2字符输入输出函数 虽然使用printf()和scanf()函数可以实现字符的输出和输入,但C语言还提供了专门的单个字符输出函数putchar()和输入函数getchar()。 1. 字符输出函数putchar() putchar()函数的功能是输出单个字符到屏幕上。一般形式为: putchar(c) 其中,参数c可以是字符常量、整型常量、字符变量或整型变量(其值在字符的ASCII码范围内),但不能是字符串。 【实例316】putchar()函数的格式和使用方法。 /*实例316*/ #include <stdio.h> int main() { char ch='a'; putchar(ch); putchar(98); putchar('\n'); 图319putchar()函数的使用 putchar(ch+2); putchar('d'); putchar('\n'); return 0; } 程序运行结果如图319所示。 说明: (1) putchar()函数不仅能输出普通字符,还可以输出转义字符。 (2) 一个putchar()函数只能输出一个字符。 2. 字符输入函数getchar() 函数getchar()的作用是从键盘读取一个字符,没有参数,函数的值就是从输入设备中得到的字符。 执行语句getchar()时,系统等待用户的输入,直到按回车键结束。如果用户输入了多个字符,则getchar()只取第一个字符,多余的字符(包括回车符)存放到键盘缓冲区中,如果再一次执行getchar(),则程序直接从键盘缓冲区读入。 【实例317】getchar()函数的格式和使用方法。 /*实例317*/ #include <stdio.h> int main() { char ch1,ch2; ch1=getchar(); ch2=getchar(); putchar(ch1); putchar(ch2); putchar('\n'); return 0; } 图320getchar()函数的使用 程序运行结果如图320所示。 在图320中,输入了“abcdefg”总共7个字符,按回车键后,系统才开始接收数据,只接收了前面的2个字符'a'和'b'。 【实例318】用getchar()函数从键盘输入一个小写字母,把它转换为大写字母,再用putchar()函数输出该大写字母。 /*实例318*/ #include <stdio.h> int main() { char ch1,ch2; 图321小写字母转换为大写字母 ch1=getchar(); ch2=ch1-32; putchar(ch2); putchar('\n'); return 0; } 程序运行结果如图321所示。 在图321中,输入小写字母a,按回车键后直接输出大写字母A。 3.5运算符与表达式 C语言的表达式是由运算符将各种类型的变量、常量、函数等运算对象按一定的语法规则连接而成的式子。编译器能够按照运算符的运算规则完成相应的运算处理,求出运算结果,这个结果就是表达式的值。 C语言支持非常丰富的运算符: 按照操作对象的个数,运算符可以分为单目运算符、双目运算符和三目运算符; 按照功能可以分为算术运算符、关系运算符、逻辑运算符、条件运算符和逗号运算符(关系运算符、逻辑运算符、条件运算符等内容在第4章中详细介绍)。在表达式中,各运算对象参与运算的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的制约,以便确定是自左向右进行运算还是自右向左进行运算。 3.5.1算术运算符 1. 基本算术运算符 (1) 单目算术运算符。 单目算术运算符有两个: +和-,分别是求正和求负运算符。它们只有一个运算对象,并且运算符写在运算对象的左面,结合性是自右向左。 (2) 双目算术运算符。 双目算术运算符是指有两个运算对象的运算符,C语言有5个双目算术运算符。 +: 加法运算符。 -: 减法运算符。 *: 乘法运算符。 /: 除法运算符。 %: 求余运算符、模运算符。 说明: (1) C语言限定求余运算只对整型运算对象进行,求余运算的结果等于两数相除后的余数,整除时结果为0。例如: 7%5的运算结果为2。不能对float或double类型的运算对象进行求余运算。 (2) 除法运算的运算对象均为整型时,结果也为整型,舍去小数; 如果运算量中有一个是实型,则结果为双精度实型。例如: 4/5的运算结果为0,4.0/5的运算结果为0.8。 (3) 双目算术运算符的结合性都是自左向右。 (4) *、/和%三个运算符的优先级相同,+和-两个运算符的优先级相同,*、/和%优先级高于+和-。 2. 自增、自减运算符 自增运算符++的功能是使变量的值自增1,自减运算符--的功能是使变量的值自减1。自增、自减运算符的运算对象只能是变量,它们的运算优先级高于双目算术运算符,其结合性为自右向左。 假设整型变量i的值为2,自增、自减运算符有以下4个基本形式。 (1) ++i: 变量i自增1; 表达式“++i”的值为3,即变量i自增后的值。 (2) i++: 表达式“i++”的值为2,即变量i原来的值; 变量i自增1。 (3) --i: 变量i自减1; 表达式“--i”的值为1,即变量i自减后的值。 (4) i--: 表达式“i--”的值为2,即变量i原来的值; 变量i自减1。 【实例319】自增、自减运算符的应用。 /*实例319*/ #include <stdio.h> int main() { int a, i = 1; a = ++i; printf("a=%d,i=%d\n",a,i); a = i--; printf("a=%d,i=%d\n",a,i); printf("%d\n",i++); printf("%d\n",i); printf("%d\n",--i); printf("%d\n",i); return 0; } 程序运行结果如图322所示。 图322自增、自减运算符的应用 点拨: 自增、自减运算的对象是变量。一般不要在一个表达式中对同一个变量进行多次自增或自减运算,以免造成错误的理解或得到错误的运算结果。 3. 算术表达式 用基本算术运算符、自增或自减运算符和圆括号将运算对象(常量、变量、函数等)连接的式子称为算术表达式。单个常量、变量和函数可以看作是表达式的特例。 每个表达式都有一个值及其类型,也即计算表达式所得结果的值和类型。 表达式求值按运算符的优先级和规定的结合方向进行(参见附录A运算符的优先级与结合性)。基本算术运算符按先乘除后加减的规则。例如: 3+6*5结果为33,而(3+6)*5结果为45。 表达式类型规则有以下两点。 (1) 同类型数据运算结果类型不变,如整数与整数运算的结果一定是整数,舍去小数。例如: 4/5的运算结果为0。 (2) C语言支持不同类型数据的混合运算,运算结果类型由参与运算的数据决定。不同类型的数据进行运算时,运算结果一般取高一级的数据类型。 思考: 表达式1/2*2.22与1.0/2*2.22的值分别是什么? 关于运算符和表达式,需要注意以下问题。 (1) 算术表达式类似数学上的公式,但不能把算术表达式误写成对应的数学公式。例如: 3x+2y因为少了乘号*而不是合法的C语言算术表达式。 (2) 表达式a+b/c*d与(a+b)/(c*d)的意义完全不同。注意圆括号的使用方法,即使需要多层括号也一律使用圆括号,而不能用中括号或大括号代替。 (3) 利用圆括号可改变运算的优先级,但有时即使无须改变运算的优先级也可利用括号使算术表达式的运算次序清晰、直观。例如: 表达式A/B/C,就不如用A/(B*C)更容易理解。 (4) C语言基本字符集之外的字符不能出现在表达式中,如π、β、ξ等。 (5) 程序中将e视为变量,而不是数学领域认为的“自然数”; 在程序中若需要表示自然数e,可以调用函数exp()。 (6) C语言支持不同类型数据的混合运算,但计算机在计算时要将低精度的类型转换为高精度后才进行运算。考虑到运行效率,应尽可能地减少这种转换。例如: “float a,b=3; a=b/2;”就不如“float a,b=3.0; a=b/2.0;”效率高。 3.5.2赋值运算符 1. 基本赋值运算符和赋值表达式 基本赋值运算符为“=”,由“=”连接的表达式称为赋值表达式。其一般形式为: 变量=表达式 赋值表达式的功能: 先计算赋值号右边的表达式的值,再把该值赋给左边的变量。例如: 表达式“c=a+b”的功能是先计算a+b的值,再把该值赋给变量c。 赋值运算符具有右结合性。例如: “a=b=c=5”可理解为“a=(b=(c=5))”。 在C语言中,凡是表达式可以出现的地方均可出现赋值表达式。例如: 表达式“x=(a=5)+(b=8)”是合法的。它的意义是把5赋给变量a,8赋给变量b,变量a和变量b相加之和赋给变量x,故变量x等于13。但是,这样的表达式降低了程序的可读性。 按照C语言规定,任何表达式在其末尾加分号就构成为语句。例如: “x=8;”和“a=b=c=5;”都是赋值语句。 关于赋值运算,需要注意以下问题。 (1) 赋值运算符不是数学中的等号,而是进行赋值操作。例如: 表达式“a=a+10”的含义是求出a+10的值,再把该值赋给变量a。 (2) 赋值运算符的左侧只能是变量,不能是常量或表达式。例如: 假设变量a的值是10,“a+10=a*2”虽然在数学上成立,但它不是合法的C语言表达式。 (3) 在程序中可以多次给一个变量赋值,每赋一次值,相应的存储单元中的数据就被更新一次。 2. 复合赋值运算符及复合赋值表达式 在赋值运算符之前加上其他运算符可以构成复合赋值运算符。在C语言中共有10种复合赋值运算符,其中算术类的复合赋值运算符有+=、-=、*=、/=。赋值运算符的两个符号之间不能有空格。复合赋值运算符与基本赋值运算符的优先级相同,结合性为右结合。 由复合赋值运算符连接的式子称为复合赋值表达式。其一般形式为: 变量复合赋值运算符表达式 其求值过程如下。 (1) 求出右边表达式的值。 (2) 右边表达式的值与左边的变量值进行运算。 (3) 将(2)中的运算结果赋给左边的变量。 例如: x*=y+1相当于x=x*(y+1)而不是x=x*y+1。x+=x-=x相当于x=x+(x=x-x)。 【实例320】复合赋值运算符的应用。 /*实例320*/ #include <stdio.h> int main() { int a; printf("please input:"); scanf("%d",&a); a+=a*=a/=a-6; printf("the result is:%d\n",a); return 0; } 图323复合赋值运算符的应用 程序运行结果如图323所示。 点拨: 复合赋值运算符有利于提高编译效率并产生质量较高的目标代码,但在使用过程中也要注意程序的可读性。 3.5.3逗号运算符 在C语言中,逗号“,”也是一种运算符,称为逗号运算符。逗号运算符的优先级是所有运算符中最低的,结合性为左结合。 用逗号表达式将两个或多个表达式连接组成一个表达式,称为逗号表达式。其一般形式为: 表达式1,表达式2,…,表达式n 逗号表达式的求值过程是: 先求表达式1的值,再求表达式2的值,依次类推,最后求表达式n的值。而表达式n的值即作为整个逗号表达式的值。 【实例321】逗号运算符的应用。 /*实例321*/ #include <stdio.h> int main() { int a=1,b=2; a=(a=a+b,a=a*b,++a); printf("a=%d\n",a); return 0; } 图324逗号运算符的应用 程序运行结果如图324所示。 说明: (1) 程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定是要求整个逗号表达式的值。例如: 假设变量a、b和c的值分别是1、2和3,执行语句“a=a*2, b=b*2, c=c*2;”的结果是分别给变量a、b和c重新赋值为2、4和6。其中的逗号表达式“a=a*2, b=b*2, c=c*2;”的值虽然求出是6,但语句执行完之后,这个值也被丢弃。 (2) 并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明语句(例如: int a,b,c;)中和函数参数表(例如: pow(20,3))中逗号只是用作各变量之间的间隔符。 3.5.4指针运算符 与指针相关的运算符总共有两个: 一个是取地址运算符(&),另一个是间接寻址运算符(*)(也称“指针运算符”)。它们都是单目运算符。 1. 取地址运算符 取地址运算符(&)是单目运算符,用它构成表达式的一般形式是: &变量名 利用取地址运算符&可以取得变量在内存中的地址。例如: int x,*px; x=8; px=&x;/*取变量x的地址给指针变量px*/ 【实例322】取地址运算符的应用。 /*实例322*/ #include <stdio.h> int main() { int x,*px; x=8; px=&x; printf("x=%d\n",x); printf("*px=%d\n",*px); printf("px=%p\n",px); /*输出变量x在内存中的地址*/ return 0; } 图325取地址运算符的应用 程序运行结果如图325所示。 说明: 格式符%p以十六进制形式输出变量的地址。 【实例323】用指针求两个整数中的较大数。 /*实例323*/ #include <stdio.h> int main() { int a, b,*pmax=0; 图326用指针求两个整数 中的较大数 printf("please input:\n"); scanf("%d,%d",&a,&b); pmax=a>b?&a:&b; printf("%d\n",*pmax); return 0; } 程序运行结果如图326所示。 说明: (1) 语句“pmax=a>b?&a:&b;”的作用是如果a>b,则把变量a的地址赋值给指针变量pmax,也就是让指针变量pmax指向变量a,否则让指针变量pmax指向变量b。 (2) 语句“printf("%d\n",*pmax);”中的*pmax的含义是指针变量pmax指向的变量的内容,也就是所指向的变量的值。 2. 指针运算符 使用指针运算符(*)可以间接访问指针变量所指向的变量,访问过程是先取得指针变量所存放的变量地址,根据变量地址找到需要访问的变量,其使用格式为: *指针变量名 【实例324】指针运算符的应用。 /*实例324*/ #include <stdio.h> int main() { int a,*pa; float b,*pb; pa=&a; pb=&b; a=3; /*或*pa=3;*/ b=4; /*或*pb=4;*/ printf("a=%d,*pa=%d\n",a,*pa); printf("b=%3.1f,*pb=%3.1f\n",b,*pb); } 程序运行结果如图327所示。 图327指针运算符的应用 说明: (1) 语句“pa=&a;”是让指针变量pa指向变量a。 (2) *pa是先通过指针变量pa所存放的变量a的地址,找到变量a对应的内存单元,然后对其进行取值或赋值等操作。 3.6数据类型转换 在C语言表达式中,允许对不同类型的数据进行某一操作或混合运算,当不同类型的数据进行操作时,应当首先将其转换成相同的数据类型,然后进行操作。数据类型转换有赋值类型转换、自动类型转换和强制类型转换三种形式。 3.6.1赋值类型转换 当赋值运算符两边的运算对象类型不同时,编译器自动将赋值运算符右侧表达式的类型转换为左侧变量的类型,具体转换规则如下。 1. 实型与整型 将实型数据(单、双精度)赋值给整型变量时,舍弃实型的小数部分,只保留整数部分。例如,执行语句“int a=5.9;”,变量a得到的是整数5。 将整型数据赋值给实型变量,数值大小不变,只是把形式改为实型形式,即小数点后带若干0,然后赋值。 2. 单、双精度实型 float型数据只是在尾部加0延长为double型数据参加运算,然后直接赋值。double型数据转换为float型时,通过截尾数来实现,截断前要进行四舍五入操作。 3. char型与int型 int型数据赋给char型变量时,只保留其最低8位,舍弃其他位。 例如,执行语句“char a=366; printf("%x, %x", 366, a);”,输出结果为16e,6e,只取低8位。 char型数据赋给int型变量时,通常int型变量得到的是其ASCII码值,而有一些编译程序在转换时,若char型数据的ASCII码值大于127,即作为负数处理。 例如,执行语句“int a=' x'; printf("%d",a);”,输出结果为120,即小写字母x的ASCII码值。 4. short型与int型 short型占2字节,int型数据赋给short型变量时,将低16位值赋给short型变量,而将高16 位截断舍弃。将short型数据赋给int型变量时,其外部值保持不变,而内部形式有所改变。 5. 无符号整数 将一个unsigned型数据赋给一个占据同样长度存储单元的整型变量时,原值照赋,内部的存储方式不变,但外部值却可能改变。将一个非unsigned整型数据赋给长度相同的unsigned型变量时,内部存储形式不变,但外部表示时总是无符号的。 需要说明的是,赋值类型转换是编译器自动进行的,所以也可归入自动类型转换。 3.6.2自动类型转换 自动类型转换在编译时由编译程序按照一定规则自动完成,不需要人为干预。C语言要求同一运算的运算对象的数据类型必须相同。因此,在表达式中如果有不同类型的数据参与同一运算,则编译器在编译时自动按照规定的规则将其转换为相同的数据类型。转换规则如图328所示。 图328常见类型转换规则 说明: (1) 图328中横向箭头表示必需的转换,如两个float型数据参加运算,虽然它们类型相同,但仍要先转成double型再进行运算,结果为double型。 (2) 纵向箭头表示当运算符两边的运算数为不同类型时的转换,如一个long 型数据与一个int型数据一起运算,先自动将int型数据转换为long型,然后两者进行运算,结果为long型。 (3) 如果一个运算符两边的运算对象类型不同,一般是低精度类型数据转换为高精度类型,然后进行运算,这样不会降低运算的精度。 (4) 当低精度类型的数据转换为高精度类型时,一般只是形式上有所改变,不影响数据内容。 【实例325】自动类型转换与赋值类型转换。 /*实例325*/ #include <stdio.h> int main() { int a=1; float b=2.9; double c=3.8; a=a+b+c; printf("a=%d\n",a); return 0; } 图329自动类型转换与 赋值类型转换 程序运行结果如图329所示。 说明: (1) 在Visual C++6.0中编译时,语句“float b=2.9;”会产生警告“'initializing' : truncation from 'const double ' to 'float'”。这是因为对于没有专门加后缀f或F的实型常量2.9,C语言是按double型数据来处理的。把double型的常量赋值给float型的变量时,编译器自动进行数据类型转换,为了避免转换过程中出现精度降低的情况,给出这个警告。 (2) 在Visual C++6.0中编译时,语句“a=a+b+c;”会产生警告“ '=' : conversion from 'double' to 'int', possible loss of data”。这是因为表达式a+b+c的值的类型为double型,而把一个double型的值赋给一个int型的变量a时,会丢弃小数部分。 (3) 语句“a=a+b+c;”的执行过程如下。 ① 取int型的变量a 的值1,把int型的1转换为double型的1.0。 ② 取float型的变量b的值2.9,把float型的2.9转换为double型的2.9。 ③ 求出转换后的两个double型的运算对象1.0与2.9的和,结果也为double型的3.9。 ④ double型的3.9和double型的变量c的值3.8相加,得出最后double型的结果7.7。 ⑤ 丢弃double型的结果7.7的小数部分,只把其整数部分7赋值给int的变量a。 点拨: 参与运算的某个变量如果要进行自动类型转换,这个转换只是在运算过程中发生,并不会改变该变量的类型。所以执行语句“a=a+b+c;”的过程中,变量a和b的类型仍然分别是int和float型。 3.6.3强制类型转换 强制类型转换的方法是在被转换对象(或表达式)前加类型标识符,其格式为: (类型标识符) 运算对象 (double) x (int) (m+n) (float) (5%2) 例如: 表达式 (int) 1.681的值为1。 要特别注意的是,强制类型转换的对象是表达式的值,而不是被转换变量本身,被转换变量的类型是不变的,例如: int x=8; double y=(double) x; 在执行上面两条语句后,变量x的类型仍然是整型。 【实例326】强制类型转换。 /*实例326*/ #include <stdio.h> int main() { float f=8.56; printf("(int)f=%d,f=%f\n",(int)f,f); return 0; } 图330强制类型转换 程序运行结果如图330所示。 说明: 从图330的运行结果可以看出,表达式(int)f的类型是整型,而f的类型仍然是实型。 强制类型转换的运算对象是紧随其后的运算对象,如果要对整个表达式的值进行类型转换,必须给表达式加圆括号。例如: 表达式(int)(0.681+0.432)的值为int型的1,表达式(int)(0.681)+0.432的值为double型的0.432。 3.7应 用 实 例 理解常量和变量的概念,掌握C语言的常用基本数据类型、运算符和输入输出函数的使用,本节通过一个简单实例,学习如何解决实际问题。 【实例327】分别输入某个学生的5门课程成绩,求该学生的平均成绩并输出结果。 分析: (1) 根据题目要求确定变量个数,需要5个变量保存课程成绩,1个变量保存平均成绩。 (2) 明确数据类型,每门课程的成绩为整型,可以选择int型,平均值为实型,可以选择float型。 (3) 命名变量,变量的名字属于C语言的标识符,为变量命名要按照标识符的命名规则,不能出现非法的变量名。 程序代码: /*实例327*/ #include <stdio.h> int main() { int score1,score2,score3,score4,score5; float ave; printf("请输入5门课的成绩,输入的时候用逗号分开:\n"); scanf("%d,%d,%d,%d,%d",&score1,&score2,&score3,&score4,&score5); ave=(score1+score2+score3+score4+score5)/5.0; printf("The average score is %3.1f \n",ave); return 0; } 程序运行结果如图331所示。 图331求5门课的平均分 思考: 编程实现求一个三角形的周长时,需要定义什么类型的变量?共需要定义几个变量?通过键盘输入什么?屏幕上输出什么? 本 章 小 结 本章主要介绍标识符、常量与变量、基本数据类型、常用的输入输出函数、运算符与表达式、数据类型转换等内容。这些都是C语言的编程基础,只有在学习过程中深刻理解这些概念,才能在以后的编程过程中正确地使用它们。 C语言中用标识符区分变量、符号常量、数组、函数等对象。标识符是由数字、字母和下画线3种字符组成的,且第一个字符必须为字母或下画线的字符序列。 C语言程序中的数据一般以常量或变量的形式出现。常量是指在程序执行过程中,其值始终保持不变的量。C语言中的常量有两种: 直接常量和符号常量。 在程序执行过程中,其值可以变化的量称为变量。变量的实质是内存中的一块存储单元,其大小由变量的类型决定。为便于引用,每个变量都有一个名字,通过变量名对其存储空间中的数据进行操作,变量名为合法的标识符。 数据类型决定数据在内存中所占的字节数和存储方式,进而决定数据的取值范围和精度,数据类型还决定了数据运算(操作)的规则,C语言常用的数据类型有整型、实型和字符型等。 C语言的输入输出函数有很多种,主要有四个最常用的输入输出函数: printf()、scanf()、putchar()和getchar()。特别强调,如果程序中需要使用这几个函数,必须包含头文件stdio.h。 C语言的表达式是由运算符将各种类型的变量、常量、函数等运算对象按一定的语法规则连接而成的式子。编译器能够按照运算符的运算规则完成相应的运算处理,求出运算结果,这个结果就是表达式的值。 C语言支持非常丰富的运算符: 按照操作对象的个数,运算符可以分为单目运算符、双目运算符和三目运算符; 按照功能可以分为算术运算符、关系运算符、逻辑运算符、条件运算符和逗号运算符等。 在C语言表达式中,允许对不同类型的数据进行某一操作或混合运算,当不同类型的数据进行操作时,应当首先将其转换成相同的数据类型,然后进行操作。数据类型转换有赋值类型转换、自动类型转换和强制类型转换三种形式。 习题 1. 填空题。 (1) C语言的标识符只能由字母、和组成。 (2) 在Visual C++6.0中,int型数据分配的字节数是,double型数据分配的字节数是,char型数据分配的字节数是。 (3) 定义符号常量NUM为100的语句为。 (4) 设int a=1; float f=3.1; 则表达式10+'a'+3.14*f 的值的类型是。 (5) 设m是三位的正整数,百位、十位、个位上的数字可分别表示为、和。 (6) 设今天是星期六,计算100天后是星期几的表达式是。 (7) 数学上的|x-y|(x和y是实数)的C语言表达式为(注: 使用函数fabs(x)可以求出实数的绝对值)。 (8) 算术表达式5ab4c的C语言表达式为。 (9) 算术表达式s(s-a)(s-b)(s-c)的C语言表达式为。(注: 使用sqrt(x)可以求出x的算术平方根。) (10) 设int b=12; 则执行语句“b-=b+=b*b;”后,b的值是。 (11) 转义字符中,表示换行,表示反斜杠,表示水平制表。 (12) 设int a=8,b=9; 则表达式a--的值为,表达式++b的值为。 (13) 字符串常量"Nanhu"在内存中占用的字节数为。 (14) 设int a; float y=3.14; 则执行语句“a=(int)y;”后,变量a的数据类型是,变量y的数据类型是。 (15) 设int a; 逗号表达式a=10,a++,a*a的值为。 2. 编程题。 (1) 编写程序,键盘输入某班级男生人数m和女生人数f,计算并输出该班男、女生的比例(百分比形式,保留两位小数)。 (2) 某个学生的学号为202201,性别为m,身高为185cm,体重为63.5kg。编写程序,将该学生的信息保存到各个变量中(变量名自拟),并输出该学生的信息。 (3) 编写程序,键盘输入一个十进制的三位正整数,输出其按权展开的形式。例如,输入678,输出678=600+70+8。 (4) 假设从aA开始,将英文字母按1~26编号。编写程序,输入一个序号,输出对应的小写和大写英文字母。例如,输入5,输出eE。 (5) 使用指针,编写程序,求两个整数的差。