-
Notifications
You must be signed in to change notification settings - Fork 25
/
ch15s03.html
5 lines (5 loc) · 18 KB
/
ch15s03.html
1
2
3
4
5
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>3. 类型转换</title><link rel="stylesheet" href="styles.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Linux C编程一站式学习" /><link rel="up" href="ch15.html" title="第 15 章 数据类型详解" /><link rel="prev" href="ch15s02.html" title="2. 浮点型" /><link rel="next" href="ch16.html" title="第 16 章 运算符详解" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3. 类型转换</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch15s02.html">上一页</a> </td><th width="60%" align="center">第 15 章 数据类型详解</th><td width="20%" align="right"> <a accesskey="n" href="ch16.html">下一页</a></td></tr></table><hr /></div><div class="sect1" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2757924"></a>3. 类型转换</h2></div></div></div><p>如果有人问C语法规则中最复杂的是哪一部分,我一定会说是类型转换。从上面两节可以看出,有符号、无符号整数和浮点数加起来有那么多种类型,每两种类型之间都要定义一个转换规则,转换规则的数量自然很庞大,更何况由于各种体系结构对于整数和浮点数的实现很不相同,很多类型转换的情况都是C标准未做明确规定的阴暗角落。虽然我们写代码时不会故意去触碰这些阴暗角落,但有时候会不小心犯错,所以了解一些未明确规定的情况还是有必要的,可以在出错时更容易分析错误原因。本节分成几小节,首先介绍哪些情况下会发生类型转换,会把什么类型转成什么类型,然后介绍编译器如何处理这样的类型转换。</p><div class="sect2" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2757955"></a>3.1. Integer Promotion</h3></div></div></div><p>在一个表达式中,凡是可以使用<code class="literal">int</code>或<code class="literal">unsigned int</code>类型做右值的地方也都可以使用有符号或无符号的<code class="literal">char</code>型、<code class="literal">short</code>型和Bit-field。如果原始类型的取值范围都能用<code class="literal">int</code>型表示,则其类型被提升为<code class="literal">int</code>,如果原始类型的取值范围用<code class="literal">int</code>型表示不了,则提升为<code class="literal">unsigned int</code>型,这称为Integer Promotion<a id="id2758017" class="indexterm"></a>。做Integer Promotion只影响上述几种类型的值,对其它类型无影响。C99规定Integer Promotion适用于以下几种情况:</p><p>1、如果一个函数的形参类型未知,例如使用了Old Style C风格的函数声明(详见<a class="xref" href="ch03s02.html#func.ourfirstfunc">第 2 节 “自定义函数”</a>),或者函数的参数列表中有...,那么调用函数时要对相应的实参做Integer Promotion,此外,相应的实参如果是<code class="literal">float</code>型的也要被提升为<code class="literal">double</code>型,这条规则称为Default Argument Promotion<a id="id2758055" class="indexterm"></a>。我们知道<code class="literal">printf</code>的参数列表中有<code class="literal">...</code>,除了第一个形参之外,其它形参的类型都是未知的,比如有这样的代码:</p><pre class="programlisting">char ch = 'A';
printf("%c", ch);</pre><p><code class="literal">ch</code>要被提升为<code class="literal">int</code>型之后再传给<code class="literal">printf</code>。</p><p>2、算术运算中的类型转换。有符号或无符号的<code class="literal">char</code>型、<code class="literal">short</code>型和Bit-field在做算术运算之前首先要做Integer Promotion,然后才能参与计算。例如:</p><pre class="programlisting">unsigned char c1 = 255, c2 = 2;
int n = c1 + c2;</pre><p>计算表达式<code class="literal">c1 + c2</code>的过程其实是先把<code class="literal">c1</code>和<code class="literal">c2</code>提升为<code class="literal">int</code>型然后再相加(<code class="literal">unsigned char</code>的取值范围是0~255,完全可以用<code class="literal">int</code>表示,所以提升为<code class="literal">int</code>就可以了,不需要提升为<code class="literal">unsigned int</code>),整个表达式的值也是<code class="literal">int</code>型,最后的结果是257。假如没有这个提升的过程,<code class="literal">c1 + c2</code>就溢出了,溢出会得到什么结果是Undefined,在大多数平台上会把进位截掉,得到的结果应该是1。</p><p>除了+号之外还有哪些运算符在计算之前需要做Integer Promotion呢?我们在下一小节先介绍Usual Arithmetic Conversion规则,然后再解答这个问题。</p></div><div class="sect2" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2758200"></a>3.2. Usual Arithmetic Conversion</h3></div></div></div><p>两个算术类型的操作数做算术运算,比如<code class="literal">a + b</code>,如果两边操作数的类型不同,编译器会自动做类型转换,使两边类型相同之后才做运算,这称为Usual Arithmetic Conversion<a id="id2758216" class="indexterm"></a>。转换规则如下:</p><div class="orderedlist"><ol type="1"><li><p>如果有一边的类型是<code class="literal">long double</code>,则把另一边也转成<code class="literal">long double</code>。</p></li><li><p>否则,如果有一边的类型是<code class="literal">double</code>,则把另一边也转成<code class="literal">double</code>。</p></li><li><p>否则,如果有一边的类型是<code class="literal">float</code>,则把另一边也转成<code class="literal">float</code>。</p></li><li><p>否则,两边应该都是整型,首先按上一小节讲过的规则对<code class="literal">a</code>和<code class="literal">b</code>做Integer Promotion,然后如果类型仍不相同,则需要继续转换。首先我们规定<code class="literal">char</code>、<code class="literal">short</code>、<code class="literal">int</code>、<code class="literal">long</code>、<code class="literal">long long</code>的转换级别(Integer Conversion Rank)<a id="id2758331" class="indexterm"></a>一个比一个高,同一类型的有符号和无符号数具有相同的Rank。转换规则如下:</p><div class="orderedlist"><ol type="a"><li><p>如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如<code class="literal">unsigned int</code>和<code class="literal">unsigned long</code>做算术运算时都转成<code class="literal">unsigned long</code>。</p></li><li><p>否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如<code class="literal">unsigned long</code>和<code class="literal">int</code>做算术运算时都转成<code class="literal">unsigned long</code>,<code class="literal">unsigned long</code>和<code class="literal">long</code>做算术运算时也都转成<code class="literal">unsigned long</code>。</p></li><li><p>剩下的情况是:一边有符号另一边无符号,并且无符号数的Rank低于有符号数的Rank。这时又分为两种情况,如果这个有符号数类型能够覆盖这个无符号数类型的取值范围,则把无符号数转成另一边的有符号类型。例如遵循LP64的平台上<code class="literal">unsigned int</code>和<code class="literal">long</code>在做算术运算时都转成<code class="literal">long</code>。</p></li><li><p>否则,也就是这个有符号数类型不足以覆盖这个无符号数类型的取值范围,则把两边都转成有符号数的Rank对应的无符号类型。例如在遵循ILP32的平台上<code class="literal">unsigned int</code>和<code class="literal">long</code>在做算术运算时都转成<code class="literal">unsigned long</code>。</p></li></ol></div></li></ol></div><p>可见有符号和无符号整数的转换规则是十分复杂的,虽然这是有明确规定的,不属于阴暗角落,但为了程序的可读性不应该依赖这些规则来写代码。我讲这些规则,不是为了让你用,而是为了让你了解有符号数和无符号数混用会非常麻烦,从而避免触及这些规则,并且在程序出错时记得往这上面找原因。所以这些规则不需要牢记,但要知道有这么回事,以便在用到的时候能找到我书上的这一段。</p><p>到目前为止我们学过的+ - * / % > < >= <= == !=运算符都需要做Usual Arithmetic Conversion,因为都要求两边操作数的类型一致,在下一章会介绍几种新的运算符也需要做Usual Arithmetic Conversion。单目运算符+ - ~只有一个操作数,移位运算符<< >>两边的操作数类型不要求一致,这些运算不需要做Usual Arithmetic Conversion,但也需要做Integer Promotion,运算符~ << >>将在下一章介绍。</p></div><div class="sect2" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2758516"></a>3.3. 由赋值产生的类型转换</h3></div></div></div><p>如果赋值或初始化时等号两边的类型不相同,则编译器会把等号右边的类型转换成等号左边的类型再做赋值。例如<code class="literal">int c = 3.14;</code>,编译器会把右边的<code class="literal">double</code>型转成<code class="literal">int</code>型再赋给变量<code class="literal">c</code>。</p><p>我们知道,函数调用传参的过程相当于定义形参并且用实参对其做初始化,函数返回的过程相当于定义一个临时变量并且用<code class="literal">return</code>的表达式对其做初始化,所以由赋值产生的类型转换也适用于这两种情况。例如一个函数的原型是<code class="literal">int foo(int, int);</code>,则调用<code class="literal">foo(3.1, 4.2)</code>时会自动把两个<code class="literal">double</code>型的实参转成<code class="literal">int</code>型赋给形参,如果这个函数定义中有返回语句<code class="literal">return 1.2;</code>,则返回值<code class="literal">1.2</code>会自动转成<code class="literal">int</code>型再返回。</p><p>在函数调用和返回过程中发生的类型转换往往容易被忽视,因为函数原型和函数调用并没有写在一起。例如<code class="literal">char c = getchar();</code>,看到这一句往往会想当然地认为<code class="literal">getchar</code>的返回值是<code class="literal">char</code>型,而事实上<code class="literal">getchar</code>的返回值是<code class="literal">int</code>型,这样赋值会引起类型转换,可能产生Bug,我们在<a class="xref" href="ch25s02.html#stdlib.byteio">第 2.5 节 “以字节为单位的I/O函数”</a>详细讨论这个问题。</p></div><div class="sect2" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2758655"></a>3.4. 强制类型转换</h3></div></div></div><p>以上三种情况通称为隐式类型转换(Implicit Conversion,或者叫Coercion)<a id="id2758663" class="indexterm"></a><a id="id2758671" class="indexterm"></a>,编译器根据它自己的一套规则将一种类型自动转换成另一种类型。除此之外,程序员也可以通过类型转换运算符(Cast Operator)<a id="id2758683" class="indexterm"></a>自己规定某个表达式要转换成何种类型,这称为显式类型转换(Explicit Conversion)<a id="id2758692" class="indexterm"></a>或强制类型转换(Type Cast)<a id="id2758700" class="indexterm"></a>。例如计算表达式<code class="literal">(double)3 + i</code>,首先将整数3强制转换成<code class="literal">double</code>型(值为3.0),然后和整型变量<code class="literal">i</code>相加,这时适用Usual Arithmetic Conversion规则,首先把<code class="literal">i</code>也转成<code class="literal">double</code>型,然后两者相加,最后整个表达式也是<code class="literal">double</code>型的。这里的<code class="literal">(double)</code>就是一个类型转换运算符,这种运算符由一个类型名套()括号组成,属于单目运算符,后面的3是这个运算符的操作数。注意操作数的类型必须是标量类型,转换之后的类型必须是标量类型或者<code class="literal">void</code>型。</p></div><div class="sect2" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2758764"></a>3.5. 编译器如何处理类型转换</h3></div></div></div><p>以上几小节介绍了哪些情况下会发生类型转换,并且明确了每种情况下会把什么类型转成什么类型,本节介绍编译器如何处理任意两种类型之间的转换。现在要把一个M位的类型(值为X)转换成一个N位的类型,所有可能的情况如下表所示。</p><div class="table"><a id="id2758783"></a><p class="title"><b>表 15.3. 如何做类型转换</b></p><div class="table-contents"><table summary="如何做类型转换" border="1"><colgroup><col /><col /><col /><col /></colgroup><thead><tr><th>待转换的类型</th><th>M > N的情况</th><th>M == N的情况</th><th>M < N的情况</th></tr></thead><tbody><tr><td>signed integer to signed integer</td><td>如果X在目标类型的取值范围内则值不变,否则Implementation-defined</td><td>值不变</td><td>值不变</td></tr><tr><td>unsigned integer to signed integer</td><td>如果X在目标类型的取值范围内则值不变,否则Implementation-defined</td><td>如果X在目标类型的取值范围内则值不变,否则Implementation-defined</td><td>值不变</td></tr><tr><td>signed integer to unsigned integer</td><td>X % 2<sup>N</sup></td><td>X % 2<sup>N</sup></td><td>X % 2<sup>N</sup></td></tr><tr><td>unsigned integer to unsigned integer</td><td>X % 2<sup>N</sup></td><td>值不变</td><td>值不变</td></tr><tr><td>floating-point to signed or unsigned integer</td><td colspan="3">Truncate toward Zero,如果X的整数部分超出目标类型的取值范围则Undefined</td></tr><tr><td>signed or unsigned integer to floating-point</td><td colspan="3">如果X在目标类型的取值范围内则值不变,但有可能损失精度,如果X超出目标类型的取值范围则Undefined</td></tr><tr><td>floating-point to floating-point</td><td>如果X在目标类型的取值范围内则值不变,但有可能损失精度,如果X超出目标类型的取值范围则Undefined</td><td>值不变</td><td>值不变</td></tr></tbody></table></div></div><br class="table-break" /><p>注意上表中的“<span class="quote">X % 2<sup>N</sup></span>”,我想表达的意思是“<span class="quote">把X加上或者减去2<sup>N</sup>的整数倍,使结果落入[0, 2<sup>N</sup>-1]的范围内</span>”,当X是负数时运算结果也得是正数,即运算结果和除数同号而不是和被除数同号,这不同于C语言%运算的定义。写程序时不要故意用上表中的规则,尤其不要触碰Implementation-defined和Undefined的情况,但程序出错时可以借助上表分析错误原因。</p><p>下面举几个例子说明上表的用法。比如把<code class="literal">double</code>型转换成<code class="literal">short</code>型,对应表中的“<span class="quote">floating-point to signed or unsigned integer</span>”,如果原值在(-32769.0, 32768.0)之间则截掉小数部分得到转换结果,否则产生溢出,结果是Undefined,例如对于<code class="literal">short s = 32768.4;</code>这个语句<code class="literal">gcc</code>会报警告。</p><p>比如把<code class="literal">int</code>型转换成<code class="literal">unsigned short</code>型,对应表中的“<span class="quote">signed integer to unsigned integer</span>”,如果原值是正的,则把它除以2<sup>16</sup>取模,其实就是取它的低16位,如果原值是负的,则加上2<sup>16</sup>的整数倍,使结果落在[0, 65535]之间。</p><p>比如把<code class="literal">int</code>类型转换成<code class="literal">short</code>类型,对应表中的“<span class="quote">signed integer to signed integer</span>”,如果原值在[-32768, 32767]之间则值不变,否则产生溢出,结果是Implementation-defined,例如对于<code class="literal">short s = -32769;</code>这个语句<code class="literal">gcc</code>会报警告。</p><p>最后一个例子,把<code class="literal">short</code>型转换成<code class="literal">int</code>型,对应表中的“<span class="quote">signed integer to signed integer</span>”,转换之后应该值不变。那怎么维持值不变呢?是不是在高位补16个0就行了呢?如果原值是-1,十六进制表示就是ffff,要转成<code class="literal">int</code>型的-1需要变成ffffffff,因此需要在高位补16个1而不是16个0。换句话说,要维持值不变,在高位补1还是补0取决于原来的符号位,这称为符号扩展(Sign Extension)<a id="id2759092" class="indexterm"></a>。</p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch15s02.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch15.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch16.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">2. 浮点型 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 第 16 章 运算符详解</td></tr></table></div></body></html>