为什么在C语言中减去字符'0'会得到该字符所代表的数字?

41

有人能解释一下为什么这个方法有效吗?

char c = '9';
int x = (int)(c - '0');
为什么从一个字符的ASCII码中减去'0'可以得到该字符所代表的数字?
为什么从一个字符的ASCII码中减去'0'可以得到该字符所代表的数字?

9
这是一个好问题。不要被所有使用ASCII表来解释观察到的行为的答案所分散注意力。 - juanchopanza
8个回答

57
因为每个字符都用一个数字表示,而且'0'是它们中的第一个。
在下面的表格中,您可以看到:
'0' => 48
'1' => 49


'9' => 57.

结果是:('9' - '0') = (57 − 48) = 9

图片描述 来源:http://www.asciitable.com


9
在我所使用的机器上,'0' => 240,但是 '9'-'0' 仍然等于 9。 - Bo Persson
4
“EBCDIC有些奇怪,但你是对的。这就是为什么'9'-'0'是正确的做法,而应避免使用'9' - 48。我们可以始终期望字符0到9按升序设置,但它们的实际值可能会有所不同。” - Jean
2
假设正在使用 ASCII 编码。 - Lightness Races in Orbit
1
@LightnessRacesinOrbit - 不,它并不是这样的。它假设使用的任何编码都将0到9的内部表示存储在连续的位置上(并按升序排列)。它们的绝对值是无关紧要的,它们的顺序性才是最重要的。而且祝你好运找到一个相关的字符集,它们不是按顺序存储的。 - AaronHS
1
@AaronHS:你对数字的理解是正确的(它们需要按顺序连续存储,正如你所说)。我想我是在提到使用ASCII表来证明这一点 - 更合适的做法是引用有关数字表示要求的标准语录。 - Lightness Races in Orbit
显示剩余3条评论

30

char是一种整数类型,就像int和它的派生类一样。一个char类型的对象有一些数值。你在字符字面量中输入的字符(比如'0')和char对象所具有的数值之间的映射取决于该字符在执行字符集中的编码:

  • C++11 §2.14.3:

    包含单个可在执行字符集中表示的c-char的普通字符字面量具有类型char,其值等于执行字符集中c-char的编码的数值。

  • C99 §6.4.4.4:

    整数字符常量是由一个或多个多字节字符组成的序列,用单引号括起来,例如'x'

    [...]

    整数字符常量的类型是int

    请注意,int可以转换为char

执行字符集的选择取决于实现。往往情况下,选择与ASCII兼容的字符集,因此其他答案中发布的表格具有适当的值。然而,字符集不需要与ASCII兼容。但是,有一些限制。其中一个限制如下(C++11 §2.3,C99 §5.2.1):

a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " ’

[...]

In both the source and execution basic character sets, the value of each character after 0 in the above list of decimal digits shall be one greater than the value of the previous.

这意味着无论字符'0'具有什么值,字符'1'的值比'0'多一,字符'2'的值比'1'多一,以此类推。数字字符具有连续的值。您可以总结映射如下:

Character:            0    1    2    3    4    5    6    7    8    9
Corresponding value:  X    X+1  X+2  X+3  X+4  X+5  X+6  X+7  X+8  X+9

所有数字字符都与'0'的值相差一定的距离。

这意味着,如果你有一个字符,比如说'9',并从它减去'0',你得到了在执行字符集中'9''0'之间的"距离"。由于它们是连续的,那么这个距离将为9。


3
在C语言中,普通字符字面值(character literal)的类型是int - pmg
@pmg 我没有注意到问题也标记了 [tag:c]。我会加上差异。 - Joseph Mansfield
只是为了明确起见,不能保证'a' .. 'z'是连续的,实际上在EBCDIC中它们并不是。 (在ASCII / Unicode中是这样的)。你引用了一个以字母表开头的列表,但关键短语是“每个字符* 0之后*”,而该列表首先放置了字母表。 - Peter Cordes

23

由于C标准保证字符0, 1, 2, 3, 4, 5, 6, 7, 8, 9始终按照它们的数字字符代码顺序排列。因此,如果您从另一个数字中减去'0'的字符代码,则会给出相对于0的位置,这就是它的值...

来自C标准,第5.2.1节字符集:

在源和执行基本字符集中,上述十进制数字列表中每个字符的值都应比前一个大1。


1
+1,但最好添加标准中指定此内容的章节号。 - juanchopanza

7
因为字面量是按顺序排列的。
因此,如果数字字符“0”在 ASCII 码表中的值为48,则数字字符“1”的值为49,“2”的值为50等等。那么变量“x”将包含字符“9”的ASCII码值减去字符“0”的ASCII码值,即字符“9”的ASCII码值为57,因此,“x”的值将为“57 - 48 = 9”。
此外,“char”是一种整数类型。

是的,你说得对,现在我明白了,谢谢!(这不是一个很聪明的问题;D) - Silviu.
2
这不依赖于区域设置为ASCII。 - user529758

3
数字字符的ASCII码按照顺序排列,顺序为'0' '1' '2' '3' '4' '5' '6' '7' '8' '9',如ASCII表所示。
因此,如果我们计算'9'的ASCII码和'0'的ASCII码之间的差值,得到的结果将是9

3
在ASCII表中,数字按顺序排列,从最低代码的0开始。如果您从0中减去一个较大的数字,则创建两个ASCII值之间的差异。所以,9的值为57,而0的值为48,因此如果您将57从48中减去,则得到9。请查看ASCII表。这里

2

看一下ASCII表

'9' in ASCII =  57 //in Decimal

'0' in ASCII =  48 //in Decimal

57 - 48 = 9


我相信它应该是一样的,因为“0”-“9”按递增顺序排序。即使在这里中,'0' - '9'也是按递增顺序排序的。 - Thanakron Tandavas
1
没错,这就是问题所在。某处有一条规则,规定数字必须按递增顺序排列,而ASCII表遵循这个规则。因此,这种行为保证适用于其他编码。但仅引用ASCII表并不能说明问题。 - juanchopanza
谢谢您的建议。下次我会确保提供解释。;) - Thanakron Tandavas

-1

首先,尝试:

cout << (int)'0' << endl;

现在尝试:

cout << (int)'9' << endl;

字符以文本形式表示数字,但作为数字时具有不同的值。 Windows使用数字来决定要打印哪个字符。因此,数字0x30代表Windows OS中的字符0。数字0x39代表字符9。毕竟,计算机只能识别数字,它不知道“char”是什么。

不幸的是,(int)('f' - '0')并不等于15。

这为您提供了各种字符及其表示它们的数字的Windows使用情况。 http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx

如果您需要在另一个操作系统中查找,请在Google中搜索:Virtual Key Codes <OSname>,以查看其他操作系统的代码。


这与Windows(或任何其他操作系统,例如创建C语言的操作系统)无关。数字字符的连续编码是C标准所要求的,并且可以在任何符合标准的系统上依赖。 - Toby Speight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接