В потоке это кто-то заметил, что следующий код следует использовать только в «игрушечных» проектах. К сожалению, он не вернулся, чтобы сказать, почему он не промышленного качества, поэтому я надеялся, что кто-нибудь из сообщества сможет либо заверить меня, что код в порядке (потому что он мне очень нравится), либо определить, что не так.
template< class T1, class T2>
void hexascii( T1& out, const T2& in )
{
out.resize( in.size() * 2 );
const char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7','8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
T1::iterator outit = out.begin();
for( T2::const_iterator it = in.begin(); it != in.end(); ++it )
{
*outit++ = hexDigits[*it >> 4];
*outit++ = hexDigits[*it & 0xF];
}
}
template<class T1, class T2>
void asciihex( T1& out, const T2& in )
{
size_t size = in.size;
assert( !(size % 2) );
out.resize( size / 2 );
T1::iterator outit = out.begin();
for( T2::const_iterator it = in.begin(); it != in.end(); it += 2, ++outit )
{
*outit = ((( (*it > '9' ? *it - 0x07 : *it) - 0x30) << 4) & 0x00f0) +
(((*(it+1) > '9' ? *(it+1) - 0x07 : *(it+1)) - 0x30) & 0x000f);
}
}
Обновлено: Спасибо за вашу помощь, ребята, вы сделали несколько больших улучшений. Я написал функции в двух предложенных стилях из ваших ответов. Некоторое грубое тестирование предполагает, что второй метод немного быстрее, чем первый, но, IMO, это перевешивается улучшенной читабельностью первого.
template<class T1>
void asciihex2( T1& out, const std::string& in )
{
dassert( sizeof(T1::value_type)==1 );
size_t size = in.size();
assert( !(size % 2) );
out.resize( size / 2 );
T1::iterator outit = out.begin();
for( size_t i = 0; i < in.size(); i += 2 )
{
int tmp;
sscanf( in.c_str() + i, "%02X", &tmp );
*outit++ = tmp;
}
}
template<class T1>
void asciihex3( T1& out, const std::string& in )
{
dassert( sizeof(T1::value_type)==1 );
size_t size = in.size();
assert( !(size % 2) );
out.resize( size / 2 );
T1::iterator outit = out.begin();
const char hexDigits[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
for( std::string::const_iterator it = in.begin(); it != in.end(); it += 2, ++outit )
{
*outit = (hexDigits[(*it - 0x30) & 0x1f] << 4) +
hexDigits[((*(it+1) - 0x30) & 0x1f)];
}
}
Некоторые из предположений, относящихся к этому коду: 1: Они не предназначены как общие, но используются в анонимном пространстве имен для перевода данных для определенного класса. 2: Шаблон требуется, поскольку используются два отдельных типа контейнеров (один - std :: vector, другой - контейнер с аналогичным типом байтового массива из сторонней библиотеки. 3: Цель состоит в том, чтобы иметь возможность преобразовывать двоичные данные неопределенной длины в строки и обратно (0x1234abcd <-> "1234abcd") 4. Утверждение ошибок ловушек как в режиме отладки, так и в режиме выпуска 5: к моменту вызова этих функций размер строки уже будет проверен, assert используется для прекращения обработки, если что-то серьезное пошло не так 6: Это нуждается в комментариях
Любые другие идеи приветствуются.
Разве первая строка asciihex не должна быть такой: size_t size = in.size ();
У меня были те же мысли по поводу вопроса, Роберт, но я также не мог придумать ничего лаконичного и значимого.
Возможно, HexToBinary и BinaryToHex были бы более значимыми именами, термин ASCII здесь вводит в заблуждение, особенно потому, что использование шаблонов указывает на возможный Unicode. В противном случае шестнадцатеричный код ASCII.
Пожалуйста, попробуйте сделать заголовок, соответствующий вашему вопросу, а теперь и вашему ответу. Никто и никогда не сможет точно найти это
Возможно, почему бы мне не использовать эти функции для преобразования шестнадцатеричного кода в C?
@Patrick: assert можно отключить, определив NDEBUG, что многие люди делают для производственного кода.





Похоже, что много шаблонного кода позволяет достичь очень небольшого, учитывая, что у вас есть прямое шестнадцатеричное преобразование в стандартных функциях C сканф и printf. Зачем беспокоиться?
Я думал о том же.
sscanf / printf небезопасны по типу. Операторы stream << и >> есть.
Верно, но вы можете обернуть sscanf и sprintf очень простыми функциями для обеспечения безопасности типов. Вышеупомянутый шаблонный код отлично подходит для чего-то столь простого, а использование шаблонов неверно предполагает, что код удобен для типов.
Я думаю, что этот ответ исходит из неправильного понимания цели функций (ошибка заключается в вопросе, а не в том, что вы ее читаете - я добавил немного к вопросу). Я не вижу никакого «приятного» способа использования printf для преобразования байтового массива в пригодный для печати ascii и обратно.
На самом деле я вообще не понимаю, как с помощью sprintf превратить 0x30 в 0x00. Вы согласны или я туплю?
проверьте квалификатор printf% x по следующей ссылке, cplusplus.com/reference/clibrary/cstdio/printf.html, например sprintf (str, "% 02x", i). Аналогично sscanf cplusplus.com/reference/clibrary/cstdio/scanf.html, как и sscanf ("% x", & i);
Что превращает 0x00 в 0x30, но как сделать наоборот?
учитывая char s [3], int i = 15, sprintf (s, "% 02x", i) устанавливает s в "0f", теперь скажите s = "fe", sscanf (s, "% x", & i) устанавливает i к 254.
Printf великолепен, но бывают случаи, когда такие функции могут понадобиться в среде, где sscanf / printf нежелательны (или, возможно, недоступны): встроенная, низкоуровневая отладка и т. д.
@jwfearn Верно, но есть способы лучше, чем в примере выше. В частности, во встроенных, где ресурсы являются проблемой, я бы предпочел потоковый вывод, а не изменение размера буфера.
@smacl: что, если s = "12abdc34d5ba990345326234656". Когда я кодирую это с помощью scanf, это выглядит очень неправильно.
@smacl: не изменять размер буфера во встроенном из-за ресурсов? Я в замешательстве. Поток предоставляет аккуратный API для буфера, но поток по-прежнему хранит данные в буфере.
@smacl: игнорировать scanf выглядит неверным комментарием. Мне просто нужно было немного подумать об этом
Мой главный комментарий по этому поводу - это очень трудно читать.
Особенно:
*outit = ((( (*it > '9' ? *it - 0x07 : *it) - 0x30) << 4) & 0x00f0) +
(((*(it+1) > '9' ? *(it+1) - 0x07 : *(it+1)) - 0x30) & 0x000f)
Моему мозгу потребовалось бы немного времени, чтобы разобраться в этом, и меня раздражало бы, если бы я унаследовал код.
Точно так же сбивает с толку.
"Некоторое время"? Либо гений, либо недосказанность.
Эта строка определенно нуждается в комментарии, но я не вижу возможности ее упрощения, если выбран такой подход к преобразованию. Разделение на несколько строк ...
Согласился, непонятно как улучшить. Возможно перемещение некоторых проверок в подметоды. Тем не менее, у меня глаза разбегаются.
Я бы обязательно заменил "(* it> '9'? * It - 0x07: * it) - 0x30)" встроенной функцией 'getHexValue' и вызвал бы ее дважды. Я также не уверен в значении масок: если входная строка - мусор, кого волнует, какое значение выводится? Если входная строка действительна, маски не действуют.
Результатом будет «* outit = (getHexValue (it) << 4) + getHexValue (it + 1);», на что я бы согласился.
Я не возражаю против этого. Он общий (в определенных пределах), в нем используются константы, ссылки там, где это необходимо, и т. д. В нем отсутствует небольшая документация, а назначение asciihex*outit не совсем понятно на первый взгляд.
resize инициализирует ненужные элементы вывода (вместо этого используйте reserve).
Возможно, универсальность слишком гибкая: вы можете кормить алгоритмы любым типом данных, который вам нравится, в то время как вы должны давать ему только шестнадцатеричные числа (не, например, vector из double)
И действительно, это может быть немного перебор, учитывая наличие хороших библиотечных функций.
Код не будет работать, если вы используете резерв. Если out.size () равен 0 до того, как вы вызываете резерв, он все равно будет равен 0 после того, как вы вызовете резерв, поэтому циклы не будут выполняться. См. gotw.ca/gotw/074.htm
Действительно, вам потребуются дополнительные приспособления. Я хотел сказать, что объекты создавались без надобности.
Что он должен делать? Общеизвестного общепринятого значения hexascii или asciihex не существует, поэтому названия следует изменить.
[редактировать] Преобразование из двоичной в шестнадцатеричную нотацию часто не следует называть ascii ..., поскольку ascii - это 7-битный формат.
Хороший звонок, я только сейчас это заметил. С первого взгляда я предположил, что это функция для преобразования между различными типами целых чисел и шестнадцатеричным. Повторное чтение, похоже, предназначено для преобразования между блоками двоичного (в байтах) и шестнадцатеричного.
Отредактировал вопрос, изменил двоичный массив в формат для печати и обратно.
Некоторые проблемы, которые я вижу:
Это будет отлично работать, если оно используется только для контейнера ввода, который хранит 8-битные типы - например, символ или беззнаковый символ. Например, следующий код завершится ошибкой при использовании с 32-битным типом, значение которого после сдвига вправо больше 15 - рекомендуется всегда использовать маску, чтобы индекс поиска всегда находился в пределах диапазона.
*outit++ = hexDigits[*it >> 4];
Каково ожидаемое поведение, если вы передадите контейнер, содержащий беззнаковые длинные числа - для того, чтобы это был общий класс, он, вероятно, также должен иметь возможность обрабатывать преобразование 32-битных чисел в шестнадцатеричные строки.
Это работает только тогда, когда ввод - это контейнер - что, если я просто хочу преобразовать один байт? Предлагается преобразовать код в базовую функцию, которая может скрыть один байт (шестнадцатеричный => ascii и ascii => шестнадцатеричный), а затем предоставить дополнительные функции для использования этой базовой функции для покрытия контейнеров байтов и т. д.
В asciihex () могут произойти неприятности, если размер входного контейнера не делится на 2. Использование:
it != in.end(); it += 2
опасен, поскольку, если размер контейнера не делится на 2, то увеличение на два продвинет итератор за конец контейнера, и сравнение с end () никогда не сработает. Это в некоторой степени защищено с помощью вызова assert, но assert может быть скомпилирован (например, он часто компилируется в сборках релиза), поэтому было бы намного лучше сделать это оператором if.
Утверждение во второй строке asciihex () проверяет, что размер ввода делится на 2, поэтому + = 2 в этом случае безопасен - я согласен, хотя это, по крайней мере, выглядит опасно, и я думаю, что я бы немного его закодировал по-другому я.
@jrbushell, assert будет включен только в отладочные версии. Он не влияет на код выпуска и помогает при тестировании, а не во время выполнения.
assert не только проверяет код отладки в нашей среде сборки (хотя вы можете настроить его для этого)
Что случилось с
*outit = hexDigits[*it]
Почему эти две функции не могут использовать общий список hexDigits и устранить сложное (и медленное) вычисление символа ASCII?
Как связать 0x30 с 0x00? Я полагаю, вы могли бы использовать карту, но это кажется излишним.
Карта представляет собой тривиальный поиск по массиву. Будет всего несколько байтов (256 в наихудшем случае; 128 более реалистично). И он будет работать мгновенно, используя сложение и умножение, и ничего более.
Код имеет утверждения утверждения вместо правильной обработки состояния ошибки (и если ваше утверждение отключено, код может взорваться)
цикл for имеет опасное двойное увеличение итератора (it + = 2). Особенно в случае, если ваше утверждение не сработало. Что происходит, когда ваш итератор уже находится в конце, а вы ++ это делаете?
Код является шаблоном, но вы просто преобразуете символы в числа или наоборот. Это карго культ программирование. Вы надеетесь, что использование шаблонов принесет вам плоды программирования. Вы даже отметили это как вопрос шаблона, хотя аспект шаблона совершенно не имеет отношения к вашим функциям.
строка * outit = слишком сложна.
код изобретает колесо. По большому счету.
Спасибо, Торстен, я надеялся, что ты это увидишь. Что касается культовой точки карго, код позволяет мне обрабатывать 2 контейнера из разных библиотек без необходимости писать 2 функции, которые отличаются только своими параметрами, одной из целей шаблонов. Все остальные контейнеры не рассматривал :(.
Я ценю ваши мысли о возможности взаимодействия между библиотеками. Но почему вы возитесь с символьными константами и непонятными значениями ASCII? Это код «на полпути между желобом и звездами». Он пытается быть элегантным, но не выполняет этого обещания. Здесь вы можете легко использовать строки C++.
Причина, по которой я считаю это игрушечным кодом, заключается в отсутствии проверки ошибок.
Я мог бы передать ему два вектора, и он с радостью попробовал бы что-нибудь сделать и устроить полный беспорядок, генерируя случайную тарабарщину.
Проблемы, которые я обнаруживаю:
hexascii не проверяет наличие sizeof(T2::value_type)==1
hexascii дважды разыменовывает it, а еще больше - asciihex. Для этого нет причин, так как вы можете сохранить результат. Это означает, что вы не можете использовать istream_iterator.
asciihex требует случайного итератора в качестве входных данных, потому что используются (it + 1) и (it + = 2). Алгоритм мог бы работать на прямом итераторе, если вы используете только (++ it).
(*it > '9' ? *it - 0x07 : *it) - 0x30 можно упростить до *it - (*it > '9' ? 0x37 : 0x30), так что остается только одно безусловное вычитание. Тем не менее поиск по массиву был бы более эффективным. Вычтите 0x30. «0» станет 0; «A» станет 0x11, а «a» станет 0x31. Замаскируйте с 0x1f, чтобы сделать регистр нечувствительным, и вы можете выполнить поиск в char [0x20] без риска переполнения. Не-шестнадцатеричные символы просто дадут вам странные значения.
Ничего не имею против вашего вопроса, Патрик, но название довольно расплывчатое, к сожалению, я не могу придумать ничего более точного и значимого, но было бы неплохо, если бы у кого-то была хорошая идея, в конце концов, вопрос может означать буквально что угодно.