如何使用 ostream 在 c + + 中打印十六进制的无符号字符?

我想在 C + + 中处理无符号8位变量。无论是 unsigned char还是 uint8_t都可以做到这一点(这是预期的,因为 AFAIK 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,所以它也可以做同样的事情。如何正确打印变量?

编辑: 我在整个代码中的许多地方都是这样做的。有没有什么办法,我可以做这个 没有铸造到 int每次我想打印?

160467 次浏览

用途:

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 的解决方案要好得多!

这也会奏效:

std::ostream& operator<< (std::ostream& o, unsigned char c)
{
return o<<(int)c;
}


int main()
{
unsigned char a = 06;
unsigned char b = 0xff;
std::cout << "a is " << std::hex << a <<"; b is " << std::hex << b << std::endl;
return 0;
}

我建议使用以下技巧:

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;
}

它写起来很短,与原始解决方案具有相同的效率,并且允许您选择使用“原始”字符输出。而且它是类型安全的(不使用“ evil”宏: ——)

我会像 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;
}

这可能看起来有点夸张,但正如比亚内所说: “库应该易于使用,而不是易于编写”。

我建议:

std::cout << setbase(16) << 32;

摘自: Http://www.cprogramming.com/tutorial/iomanip.html

您可以尝试以下代码:

unsigned char a = 0;
unsigned char b = 0xff;
cout << hex << "a is " << int(a) << "; b is " << int(b) << endl;
cout << hex
<<   "a is " << setfill('0') << setw(2) << int(a)
<< "; b is " << setfill('0') << setw(2) << int(b)
<< endl;
cout << hex << uppercase
<<   "a is " << setfill('0') << setw(2) << int(a)
<< "; b is " << setfill('0') << setw(2) << int(b)
<< endl;

产出:

a is 0; b is ff

a is 00; b is ff

a is 00; b is FF

我认为 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;
}

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

我已经用这种方式。

    char strInput[] = "yourchardata";
char chHex[2] = "";


int nLength = strlen(strInput);
char* chResut = new char[(nLength*2) + 1];
memset(chResut, 0, (nLength*2) + 1);






for (int i = 0; i < nLength; i++)
{
sprintf(chHex, "%02X", strInput[i]& 0x00FF);
memcpy(&(chResut[i*2]), chHex, 2);
}


printf("\n%s",chResut);
delete chResut;
chResut = NULL;

嗯,看起来我昨天重新发明了轮子... ... 但是,嘿,至少这次是一个普通的轮子:) char印有两个十六进制数字,short印有4个十六进制数字等等。

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;
}

我想张贴我的重新发明版本的基础上@FredOverflow 的。我做了以下修改。

修正:

  • operator<<的 Rhs 应该是 const参考类型。在@FredOverflow 的代码中,h.x >>= 4改变了输出 h,令人惊讶的是它与标准库不兼容,并且类型 T要求是可复制构造的。
  • 假设只有 CHAR_BITS是4的倍数。@ FredOverflow 的代码假设 char是8位的,这在 DSP 上的一些实现中并不总是正确的,特别是,char是16位、24位、32位等等的情况并不少见。

改善:

  • 支持所有其他可用于整型的标准库操作器,例如 std::uppercase。因为在 _print_byte中使用了格式输出,所以仍然可以使用标准库操作器。
  • 添加 hex_sep以打印单独的字节(注意,在 C/C + + 中,“ byte”根据定义是大小为 char的存储单元)。添加一个模板参数 Sep,并分别在 hexhex_sep中实例化 _Hex<T, false>_Hex<T, true>
  • 避免二进制代码膨胀。函数 _print_byteoperator<<中提取出来,带有 函数参数函数参数 size,以避免对不同的 Size进行实例化。

更多关于二进制代码膨胀的资料:

正如改进3中提到的,无论 hexhex_sep使用得多么广泛,在二进制代码中只会存在(几乎)两个重复函数的副本: _print_byte<true>_print_byte<false>。您可能意识到,使用完全相同的方法也可以消除这种重复: 添加一个函数参数 sep。是的,但是如果这样做,则需要一个运行时 if(sep)。我想要一个通用的库工具,可以在程序中广泛使用,因此我妥协的复制,而不是运行时开销。我通过使用编译时 if: C + + 11 std::conditional实现了这一点,函数调用的开销有望被 inline优化掉。

H:

namespace Hex
{
typedef unsigned char Byte;


template <typename T, bool Sep> struct _Hex
{
_Hex(const T& t) : val(t)
{}
const T& val;
};


template <typename T, bool Sep>
std::ostream& operator<<(std::ostream& os, const _Hex<T, Sep>& h);
}


template <typename T>  Hex::_Hex<T, false> hex(const T& x)
{ return Hex::_Hex<T, false>(x); }


template <typename T>  Hex::_Hex<T, true> hex_sep(const T& x)
{ return Hex::_Hex<T, true>(x); }


#include "misc.tcc"

返回文章页面

