The C Programming Language(第 2 版) 笔记 / 2 类型、运算符与表达式 / 2.7 类型转换

目录、参考文献


2.7 类型转换

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

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

/* atoi: convert s to integer */ 
int atoi(char s[]) 
{ 
    int i, n; 
    n = 0; 
    for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i) 
        n = 10 * n + (s[i] - '0'); 
    return n; 
}

我们在第 1 章讲过,表达式 s[i] - '0' 能够计算出 s[i] 中存储的字符所对应的数字值
这是因为 '0''1' 等在字符集中对应的数值是一个连续的递增序列

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

/* lower: convert c to lower case; ASCII only */ 
int lower(int c) 
{ 
    if (c >= 'A' && c <= 'Z') 
        return c + 'a' - 'A'; 
    else 
        return c; 
}

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

附录 B 介绍的标准头文件 <ctype.h> 定义了一组与字符集无关的测试和转换函数
例如 tolower(c) 函数将 c 转换为小写形式(如果 c 为大写形式的话)
可以使用 tolower 替代上述 lower 函数
类似地,测试语句 c >= '0' && c <= '9' 可以用该标准库中的函数 isdigit(c) 替代
在本书的后续内容中,我们将使用 <ctype.h> 中定义的函数

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

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

当关系表达式(如 i > j)以及由 &&|| 连接的逻辑表达式的判定结果为真时,表达式的值为 1,当判定结果为假时,表达式的值为 0
因此,对于赋值语句 d = c >= '0' && c <= '9' 来说,当 c 为数字时,d 的值为 1,否则 d 的值为 0
但是,某些函数(比如 isdigit)在结果为真时可能返回任意的非 0 值
ifwhilefor 等语句的测试部分中,“ 真 ” 就意味着 “非 0”,这二者之间没有区别

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

  • 如果其中一个操作数的类型为 long double,则将另一个操作数转换为 1ong double 类型
  • 如果其中一个操作数的类型为 double,则将另一个操作数转换为 double 类型
  • 如果其中一个操作数的类型为 float,则将另一个操作数转换为 float 类型
  • charshort 类型的操作数转换为 int 类型
  • 如果其中一个操作数的类型为 long,则将另一个操作数也转换为 long 类型

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

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

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

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

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

int i; 
char c; 
i = c; 
c = i;

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

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

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

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

我们可以这样来理解强制类型转换的准确含义:
在上述语句中,表达式首先被赋值给类型名指定的类型的某个变量,然后再用该变量替换上述整条语句
例如,库函数 sqrt 的参数为 double 类型,如果处理不当,结果可能会无意义(sqrt<math.h> 中声明)
因此,如果 n 是整数,可以使用 sqrt((double) n),在把 n 传递给函数 sqrt 之前先将其转换为 double 类型
注意,强制类型转换只是生成一个指定类型的 n 的值,n 本身的值并没有改变
强制类型转换运算符与其它一元运算符具有相同的优先级,表 2-1(2.12 节)对运算符优先级进行了总结

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

root2 = sqrt(2);

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

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

unsigned long int next = 1;

/* rand: return pseudo-random integer on 0..32767 */ 
int rand(void) 
{ 
    next = next * 1103515245 + 12345; 
    return (unsigned int)(next / 65536) % 32768; 
}

/* srand: set seed for rand() */ 
void srand(unsigned int seed) 
{ 
    next = seed; 
}

目录、参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值