第3章
数据类型与输入输出
学
习
目
标 了解C语言数据类型的分类。
掌握整型数据的内部表示、整型常量的表示、整型数据的输出和输入。
理解int、short和long 3种整型数据类型的区别。
掌握浮点常量的表示、浮点数的内部表示、浮点数的输出和输入。
理解float和double两种数据类型的区别。
掌握字符型常量和变量的表示和声明、字符型数据的内部表示、字符的输出和输入,掌握常见的字符处理技巧。
理解数组的内部表示,掌握数组元素的访问和初始化。
理解如何表示字符串常量,如何用数组表示来存储和处理字符串。
掌握字符串的输出和输入。
掌握如何向文本文件中输入和输出信息。
C语言是一种强类型语言,也就是说,所有的数据都是具有某种数据类型的,而且必须先声明后使用。C语言提供的数据类型非常丰富,C语言除了提供整型、字符型和浮点型等基本数据类型外,还提供数组、结构体、共用体和指针等数据类型。利用这些数据类型能方便地描述较复杂的数据对象。
C语言的数据类型分类如图3.1所示。
图3.1C语言的数据类型分类
本章将介绍基本类型,其他类型将在后面章节介绍。C程序设计:方法与实践第3章数据类型与输入输出3.1整型
整型是表示整数的数据类型。为了表示不同范围的整数,C语言提供了丰富的整型类型,它们有的可以表示高达19位数的整数,有的只能表示5位数的整数;有的可以表示有符号数,如-23、-98,有的只能表示无符号的数,如1、917。
C语言中的整型类型可以总结成表3.1。其中括号内的内容可以省略。表3.1C语言整型类型
有符号型(默认)说明无 符 号 型说明(signed) int基本整型unsigned int无符号基本整型(signed) short (int)短整型unsigned short (int)无符号短整型(signed) long (int)长整型unsigned long (int)无符号长整型(signed) long long (int)unsigned long long (int)C整型类型分为有符号(signed)和无符号(unsigned)两大类,分别表示有符号数和无符号数。对于有符号数,存储单元的*高位用来存储符号,0表示+,1表示-。对于无符号数,存储单元中全部二进制位都用来表示值,而不包括符号。无符号型变量只能存放不带符号的整数,如23、507等,而不能存放负数,如-23、-98。在默认情况下,整型是有符号的,如果要表示无符号整型,需要显式地加上unsigned来限定。
C标准没有具体规定以上各类数据所占内存字节数(也称为宽度),各种平台上有所不同,但是遵循以下原则: long型数据的字节数应不小于int型,short型不长于int型。例如,对于Win32平台,在Visual Studio编译系统中,各整型类型宽度和取值范围如表3.2所示。在本书中,假定整型数据的规格(宽度、取值范围)与表3.2保持一致。表3.2整型类型的规格
类型所占字节取 值 范 围int4-2 147 483 648~2 147 483 647,即-231~231-1short2-32 768~32 767,即-215~215-1long4-2 147 483 648~2 147 483 647,即-231~231-1long long8-9 223 372 036 854 775 808~9 223 372 036 854 775 807,
即-263~263-1unsigned int40~4 294 967 295,即0~232-1unsigned short20~65 535,即0~216-1unsigned long40~4 294 967 295,即0~232-1unsigned long long80~18 446 744 073 709 551 615,即0~264-1long long和unsigned long long是在C99标准中引入的,目前主流的编译器都支持,但是旧的编译器可能不支持,如Visual C++ 6.0,Turbo C 2.0/3.0。具体到某一个平台和编译系统,可以用sizeof()运算符来获取某一种数据类型或变量的宽度。其用法是在括号中写需要获取宽度的类型名或变量名。例如:
printf("%d", sizeof(int));/输出int型的宽度/
或者
int a;
printf("%d", sizeof(a));/输出int型变量a的宽度/
都可以输出int型的宽度。
3.1.1整数的内部表示
在计算机内部,数据都以二进制形式存在。那么整数在内存中是如何表示的呢?
无符号整数的表示比较简单,直接采用整数的二进制表示。有符号数的*高位用于表示符号位,用0表示+,1表示负号。但是,剩余的二进制位并不是二进制表示。从原理上来说,有符号整数在内部采用补码表示。对于一个数,计算机要使用一定的编码方式进行存储。原码、反码、补码是机器存储一个整数的编码方式。
① 原码。原码就是符号位加上整数的绝对值,即用*位表示符号,其余位表示值。原码是人脑*容易理解和计算的表示方式。
② 反码。反码的表示方法是: 正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
③ 补码。补码的表示方法是: 正数的补码就是其本身;负数的补码是在反码的基础上加1。
例如,如果用8位二进制表示整数,那么+1=(00000001)原码=(00000001)反码=(00000001)补码
-1=(10000001)原码=(11111110)反码=(11111111)补码图3.2给出了几个例子,其中左边是高位,右边是低位。图3.2整数的内部表示示例
为什么int型整数可以表示-231呢?这要从引入补码的原因说起。补码有一个独特的特征,即 a补码+b补码=(a+b)补码。以8位二进制为例,
1补码+(-1)补码= 0 0000001+1 1111111=0 0000000=0补码
8位二进制可以表示-128,可以认为这样产生的:
(-128)补码=(-1)补码+(-127)补码=1 1111111+1 0000001=1 0000000
3.1.2整型常量
整型常量即整型常数。C语言整型常数可用3种表示方式:
十进制整数。如124、234、-23、0等。
八进制整数。以0开头的数是八进制数。如0234表示八进制数(234)8,它等于十进制数156。
十六进制整数。以0x或0X开头的数是十六进制数。如0x234表示十六进制数(234)16,它等于十进制数564。注意,十六进制数只能由数字0~9和字母a~f(或A~F)组成。
当程序中出现整型常量时,如果它属于int类型的取值范围,那么编译器会自动将它当作int型整数来处理,否则作为更宽的数据类型来处理。为了显式地要求编译器把一个常量作为long型处理,可以在后面加一个字母L(或l),如30L、05647L、0x8abfl。为了指明是无符号常量,可以在后面加上字母U(或u),如30U、05647U、0x8abfu。还可以同时加上U和L表示无符号长整型,如30LU、05647LU、0x8abfLU。
3.1.3整数的输出
整数用printf进行格式化输出。printf函数的一般调用格式为: printf(格式化字符串, 输出参数列表);
其中,格式化字符串是用双引号括起来的字符串,它包括两种信息:
① 格式说明,由%和格式字符组成,如%d、%f等。它的作用是将输出的对象采用指定的格式输出。格式说明总是由%字符开始的。
② 普通字符,即需要原样输出的字符,它可以是一般字符,也可以是转义字符。
在用printf函数输出一个整数时,要考虑两个因素: 整数的类型(int、short、long还是long long)和以什么形式输出(什么进制,有符号还是无符号)。
可用的格式控制符如表3.3所示,它们分为两组,一组指示以什么形式输出,另一组告诉printf函数该数据是什么类型。这些格式控制符的组合如表3.4所示。表3.3printf格式字符
作用格式字符(%)说明输出形式d以带符号的十进制形式输出整数(正数不输出符号)o以无符号八进制形式输出整数(不输出前导符0)x以无符号十六进制形式输出整数(不输出前导符0x)u以无符号十进制形式输出整数数据类型h用于短整型l用于长整型ll用于long long类型表3.4printf格式字符的组合
数据类型输 出 形 式duox十进制形式
输出int无符号十进制形式
输出unsigned int无符号八进制形式
输出unsigned int无符号十六进制形式
输出unsigned inth十进制形式
输出short无符号十进制形式
输出unsigned short无符号八进制形式
输出unsigned short无符号十六进制形式
输出unsigned shortl十进制形式
输出long无符号十进制形式
输出unsigned long无符号八进制形式
输出unsigned long无符号十六进制形式
输出unsigned longll十进制形式
输出long long无符号十进制形式
输出unsigned long long无符号八进制形式
输出unsigned long long无符号十六进制形式
输出unsigned long long说明:
① 在选择格式控制符的时候,要根据原本的数据类型和期望的输出形式(十进制、八进制还是十六进制)来确定,尤其是数据类型说明符(h、l、ll)。
② 这些格式控制符只是指示printf函数如何解读、输出整数值,不会影响整数值原本的数据类型和存储形式。
③ 同一个值用不同的格式控制符输出的时候,结果不同,不是值发生了变化,而是对值的解读不同。
④ 用u、o和x格式控制符的时候,将数据解读为无符号数,本应用于输出无符号数,但是也可以用于有符号数,前提是这种解读不会造成曲解。C语言中任何数据都属于某种数据类型,而且任何一个值的数据类型编译系统都是知道的,那么printf函数的格式化字符串中为什么还要有h、l、ll这些与数据类型相关的格式控制字符呢?其实,这些格式控制字符只是告诉系统如何来看待后面的值,也就是说,同一个值可以“当作”不同类型的值来输出。例3.1整型数据的格式化输出。
1#include
2int main() {
3 int a_int=2, b_int=-2;
4 unsigned int c_uint=4294967293;
5 short d_short=2;
6 long e_long=4294967294;
8 printf("a_int: %d, %u, %o, %x\\n", a_int, a_int, a_int, a_int);
9/a_int: 2, 2, 2, 2/
10 printf("b_int: %d, %u, %o, %x\\n", b_int, b_int, b_int, b_int);
11/b_int: -2, 4294967294, 37777777776, fffffffe/
12 printf("a_int: %ld, %lu, %lo, %lx\\n", a_int, a_int, a_int, a_int);
13/a_int: 2, 2, 2, 2/
14 printf("c_uint: %d, %u\\n", c_uint, c_uint);
15/c_uint: -3, 4294967293/
16 printf("d_short: %d, %u\\n", d_short, d_short);
17/d_short: 2, 2/
18 printf("d_short: %hd, %hu\\n", d_short, d_short);
19/d_short: 2, 2/
20 printf("e_long: %hd, %hu\\n", e_long, e_long);
21/e_long: -2, 65534/
22 return 0;
23}
为了方便对照,每一个printf语句的输出结果显示在下一行的注释中。
对比变量的原始值,可以分析如下:
第8行变量a_int用%u、%o和%x 3种格式符输出的时候没有错误,虽然这3种格式本用来输出无符号数。这是因为a_int为正数,以无符号数来解读其内部表示的时候值不变。
第10行变量b_int用%u、%o和%x 3种格式符输出的时候出现错误,因为b_int为有符号数,以无符号数来解读其内部表示的时候得到的是不同的值。
第12行将变量a_int以长整型的形式输出,结果没有问题,因为在Win32平台上int和long两个数据类型是完全相同的。
第14行变量c_uint用%d格式符输出的时候出现问题,因为%d格式符是按有符号数输出,而c_uint的*高为1,被解读为一个负数。
第16行变量d_short用%d和%u输出的时候没有错误。这是因为short型数据会被自动提升为int型。实际上,对于short型数据,既可以用%d、%u也可以用%hd和%hu来输出。
第20行变量e_long用%hd和%hu输出的时候存在问题,因为它被当作short类型(2B)来解读,也就是说,只会考察低位的两个字节,这两个字节的内容按照有符号数来解读是-2,按照无符号数来解读是65534。
上面所用的格式符都是按数据的实际长度输出,为了输出排列的需要,有时要指定每一个数据的输出宽度和对齐方式。指定输出宽度和对齐方式需用到两个附加格式符m和-。附加格式符放在%和格式符之间使用。
m为一正整数,用来指定输出宽度,如果数据的实际宽度比指定输出宽度小,则补上空格后按指定宽度输出;如果数据的实际宽度比指定输出宽度大,则按实际宽度输出。
附加格式符“-”用来说明采用左对齐方式,没有“-”时默认是右对齐方式。
例3.2整型数据按照指定宽度和对齐方式输出。
1#include
2int main(){
3int a=4, b=45, c=456, d=4567;
4unsigned u=456;
5long l=456;
6printf("a=%3d, b=%3d, c=%3d, d=%3d\\n", a, b, c, d);
7/a=4, b=45, c=456, d=4567/
8printf("a=%-3d, b=%-3d, c=%-3d, d=%-3d\\n", a, b, c, d);
9/a=4, b=45 , c=456, d=4567/
10printf("u=%-5u, u=%5u, l=%-5ld, l=%5ld\\n", u, u, l, l);
11/u=456, u=456, l=456, l=456/
12return 0;
13}
其中,每一个printf语句的输出结果显示在下一行的注释中。
3.1.4整数的输入
可以用scanf函数来输入数据给整型变量。回忆一下,scanf函数调用的一般形式为: scanf(格式化字符串, 地址列表);
其中,格式化字符串的含义同printf函数类似,地址列表是由若干个地址组成的列表,可以是变量的地址,或字符串的首地址。
在介绍更多的例子之前,先介绍scanf函数的原理。
scanf函数以及其他的标准输入函数并不是直接从输入设备(如键盘)读取数据,而是从内存中的输入缓冲区中读取数据。如果scanf函数要读取数据,而内存缓冲区为空,scanf函数就会被阻塞,等待用户输入。用户输入数据并按回车键后,所输入的内容才送到内存输入缓冲区中。
scanf函数的*个参数格式化字符串指示了如何从输入缓冲区中读取数据。格式化字符串中可以包含以下内容:
格式化字符,由%引导的字符。格式化字符导致scanf读入若干字符并将其转换为某种类型的数据,该数据会被存入到地址列表中的某一地址。
除%外的非空白字符。一个非空白字符导致scanf读入一个相同的字符但并不存储该字符。如果scanf读不到一个相同的字符,scanf会中断。
空白字符,包括空格、跳格('\\t')和换行符('\\n')。一个空白字符导致scanf读入后面连续的空白符,直到下一个非空白符。
scanf使用的具体格式符与printf中类似,如表3.5所示。表3.5scanf整数输入格式符
作用格式字符(%)说明输入形式d期望读入一个十进制数o期望读入一个八进制数x期望读入一个十六进制数u期望读入一个无符号数数据类型h期望读入一个 short型数据l期望读入一个long型数据ll期望读入一个long long型数据附加格式符m(正整数)指定输入数据所占的宽度(列数)表示本输入项在读入后不赋给相应的变量下面是scanf在使用时需要注意的几个地方。
(1) 输入数据的格式注意要与scanf格式化字符串的格式一致。例如:
……