将十六进制字符串转换为字节数组

54

如何将一个可变长度的十六进制字符串(例如"01A1")转换为包含该数据的字节数组。

即将此转换为:

std::string = "01A1";

变成这样

char* hexArray;
int hexLength;

或者这个

std::vector<char> hexArray;

我希望将其写入文件,并使用hexdump -C命令,以便获取包含01A1的二进制数据。


16
@alexvii 这并不是这个问题的答案。 - dhavenith
2
您可以将std::streams设置为十六进制模式,以便以十六进制格式读写数字。 - πάντα ῥεῖ
@makulik 我尝试使用流和std::hex,但是无法使其正常工作。你能给我一个例子吗?谢谢。 - oracal
我认为不需要任何ASCII减法,只需使用C API将其转换为字符数组,除非我理解问题有误。我在下面的答案中指出了API http://stackoverflow.com/a/17273020/986760。 - fkl
根据您对另一个答案的评论,我认为您需要在问题中添加一些内容,以说明当输入字符数为奇数时应该发生什么。缺失的0应该添加到字符串的开头还是结尾? - Zan Lynx
@oracal 请看我的答案,使用stringstream方法。 - TheoretiCAL
23个回答

0

我发现了这个问题,但是被接受的答案对我来说不像是用C++解决任务的方式(这并不意味着它是一个坏答案或者其他什么,只是解释了添加这个答案背后的动机)。我回忆起了这个好的答案,并决定实现类似的东西。这里是我最终得到的完整代码(它也适用于std::wstring):

#include <cctype>
#include <cstdlib>

#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <stdexcept>
#include <string>
#include <vector>

template <typename OutputIt>
class hex_ostream_iterator :
    public std::iterator<std::output_iterator_tag, void, void, void, void>
{
    OutputIt out;
    int digitCount;
    int number;

public:
    hex_ostream_iterator(OutputIt out) : out(out), digitCount(0), number(0)
    {
    }

    hex_ostream_iterator<OutputIt> &
    operator=(char c)
    {
        number = (number << 4) | char2int(c);
        digitCount++;

        if (digitCount == 2) {
            digitCount = 0;
            *out++ = number;
            number = 0;
        }
        return *this;
    }

    hex_ostream_iterator<OutputIt> &
    operator*()
    {
        return *this;
    }

    hex_ostream_iterator<OutputIt> &
    operator++()
    {
        return *this;
    }

    hex_ostream_iterator<OutputIt> &
    operator++(int)
    {
        return *this;
    }

private:
    int
    char2int(char c)
    {
        static const std::string HEX_CHARS = "0123456789abcdef";

        const char lowerC = std::tolower(c);
        const std::string::size_type pos = HEX_CHARS.find_first_of(lowerC);
        if (pos == std::string::npos) {
            throw std::runtime_error(std::string("Not a hex digit: ") + c);
        }
        return pos;
    }
};

template <typename OutputIt>
hex_ostream_iterator<OutputIt>
hex_iterator(OutputIt out)
{
    return hex_ostream_iterator<OutputIt>(out);
}

template <typename InputIt, typename OutputIt>
hex_ostream_iterator<OutputIt>
from_hex_string(InputIt first, InputIt last, OutputIt out)
{
    if (std::distance(first, last) % 2 == 1) {
        *out = '0';
        ++out;
    }
    return std::copy(first, last, out);
}

int
main(int argc, char *argv[])
{
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " hexstring" << std::endl;
        return EXIT_FAILURE;
    }

    const std::string input = argv[1];
    std::vector<unsigned char> bytes;
    from_hex_string(input.begin(), input.end(),
                    hex_iterator(std::back_inserter(bytes)));

    typedef std::ostream_iterator<unsigned char> osit;
    std::copy(bytes.begin(), bytes.end(), osit(std::cout));

    return EXIT_SUCCESS;
}

执行命令 ./hex2bytes 61a062a063 | hexdump -C 的输出结果为:

00000000  61 a0 62 a0 63                                    |a.b.c|
00000005

关于 ./hex2bytes 6a062a063 | hexdump -C(注意字符数为奇数)的内容:

00000000  06 a0 62 a0 63                                    |..b.c|
00000005

非常好的char2int()函数!但是我担心当十六进制数字位数为奇数时,结果可能不符合预期。例如,尝试使用6a062a063。我期望得到的结果是6 a0 62 a0 63,但是你的代码却输出了6a 06 2a 06 3。 - Christophe
1
你说得对,@Christophe。十六进制数字的位数是奇数。谢谢!我已经更新了代码以处理这种情况(顺便说一下,对于被接受的答案来说并不正确,最好还是处理这样的字符串)。 - xaizek
需要注意的是,我编写了被接受的答案作为 OP 问题的最佳性能完整解决方案 :) 没有关于异常情况的问题,因此我假设(就像许多 stdc 函数一样)输入已经过预先净化。 - Niels Keurentjes

0
如果你能让你的数据看起来像这样,例如数组 "0x01"、"0xA1", 那么你就可以遍历你的数组并使用 sscanf 创建值数组。
unsigned int result;
sscanf(data, "%x", &result);         

2
这是一个“提示”还是一个答案?“试试这个”是什么意思?它会起作用吗?它与现有的答案不同吗?怎么不同? - jogojapan
@jogojapan 我很高兴编写整个代码,你真的需要吗? 你能看到基本方法的区别吗? - Anand Rathi
2
我的问题是我不明白你试图告诉我们什么。有一个提示,有一个字符串(后面跟着另一个带有“0x”前缀的版本),然后是关于某个迭代的非常简短的陈述。所有这些的含义,特别是在现有答案的背景下,对我来说并不清楚。这将影响您因此获得的赞成票/反对票。 - jogojapan

0
这是一个 C++23 的解决方案。如果你愿意,你甚至可以将它变成一个一行代码的 std::vector<std::byte> vec(...);
std::string_view in("09e1c5f70a65ac519458e7e53f36");

auto view = in
    | std::views::transform([](char c) {
        // map ASCII characters onto hex digit numeric values
        // (doesn't work correctly if c isn't a hex digit)
        return c >= '0' && c <= '9' ? c - '0' : 10 + c - 'a';
    })
    | std::views::adjacent_transform<2>([](unsigned char hi, unsigned char lo) {
        return std::byte(hi << 4 | lo);
    });

// note: you might not even need the vector and could work with the view directly

// create a vector containing all the elements in the view
std::vector<std::byte> bytes(std::from_range, view);

// if std::from_range is not support yet:
std::vector<std::byte> bytes(view.begin(), view.end());

请查看在编译器资源管理器上的实时示例

请注意,char 不适合作为“字节”类型;尽可能使用 std::byte 来代替。


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