一个未签名字符是什么?

544
在C/C++中,unsigned char有什么用?它与普通的char有何不同?
16个回答

614
在C++中,有三种不同的字符类型:
1. char 2. signed char 3. unsigned char 1. char 如果您正在使用字符类型来表示文本,请使用未经限定的char: - 它是字符字面值的类型,例如'a'或'0'(仅适用于C++,在C中它们的类型是int) - 它是组成C字符串的类型,例如"abcde"
它也可以作为一个数值进行运算,但是该值是被视为有符号还是无符号的是未指定的。请注意,在进行字符比较时要小心使用不等式 - 尽管如果您限制在ASCII范围内(0-127),基本上是安全的。
2. signed char / 3. unsigned char 如果您将字符类型用作数字,请使用: - signed char:带符号的字符类型 - unsigned char:无符号的字符类型
  • signed char,至少提供-127到127的范围(通常为-128到127)。
  • unsigned char,至少提供0到255的范围。这对于显示八位字节的十六进制值可能很有用。

“至少”是因为C++标准只给出了每种数值类型所需覆盖的最小值范围。sizeof (char)要求为1(即一个字节),但理论上一个字节可以是32位。sizeof仍然会报告其大小为1 - 这意味着你可能sizeof (char) == sizeof (long) == 1


5
请确认一下:你是否可以拥有32位字符和32位整数,并且sizeof(int) != sizeof(char)?我知道标准规定sizeof(char) == 1,但是int的相对大小是基于实际大小差异还是范围差异? - Joseph Garvin
22
在C++中有四种不同的字符类型,其中wchar_t是其中之一。 - Eric Z
19
自C++11以来,你有6种不同的类型:char、signed char、unsigned char、wchar_t、char16_t和char32_t。 - marcinj
14
常常在 sizeof 后面加上空格是因为它不是一个函数而是一个运算符。在获取变量的大小时省略括号,比如 sizeof *p 或者 sizeof (int),这样可以清楚地表明它是应用于类型还是变量,这样做甚至被认为是更好的代码风格。同样,在 return 后面加上括号是多余的,因为它不是一个函数。 - Patrick Schlüter
5
在这个上下文中,“byte”指的是内存中最小可寻址单元。C和C++标准要求一个字节至少包含8位,但它们没有指定最大值。在今天几乎所有通用计算机(包括符合最近版本的posix的任何东西)上,一个字节恰好是8位,但专用的DSP平台和复古系统可能有更大的字节。 - plugwash
显示剩余10条评论

105
这是实现相关的,因为C标准没有定义char的符号性。根据平台的不同,char可能是带有符号或无符号的,所以如果您的实现依赖于它,您需要显式地请求signed charunsigned char。如果您打算表示字符串中的字符,请使用char,因为这将与您的平台放入字符串中的内容相匹配。 signed charunsigned char之间的区别如您所预期的那样。在大多数平台上,signed char将是一个8位二进制补码数字,范围从-128127,而unsigned char将是一个8位无符号整数(0255)。请注意,标准不要求char类型具有8位,只要求sizeof(char)返回1。您可以使用limits.h中的CHAR_BIT获得char的位数。虽然今天几乎没有任何平台会出现其他情况,但这仍是可能的。
这里有一个关于这个问题的很好的总结
正如其他人在我发布这篇文章后所提到的,如果您真的想表示小整数,最好使用int8_tuint8_t

3
signed char 类型的取值范围为 -127 到 127,而不是从 -128 到 127。 - 12431234123412341234123
6
从技术上讲,C标准将-127到127定义为最小范围。虽然如此,我挑战你找到一个不使用二进制补码算术的平台。在几乎所有现代平台上,有符号字符的实际范围将是-128到127。 - Todd Gamblin
按照标准要求,CHAR_BIT 至少为8位。 - martinkunev

45
因为我觉得这真的很必要,所以我想声明一些关于 C 和 C++ 的规则(在这方面它们是相同的)。首先,unsigned char 的所有位都参与确定任何 unsigned char 对象的值。其次,unsigned char 明确地声明为无符号。
现在,我和某人讨论了将 int 类型的值 -1 转换为 unsigned char 时会发生什么。他拒绝了这样一个想法:转换后的 unsigned char 的所有位都设置为 1,因为他担心符号表示。但他不必担心。根据这个规则,转换正是预期的:

如果新类型是无符号的,则通过重复加上或减去可以在新类型中表示的最大值加 1 直到该值在新类型范围内来转换该值。

这是数学定义。C++ 用模运算描述它,得出相同的规则。无论如何,不能保证整数 -1 的所有位都是 1,然后再进行转换。那么,我们有什么依据可以声称转换后的 unsigned char 的所有 CHAR_BIT 位都被置为 1 呢?
- 所有位都参与确定其值 - 即对象中不存在填充位。 - 仅添加一次 UCHAR_MAX+1-1 就会产生在范围内的值,即 UCHAR_MAX 实际上这已经足够了!所以,每当你想要一个所有位都是 1 的 unsigned char 时,就做:
unsigned char c = (unsigned char)-1;

同时也可以得出,一个转换并不仅仅是截断高阶位。对于二进制补码而言,它很幸运地只是一种截断方式,但是对于其他符号表示法而言,并非总是如此。


2
为什么不直接使用 UCHAR_MAX - Nicolás
1
因为(unsigned type)-1是某种习惯用法,而~0则不是。 - Patrick Schlüter
1
如果我有这样的代码 int x = 1234char *y = &x1234 的二进制表示为 00000000 00000000 00000100 11010010。我的机器是小端模式,所以它会反转并将其存储在内存中,即 11010010 00000100 00000000 00000000,LSB 先出现。现在主要问题是,如果我使用 printf("%d", *p)printf 只会读取第一个字节 11010010,但输出结果是 -46,而 11010010210,那么为什么会打印 -46。我真的很困惑,我猜可能是某种字符到整数的提升导致了一些问题,但我不知道具体原因。 - Suraj Jain

