如何在C++中使用ostream将无符号字符打印为十六进制?

86

我想在C++中使用无符号8位变量。在算术方面,unsigned charuint8_t都可以胜任(这是预期的,因为据我所知,uint8_t只是unsigned char的别名,或者调试器呈现它。

问题是,如果我使用C ++中的ostream打印变量,它将其视为char类型。如果我有:

unsigned char a = 0;
unsigned char b = 0xff;
cout << "a is " << hex << a <<"; b is " << hex << b << endl;

那么输出结果为:

a is ^@; b is 377

而不是

a is 0; b is ff

我试着使用uint8_t,但就像我之前提到的那样,它是 unsigned char 的 typedef,所以它做了相同的事情。怎样才能正确地打印我的变量?

编辑:我在代码中的许多地方都这样做。有没有办法可以不需要每次打印时都进行int转换?


3
我认为MartinStettner的回答相当混乱,我不认为值得实现额外的struct和额外的流操作符。anon的解决方案很直接并且对我来说已经足够好了。 - tdihp
在C++中,std::cout将类型为charunsigned char的变量视为字符类型,并将它们输出为字符。要输出它们的整数值,需要将它们强制转换为int类型。std::cout << std::setw(2) << std::setfill('0') << std::hex << (int)(unsigned char)c; // 或者 std::cout << std::setw(2) << std::setfill('0') << std::hex << (c & 0xff); - Nuo Y
17个回答

62

使用:

cout << "a is " << hex << (int) a <<"; b is " << hex << (int) b << endl;

如果你想要在前面填充零,则可以这样做:

#include <iomanip>
...
cout << "a is " << setw(2) << setfill('0') << hex << (int) a ; 

既然我们正在使用C风格的转换,为什么不彻底地使用终端C++的糟糕特性并使用宏!

#define HEX( x )
   setw(2) << setfill('0') << hex << (int)( x )

然后你可以说

cout << "a is " << HEX( a );

编辑:话虽如此,MartinStettner的解决方案更好!


4
我能说服你彻底避免使用邪恶的C风格转换吗?https://dev59.com/_XRB5IYBdhLWcg3wvpmc#528009 - Konrad Rudolph
1
不是在这种情况下 - 我认为这是他们唯一合理的地方。 - anon
我会做同样的事情,只是为了避免强制类型转换,我会使用cout << hex << int(a); 这意味着与强制类型转换相同,但没有强制类型转换。 :) - Brian Neal

59

我建议使用以下技术:

struct HexCharStruct
{
  unsigned char c;
  HexCharStruct(unsigned char _c) : c(_c) { }
};

inline std::ostream& operator<<(std::ostream& o, const HexCharStruct& hs)
{
  return (o << std::hex << (int)hs.c);
}

inline HexCharStruct hex(unsigned char _c)
{
  return HexCharStruct(_c);
}

int main()
{
  char a = 131;
  std::cout << hex(a) << std::endl;
}

这种解决方案写起来很简单,效率与原始解决方案相同,并且允许您选择使用“原始”字符输出。而且它是类型安全的(不使用“邪恶”的宏:-))


11
只要我们避免使用宏:为了更好地符合 C++ 的语法,你是否应该将 (int)hs.c 改写为 static_cast<int>(hs.c)? :P - Seth Johnson
15
你的代码有一个小错误。如果在代码中输入负字符,十六进制值会变成4个字节而不是2个。将类型改为 "unsigned char" 可以解决这个问题。 - Ted
14
还有一个bug(?)是上面的operator<<会改变给定流的模式为十六进制:cout << hex(a) << 100会让你感到惊讶。在修改之前应该保存流的状态,并在修改后恢复它。 - musiphil
10
我真的很惊讶C++在将char转换为十六进制方面有多么差。 - Craig Ringer
@CraigRinger,为了让乐趣更加精致:cout << hex << (int)'x' << ", " << (uint8_t)'x';仍然只会给出第一个的十六进制,而不是第二个,因为uint8_t甚至不是一个数字,而只是一个字符typedef。确实,... << (byte)'x'甚至无法编译,因为没有<<适用于byte。:)这确实很有趣。 - Sz.

40

您可以在http://cpp.indi.frih.net/blog/2014/09/tippet-printing-numeric-values-for-chars-and-uint8_t/http://cpp.indi.frih.net/blog/2014/08/code-critique-stack-overflow-posters-cant-print-the-numeric-value-of-a-char/上了解更多。我之所以发布这篇文章,是因为明显作者并没有打算这样做。

将字符打印为十六进制的最简单和最正确的技巧是

unsigned char a = 0;
unsigned char b = 0xff;
auto flags = cout.flags(); //I only include resetting the ioflags because so
                           //many answers on this page call functions where
                           //flags are changed and leave no way to  
                           //return them to the state they were in before 
                           //the function call
cout << "a is " << hex << +a <<"; b is " << +b << endl;
cout.flags(flags);

简而言之,这个方法的作用是使用一元加号运算符将一个无操作类型转换为具有正确符号的整数。因此,无符号字符转换为无符号整数,有符号字符转换为整数,而字符转换为无符号整数或整数,具体取决于您的平台上char是有符号还是无符号(许多人会感到惊讶,char是特殊的,没有指定为有符号或无符号)。

