5.3 指定值

我们已经看到了几种可以指定数值常量(文面量C)的方法。

123十进制整型常量C。对我们大多数人来说,这是最自然的选择。

077八进制整型常量C。这是由一系列数字指定的,第一个数是0,后面的数介于07之间。例如,077的值是63。这种类型仅仅具有历史价值,现在很少使用。通常只使用一个八进制字面量:0本身。

0xFFFF十六进制整型常量C。这是通过以0x开头,后跟0...9之间,以及a...f之间的数字序列来指定的。例如,0xbeaf的值是48815。a..fx也可以用大写字母写为0XBEAF

1.7E-13十进制浮点常量C。很熟悉有小数点的版本。但是也有一个带指数的“科学”表示法。指数表示法。一般情况下,mEe被解释为m·10e

0x1.7aP-13十六进制浮点常量C。通常用于以一种易于指定具有精确表示的值的形式来描述浮点值。通用形式0XhPe被解释为h·2e。这里,h被指定为十六进制分数。指数e仍然被指定为十进制数。

' a '整型字符常量C。这些字符放在单引号'之间,例如'a''?'。它们的值仅由C标准暗中固定下来。例如,'a'对应于拉丁字母中字母a的整数代码。在字符常量中,\字符有特殊的含义。例如,我们已经看到的换行符'\n'

"hello"字符串文字C。它们指定文本,例如printfputs函数所需的文本。同样,与字符常量一样,\字符也是有特殊意义的[1]

除了最后一个,其余的都是数字常量:它们指定数字[2]。字符串文字是一个例外,可以用来指定在编译时已知的文本。如果不允许我们将字符串文本分割成块,那么将大文本集成到代码中既冗长又乏味:

064-01

要点5.18 连续的字符串字面量被连接起来。

数字规则要复杂一点。

要点5.19 数字字面量从不为负值。

也就是说,如果我们写类似-34或者-1.5E-23这样的数字,前面的符号不是数字的一部分而是后面数字的非运算符。我们很快就会看到这一点的重要性。听起来可能很奇怪,指数中的负号被认为是浮点字面量的一部分。

我们已经看到(要点5.3),所有的字面量不仅要有一个值,还要有一个类型。不要把一个有正值的常量与其类型相混淆,这个类型可以是signed

要点5.20 十进制整型常量是有符号的。

这是一个重要的特性:我们可能希望表达式-1是一个带符号的负值。

为了确定整型字面量的确切类型,我们始终有一个首先匹配(first fit)规则。

要点5.21 十进制整型常量有三种适合它的有符号类型中的第一种。

这条规则会产生惊人的效果。假设在一个平台上,最小的signed类型值为-215 = - 32768,最大值为215 - 1 = 32767signed类型容纳不了常量32768,所以使用signed long。因此,表达式-32768拥有类型signed long。因此,在这种平台上signed类型的最小值不能写成字面常量[练习1]

要点5.22 同一个值可以有不同的类型。

推导八进制或十六进制常量的类型要稍微复杂一些。如果值不适用有符号类型,则可以使用无符号类型。在前面的示例中,十六进制常量0x7FFF的值为32767,因此是signed类型。除了十进制常量之外,常量0x8000(值32768的十六进制写法)是unsigned类型,表达式-0x8000也是unsigned类型[练习2]

要点5.23 不要使用八进制或十六进制常量来表示负值。

因此,对于负值只有一个选择。

要点5.24 使用十进制常量来表示负值。

整型常量可以被强制为无符号,或者是具有最小宽度的类型。这是通过在文字后面附加U、L或LL来实现的。例如,1U的值为1,类型为unsigned1Lsigned long类型,1ULL的值同样为1,但是类型为unsigned long long[练习3]。注意,我们用打字机字体来表示像1ULL这样的C常量,并将它们与普通字体中的数值1区分开来。

一个常见的错误是试图将一个十六进制常量赋给一个signed类型,期望用它来表示一个负值。考虑一个像int x =0xFFFFFFFF这样的声明。这是基于假设十六进制值与有符号值- 1具有相同的二进制表示。在大多数32位signed的架构上,这是可以的(但并不是在所有架构上),但无法保证其合法值。