33
关于 unsigned char 的用法示例:
在计算机图形学中,通常将每个颜色分量分配一个字节(byte),因此经常使用unsigned char。很常见地,RGB(或RGBA)颜色被表示为24(或32)位,每个颜色分量均为一个unsigned char。由于unsigned char的值范围在[0,255]之间,这些值通常被解释为:
  • 0 表示完全缺少某种颜色分量。
  • 255 表示某种颜色颜料的100%。 因此,你可以得到红色RGB为(255,0,0) -> (100%红色,0%绿色,0%蓝色)。
    为什么不使用signed char?算术和位移变得麻烦。如前所述,signed char的范围实际上向左移了128。将RGB转换为灰度的一种非常简单和幼稚(大多数人都不使用)的方法是平均所有三个颜色分量,但是当颜色分量的值为负值时,会遇到问题。红色(255,0,0)使用unsigned char算术平均值为(85,85,85)。然而,如果值是signed char(127,-128,-128),我们将得到(-99,-99,-99),这将是我们unsigned char空间中的(29,29,29),这是不正确的。

1
我可能漏掉了什么,但我不明白一个固定的移位如何会破坏算术平均值。127、-128和-128的平均值为-43,而不是-99。如果你加上128,你得到85,这与你的无符号示例相同。 - Icydog

13

signed char 范围为-128到127;unsigned char 范围为0到255。

char 可能等同于 signed char 或 unsigned char,具体取决于编译器,但这是一种不同的类型。

如果您正在使用 C 风格的字符串,请使用 char。如果您需要对字符进行算术运算(非常罕见),请明确指定 signed 或 unsigned 以实现可移植性。


如何使用pybind11将"unsigned char *"转换为Python字节(bytes)? - CS QGB

11

unsigned char 表示仅支持正数值,例如0255

相比之下,

signed char 可以表示正数和负数,例如-128+127


10

charunsigned char在所有平台上不能保证是8位类型,但它们保证是8位或更大的类型。一些平台拥有9位、32位或64位字节。然而,今天最常见的平台(Windows、Mac、Linux x86等)具有8位字节。


10

unsigned char是一个无符号的字节值(0到255)。 您可能会认为char是指“字符”,但实际上它是一个数值。常规的char是有符号的,因此您有128个值,并且这些值使用ASCII编码映射到字符。 但无论哪种情况,您在内存中存储的都是字节值。


"常规字符是有符号的": 不是的,这取决于实现。而且不能保证无符号字符的值范围从0到255:至少是这样,但它可能更广。 - Fabio says Reinstate Monica
char 不能保证是一个字节。 - qwr
@qwr sizeof(char)保证为1,sizeof(signed char)sizeof(unsigned char)同样也是1。所以是的,char始终是1字节。这里有一个支持的答案。字节不总是确切地8位(至少8位),因此unsigned char的范围不一定是0到255。但这是一个无关的讨论。 - Alexander Guyer

9

在直接数值方面,当数值在CHAR_MINCHAR_MAX之间时,通常使用常规的char,而无符号的char提供了正数端两倍的范围。例如,如果CHAR_BIT为8,则常规char的范围仅保证为[0, 127](因为它可以是有符号或无符号),而unsigned char将为[0, 255],signed char将为[-127, 127]。

就其用途而言,标准允许将POD(纯旧数据)对象直接转换为无符号char数组。这使您可以检查对象的表示和位模式。对于char或signed char,不存在相同的安全类型强制转换的保证。


实际上,它通常会是[-128, 128]。 - RastaJedi
标准只是正式定义对象表示为“unsigned char”序列,而不是特定的数组,并且任何“转换”只是通过从对象复制到已声明的“unsigned char”数组,然后检查后者来正式定义的。不清楚OR是否可以直接重新解释为这样的数组,以及它所涉及的指针算术的允许性,即在此用法中,“sequence”是否等于“array”。有一个核心问题#1701开放,希望得到澄清。值得庆幸的是,最近这种歧义真的让我很烦恼。 - underscore_d
2
@RastaJedi 不,不行。这是物理上不可能用8位表示的范围,-128…+128只支持2^8 == 256个离散值,但是对于0 = 257,-128…+128 = 2 * 128 + 1。补码表示允许-127…+127,但有两个(双极)零。二进制补码表示保留一个零,但通过在负数侧多取一个值来弥补范围;它允许-128…+127。(在更大的位宽度下也是如此。) - underscore_d
关于我的第二条评论,可以合理地_假设_我们可以获取到OR的第一个unsigned char指针,然后使用++ ptr从那里开始读取每个字节...但是据我所知,它没有被明确定义为允许的,因此我们需要从很多其他段落(并且在许多方面,仅仅存在memcpy)中推断出这是_'可能是可以的'_,就像拼图一样。这不是理想的情况。好吧,也许措辞最终会得到改善。这是我提到但没有链接空间的CWG问题 - http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1701 - underscore_d
@underscore_d 抱歉,那是个打错字。我本来想打的是[-128, 127] :p。是的,我知道有关于带符号/幅值的双零('正'和'负'零)的问题。我可能当时太累了 :p。 - RastaJedi

8

unsigned char 是所有位操作的核心。在几乎所有平台和编译器中,unsigned char 简单地是一个 字节,通常是一个无符号 8 位整数,可以被当作小整数或一组位来处理。

此外,正如其他人所说,标准并未定义 char 的符号。因此,您有三种不同的 char 类型:charsigned charunsigned char


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