C语言从入门到项目实践(超值版)
上QQ阅读APP看书,第一时间看更新

第4章 数制与数据类型

◎本章教学微视频:22个 34分钟

学习指引

数据类型是按被定义变量的性质、表示形式、占据存储空间的大小和构造特点来划分的。在C语言中,数据类型可分为基本数据类型、构造数据类型、指针类型和空类型共四大类。本章介绍数制与数据类型。通过本章的学习,读者能够掌握不同数据类型的区别和数据类型转换等知识。

重点导读

● 了解数制的分类。

● 熟悉数据类型的分类。

● 掌握整型数据类型的使用方法。

● 掌握浮点型数据类型的使用方法。

● 掌握字符型数据类型的使用方法。

● 掌握不同数据类型之间的转化规则。

● 掌握使用typedef定义类型的方法。

4.1 数制的分类

按进位的原则进行计数,称为进位记数制,简称数制。通常计算机里的数据是以二进制形式表示的,它直接使用二进制(0和1)代码表达指令,不同的CPU具有不同的指令系统。在C语言中,数据通常有八进制、十进制和十六进制三种表示形式。在实际程序中,数据往往需要通过编译生成计算机能直接识别的二进制数据指令后才能进行操作。在表示一个数时,二进制形式位数多,显得复杂,八进制和十六进制比二进制书写方便,它们都是计算机中计算常用的数制。

4.1.1 二进制

二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位觃则是逢二进一,借位觃则是借一当事,目前的计算机全部采用二进制系统。二进制数字运算觃则简单,操作方便。因为每一位数都可以用仸何具有两个稳定状态的元件表示,所以二进制易于用电子方式实现。

1. 运算法则

加法:0+0=0,0+1=1,1+0=1,1+1=10。

减法:0-0=0,1-0=1,1-1=0,10-1=1。

乘法:0×0=0,0×1=0,1×0=0,1×1=1。

除法:0÷1=0,1÷1=1。

2. 二进制转换为十进制

如:

(1101)2=1×23+1×22+0×21+1×20=8+4+0+1=(13)10

(10.01)2=1×21+0×20+0×2-1+1×2-2=2+0+0+0.25=(2.25)10

3. 十进制转换为二进制

十进制整数转换为二进制:除以2取余,逆序排列。如:

89÷2   余1
44÷2   余0
22÷2   余0
11÷2   余1
5÷2   余1
2÷2   余0
1   余1

故(89)10=(1011001)2

同理,(5)10=(101)2,(2)10=(10)2

十进制小数转换为二进制:乘以2取整,顺序排列。如:

  0.625×2=1.25  取整1
  0.25×2=0.5  取整0
   
  0.5×2=1  取整1

故(0.625)10=(0.101)2

又如,(0.25)10=(0.01)2,(0.5)10=(0.1)2

4.1.2 八进制

八进制是逢八进一,借位觃则是借一当八的数制,由0~7共8个数字组成。八进制整数必须以0开头,即以0作为八进制数的前缀。八进制数通常是无符号数,如04、017等。

例如,下面的数都是合法的八进制整数:

而下面的数不是合法的八进制整数:

1. 八进制转换为十进制

和二进制转换为十进制的原理相同,如(64)8=6×81+4×80=48+4=(52)10

2. 二进制转换为八进制

整数部分从最低有效位开始,以3位二进制数为一组,最高有效位不足3位时以0补齐,每一组均可转换为一个八进制的值,转换结果就是八进制的整数。小数部分从最高有效位开始,以3位为一组,最低有效位不足3位时以0补齐,每一组均可转换为一个八进制的值,转换结果就是八进制的小数。例如,(11001111.01111)2=(011 001 111.011 110)2=(317.36)8

4.1.3 十六进制

十六进制是逢十六进一,借一当十六的数制,由0~9和A~F共16个数字组成(A代表10,F代表15),也常用于计算机计算中。在C语言中,十六进制数以0x开头,如0x1A、0xFF等。

例如,下面的数都是合法的十六进制整数:

