《C程序设计语言(第2版新版)典藏版》 —2.7 类型转换

举报
华章计算机 发表于 2019/12/06 19:48:08 2019/12/06
【摘要】 本节书摘来自华章计算机《C程序设计语言(第2版新版)典藏版》一书中第2章,第2.7节,作者是[美]布莱恩W. 克尼汉 (Brian W. Kernighan) 丹尼斯M. 里奇 (Dennis M. Ritchie),徐宝文 李志译 尤晋元 审校。

2.7   类型转换

当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型。一般来说,自动转换是指把“比较窄的”操作数转换为“比较宽的”操作数,并且不丢失信息的转换,例如,在计算表达式f+i时,将整型变量i的值自动转换为浮点型(这里的变量f为浮点型)。不允许使用无意义的表达式,例如,不允许把float类型的表达式作为下标。针对可能导致信息丢失的表达式,编译器可能会给出警告信息,比如把较长的整型值赋给较短的整型变量,把浮点型值赋给整型变量,等等,但这些表达式并不非法。

由于char类型就是较小的整型,因此在算术表达式中可以自由使用char类型的变量,这就为实现某些字符转换提供了很大的灵活性。比如,下面的函数atoi就是一例,它将一串数字转换为相应的数值:

image.png

image.png

我们在第1章讲过,表达式

image.png

能够计算出s[i]中存储的字符所对应的数字值,这是因为'0'、'1'等在字符集中对应的数值是一个连续的递增序列。

函数lower是将char类型转换为int类型的另一个例子,它将ASCII字符集中的字符映射到对应的小写字母。如果待转换的字符不是大写字母,lower函数将返回字符本身。

image.png

上述这个函数是为ASCII字符集设计的。在ASCII字符集中,大写字母与对应的小写字母作为数字值来说具有固定的间隔,并且每个字母表都是连续的,也就是说,在A~Z之间只有字母。但是,后面一点对EBCDIC字符集是不成立的,因此这一函数作用在EBCDIC字符集中就不仅限于转换字母的大小写。

附录B介绍的标准头文件<ctype.h>定义了一组与字符集无关的测试和转换函数。例如,tolower(c)函数将c转换为小写形式(如果c为大写形式的话),可以使用tolower替代上述lower函数。类似地,测试语句

image.png

可以用该标准库中的函数

image.png

替代。在本书的后续内容中,我们将使用<ctype.h>中定义的函数。

将字符类型转换为整型时,我们需要注意一点。C语言没有指定char类型的变量是无符号变量(signed)还是带符号变量(unsigned)。当把一个char类型的值转换为int类型的值时,其结果有没有可能为负整数?对于不同的机器,其结果也不同,这反映了不同机器结构之间的区别。在某些机器中,如果char类型值的最左一位为1,则转换为负整数(进行“符号扩展”)。而在另一些机器中,把char类型值转换为int类型值时,在char类型值的左边添加0,这样导致的转换结果值总是正值。

C语言的定义保证了机器的标准打印字符集中的字符不会是负值,因此,在表达式中这些字符总是正值。但是,存储在字符变量中的位模式在某些机器中可能是负的,而在另一些机器中可能是正的。为了保证程序的可移植性,如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。

当关系表达式(如i>j)以及由&&、||连接的逻辑表达式的判定结果为真时,表达式的值为1;当判定结果为假时,表达式的值为0。因此,对于赋值语句

image.png

来说,当c为数字时,d的值为1,否则d的值为0。但是,某些函数(比如isdigit)在结果为真时可能返回任意的非0值。在if、while、for等语句的测试部分中,“真”就意味着“非0”,这二者之间没有区别。

C语言中,很多情况下会进行隐式的算术类型转换。一般来说,如果二元运算符(具有两个操作数的运算符称为二元运算符,比如+或*)的两个操作数具有不同的类型,那么在进行运算之前先要把“较低”的类型提升为“较高”的类型。运算的结果为较高的类型。附录A.6节详细地列出了这些转换规则。但是,如果没有unsigned类型的操作数,则只要使用下面这些非正式的规则就可以了:

如果其中一个操作数的类型为long double,则将另一个操作数转换为long double类型;

如果其中一个操作数的类型为double,则将另一个操作数转换为double类型;

如果其中一个操作数的类型为float,则将另一个操作数转换为float类型;

将char与short类型的操作数转换为int类型;

如果其中一个操作数的类型为long,则将另一个操作数也转换为long类型。

注意,表达式中float类型的操作数不会自动转换为double类型,这一点与最初的定义有所不同。一般来说,数学函数(如标准头文件<math.h>中定义的函数)使用双精度类型的变量。使用float类型主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间(双精度算术运算特别费时)。

当表达式中包含unsigned类型的操作数时,转换规则要复杂一些。主要原因在于,带符号值与无符号值之间的比较运算是与机器相关的,因为它们取决于机器中不同整数类型的大小。例如,假定int类型占16位,long类型占32位,那么,-1L<1U,这是因为unsighed int类型的1U将被提升为signed long类型;但-1L>1UL,这是因为-1L将被提升为unsigned long类型,因而成为一个比较大的正数。

赋值时也要进行类型转换。赋值运算符右边的值需要转换为左边变量的类型,左边变量的类型即赋值表达式结果的类型。

前面提到过,无论是否进行符号扩展,字符型变量都将被转换为整型变量。

当把较长的整数转换为较短的整数或char类型时,超出的高位部分将被丢弃。因此,下列程序段

image.png

执行后,c的值将保持不变。无论是否进行符号扩展,该结论都成立。但是,如果把两个赋值语句的次序颠倒一下,则执行后可能会丢失信息。

如果x是float类型,i是int类型,那么语句x=i与i=x在执行时都要进行类型转换。当把float类型转换为int类型时,小数部分将被截取掉;当把double类型转换为float类型时,是进行四舍五入还是截取取决于具体的实现。

由于函数调用的参数是表达式,所以在把参数传递给函数时也可能进行类型转换。在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。因此,即使调用函数的参数为char或float类型,我们也把函数参数声明为int或double类型。

最后,在任何表达式中都可以使用一个称为强制类型转换的一元运算符强制进行显式类型转换。在下列语句中,表达式将按照上述转换规则被转换为类型名指定的类型:

(类型名)表达式

我们可以这样来理解强制类型转换的准确含义:在上述语句中,表达式首先被赋值给类型名指定的类型的某个变量,然后再用该变量替换上述整条语句。例如,库函数sqrt的参数为double类型,如果处理不当,结果可能会无意义(sqrt在<math.h>中声明)。因此,如果n是整数,可以使用

image.png

在把n传递给函数sqrt之前先将其转换为double类型。注意,强制类型转换只是生成一个指定类型的n的值,n本身的值并没有改变。强制类型转换运算符与其他一元运算符具有相同的优先级,表2-1对运算符优先级进行了总结。

在通常情况下,参数是通过函数原型声明的。这样,当函数被调用时,声明将对参数进行自动强制转换。例如,对于sqrt的函数原型

image.png

下列函数调用

不需要使用强制类型转换运算符就可以自动将整数2强制转换为double类型的值2.0。

标准库中包含一个可移植的实现伪随机数发生器的函数rand以及一个初始化种子数的函数srand。前一个函数rand使用了强制类型转换。

image.png

image.png

练习2-3   编写函数htoi(s),把由十六进制数字组成的字符串(包含可选的前缀0x或0X)转换为与之等价的整型值。字符串中允许包含的数字包括:0~9、a~f以及A~F。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。