为什么C++流使用char而不是unsigned char?

57
我一直想知道为什么C++标准库使用char类型而不是unsigned char类型来实例化basic_[io]stream及其所有变体。根据char是有符号还是无符号,你可能会在像get()这样的操作中发生溢出和下溢,这将导致涉及的变量具有实现定义的值。另一个例子是当你想要使用它的put函数将字节未格式化地输出到ostream时。
任何想法吗?
注意:我仍然不太确定。如果你知道确定的答案,确实可以发表回复。

我不知道为什么,但我知道在GCC中字符的有符号性取决于底层CPU和操作系统。因此,约定会随着一个CPU/OS而改变到另一个CPU/OS。我只是无法解释为什么它会改变。 - Max Lybbert
1
好问题!希望有人能给我们一个好的理由。ACE团队使用无符号字符作为他们的ACE_Byte类型(http://www.aoc.nrao.edu/php/tjuerges/ALMA/ACE-5.5.2/html/ace/Basic__Types_8h-source.html#l00272)。 - Fernando N.
或者为什么要从5种不同的8位类型中选择char、signed char、unsigned char、int8_t和uint8_t中的哪一个。(在这个列表中,我的选择是最后一个) - slashmais
4个回答

29
可能我对问题的理解有误,但从无符号字符转换为字符不是未指定的,而是依赖于实现(C++标准中的4.7-3)。
在C++中,1字节字符的类型是“char”,而不是“unsigned char”。这给了实现更多的自由,在平台上做最好的事情(例如,标准机构可能认为存在一些CPU,其中有符号字节算术比无符号字节算术更快,尽管这是我的猜测)。另外也考虑了与C语言的兼容性。从C++中消除这种存在主义的不确定性的结果是C# ;-)
鉴于“char”类型存在,我认为通常流使用它是有意义的,即使其符号性没有定义。因此,也许您的问题的答案可以通过回答“为什么不将char定义为无符号?”来得到回答。

1
我以为实现相关与未指定是一样的。我会更正我的问题并查阅区别。谢谢你告诉我 :) - Johannes Schaub - litb
3
未指定意味着实现可以将任何值放入其中(包括每次随机选择一个),并且不必记录其操作。依赖于实现意味着实现必须记录它放入其中的值。 - Steve Jessop
7
我听说从C++中去掉C遗产就得到了D :) - xtofl
如果有符号字节算术比无符号字节算术更快,那么对于((un)?signed)? char进行任何算术运算都会被提升为(unsigned)? int。只需执行+c即可触发提升(这是一元+的主要用途)。这与C不同:在C中,对((un)?signed)? char的值(而非lvalue)的任何使用都将使其提升。 - curiousguy
1
@xtofl:...不,它会产生“peepee”,天哪,伙计... :) - slashmais
显示剩余5条评论

16
我一直这样理解:iostream类的目的是读取和/或写入字符流,如果你仔细想想,字符只是抽象实体,只能使用字符编码来表示。C++标准非常努力地避免固定字符编码,只说“声明为字符(char)的对象应足够大,可以存储实现基本字符集中的任何成员”,因为它不需要强制“实现基本字符集”来定义C++语言;标准可以将使用哪种字符编码的决定留给实现(编译器和STL实现),并且只注明char对象表示某些编码中的单个字符。
实现编写者可以选择单字节编码,例如ISO-8859-1,甚至双字节编码,例如UCS-2。无所谓,只要char对象“足够大,可以存储实现基本字符集中的任何成员”(请注意,这明确禁止可变长度编码),那么实现甚至可以选择一种根本不兼容任何常见编码的表示基本拉丁文的编码! charsigned charunsigned char类型在名称中共享“char”,这很令人困惑,但重要的是要记住char不属于与signed charunsigned char相同的基本类型家族。 signed char属于带符号整数类型的家族:

有四种带符号整数类型:“signed char”、“short int”、“int”和“long int”。

unsigned char 是无符号整数类型家族的一员:

对于每个有符号整数类型,都存在一个相应的(但不同的)无符号整数类型: "unsigned char", "unsigned short int", "unsigned int" 和 "unsigned long int",...

charsigned charunsigned char 这三种类型之间唯一的共同点是它们“占用相同数量的存储空间并具有相同的对齐要求”。因此,您可以使用 reinterpret_castchar * 转换为 unsigned char *,以确定执行字符集中字符的数字值。

回答您的问题,STL 使用 char 作为默认类型的原因是标准流用于读取和/或写入由 char 对象表示的字符流,而不是整数(signed charunsigned char)。使用 char 而不是数值是分离关注点的一种方式。


1
charsigned char 不一样吗?哇,+1!正如 Scott Meyers 所说,Aha!http://www.artima.com/cppsource/top_cpp_aha_moments.html - Gabriel
istreamfread(来自 C 语言)都可以从流中读取字符,但是 fread 使用 unsigned char,而 istream 使用 char - qbt937

4

char代表字符,unsigned char代表原始数据的字节,signed char则代表带符号的数据。

标准并未规定在实现char时使用signed还是unsigned - 这取决于编译器。标准只规定“char”将足以在您的系统上容纳字符 - 字符在那些日子里的方式,即没有UNICODE。

使用“char”表示字符是标准的方法。使用unsigned char是一种hack,尽管它会在大多数平台上与编译器的char实现匹配。


2
使用“char”来表示字符是标准的方法。使用unsigned char只是一种hack方式。流不仅用于交换基本字符,还用于交换二进制数据(毕竟这就是ios_base::binary的作用)。如果使用unsigned char,我们将不必担心负char值,并始终获得正值。这似乎会更好。 - Johannes Schaub - litb

0

我认为this评论已经很好地解释了。引用如下:

signed char和unsigned char是算术整数类型,就像int和unsigned int一样。另一方面,char明确旨在成为“I/O”类型,表示平台上某些不透明的系统特定基本数据单元。我会按照这种精神使用它们。


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