如何将字节打印成十六进制?

42

我知道在C#中可以使用String.Format方法。但是在C++中要怎么做呢?是否有一种方法可以将一个字节转换为十六进制?只需要将一个长度为8字节的数据转换为十六进制,我该怎么做呢?


sprintf()?填充填充填充 - Martin James
@arrowdodger 抱歉,是的,我想要十六进制的。 - Danny
10个回答

79

如果您想使用C++流而不是C函数,可以执行以下操作:

int ar[] = { 20, 30, 40, 50, 60, 70, 80, 90 };
const int siz_ar = sizeof(ar) / sizeof(int);

for (int i = 0; i < siz_ar; ++i)
    cout << ar[i] << " ";
cout << endl;

for (int i = 0; i < siz_ar; ++i)
    cout << hex << setfill('0') << setw(2) << ar[i] << " ";
cout << endl;

非常简单。

输出:

20 30 40 50 60 70 80 90
14 1e 28 32 3c 46 50 5a 

59
值得注意的是,你应该 #include <iomanip>,而且 hexsetfillsetw 都在 std 命名空间中。另外 hex 是持久的,所以你可以将其放在循环之外,并且在完成后应调用 dec,这样流就会把未来的整数值打印成十进制,假设这是你想要的。 - Drew Noakes
3
非常好的答案,可以加上一些例子来解释hexsetfill是什么。 - jb.
8
为了进一步解释@Andrew所说的,如果你有一个字符c并且想要将大于0x80的值以十六进制形式打印出来,你需要将它转换为(unsigned int)(unsigned char)c。否则,你会打印出32位的二进制补码。 - jtbr
@DrewNoakes 我已经尝试在以下网址中涵盖了这些要点:https://dev59.com/sWkv5IYBdhLWcg3wbgXQ#53673624 - Ciro Santilli OurBigBook.com
3
@jbr unsigned char 不起作用。对于新手来说,如果你有 char a = 20; cout << hex << a << endl;,它会给你垃圾值。因为 "char 具有带有 operator<< 的 ostream 特殊重载"。所以你需要转换为例如 int,像这样 cout << hex << (int)a << endl;。至于打印大于 0x80 的值作为十六进制,也需要进行转换。原因是 在 c 中打印十六进制字符 - Rick
刚注意到这可能不是OP想要的。OP想要的是该数组可能是00 00 00 20 00 00 00 30 - Ciro Santilli OurBigBook.com

38

你可以将一个字节(无符号字符)分别转换为数组,如下所示:

char buffer [17];
buffer[16] = 0;
for(j = 0; j < 8; j++)
    sprintf(&buffer[2*j], "%02X", data[j]);

我假设他想将一个数字数组转换为十六进制数字数组,但我猜他可能想要一个十六进制字符串。 - bentech
9
我会指出这实际上是C语言的写法,而不是C ++。我更愿意接受@Component 10的答案。 - arrowd
3
如果所讨论的字符为负数,可变参数函数的类型提升将导致前六个字符变为“F”。 - geometrian
1
你可能错过了在buffer[2*j]之前的&符号。 - Arthur P. Golubev
@imallett 是的,肯定应该使用%hhxunsigned char。对于新手:在C语言中打印十六进制字符 - Rick
这个问题是关于C++,不是C。 - leo60228

21
static void print_buf(const char *title, const unsigned char *buf, size_t buf_len)
{
    size_t i = 0;
    fprintf(stdout, "%s\n", title);
    for(i = 0; i < buf_len; ++i)
    fprintf(stdout, "%02X%s", buf[i],
             ( i + 1 ) % 16 == 0 ? "\r\n" : " " );

}

C++:

void print_bytes(std::ostream& out, const char *title, const unsigned char *data, size_t dataLen, bool format = true) {
    out << title << std::endl;
    out << std::setfill('0');
    for(size_t i = 0; i < dataLen; ++i) {
        out << std::hex << std::setw(2) << (int)data[i];
        if (format) {
            out << (((i + 1) % 16 == 0) ? "\n" : " ");
        }
    }
    out << std::endl;
}

19