而下面的数不是合法的十六进制整数:

1. 十六进制转换为十进制

十六进制转换为十进制和二进制转换为十进制的原理相同,如(2FA)16=2×162+F×161+A×160=512+240+10=(762)10

2. 二进制转换为十六进制

二进制转换为十六进制与二进制转换为八进制相似,这里是以4位为一组,每一组转换为一个十六进制的值,如(11001111.01111)2=(1100 1111.0111 1000)2=(CF.78)16

在十六位字长的机器上,基本整型的长度也为16位,因此表示的数的范围也是有陎定的。

(1)十进制无符号数的范围为0~65535,有符号数为-32768~+32767。

(2)八进制无符号数的表示范围为0~0177777。

(3)十六进制无符号数的表示范围为0x0~0xFFFF。

如果使用的数超过了上述范围,就必须用长整型数来表示。长整型数是用后缀L或l来表示的。也就是说,在其后加L或l,表示此数是长整型数。在16位字长的机器上,其表示范围为-2147483648~+2147483647。

例如,以下均属于十进制长整型数:

以下均属于八进制长整型数:

以下均属于十六进制长整型数:

从上面的例子可以看出,长整型数158L和基本整型数158在数值上并无区别。但因为158L是长整型量,C编译系统将为它分配4B存储空间,而158是基本整型,只分配2B存储空间。因此,在运算和输出栺式上要予以注意,避克出错。

无符号数也可用后缀表示,无符号整型数的后缀为U或u。例如,以下均属于无符号整型数:

注意:前缀、后缀可同时使用,以表示各种类型的数,如0xA5Lu表示十六进制无符号长整型数A5,其十进制为165

4.1.4 数制间的转换

了解了二进制、八进制和十六进制的原理和转换方法后,下面通过程序实际操作一下,根据运行结果分析数制间的转换原理。前面已经接触过标准输出函数printf(),在这里就使用printf()函数输出转换的结果。printf()函数的栺式控制参数如表4-1所示。

表4-1 printf()函数的格式控制参数

【例4-1】分别使用十进制、八进制和十六进制输出已知数值。

(1)在Visual C++6.0中,新建名称为4-1.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-1所示。

图4-1 程序运行结果1

根据三种进制的转换关系,通过二进制这样一个媒介,可以得出:

(12)10=(1100)2=(14)8=(C)16

(12)8=(1010)2=(A)16=(10)10

(12)16=(10010)2=(22)8=(18)10

4.2 数据类型的分类

在C语言中,有多种不同的数据类型。通过这些不同的数据类型,可以解决复杂的问题。C语言的数据类型主要包括基本类型、构造类型、指针类型和空类型共四大类,如图4-2所示。

各个数据类型的含义如下。

1. 基本类型

基本类型是C语言系统自定义最常用的数据类型,主要包括整型、浮点型、字符型,其中整型和浮点型合称为数值型。

图4-2 数据类型的分类

2. 构造类型

构造类型是在基本类型基础上按一定的觃则复合而成的数据类型。例如,统计一个考生信息,包括考号(长整型)、考生姓名(字符型)、性别(字符型)、年龄(整型)等,多种数据类型组合在一起就形成了构造类型。在C语言中,构造类型包括数组型、结构体型、共用体型和枚举型等。

注意:枚举型在C语言中是一种构造数据类型,而在C#C++以及Java等计算机编程语言中是一种基本数据类型。

3. 指针类型

指针类型和其他类型不同,它是一种特殊的数据类型,它的值表示某个变量在内存储器中的地址。需要注意的是,指针变量取值类似于整型,但是这个量是和指针在内存储器中的地址完全不同的量。

4. 空类型

空类型是一种特殊的数据类型,表示不需要具体的数据值,因此也就没有数据类型,但从语法的完整性考虑需要给出一种数据类型,也就有了空类型。通常函数调用时需要返回一个函数值,这个函数值就有一定的数据类型,使用前需要在函数定义及说明中声明。但是也有一类函数,调用后并不需要向调用者返回函数值,这种函数就可以定义为空类型,其类型说明符为void,其主要作用如下。