唯一的缺点是对于不熟悉该技术的人来说可能不太明显发生了什么。然而,我认为最好使用正确的技术并教导他人,而不是做一些不正确但更容易理解的事情。


3
为什么这不是最佳答案?它简明有效。 - James Pack
3
这项技术对小于无符号整数 / 整数的类型进行符号扩展,这意味着对于负的有符号字符(或例如int16_t),您将获得8个输出半字节,其中前6个是“F”。 - Brian McFarland
7
我注意到即使是对无符号字符(当大于127时),也存在该问题。因此我用掩码来解决它:(+c & 0xFF)。请注意,这里的“+”符号表示将字符强制转换为整数类型。 - To마SE
2
@To마SE,一元运算符+是不必要的,二元运算符&已经将其转换为int - Adrian

33

好的,这对我有用:

std::cout << std::hex << (0xFF & a) << std::endl;

如果您只是像建议的那样使用(int)进行转换,那么如果最高有效位为1,它可能会在a左侧添加1。因此,进行二进制AND操作可以保证输出左侧位填充为0,并将其转换为无符号整数,强制cout将其作为十六进制打印出来。
希望这可以帮助您。

4
这是一种最简单、最干净的方法。 - Adrian
我也非常喜欢这个答案!我使用了这个解决方案。 - sep
这个可以运行,但是你有什么建议吗?我该如何在数字小于等于9的情况下添加前缀0? - Shane Sepac
1
@Shn_Android_Dev 你可以这样做:https://dev59.com/jXI-5IYBdhLWcg3wta1q - VinGarcia

24
在C++20中,您可以使用std::format来实现此操作。
std::cout << std::format("a is {:x}; b is {:x}\n", a, b);

输出:

a is 0; b is ff

与此同时,您可以使用基于其构建的{fmt}库print函数使其更加容易高效(godbolt):

fmt::print("a is {:x}; b is {:x}\n", a, b);

免责声明:我是{fmt}和C++20 std::format的作者。


6

嗯,昨天似乎我重新发明了轮子...但是,这次至少是通用的轮子 :) 使用两个十六进制数字打印char,使用四个十六进制数字打印short等。

template<typename T>
struct hex_t
{
    T x;
};

template<typename T>
hex_t<T> hex(T x)
{
    hex_t<T> h = {x};
    return h;
}

template<typename T>
std::ostream& operator<<(std::ostream& os, hex_t<T> h)
{
    char buffer[2 * sizeof(T)];
    for (auto i = sizeof buffer; i--; )
    {
        buffer[i] = "0123456789ABCDEF"[h.x & 15];
        h.x >>= 4;
    }
    os.write(buffer, sizeof buffer);
    return os;
}

2
我喜欢它。简洁明了,不需要冗余信息。我希望有一个布尔值来指示是否需要大写或小写字母。或者遵守 std::uppercasestd::nouppercase 操纵器(这些操纵器操作 std::ios_base::flags::uppercase iosflag)。 - sehe

5

我认为TrungTN和anon的答案还可以,但是MartinStettner实现hex()函数的方式并不是很简单,而且太过复杂,考虑到hex << (int)mychar已经是一个解决方法。

这里是我让"<<"运算符更容易的解决方案:

#include <sstream>
#include <iomanip>

string uchar2hex(unsigned char inchar)
{
  ostringstream oss (ostringstream::out);
  oss << setw(2) << setfill('0') << hex << (int)(inchar);
  return oss.str();
}

int main()
{
  unsigned char a = 131;
  std::cout << uchar2hex(a) << std::endl;
}

实现流操作符并不值得 :-)



4

我认为我们缺少对这些类型转换如何工作的解释。

char 是平台相关的 signedunsigned。在 x86 中,char 等效于 signed char

当整数类型(charshortintlong)转换为更大容量类型时,如果是 unsigned 类型,则通过在左侧添加零来进行转换;如果是 signed 类型,则通过符号扩展进行转换。符号扩展是指将原始数字最高(最左侧)位向左复制,直到达到目标类型的位大小。

因此,如果我在一个默认为 signed char 的系统中执行以下操作:

char a = 0xF0; // Equivalent to the binary: 11110000
std::cout << std::hex << static_cast<int>(a);

我们将获得F...F0,因为前导的1位已被扩展。
如果我们想确保在任何系统中只打印F0,我们需要进行额外的中间类型转换为unsigned char,以添加零,由于它们对于仅具有8位的整数不重要,因此不会被打印:
char a = 0xF0; // Equivalent to the binary: 11110000
std::cout << std::hex << static_cast<int>(static_cast<unsigned char>(a));

这将生成F0

3
我会像MartinStettner一样做,但会添加一个额外的参数来指定数字的位数:
inline HexStruct hex(long n, int w=2)
{
  return HexStruct(n, w);
}
// Rest of implementation is left as an exercise for the reader

默认情况下,您有两位数字,但如果需要,可以设置为四位、八位或其他位数。

例如:

int main()
{
  short a = 3142;
  std:cout << hex(a,4) << std::endl;
}

这可能看起来有些过度,但正如Bjarne所说:“库应该易于使用,而不是易于编写”。


2

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