你可以使用C++20的std::format,它类似于C#中的String.Format

std::string s = std::format("{:x}", std::byte(42)); // s == 2a

std::format 得到广泛应用之前,您可以使用 {fmt} 库std::format 是基于 (godbolt)。

std::string s = fmt::format("{:x}", std::byte(42)); // s == 2a

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


1
很棒的库...!很抱歉说这对我不起作用。您的godbolt链接如此说明,我的本地编译器也是如此,在从GitHub获得fmt的新检查时。 - Oliver Schönrock
似乎在主干上出现了回归问题。8.1.1 版本正常工作。godbolt 在主干上,没有 8.1.1 版本可用。在 gcc-11 和 clang-13 上的结果相同。 - Oliver Schönrock
已在此处报告:https://github.com/fmtlib/fmt/issues/2792 - Oliver Schönrock
1
感谢您的报告。一个错误的头文件已经被包含了:现在已经修复。 - vitaut
1
我认为std::format不支持std::byte。虽然{fmt}支持,但是你在{fmt}中使用的枚举类型不在std中。 - Barry

11

在现代C++中打印任意结构

到目前为止,所有答案都只告诉您如何打印整数数组,但是我们也可以打印任意结构,只要我们知道它的大小。下面的示例创建了这样的结构,并通过其字节迭代指针,将它们打印到输出:

#include <iostream>
#include <iomanip>
#include <cstring>

using std::cout;
using std::endl;
using std::hex;
using std::setfill;
using std::setw;

using u64 = unsigned long long;
using u16 = unsigned short;
using f64 = double;

struct Header {
    u16 version;
    u16 msgSize;
};

struct Example {
    Header header;
    u64 someId;
    u64 anotherId;
    bool isFoo;
    bool isBar;
    f64 floatingPointValue;
};

int main () {
    Example example;
    // fill with zeros so padding regions don't contain garbage
    memset(&example, 0, sizeof(Example));
    example.header.version = 5;
    example.header.msgSize = sizeof(Example) - sizeof(Header);
    example.someId = 0x1234;
    example.anotherId = 0x5678;
    example.isFoo = true;
    example.isBar = true;
    example.floatingPointValue = 1.1;

    cout << hex << setfill('0');  // needs to be set only once
    auto *ptr = reinterpret_cast<unsigned char *>(&example);
    for (int i = 0; i < sizeof(Example); i++, ptr++) {
        if (i % sizeof(u64) == 0) {
            cout << endl;
        }
        cout << setw(2) << static_cast<unsigned>(*ptr) << " ";
    }

    return 0;
}

这里是输出结果:

05 00 24 00 00 00 00 00 
34 12 00 00 00 00 00 00 
78 56 00 00 00 00 00 00 
01 01 00 00 00 00 00 00 
9a 99 99 99 99 99 f1 3f

注意到这个例子还展示了内存对齐的工作。我们可以看到version占用2个字节(05 00),其后是2个字节的msgSize24 00),然后是4个字节的填充,再之后是someId34 12 00 00 00 00 00 00)和anotherId78 56 00 00 00 00 00 00)。接着是占用1个字节的isFoo01)和另一个字节的isBar01),再加上6个字节的填充,最后以IEEE 754标准表示的floatingPointValue双精度字段结束。
此外,请注意所有值都表示为小端(最不重要的字节先出现),因为它是在Intel平台上编译和运行的。

4

这是一种改良版的Nibble转Hex方法。

void hexArrayToStr(unsigned char* info, unsigned int infoLength, char **buffer) {
    const char* pszNibbleToHex = {"0123456789ABCDEF"};
    int nNibble, i;
    if (infoLength > 0) {
        if (info != NULL) {
            *buffer = (char *) malloc((infoLength * 2) + 1);
            buffer[0][(infoLength * 2)] = 0;
            for (i = 0; i < infoLength; i++) {
                nNibble = info[i] >> 4;
                buffer[0][2 * i] = pszNibbleToHex[nNibble];
                nNibble = info[i] & 0x0F;
                buffer[0][2 * i + 1] = pszNibbleToHex[nNibble];
            }
        } else {
            *buffer = NULL;
        }
    } else {
        *buffer = NULL;
    }
}