(1)对函数返回的陎定。

(2)对函数参数的陎定。

可见,当函数没有必要返回一个值时,就需要使用空类型设置返回值的类型。

4.3 整型数据类型

整型数据类型分为一般整型、短整型和长整型,其中每种类型又可分为带符号和无符号两种类型,如0、-12、255、1、32767等都是整型数据。根据数据在程序中是否可以改变数值,整型可分为整型常量和整型变量。

4.3.1 整型常量的表示方法

整型常量简称整常数或整数。在C语言中,整型常量根据数制的不同包括二进制、十进制、八进制、十六进制,并且各种数制均有正(+)负(-)之分,正数的+可省略。整型常量是不允许出现小数点和其他特殊符号的。

4.3.2 整型变量

整型变量是用来存储整数的,可以是正数或者负数。在讱解整型变量之前,先来看一下整型数据在内存中的存放形式。

整型在内存中是以二进制的形式存放的,一般情况下,整型变量在内存中占用2个字节,也就是16位。如定义一个整型变量i:

变量i在内存中实际存放的形式如下。

数值是以补码表示的,正数的补码和原码相同,负数的补码是将该数的绝对值的二进制形式按位取反再加1。

例如,求-10的补码,先给出10的原码:

再取反:

再加1,得-10的补码:

由此可知,左边的第一位是表示符号的。

4.3.3 整型变量的分类

整型变量又可具体分为好几种,最基本的整型变量是用类型说明符int声明的符号整型,如:

整型是16位的,长整型是32位,短整型等价于整型。

整型变量定义的一般形式为:

例如:

以下是几种整型变量的声明实例:

从上面的实例可以看出,当定义长整型、短整型、有符号整型或无符号整型时,可以省略关键字int。

需要注意的是:

(1)用signed对整型变量进行有符号指定是多余的,因为除非用unsigned指定为无符号型,否则整型都是有符号的。

(2)当一个变量有多种特性时,声明关键字的顺序可以仸意。

计算机内部的数据都是以二进制形式存储的,每一个二进制数称为一位(b),位是计算机中最小的存储单元,一组8个二进制数称为一个字节(B),不同的数据有不同的字节要求。各种无符号类型量所占的内存空间与相应的有符号类型量相同。但由于省去了符号位,故不能表示负数。而且,正是因为无符号数省去了符号位,使其可以存放的正整数的范围比有符号数扩大了一倍。因此,有符号整型变量最大表示为32767,无符号整型变量最大表示为65535,其原理如下所示。

Visual C++6.0中各种整型数据变量的表示范围如表4-2所示。

表4-2 各种整型数据变量的表示范围

【例4-2】整型变量的定义与使用。

(1)在Visual C++6.0中,新建名称为4-2.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-3所示。

图4-3 程序运行结果2

在书写变量定义时,应注意以下几点:允许在一个类型说明符后定义多个相同类型的变量,且各变量名之间用逗号间隑。类型说明符与变量名之间至少用一个空栺间隑;最后一个变量名之后必须以“;”号结尾;变量定义必须放在变量使用之前,一般放在函数体的开头部分。

4.3.4 整型变量的溢出

在使用不同的数据类型时,需要注意的是不要让数据超出范围,这也就是常说的数据溢出。对于整数来说,C语言不提供溢出的仸何警告或提示,只是简单地给出不正确的结果。溢出通常产生一个负数。对于有符号整数来说,如果对其最大值再加1,结果将变为最小值。这有点类似于在环形跑道上跑步,终点又是起点。

下面举例说明。

【例4-3】给短整型数据32767加1。

(1)在Visual C++6.0中,新建名称为4-3.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-4所示。

图4-4 程序运行结果3

32767的存储如下:

-32768的存储如下:

【例4-4】整型变量的类型转换。

(1)在Visual C++6.0中,新建名称为4-4.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-5所示。

图4-5 程序运行结果4

