преобразовать std::массив байтов в шестнадцатеричный std::string

c

#c

Вопрос:

Я хотел бы найти способ взять массив байтов произвольного размера и вернуть шестнадцатеричную строку. Специально для протоколирования пакетов, отправляемых по сети, но я использую эквивалентную функцию, которая часто использует std::vector . Что-то вроде этого, возможно, шаблон?

std::string hex_str(const std::array<uint8_t, ??? > array);

Я искал, но все решения говорят: «рассматривайте это как массив в стиле C», и я специально спрашиваю, есть ли способ не делать этого. Я предполагаю, что причина, по которой это не в каждом FAQ по C , заключается в том, что это невозможно, и если да, может кто-нибудь объяснить, почему?

У меня уже есть эти перегрузки, а вторую можно использовать для std::array, разложив в массив в стиле C, поэтому, пожалуйста, не говорите мне, как это сделать.

std::string hex_str(const std::vector<uint8_t> amp;data);
std::string hex_str(const uint8_t *data, const size_t size);

(редактировать: вектор — это ссылка в моем коде)

Комментарии:

1. Что вы имеете в виду шестнадцатеричную строку ? Кодировка Base64 или что?

2. Кажется, вам нужен шаблон? template <std::size_t N> std::string hex_str(const std::array<uint8_t, N> array);

3. std::array Известны ли размеры во время компиляции? Если нет, то нет, размер должен быть известен во время компиляции. Что, если вместо этого вы взяли итераторы? Таким образом, кто-то может использовать любой контейнер, который он захочет.

4. Почему не один шаблон, который принимает оба std::vector и std::array ? Или, что еще лучше, диапазон итераторов? Весь смысл стандартных контейнеров, имеющих согласованный интерфейс, предназначен для подобных случаев, поэтому вы можете написать единый алгоритм, который одинаково обрабатывает несколько типов контейнеров

5. @Yksisarvinen да. Но я изо всех сил пытался написать то, что, как я полагаю, является довольно тривиальным кодом, чтобы на самом деле это сделать. Я никогда раньше не писал шаблон

Ответ №1:

Вам следует подумать о написании функции для работы с итераторами, как это делают стандартные алгоритмы. Затем вы можете использовать его с обоими std::vector std::array входами и, например:

 template<typename Iter>
std::string hex_str(Iter begin, Iter end)
{
    std::ostringstream output;
    output << std::hex << std::setw(2) << std::setfill('0');
    while(begin != end)
        output << static_cast<unsigned>(*begin  );
    return output.str();
}
 

Онлайн-демонстрация

Если вы действительно хотите избежать необходимости вызывать begin() / end() в любом контейнере, который вы передаете, вы можете определить помощника для обработки этого для вас, например:

 template<typename C>
std::string hex_str(const C amp;data) {
    return hex_str(data.begin(), data.end());
}
 

Онлайн-демонстрация

Или, если вы действительно хотите, вы можете просто свести все это к одной функции, например:

 template <typename C>
std::string hex_str(const Camp; data)
{
    std::ostringstream output;
    output << std::hex << std::setw(2) << std::setfill('0');
    for(const auto amp;elem : data)
        output << static_cast<unsigned>(elem);
    return output.str();
}
 

Онлайн-демонстрация

Комментарии:

1. Могу я просто спросить, и я не хочу показаться грубым, но действительно ли люди пишут код, используя такие потоки по-настоящему? Мне это кажется саркастичным, как будто вы говорите: «и вот почему потоки — плохая идея». Я имею в виду, что, по крайней мере, вам нужно обернуть его в «сохранить состояние потока» … «восстановить состояние потока» (если вы делаете это с помощью cout).

2. @CodeAbominator что не так с тем, как это написано? В этом случае нет состояния, заслуживающего «сохранения и восстановления». Чего мне не хватает? Конечно, манипуляторы в этом случае на самом деле не нужно повторять снова и снова, по крайней мере. Я обновил свой пример относительно этого.

3. по сравнению с printf(«% 02x») он кажется подробным и трудным для понимания. Я подозреваю, что, поскольку я не понимаю, насколько задействована магия компилятора, мне кажется, что «сначала создайте поток. Затем для каждого байта задайте потоку нужные характеристики, затем выведите в него байт. Когда вы закончите, преобразуйте его в строку» многословно и, вероятно, будет медленным, а также подверженным ошибкам. Дело даже не в том, что за несколько часов работы я могу показать, что эта точная деталь работает, а в том, что C , похоже, делает это дюжину раз в день, каждый день.

4. В чем преимущество приведения u8 к общему беззнаковому здесь? Это потому, что собственный размер быстрее или здесь есть какое-то подлинное «вы всегда должны»? Например, ошибка компилятора, если данные являются недопустимым типом для приведения или что-то в этом роде?

5. @CodeAbominator » это кажется подробным и трудным для понимания » — неважно. Вопрос не в том, как использовать потоки, а в том, как реализовать hex_str() . Я решил использовать потоки для использования std::hex манипулятора. Используйте любую реализацию, которую вы хотите. «В чем преимущество приведения u8 к общему беззнаковому здесь? » — operator<< выводит 8-битные значения в виде текстовых символов, будь то char , unsigned char , (u)int8_t , и т.д. В данном случае мы этого не хотим, поэтому приведение к int> 8 бит. Я мог бы использовать uint16_t , но unsigned это более естественный выбор для компилятора

Ответ №2:

Если вы знаете размер std::array во время компиляции, вы можете использовать параметр шаблона, не относящийся к типу.

 template<std::size_t N>
std::string hex_str( const std::array<std::uint8_t, N>amp; buffer )
{ /* Implementation */ }

int main( )
{   
    // Usage.
    std::array<std::uint8_t, 5> bytes = { 1, 2, 3, 4, 5 };
    const auto value{ hex_str( bytes ) };
}
 

Или вы можете просто создать шаблон для всего контейнера (сократить ваши перегрузки).

 template<typename Container>
std::string hex_str( const Containeramp; buffer ) 
{ /* Implementaion */ }

int main( )
{   
    // Usage.
    std::array<std::uint8_t, 5> bytes = { 1, 2, 3, 4, 5 };
    const auto value{ hex_str( bytes ) };
}
 

Комментарии:

1. большое вам спасибо. Это выглядит достаточно тривиально, и я рад, что потратил всего пару часов, пытаясь сделать это, прежде чем спрашивать.

2. @CodeAbominator Я отредактировал ответ, чтобы показать вам, как вы также можете создать шаблон для всего контейнера.

3. Если у вас есть std::array , вы можете быть уверены, что у вас есть размер во время компиляции. Я предпочитаю итераторный подход, предложенный Реми выше, но, поскольку это не обсуждается, это то, что я бы сделал.

4. И я полагаю, что могу использовать static_assert и ввести материал для самоанализа, чтобы ограничить входные данные вещами, которые действительно действительны.

5. @user4581301 Я упомянул об iterator этом подходе в комментариях, но оператору эта идея не понравилась.