+4294967295被转换为值-1。表5.3提供了一些有趣的常量、它们的值及类型的例子。

表5.3 常量及其类型的示例。这是假设signedunsigned与32位类型具有常用的表示

065-01

记住,值0很重要。尤为重要的是,它有很多等价的拼法:0,0 x0,和'\0'都是相同的值,0signed int类型。0没有十进制整型拼法:0.0值0的十进制拼法,但被视为一个类型为double的浮点值。

要点5.25 不同的字面量可以具有相同的值。

对于整数,这个规则看起来很简单,但是对于浮点常量就不那么明显了。浮点值只是它们字面上表示的值的近似值,因为小数部分的二进制数字可能会被截断或四舍五入。

要点5.26 十进制浮点常量的有效值可能与其字面值不同。

例如,在我的机器上,常量0.2的值是0.2000000000000000111,因此常量0.20.2000000000000000111的值是相同的。

十六进制浮点常量已经被设计出来,因为它们更好地对应于浮点值的二进制表示。事实上,在大多数现代体系架构中,这样的常量(没有太多的数字)将与字面值完全对应。不幸的是,它们对于人类来说几乎是不可读的。例如,考虑两个常量0x1.99999AP-30xC.CCCCCCCCCCCCCCDP-6。第一个对应1.60000002384*2-3,第二个对应12.8000000000000000002*2-6。用十进制浮点数表示,它们的值分别近似为0.20000000298和0.200000000000000000003。因此,这两个常量的值非常接近,而十六进制浮点常量的表示似乎使它们相距甚远。

最后,浮点常量后面可以加fF来表示float数,也可以加lL来表示long double数。否则,它们是double类型。请注意,不同类型的常量通常会导致同一字面的不同值。下面是一个典型的例子:

066-01

要点5.27 字面有值、类型和二进制表示。

复数常量

所有C平台不一定都支持复数类型。可以通过检查__STDC_NO_COMPLEX__来检验。要完全支持复数类型,应该包括头文件complex.h。如果对数学函数使用tgmath.h,这已经隐含地完成了复数类型。

不幸的是,C语言没有提供指定复数类型常量的字面量。它只有几个宏[3]可以简化对这些类型的操作。

指定复数值的第一种方式是使用宏CMPLX,它在一个复数值中包含两个浮点值,实数和虚数部分。例如,CMPLX(0.5, 0.5)是一个double complex,其实数和虚数部分一半一半。类似地,有用于float complexCMPLXF和用于long double complexCMPLXL

另一种更方便的方式是由宏I提供的,它表示类型为float complex的常量值,这样I*I的值为-1。大写的单字符宏名通常用于整个程序中固定的数字。就其本身而言,这不是一个好主意(提供单字符名称是有限的),但你一定不要管I

要点5.28 I是保留给虚数单位的。

I可以被用来指定类似于常用数学符号的复数类型的常量。例如,0.5 + 0.5*I的类型是double complex,而0.5 F + 0.5 F *I的类型是float complex。如果我们把它们混合使用,编译器会隐含地将结果转换C为较宽的类型,例如,对于实数和虚数部分使用floatdouble常量。

挑战5 复数

你能否将导数(挑战2)扩展到复数域:即接收和返回double complex值的函数?


[1]如果在printf函数的上下文中使用,别的字符也会变为具有“特殊意义的”%字符。如果要用printf打印文字%,必须将其写2遍。

[2]你可能已经注意到复数并不包括在这个列表中。我们将在5.3.1节中详细说明。

[3]我们将在5.6.3节中看到宏的真正含义。现在,只需将它们作为编译器所关联的某些特定属性的名称。

[练习1]表示如果signed long long的最小值和最大值具有类似的属性,则不能将平台的最小整型值写成一个减号与字面量的组合。

[练习2]表示如果最大的unsigned值是216- 1,那么-0x8000也拥有值32768。

[练习3]表示表达式-1U-1UL-1ULL使最大值和类型分别作为三种未提升的无符号类型。