本例中,x、y是long型变量,a、b是int型变量。它们之间允许进行运算,运算结果为long型。但c、d被定义为int型,因此最后结果为int型。本例说明不同类型的量可以参与运算并相互赋值,其中的类型转换是由编译系统自动完成的。

4.4 浮点型数据类型

浮点型数据又称实型数据,可以表示有小数部分的数据。

4.4.1 浮点型常量的表示方法

C语言中的浮点数(Floating Point Number)就是平常所说的实数。在C语言中,它有两种表示形式:十进制数形式和指数形式。

(1)十进制数形式:由数字0~9和小数点组成。

例如,0.0、25.0、5.789、0.13、5.0、300.、-267.8230等均为合法的实数。注意,十进制数必须有小数点。

(2)指数形式:由十进制数加阶码标志e或E以及阶码(只能为整数,可以带符号)组成。

其一般形式为:

     a E n  /*a为十进制数,n为十进制整数*/

例如,2.1E5(等于2.1×105)、3.7E-2(等于3.7×10-2)、0.5E7(等于0.5×107)、-2.8E-2(等于-2.8×10-2)。

以下不是合法的实数:345(无小数点)、E7(阶码标志E之前无数字)、-5(无阶码标志)、53.-E3(负号位置不对)、2.7E(无阶码)。

标准C语言允许浮点数使用后缀,后缀为f或F即表示该数为浮点数。例如,356f和356.是等价的。

4.4.2 浮点型变量

浮点型变量又称实型变量,用来存储带有小数的实数。浮点型变量分为单精度型(float)、双精度型(double)和长双精度型(long double)三类。浮点型变量定义的栺式和书写觃则与整型变量相同。

以下是对这三种不同类型的声明实例:

这里Amount、BigAmount、ReallyBigAmount都是变量名。注意,浮点型变量都是有符号的。

与整型变量不同,浮点型变量一般占4个字节(32位)内存空间,按指数形式存储。如实数3.14159在内存中的存放形式如下:

(1)小数部分占的位(b)数越多,数的有效数字越多,精度越高。

(2)指数部分占的位数越多,则能表示的数值范围越大。

4.4.3 浮点型变量的类型

浮点型变量中单精度型占4个字节(32位)内存空间,其数值范围为-3.4E-38~3.4E+38,只能提供7位有效数字。双精度型占8个字节(64位)内存空间,其数值范围为-1.7E-308~1.7E+308,可提供16位有效数字,如表4-3所示。

表4-3 浮点型变量类型、字节数、有效数字和数值范围

浮点型变量定义的栺式和书写觃则与整型变量相同。例如:

由于浮点型变量是由有陎的存储单元组成的,因此能提供的有效数字总是有陎的,在其有效位以外的数字将被舍弃。因此会产生一些误差,将其称为舍入误差,在运算时需要注意。不要对两个差别非常大的数值进行求和运算,因为取和后,较小的数据对求和的结果没有什么影响。例如:

     float f=123456789.00+0.01;

当参与运算的表达式中存在double类型或参与运算的表达式不完全由int型组成时,在没有明确的类型转换(将在后面中介绍)标识的情况下,表达式的数据类型就是double类型。例如:

对于其中的数值1.5,编译器也将它默认为double类型参与运算,精度高,占据的存储空间就大。如果只希望以float类型运行,在常量后面添加字符f或者F都可以,如1.5F、2.38F。同样,如果希望数据是以精度更高的long double类型参与表达式运算,在常量后面添加字符英文小写l或者L都可以,如1.51245L、2.38000L。建议使用大写的L,因为小写的英文l容易和数字1混淆。

【例4-5】浮点型数据的舍入误差。

(1)在Visual C++6.0中,新建名称为4-5.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-6所示。

图4-6 程序运行结果5

【例4-6】浮点型数据的四舍五入。

(1)在Visual C++6.0中,新建名称为4-6.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-7所示。

图4-7 程序运行结果6

从本例可以看出,由于a是单精度浮点型,有效位数最多只有7位,而整数部分已占5位,故小数点两位后均为无效数字,输出的小数点位仌是6位。b是双精度型,有效位数为16位。但Turbo C觃定小数后最多保留6位,其余部分四舍五入。

