对于8位架构,所有列出的类型在某种程度上都是模拟具有8位的东西。然而,使用情况基本不同,只有其中一些类型被保证具有特殊属性,使它们可用作字节类型。
概述
类型 |
定义 |
目的 |
std::byte |
enum class byte : unsigned char {}; |
规范的字节类型 ✔️ 所有特殊属性 |
unsigned char |
基本类型 |
字符 / 旧字节类型 / 小算术类型 ✔️ 所有特殊属性 |
signed char |
基本类型 |
字符 / 小算术类型 ❌ 没有特殊属性 |
char |
基本类型,与
signed char 或 unsigned char 具有相同的底层类型 |
字符 ⚠️ 仅有部分特殊属性 |
char8_t |
基本类型,底层类型为 unsigned char |
UTF-8 字符 ❌ 没有特殊属性 |
std::uint8_t |
typedef unsigned char uint8_t; (这不是保证,只是最常见的实现方式。) |
8 位无符号算术 ⚠️ 不保证特殊属性 |
std::bitset<8> |
template <std::size_t N>
class bitset; |
8 位比特集合;可能比 8 位更宽 ❌ 没有特殊属性 |
请参阅问题末尾的附录,其中列出了所有这些特殊属性,按类型分类。
std::byte(C++17)
这是C++中的规范字节类型。每当你自问“我应该使用哪种类型来表示这些字节?”时,std::byte就是答案。
请注意,std::byte非常特殊,因为有许多放宽规定,允许您以其他未定义的方式使用该类型。例如,对于std::byte,严格别名规则被放宽(参见
[basic.lval] p11),这意味着您可以将任何对象视为std::byte数组进行检查。
大多数其他类型都没有这些特殊能力,试图将它们用作字节将导致未定义行为。
适用于原始内存操作的std::byte虽然合适,但许多旧的API(如iostream库)是在其之前设计的,不适用于它。
这种类型也有些笨重(例如,my_byte == 0是不可能的)。
不要试图强行在不适用std::byte的库中使用它。
另请参阅:C++中是否有“byte”数据类型?, std::byte的目的是什么?, P0298 - 字节类型定义*
unsigned char
在C++17之前,这是最接近“byte”的东西。
unsigned char
具有std::byte
的所有特殊属性。
然而,名称非常令人困惑,有时也被视为字符。例如,std::ostream::operator<<
将其打印为ASCII字符,而不是打印其数值。此外,在使用unsigned char
进行算术运算时,会将其提升为int
,这对于一个“byte”来说似乎不合适。
总的来说,它是一种模糊不清的类型,同时是一个字节、一个字符和一个算术类型。建议使用
std::byte
、
char
、
std::uint8_t
或
std::uint_least8_t
代替。
参见:
如何在需要旧式无符号字符的地方使用新的std::byte类型?
signed char
有符号字符是无符号字符的对应物,同样令人困惑。它几乎没有std::byte和无符号字符的特殊属性,而是一种奇怪的算术和字符类型的混合体。应该避免使用它。
一个更好的选择是std::int_least8_t,它也是有符号的,也保证至少有8位宽度,但没有奇怪的字符含义。
另请参阅:有符号/无符号字符的区别
char
这是一个独立的类型,其底层类型与signed char
或unsigned char
相同。
它具有unsigned char
和std::byte
的大部分(但不是全部)特性。
例如,与unsigned char
不同,它不为在char[]
中创建的对象提供存储空间([intro.object] p3)。
char
应该用于其名称所指的用途:一个字符。
另请参阅:char!=(有符号字符),char!=(无符号字符)
char8_t
(C++20)
最初有关此类型具有类似于char
的特殊属性的讨论,但最终没有任何特殊属性。
它的底层类型是unsigned char
,但与std::byte
不同,这并不意味着它继承了任何属性。
它应该用作UTF-8字符,可能在UTF-8编码的字符串中使用。
std::uint8_t
(C++11)
这种类型是一个在C中开始的设计错误。
虽然这并不保证,但通常实现为类型别名,如
typedef unsigned char uint8_t;
这意味着它在实践中具有无符号字符的特殊属性(因为所有编译器都是这样实现的),但这些特性在标准中并没有得到保证。
它能够与其他类型进行别名处理的事实也可能对性能产生不利影响,与其作为唯一类型的别名相比。
需要注意的一点是,在C++中,并不能保证一个字节是8位。
许多人使用std::uint8_t,因为它提供了“真正”的8位安全性。
然而,std::uint8_t是可选的,并且在字节宽度大于8位的平台上不存在,因此它的可移植性并不比以下代码更好:
#include <climits>
static_assert(CHAR_BIT == 8);
对于更便携的8位算术类型,有
std::uint_fast8_t
和
std::uint_fast8_t
,它们保证存在但可能比8位更宽。
请注意,
std::uint8_t
、
std::uint_least8_t
和
std::uint_fast8_t
都可以像
unsigned char
一样提升为
int
。
参见:
uint8_t vs unsigned char,
What platforms have something other than 8-bit char?
std::bitset<8>
这是与"字节"类型最不相似的类型。
它模拟了一系列的位,或者根据视角而定的一组数字。
在大多数实现中,
std:bitset<8>
至少与
int
一样大,所以它甚至不是8位大。只有在需要一组位时才使用这种类型。它不是一个字节。
结论
std::byte
是唯一模拟字节的类型,没有多余的东西,也没有少了的东西。在可能的情况下,应该优先选择它作为字节类型。
所有其他类型要么缺少关键属性,要么具有与字节完全不同的目的。
附录
std::byte
和普通字符类型的特殊属性
部分 |
受影响的类型 |
特殊属性 |
[intro.object] p3 |
unsigned char[] , std::byte[] |
数组提供了用于放置对象的存储空间 |
[intro.object] p13 |
unsigned char[] , std::byte[] |
数组在其生命周期开始时隐式创建对象 |
[basic.life] p6.4 |
cv char* , cv unsigned char* , 和 cv std::byte* |
允许对超出生命周期的对象指针进行static_cast |
[basic.indet] |
unsigned ordinary character types, std::byte |
在初始化和赋值时允许不确定的结果 |
[basic.types.general] p2 |
char[] , unsigned char[] , std::byte[] |
可以通过数组传递可平凡复制的对象的值 |
[basic.lval] p11.3 |
char , unsigned char , std::byte |
宽松的严格别名 |
[expr.new] p16 |
char[] , unsigned char[] , std::byte[] |
new-expression中的更严格的对齐 |
[bit.cast] p2 |
unsigned ordinary character types, std::byte |
std::bit_cast 允许不确定的结果 |
注意:目前还不清楚“无符号普通字符类型”实际上是什么意思。请参阅
编辑问题 5070。