如何使用ostream在c ++中打印unsigned char作为十六进制?

时间:2009-03-23 12:42:14

标签: c++ formatting ostream

我想在C ++中使用无符号的8位变量。就算术而言unsigned charuint8_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

17 个答案:

答案 0 :(得分:49)

我建议使用以下技术:

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

写入时间短,效率与原始解决方案相同,它允许您选择使用“原始”字符输出。它是类型安全的(不使用“邪恶”宏: - ))

答案 1 :(得分:44)

使用:

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的解决方案更好!

答案 2 :(得分:30)

您可以在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/详细了解相关信息。我只发布这个,因为很明显上述文章的作者并不打算这样做。

将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);

读者摘要的工作原理是,unary +运算符强制将no op类型转换为具有正确签名的int。因此,unsigned char转换为unsigned int,signed char转换为int,char将转换为unsigned int或int,具体取决于char是否在您的平台上签名或签名(对于很多char来说,这是特别令人震惊的并且未指定为有符号或无符号)。

这种技术的唯一不利之处在于,对于不熟悉它的人来说可能并不明显。但是,我认为最好使用正确的技术并教导其他人,而不是做一些不正确但更直接明确的事情。

答案 3 :(得分:12)

在C ++ 20中,您将可以使用std::format来做到这一点:

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

输出:

a is 0; b is ff

在此期间,您可以使用the {fmt} librarystd::format是基于。 {fmt}还提供了print功能,使此操作变得更加轻松和高效(godbolt):

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

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

答案 4 :(得分:5)

嗯,好像我昨天重新发明了轮子......但是嘿,这次至少它是一个通用的轮子:) char s印有两个十六进制数字,short s 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;
}

答案 5 :(得分:4)

我认为TrungTN和anon的答案是可以的,但是MartinStettner实现hex()函数的方式并不简单,而且太暗,考虑到hex&lt;&lt; (int)mychar已经是一种解决方法。

这是我的解决方案“&lt;&lt;”操作员更容易:

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

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

答案 6 :(得分:4)

我会像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所说:“图书馆应该易于使用,不易写”。

答案 7 :(得分:4)

嗯,这对我有用:

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

如果您只是按照建议转换(int),如果其最高有效位为1,它可能会在a的左侧添加1。因此,使这个二进制AND运算保证输出将填充左边的位按0并将其转换为unsigned int,强制cout将其打印为十六进制。

我希望这会有所帮助。

答案 8 :(得分:2)

您可以尝试以下代码:

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

答案 9 :(得分:2)

我建议:

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

取自: http://www.cprogramming.com/tutorial/iomanip.html

答案 10 :(得分:2)

我在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();
}

答案 11 :(得分:2)

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

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

答案 12 :(得分:1)

我想发布基于@FredOverflow的重新发明版本。我做了以下修改。

修复:

  • operator<<的Rhs应为const引用类型。在@ FredOverflow的代码中,h.x >>= 4更改输出h,这与标准库非常不兼容,并且类型T必须是可复制构造的。
  • 假设只有CHAR_BITS是4的倍数。@ FredOverflow的代码假设char是8位,在某些DSP实现中并不总是如此,特别是char { {1}}是16位,24位,32位等。

改善:

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

有关二进制代码膨胀的更多内容:

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

hex_print.h:

inline

hex_print.tcc:

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;

答案 13 :(得分:1)

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

这将一元operator+技巧与std::make_unsigned中的<type_traits>相结合,以防止在{{}}中出现符号延伸的负int8_tsigned char值问题{3}}

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

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的想法,可以将模板和一元操作员技巧与接受的答案的基于结构的解决方案相结合下列。请注意,在这里,我通过删除整数类型的检查来修改模板。 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;

答案 14 :(得分:0)

如果您使用的是预填充字符和签名字符,请注意不要添加不需要的'F's

char out_character = 0xBE; cout << setfill('0') << setw(2) << hex << unsigned short(out_character);

打印:ffbe

使用int而不是ffffffbe中的短结果

要防止不必要的f,您可以轻松地将其屏蔽掉。

char out_character = 0xBE; cout << setfill('0') << setw(2) << hex << unsigned short(out_character) & 0xFF;

答案 15 :(得分:-1)

这也有效:

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

答案 16 :(得分:-2)

我用过这种方式。

    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;