- 现代C:概念剖析和编程实践
- (德)延斯·古斯泰特
- 2726字
- 2025-04-08 00:44:20
5.3 指定值
我们已经看到了几种可以指定数值常量(文面量C)的方法。
123
十进制整型常量C。对我们大多数人来说,这是最自然的选择。
077
八进制整型常量C。这是由一系列数字指定的,第一个数是0
,后面的数介于0
和7
之间。例如,077
的值是63
。这种类型仅仅具有历史价值,现在很少使用。通常只使用一个八进制字面量:0
本身。
0xFFFF
十六进制整型常量C。这是通过以0x
开头,后跟0
,...
,9
之间,以及a...f
之间的数字序列来指定的。例如,0xbeaf
的值是48815。a..f
和x
也可以用大写字母写为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。它们指定文本,例如printf
和puts
函数所需的文本。同样,与字符常量一样,\
字符也是有特殊意义的[1]。
除了最后一个,其余的都是数字常量:它们指定数字[2]。字符串文字是一个例外,可以用来指定在编译时已知的文本。如果不允许我们将字符串文本分割成块,那么将大文本集成到代码中既冗长又乏味:

要点5.18 连续的字符串字面量被连接起来。
数字规则要复杂一点。
要点5.19 数字字面量从不为负值。
也就是说,如果我们写类似-34
或者-1.5E-23
这样的数字,前面的符号不是数字的一部分而是后面数字的非运算符。我们很快就会看到这一点的重要性。听起来可能很奇怪,指数中的负号被认为是浮点字面量的一部分。
我们已经看到(要点5.3),所有的字面量不仅要有一个值,还要有一个类型。不要把一个有正值的常量与其类型相混淆,这个类型可以是signed
。
要点5.20 十进制整型常量是有符号的。
这是一个重要的特性:我们可能希望表达式-1
是一个带符号的负值。
为了确定整型字面量的确切类型,我们始终有一个首先匹配(first fit)规则。
要点5.21 十进制整型常量有三种适合它的有符号类型中的第一种。
这条规则会产生惊人的效果。假设在一个平台上,最小的signed
类型值为-215 = - 32768
,最大值为215 - 1 = 32767
。signed
类型容纳不了常量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
,类型为unsigned
,1L
是signed long
类型,1ULL
的值同样为1,但是类型为unsigned long long
[练习3]。注意,我们用打字机字体来表示像1ULL
这样的C常量,并将它们与普通字体中的数值1区分开来。
一个常见的错误是试图将一个十六进制常量赋给一个signed
类型,期望用它来表示一个负值。考虑一个像int
x =0xFFFFFFFF
这样的声明。这是基于假设十六进制值与有符号值- 1
具有相同的二进制表示。在大多数32位signed
的架构上,这是可以的(但并不是在所有架构上),但无法保证其合法值。
+4294967295被转换为值-1。表5.3提供了一些有趣的常量、它们的值及类型的例子。
表5.3 常量及其类型的示例。这是假设signed
和unsigned
与32位类型具有常用的表示

记住,值0很重要。尤为重要的是,它有很多等价的拼法:0
,0 x0
,和'\0'
都是相同的值,0
是signed int
类型。0没有十进制整型拼法:0.0
是值0的十进制拼法,但被视为一个类型为double
的浮点值。
要点5.25 不同的字面量可以具有相同的值。
对于整数,这个规则看起来很简单,但是对于浮点常量就不那么明显了。浮点值只是它们字面上表示的值的近似值,因为小数部分的二进制数字可能会被截断或四舍五入。
要点5.26 十进制浮点常量的有效值可能与其字面值不同。
例如,在我的机器上,常量0.2
的值是0.2000000000000000111,因此常量0.2
和0.2000000000000000111
的值是相同的。
十六进制浮点常量已经被设计出来,因为它们更好地对应于浮点值的二进制表示。事实上,在大多数现代体系架构中,这样的常量(没有太多的数字)将与字面值完全对应。不幸的是,它们对于人类来说几乎是不可读的。例如,考虑两个常量0x1.99999AP-3
和0xC.CCCCCCCCCCCCCCDP-6
。第一个对应1.60000002384*2-3,第二个对应12.8000000000000000002*2-6。用十进制浮点数表示,它们的值分别近似为0.20000000298和0.200000000000000000003。因此,这两个常量的值非常接近,而十六进制浮点常量的表示似乎使它们相距甚远。
最后,浮点常量后面可以加f
或F
来表示float
数,也可以加l
或L
来表示long double
数。否则,它们是double
类型。请注意,不同类型的常量通常会导致同一字面的不同值。下面是一个典型的例子:

要点5.27 字面有值、类型和二进制表示。
复数常量
所有C平台不一定都支持复数类型。可以通过检查__STDC_NO_COMPLEX__
来检验。要完全支持复数类型,应该包括头文件complex.h
。如果对数学函数使用tgmath.h
,这已经隐含地完成了复数类型。
不幸的是,C语言没有提供指定复数类型常量的字面量。它只有几个宏[3]可以简化对这些类型的操作。
指定复数值的第一种方式是使用宏CMPLX
,它在一个复数值中包含两个浮点值,实数和虚数部分。例如,CMPLX
(0.5, 0.5)
是一个double complex
,其实数和虚数部分一半一半。类似地,有用于float complex
的CMPLXF
和用于long double complex
的CMPLXL
。
另一种更方便的方式是由宏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为较宽的类型,例如,对于实数和虚数部分使用float
和double
常量。
挑战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
使最大值和类型分别作为三种未提升的无符号类型。