3
我不知道比以下方法更好的办法:

unsigned char byData[xxx]; 

int nLength = sizeof(byData) * 2;
char *pBuffer = new char[nLength + 1];
pBuffer[nLength] = 0;
for (int i = 0; i < sizeof(byData); i++)
{
    sprintf(pBuffer[2 * i], "%02X", byData[i]);
}

您可以使用“Nibble to Hex”方法加快速度
unsigned char byData[xxx];

const char szNibbleToHex = { "0123456789ABCDEF" };

int nLength = sizeof(byData) * 2;
char *pBuffer = new char[nLength + 1];
pBuffer[nLength] = 0;
for (int i = 0; i < sizeof(byData); i++)
{
    // divide by 16
    int nNibble = byData[i] >> 4;
    pBuffer[2 * i]  = pszNibbleToHex[nNibble];

    nNibble = byData[i] & 0x0F;
    pBuffer[2 * i + 1]  = pszNibbleToHex[nNibble];

}

使用 sprintf() 通常被认为是危险的,有充分的理由。在这里,只要你仔细地分配了足够的空间,使用它就可以了,但一般情况下,我会避免使用它。我更喜欢使用 nibble 方法。 - cmaster - reinstate monica
应该是 const char* szNibbleToHex - lbenini
或者应该是const char szNibbleToHex[16] = {"0123456789ABCDEF"}; - Codebeat

1
如果字节数组被定义为char[],并且大写字母之间用空格分隔,请参考以下答案。
void debugArray(const unsigned char* data, size_t len) {
    std::ios_base::fmtflags f( std::cout.flags() );
    for (size_t i = 0; i < len; ++i)
        std::cout << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << (((int)data[i]) & 0xFF) << " ";
    std::cout << std::endl;
    std::cout.flags( f );
}

例子:

unsigned char test[]={0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
debugArray(test, sizeof(test));

输出:

01 02 03 04 05 06


0

又一个 C++17 的替代方案,为什么不呢!

std::cout<<std::hex<<std::setfill('0');

struct {
    std::uint16_t first{666};
    std::array<char,4> second{'a','b','c','d'};
} my_struct;

auto ptr = reinterpret_cast<std::byte*>(&my_struct);
auto buffer = std::vector<std::byte>(ptr, ptr + sizeof(my_struct));
std::for_each(std::begin(buffer),std::end(buffer),[](auto byte){
    std::cout<<std::setw(2)<<std::to_integer<int>(byte)<<' ';
});

可执行代码在这里


0

使用C++流并在之后恢复状态

这是如何将字节打印为十六进制?的一个变体,但是:

main.cpp

#include <iomanip>
#include <iostream>

int main() {
    int array[] = {0, 0x8, 0x10, 0x18};
    constexpr size_t size = sizeof(array) / sizeof(array[0]);

    // Sanity check decimal print.
    for (size_t i = 0; i < size; ++i)
        std::cout << array[i] << " ";
    std::cout << std::endl;

    // Hex print and restore default afterwards.
    std::ios cout_state(nullptr);
    cout_state.copyfmt(std::cout);
    std::cout << std::hex << std::setfill('0') << std::setw(2);
    for (size_t i = 0; i < size; ++i)
        std::cout << array[i] << " ";
    std::cout << std::endl;
    std::cout.copyfmt(cout_state);

    // Check that cout state was restored.
    for (size_t i = 0; i < size; ++i)
        std::cout << array[i] << " ";
    std::cout << std::endl;
}

编译并运行:

g++ -o main.out -std=c++11 main.cpp
./main.out

输出:

0 8 16 24 
00 8 10 18 
0 8 16 24

在Ubuntu 16.04,GCC 6.4.0上进行了测试。


这不是线程安全的。这可能或可能不重要,这取决于使用情况。然而,我认为至少值得一提。 - Lightness Races in Orbit
哦,而且它也不是异常安全的。这就是为什么你链接的页面有更好的替代方案的原因 ;) - Lightness Races in Orbit

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