namespace Hex
{


struct Put_space {
static inline void run(std::ostream& os) { os << ' '; }
};
struct No_op {
static inline void run(std::ostream& os) {}
};


#if (CHAR_BIT & 3) // can use C++11 static_assert, but no real advantage here
#error "hex print utility need CHAR_BIT to be a multiple of 4"
#endif
static const size_t width = CHAR_BIT >> 2;


template <bool Sep>
std::ostream& _print_byte(std::ostream& os, const void* ptr, const size_t size)
{
using namespace std;


auto pbyte = reinterpret_cast<const Byte*>(ptr);


os << hex << setfill('0');
for (int i = size; --i >= 0; )
{
os << setw(width) << static_cast<short>(pbyte[i]);
conditional<Sep, Put_space, No_op>::type::run(os);
}
return os << setfill(' ') << dec;
}


template <typename T, bool Sep>
inline std::ostream& operator<<(std::ostream& os, const _Hex<T, Sep>& h)
{
return _print_byte<Sep>(os, &h.val, sizeof(T));
}


}

测试:

struct { int x; } output = {0xdeadbeef};
cout << hex_sep(output) << std::uppercase << hex(output) << endl;

产出:

de ad be ef DEADBEEF

你可以在 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);

读者可以简要了解这种方法的工作原理,单元 + 运算符强制将 no op 类型转换为具有正确符号的 int。因此,一个无符号字符转换为无符号整数,一个有符号字符转换为整数,一个字符转换为无符号整数或整数取决于是否有符号字符或无符号在您的平台上(这是一个令人震惊的很多字符是特殊的,没有指定要么有符号或无符号)。

这种技术的唯一缺点是,对于一个不熟悉它的人来说,它可能并不明显。然而,我认为最好是使用正确的技术,并教给别人,而不是做一些不正确的事情,但更立即清楚。

我意识到这是一个老问题,但它也是谷歌搜索一个非常类似问题的解决方案的最佳结果,这个问题就是希望在模板类中实现任意整数到十六进制字符串的转换。我的最终目标实际上是一个 Gtk::Entry子类模板,它允许编辑十六进制中的各种整数宽度,但这不是重点。

这将一元 operator+技巧与 <type_traits>中的 std::make_unsigned结合起来,以防止 这个答案中出现符号延伸负 int8_tsigned char值的问题

无论如何,我相信这是比任何其他通用解决方案更简洁。它应该适用于 任何有符号或无符号整数类型,如果尝试使用任何非整数类型实例化函数,则会引发编译时错误。

template <
typename T,
typename = typename std::enable_if<std::is_integral<T>::value, T>::type
>
std::string toHexString(const T v)
{
std::ostringstream oss;
oss << std::hex << +((typename std::make_unsigned<T>::type)v);
return oss.str();
}

一些例子的用法:

int main(int argc, char**argv)
{
int16_t val;
// Prints 'ff' instead of "ffffffff". Unlike the other answer using the '+'
// operator to extend sizeof(char) int types to int/unsigned int
std::cout << toHexString(int8_t(-1)) << std::endl;


// Works with any integer type
std::cout << toHexString(int16_t(0xCAFE)) << std::endl;


// You can use setw and setfill with strings too -OR-
// the toHexString could easily have parameters added to do that.
std::cout << std::setw(8) << std::setfill('0') <<
toHexString(int(100)) << std::endl;
return 0;
}

更新: 或者,如果您不喜欢使用 ostringstream的想法,您可以将模板和一元操作符技巧与接受的答案的基于 struct 的解决方案结合起来。注意,在这里,我通过删除整数类型的检查来修改模板。make_unsigned的使用可能足以保证编译时类型的安全性。

template <typename T>
struct HexValue
{
T value;
HexValue(T _v) : value(_v) { }
};


template <typename T>
inline std::ostream& operator<<(std::ostream& o, const HexValue<T>& hs)
{
return o << std::hex << +((typename std::make_unsigned<T>::type) hs.value);
}


template <typename T>
const HexValue<T> toHex(const T val)
{
return HexValue<T>(val);
}


// Usage:
std::cout << toHex(int8_t(-1)) << std::endl;

我在 win32/linux (32/64位)上使用以下代码:

#include <iostream>
#include <iomanip>


template <typename T>
std::string HexToString(T uval)
{
std::stringstream ss;
ss << "0x" << std::setw(sizeof(uval) * 2) << std::setfill('0') << std::hex << +uval;
return ss.str();
}

我觉得这样挺好:

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

如果你只是按照建议转换 (int),如果 a的最高有效位是1,那么它可能会在 a的左边加上1。因此,使用这个二进制 AND 操作可以保证输出的左边位被0填充,并且还可以将其转换为无符号 int,从而迫使 cout 将其作为十六进制打印出来。

希望这个能帮上忙。

如果您使用的是预填充和有符号字符,请注意不要添加不需要的“ F”

字符 = 0xBE; Cout < < setfill (’0’) < < setw (2) < < hex < < unsignedshort (out _ role) ;

指纹: ffbe

在 ffffbe 中使用 int 代替短结果

为了防止不必要的 F,你可以很容易地掩盖它们。

字符 = 0xBE; Cout < < setfill (’0’) < < setw (2) < < hex < < unsignedshort (out _ role) & 0xFF;

我认为我们忽略了对这些类型转换如何工作的解释。

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

在 C + + 20中,你可以使用 std::format来完成以下工作:

std::cout << std::format("a is {:x}; b is {:x}\n", a, b);

产出:

a is 0; b is ff

同时你可以使用 { fmt }库std::format是基于。{ fmt }还提供了 print函数,使得这个过程更加简单和高效(Godbolt) :

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

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