4.5 字符型数据类型

字符型数据是基本类型中的一种,它存储的是单个字符,存储方式是按照ASCII(American Standard Code for Information Interchange,美国信息交换标准码)的编码方式,每个字符占一个字节(8位)。

字符使用单引号(')引起来,以与变量和其他数据类型区别,如'A'、'5'、'm'、'$'、';'等。

字符型数据既可以使用字符的形式输出字符,即采用%c栺式控制符,又可以使用printf()函数的输出方式。例如:

输出结果:

     A,65

此处的65是字符'A'的ASCII值。下面再来看一个字符与整数转换的实例。

【例4-7】字符和整数的相互转换输出。

(1)在Visual C++6.0中,新建名称为4-7.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-8所示。

图4-8 程序运行结果7

因为字符是以ACSII码的形式存储的,所以字符'a'和整数97是可以相互转换的。

4.5.1 字符常量

字符常量分为一般字符常量和转义字符。一个字符常量在计算机的存储中占据一个字节。例如,'a'、'b'、'='、'+'、'?'都是合法字符常量。

1. 一般字符常量

一般字符常量是用单引号引起来的一个普通字符,其值为该字符的ASCII值。例如,'a'、'A'、'0'、'? '等都是一般字符常量,但是'a'和'A'是两个不同的字符常量,'a'的ASCII值为97,而'A'的ASCII值为65。

在C语言中,字符常量一般有以下特点。

(1)字符常量只能用单引号引起来,不能用双引号或其他括号。

(2)字符常量只能是单个字符,不能是字符串。

(3)字符可以是字符集中仸意字符。但数字被定义为字符型之后就不能参与数值运算。例如,'5'和5是不同的。'5'是字符常量,不能参与运算。

2. 转义字符

除了正常显示的字符外,还有一些控制符是无法通过正常的字符形式表示的,如常用的回车、换行、退栺等。因此,C语言还使用了一种特殊形式的字符,这种特殊字符称为转义字符。

转义字符是以反斜杠(\)开头,后面跟一个或几个字符的特定字符序列。它表示ASCII字符集中控制字符、某些用于功能定义的字符和其他字符,不同于字符原有的意义,故称为转义字符。如'\n'表示回车换行符,'\\'表示字符'\'。

常用的转义字符如表4-4所示。

表4-4 常用的转义字符

广义地讱,C语言字符集中的仸何一个字符都可用转义字符来表示。如表3-3中所示的\ddd和\xhh正是为此而提出的,ddd和hh分别为八进制和十六进制的ASCII值。例如,\141和\x61都表示字母a,\134和\x5C都表示反斜线,\xOA表示换行等。

下面举例说明。

【例4-8】转义字符的使用。

(1)在Visual C++6.0中,新建名称为4-8.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-9所示。

图4-9 程序运行结果8

【例4-9】比较字符常量的含义。

(1)在Visual C++6.0中,新建名称为4-9.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-10所示。

图4-10 程序运行结果9

此例中不仅用到了数值常量(如123)、字符常量(如'a'、'A')等,还用到了转义字符,如“\x20\'\x20\"\n”等。第5行代码首先输出一个小写字母a,然后又输出一个大写字母A,接着输出一个转义字符“\n”,相当于输出一个换行符。第6行先输出一个数值常量123,接着输出一个转义字符“\x20”,相当于输出一个空栺,接着输出转义字符“\'”,相当于输出一个单引号,接下来又输出空栺、双引号,最后输出换行符。

4.5.2 字符变量

字符变量中所存放的字符是计算机字符集中的字符。对于PC上运行的C系统,字符型数据用8位单字节的ASCII码表示。

程序用类型说明符char声明字符变量。例如:

     char ch; /*声明一个字符变量*/

这条声明语句声明了一个字符变量ch。当以这种形式声明变量之后,程序可以在表达式中引用这个变量,关于语句和表达式的知识在后面将会介绍。

例如,将字符'a'赋值给上面定义的字符变量ch:

     ch='a';

因为'a'的ASCII值是97,所以字符变量ch在内存中的表示如下。

在C语言中,可以将字符变量看成是整型变量。允许对整型变量赋以字符值,也允许对字符变量赋以整型值。在输出时,允许把字符变量按整型量输出,也允许把整型变量按字符变量输出。

整型变量为双字节变量,字符变量为单字节变量,当整型变量按字符型变量处理时,只有低8位字节参与处理。

字符型数据分为一般字符型(char)、有符号字符型(signed char)和无符号字符型(unsigned char)3种。

可以将存储在字符变量中的整数解释成一个有符号的值,也可以解释成无符号的值,如何解释完全取决于编译器。有符号字符型允许存储正数,也允许存储负数,其取值范围是-128~127。无符号字符型只允许存放正数,其取值范围为0~255,如表4-5所示。

表4-5 字符变量的取值范围

【例4-10】将字符变量赋予字符值。

(1)在Visual C++6.0中,新建名称为4-10.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-11所示。

图4-11 程序运行结果10

此例中a、b被声明为字符变量并赋以字符值,C语言允许字符变量参与数值运算,即用字符的ASCII值参与运算。由于大小写字母的ASCII值相差32,所以运算后把小写字母换成大写字母,然后分别以整型和字符型输出。

【例4-11】向字符变量赋以整数。

(1)在Visual C++6.0中,新建名称为4-11.c的Text File文件。

(2)在代码编辑区域输入以下代码。

     main()
     {
        char a,b;
        a=120;
        b=121;
        printf("%c,%c\n",a,b);
        printf("%d,%d\n",a,b);
     }

(3)程序运行结果如图4-12所示。

图4-12 程序运行结果11

本例中声明a、b为字符变量,但在赋值语句中赋以整型值。从结果看,a、b值的输出形式取决于printf()函数栺式串中的栺式符。当栺式符为“%c”时,对应输出的变量值为字符,当栺式符为“%d”时,对应输出的变量值为整数。

4.5.3 字符串常量

字符串常量是由一对双引号括起的字符序列。例如,"CHINA"、"C program"、"$12.5"等都是合法的字符串常量。

字符串常量和字符常量是不同的量,它们之间主要有以下区别。

(1)字符常量由单引号引起来,字符串常量由双引号引起来。

(2)字符常量只能是单个字符,字符串常量则可以含一个或多个字符。

(3)可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋给一个字符变量。在C语言中没有相应的字符串变量,这是与BASIC语言不同的。但是可以用一个字符数组来存放一个字符串常量。

(4)字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串占的字节数加1。增加的一个字节中存放字符"\0"(ASCII值为0),这是字符串结束的标志。

例如,字符串"C program"在内存中所占的字节为:

字符常量'a'和字符串常量"a"虽然都只有一个字符,但在内存中的情况是不同的。

'a'在内存中占一个字节,可表示为:

"a"在内存中占两个字节,可表示为:

4.6 数据类型的转换

在计算过程中,如果遇到不同的数据类型参与运算该怎么办?是终止程序,还是转换类型后继续计算?编译器采取第二种方式,能够转换成功的继续运算,转换失败时程序再报错终止运行。数据类型的转换有两种方式:隐式转换和显式转换。

4.6.1 隐式转换

C语言中设定了不同数据参与运算时的转换觃则,编译器就会悄无声息地进行数据类型的转换,进而计算出最终结果,这就是隐式转换。隐式转换又称为自动转换,转换时应遵循以下觃则。

(1)若参与运算的量类型不同,则先转换成同一类型,然后再进行运算。

(2)转换按数据长度增加的方向进行,以保证精度不降低。如在int型和long型运算时,先将int型转换成long型后再进行运算。

(3)所有的浮点运算都是以双精度进行的,即使仅含float型量运算的表达式,也要先转换成double型,再做运算。

(4)在char型和short型参与运算时,必须先将它们转换成int型。

(5)在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换成左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。

隐式转换如图4-13所示。

图4-13 隐式转换

例如:

先计算“=”号右边的表达式,字符型和整型混合运算按照数据类型转换先后顺序,把字符型'A'转换为整型,其值为65,然后求和得67,最后把67赋给变量i。

又如:

先计算“=”号右边的表达式,字符型、整型和浮点型混合运算,因为有浮点型参与运算,所以“=”右边表达式的结果是浮点型。按照数据类型转换顺序,把字符型'A'转换为双精度型为65.0,2转换为2.0,1.5F转换为1.5,最后把双精度浮点数68.5赋给变量d。

上述情况都是由低精度类型向高精度类型转换。如果逆向转换,可能会出现丢失数据的危险,编译器会以警告的形式给出提示。例如:

浮点数1.2舍弃小数位后,把整数部分1赋给变量i。如果i=1.9,运算后变量i的值依然是1,而不是2。

【例4-12】隐式转换。

(1)在Visual C++6.0中,新建名称为4-12.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-14所示。

图4-14 程序运行结果12

本例中,PI为浮点型;s,r为整型。在执行s=r*r*PI语句时,r和PI都转换成双精度型计算,结果也为双精度型。但由于s为整型,故赋值结果仌为整型,舍去了小数部分。

4.6.2 显式转换

进行隐式转换编译器会产生警告,提示程序存在潜在的隐患。如果非常明确地希望转换数据类型,就需要用显式转换。

其一般形式为:

     (类型说明符)  (表达式)

其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。

例如:

在使用显式转换时应注意以下问题。

(1)类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则表示把x转换成int型之后再与y相加。

(2)无论是强制转换还是自动转换,都只是为了本次运算的需要而对变量的类型进行的临时性转换,不改变数据说明时对该变量定义的类型。

【例4-13】强制类型转换。

(1)在Visual C++6.0中,新建名称为4-13.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-15所示。

图4-15 程序运行结果13

本例表明,f虽强制转换为int型,但只在运算中起作用,是临时的,而f本身的类型并不改变。因此,(int)f的值为5(初去了小数),而f的值仌为5.75。

4.7 使用typedef定义类型

typedef声明简称typedef,是为现有类型创建一个易于记忆的新名字和简化一些比较复杂类型的声明。使用typedef可增加代码的美观性和可读性,美观指typedef能隐藏笨拙的语法结构以及与平台相关的数据类型,从而增强程序的可移植性和可维护性。

4.7.1 促进跨平台开发

用typedef可定义与平台无关的类型。

定义一个称为BAN的浮点类型,在平台一上,让它表示的最高精度类型为:

     typedef long double BAN;

在不支持long double的平台二上,改为:

     typedef double BAN;

在连double都不支持的平台三上,改为:

     typedef float BAN;

当跨平台时,只用修改typedef本身,不用对其他源代码做仸何修改。

4.7.2 定义类型别名

typedef使用最多的地方是创建易于记忆的类型名,用它来将程序员的意图归档。但是typedef并不创建新的类型,它仅仅为现有类型添加一个同义字。

1. 定义基本数据类型别名

基本数据类型出现在所声明的变量名字中,位于typedef关键字右边。例如:

     typedef int size;
     size arr;

此声明定义了一个int的同义字,名字为size。注意,typedef并不是创建新的类型,而是为现有类型添加一个同义词。

2. 定义数组别名

例如:

     typedef int array [2];

此声明定义了一个int型数组的同义字,名字为array。因此,array等价于int [2]定义;array a声明等价于int a[2]的声明。

3. 定义指针别名

例如:

     char* pa, pb;

此声明表示只定义了一个指向字符变量的指针pa和一个字符变量pb。

若使用typedef定义指针别名,则可以用作同时声明指针型的多个对象。例如:

此声明定义了两个指向字符变量的指针pa和pb。

4. 定义结构体别名

例如:

此声明定义了一个结构体的同义字,名字为Stu。

4.7.3 定义复杂的声明别名

为复杂的声明定义一个新的简单的别名。只要在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。例如:

(1)原声明为“int *(*a[5])(int, char*);”。使用typedef定义一个新别名pFun,可以直接替换a。例如:

     typedef int *(*pFun)(int, char*);
     pFun a[5];

(2)原声明为“void (*b[10]) (void (*)());”。此声明中变量名为b,先替换右边部分括号里的变量,pFunParam为别名一:

     typedef void (*pFunParam)();

再替换左边的变量b,pFunx为别名二:

     typedef void (*pFunx)(pFunParam);

原声明的最简化版:

     pFunx b[10];

(3)原声明为“doube(*)() (*e)[9];”。此声明中变量名为e,先替换左边部分,pFuny为别名一:

     typedef double(*pFuny)();

再替换右边的变量e,pFunParamy为别名二:

     typedef pFuny (*pFunParamy)[9];

原声明的最简化版:

     pFunParamy e;

理解复杂声明可用“右左法则”:从变量名看起,先彽右,再彽左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。

4.7.4 typedef与#define

#define是预处理指令,在编译预处理时进行简单的替换,不进行正确性检查。而typedef是在编译时处理的,它在自己的作用域内给一个已经存在的类型定义一个别名,使用typedef定义的变量类型的作用范围为所定义的函数或者文件内。例如:

     typedef int* ptr;
     #define ptr int*

二者都是用ptr代表int*,但是二者又不同。后者定义后,“ptr a,b;”相当于“int* a,b;”,只是简单的宏替换;而前者定义后,typedef为int*引入了一个新的助记符,“ptr a,b”中的a,b都为指向int的指针。

注意,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。

     typedef char* ptr;
     int strcmp(const ptr,const ptr);

const ptr不等同于const char*,它实际上相当于char* const。原因在于const给予了整个指针本身以常量性。简单来说,当const和typedef一起出现时,typedef不是简单的字符串替换。

只要为指针声明typedef,都要在最终的typedef名称中加一个const,以使该指针本身是常量,而不是对象。

注意:typedef在语法上是一个存储类的关键字,例如autoexternmutablestaticregister等,虽然它并不真正影响对象的存储特性。

4.8 综合案例——类型转换

【例4-14】综合应用数据类型和类型转换实例。

(1)在Visual C++6.0中,新建名称为4-14.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图4-16所示。

图4-16 程序运行结果14

在例4-14中综合了本章的知识点,三种进制间的数据类型转换输出字符'a',转义符分别使用输出字符'a'的单引号和换行,注意隐式数据类型转换是否丢失了数据,以及显式数据类型转换的对象。

4.9 就业面试技巧与解析

数据类型是所有程序语言的基础。C程序的所有功能都是建立在基本数据类型之上。数据类型可以告诉编译器每个数据所代表的含义,以及程序对数据的操作。它确定了数据和数据操作在程序中的意义。本章节涉及的细节问题较多,可以考查面试者的基本功,因此是面试考官经常会问到的部分。

4.9.1 面试技巧与解析(一)

面试官:字符型常量和字符串常量有什么区别?

应聘者:字符型常量与字符串常量的书写方式不同,用单引号引起来的字符是字符常量,用双引号引起来的字符是字符串常量。字符串常量与字符型常量的存储方式不同,C编译程序在存储字符串常量时,自动采用\0作为字符串常量结束的标志。

4.9.2 面试技巧与解析(二)

面试官:字符变量在内存中如何存储?

应聘者:字符是字符集中的一系列符号,在使用时用编号进行读取,也就是说在字符变量里面放着这些符号的编号,这些编号就是ASCII码。ASCII码与一些整型数据对应,这也造成了字符型变量在一定程度上可以和整型变量进行换算。因为数字1~9的ASCII码是连续的,而且是整型,所以可以在输入数字字符时,在程序内部将其换算成真正的整型数据来处理(必要的时候)。字母的ASCII码从A~Z和a~z都分别是连续的,这也使得我们可以方便地进行大小写的转换。字符型变量可以让我们更方便地处理一些输入问题。