Двоичные литералы?
В коде я иногда вижу, как люди указывают константы в шестнадцатеричном формате, например так:
const int has_nukes = 0x0001;
const int has_bio_weapons = 0x0002;
const int has_chem_weapons = 0x0004;
// ...
int arsenal = has_nukes | has_bio_weapons | has_chem_weapons; // all of them
if(arsenal &= has_bio_weapons){
std::cout << "BIO!!"
}
Но мне не имеет смысла использовать здесь шестнадцатеричный формат. Есть ли способ сделать это непосредственно в двоичном коде? Что-то вроде этого:
const int has_nukes = 0b00000000000000000000000000000001;
const int has_bio_weapons = 0b00000000000000000000000000000010;
const int has_chem_weapons = 0b00000000000000000000000000000100;
// ...
Я знаю, что компиляторы C/C++ не скомпилируют это, но должен быть обходной путь? Возможно ли это на других языках, таких как Java?
19 ответов
Я бы использовал оператор сдвига битов:
const int has_nukes = 1<<0;
const int has_bio_weapons = 1<<1;
const int has_chem_weapons = 1<<2;
// ...
int dangerous_mask = has_nukes | has_bio_weapons | has_chem_weapons;
bool is_dangerous = (country->flags & dangerous_mask) == dangerous_mask;
Это даже лучше, чем поток 0.
В C++14 вы сможете использовать двоичные литералы со следующим синтаксисом:
0b010101010 /* more zeros and ones */
Эта функция уже реализована в последней clang
а также gcc
, Вы можете попробовать это, если вы запускаете эти компиляторы с -std=c++1y
вариант.
Кстати, следующая версия C++ будет поддерживать пользовательские литералы. Они уже включены в рабочий проект. Это позволяет такие вещи (будем надеяться, что у меня не так много ошибок):
template<char... digits>
constexpr int operator "" _b() {
return conv2bin<digits...>::value;
}
int main() {
int const v = 110110110_b;
}
conv2bin
будет такой шаблон:
template<char... digits>
struct conv2bin;
template<char high, char... digits>
struct conv2bin<high, digits...> {
static_assert(high == '0' || high == '1', "no bin num!");
static int const value = (high - '0') * (1 << sizeof...(digits)) +
conv2bin<digits...>::value;
};
template<char high>
struct conv2bin<high> {
static_assert(high == '0' || high == '1', "no bin num!");
static int const value = (high - '0');
};
Что ж, мы получаем двоичные литералы, которые полностью оценивают уже во время компиляции из-за описанного выше "constexpr". Выше используется жестко закодированный тип возврата int. Я думаю, что можно даже сделать это зависит от длины двоичной строки. Он использует следующие функции, для всех, кто заинтересован:
- Обобщенные выражения констант.
- Вариадические шаблоны. Краткое введение можно найти здесь
- Статические утверждения (static_assert)
- Пользовательские литералы
На самом деле, текущая магистраль GCC уже реализует шаблоны с переменными параметрами и статические утверждения. Будем надеяться, что это скоро поддержит двух других. Я думаю, что C++1x раскачивает дом.
Стандартная библиотека C++ - ваш друг:
#include <bitset>
const std::bitset <32> has_nukes( "00000000000000000000000000000001" );
GCC поддерживает двоичные константы как расширение начиная с 4.3. Смотрите объявление (см. Раздел "Новые языки и языковые улучшения").
Вы можете использовать <<, если хотите.
int hasNukes = 1;
int hasBioWeapons = 1 << 1;
int hasChemWeapons = 1 << 2;
Вы хотите термин бинарные литералы
У Ruby они с синтаксисом, который вы даете.
Один из вариантов - определить вспомогательные макросы для конвертации. Я нашел следующий код на http://bytes.com/groups/c/219656-literal-binary
/* Binary constant generator macro
* By Tom Torfs - donated to the public domain
*/
/* All macro's evaluate to compile-time constants */
/* *** helper macros *** */
/* turn a numeric literal into a hex constant
* (avoids problems with leading zeroes)
* 8-bit constants max value 0x11111111, always fits in unsigned long
*/
#define HEX_(n) 0x##n##LU
/* 8-bit conversion function */
#define B8_(x) ((x & 0x0000000FLU) ? 1:0) \
| ((x & 0x000000F0LU) ? 2:0) \
| ((x & 0x00000F00LU) ? 4:0) \
| ((x & 0x0000F000LU) ? 8:0) \
| ((x & 0x000F0000LU) ? 16:0) \
| ((x & 0x00F00000LU) ? 32:0) \
| ((x & 0x0F000000LU) ? 64:0) \
| ((x & 0xF0000000LU) ? 128:0)
/* *** user macros *** /
/* for upto 8-bit binary constants */
#define B8(d) ((unsigned char) B8_(HEX_(d)))
/* for upto 16-bit binary constants, MSB first */
#define B16(dmsb, dlsb) (((unsigned short) B8(dmsb) << 8) \
| B8(dlsb))
/* for upto 32-bit binary constants, MSB first */
#define B32(dmsb, db2, db3, dlsb) (((unsigned long) B8(dmsb) << 24) \
| ((unsigned long) B8( db2) << 16) \
| ((unsigned long) B8( db3) << 8) \
| B8(dlsb))
/* Sample usage:
* B8(01010101) = 85
* B16(10101010,01010101) = 43605
* B32(10000000,11111111,10101010,01010101) = 2164238933
*/
Это обсуждение может быть интересным... Возможно, так как ссылка, к сожалению, мертва. Здесь описан подход на основе шаблонов, аналогичный другим ответам здесь.
А также есть вещь, которая называется BOOST_BINARY.
Следующая версия C++, C++0x, представит пользовательские литералы. Я не уверен, будут ли двоичные числа частью стандарта, но в худшем случае вы сможете включить его самостоятельно:
int operator "" _B(int i);
assert( 1010_B == 10);
Я пишу двоичные литералы, как это:
const int has_nukes = 0x0001;
const int has_bio_weapons = 0x0002;
const int has_chem_weapons = 0x0004;
Он более компактен, чем вы предлагаете, и его легче читать. Например:
const int upper_bit = 0b0001000000000000000;
против:
const int upper_bit = 0x04000;
Вы заметили, что двоичная версия не была кратна 4 битам? Вы думали, что это был 0x10000?
С небольшой практикой шестнадцатеричный или восьмеричный легче для человека, чем двоичный. И, на мой взгляд, легче читать, что с помощью операторов сдвига. Но я признаю, что мои годы работы на ассемблере могут повлиять на меня в этом вопросе.
Как в сторону:
Особенно, если вы имеете дело с большим набором, вместо того, чтобы выполнять [незначительные] умственные усилия по написанию последовательности величин сдвига, вы можете сделать каждую константу зависимой от ранее определенной константы:
const int has_nukes = 1;
const int has_bio_weapons = has_nukes << 1;
const int has_chem_weapons = has_bio_weapons << 1;
const int has_nunchuks = has_chem_weapons << 1;
// ...
Выглядит немного избыточно, но менее подвержено опечаткам. Кроме того, вы можете просто вставить новую константу посередине, не касаясь какой-либо другой строки, кроме той, которая следует сразу за ней:
const int has_nukes = 1;
const int has_gravity_gun = has_nukes << 1; // added
const int has_bio_weapons = has_gravity_gun << 1; // changed
const int has_chem_weapons = has_bio_weapons << 1; // unaffected from here on
const int has_nunchuks = has_chem_weapons << 1;
// ...
Сравнить с:
const int has_nukes = 1 << 0;
const int has_bio_weapons = 1 << 1;
const int has_chem_weapons = 1 << 2;
const int has_nunchuks = 1 << 3;
// ...
const int has_scimatar = 1 << 28;
const int has_rapier = 1 << 28; // good luck spotting this typo!
const int has_katana = 1 << 30;
А также:
const int has_nukes = 1 << 0;
const int has_gravity_gun = 1 << 1; // added
const int has_bio_weapons = 1 << 2; // changed
const int has_chem_weapons = 1 << 3; // changed
const int has_nunchuks = 1 << 4; // changed
// ... // changed all the way
const int has_scimatar = 1 << 29; // changed *sigh*
const int has_rapier = 1 << 30; // changed *sigh*
const int has_katana = 1 << 31; // changed *sigh*
Кроме того, вероятно, одинаково трудно обнаружить опечатку, подобную этой:
const int has_nukes = 1;
const int has_gravity_gun = has_nukes << 1;
const int has_bio_weapons = has_gravity_gun << 1;
const int has_chem_weapons = has_gravity_gun << 1; // oops!
const int has_nunchuks = has_chem_weapons << 1;
Итак, я думаю, что основное преимущество этого каскадного синтаксиса заключается в работе со вставками и удалениями констант.
Если вы хотите использовать наборы битов, авто, шаблоны переменных, определяемые пользователем литералы, static_assert, constexpr и noexcept, попробуйте это:
template<char... Bits>
struct __checkbits
{
static const bool valid = false;
};
template<char High, char... Bits>
struct __checkbits<High, Bits...>
{
static const bool valid = (High == '0' || High == '1')
&& __checkbits<Bits...>::valid;
};
template<char High>
struct __checkbits<High>
{
static const bool valid = (High == '0' || High == '1');
};
template<char... Bits>
inline constexpr std::bitset<sizeof...(Bits)>
operator"" bits() noexcept
{
static_assert(__checkbits<Bits...>::valid, "invalid digit in binary string");
return std::bitset<sizeof...(Bits)>((char []){Bits..., '\0'});
}
Используйте это так:
int
main()
{
auto bits = 0101010101010101010101010101010101010101010101010101010101010101bits;
std::cout << bits << std::endl;
std::cout << "size = " << bits.size() << std::endl;
std::cout << "count = " << bits.count() << std::endl;
std::cout << "value = " << bits.to_ullong() << std::endl;
// This triggers the static_assert at compile-time.
auto badbits = 2101010101010101010101010101010101010101010101010101010101010101bits;
// This throws at run-time.
std::bitset<64> badbits2("2101010101010101010101010101010101010101010101010101010101010101bits");
}
Благодаря @ Йоханнес-Шауб-Литб
В C++ нет синтаксиса для буквенных двоичных констант, как для шестнадцатеричных и восьмеричных. Вероятнее всего, самое близкое к тому, что вы пытаетесь сделать, это изучить и использовать bitset.
К сожалению, Java не поддерживает двоичные литералы. Тем не менее, он имеет перечисления, которые могут быть использованы с EnumSet
, EnumSet
представляет значения перечисления внутри с битовыми полями, и представляет Set
интерфейс для управления этими флагами.
В качестве альтернативы, вы можете использовать битовые смещения (в десятичном формате) при определении ваших значений:
const int HAS_NUKES = 0x1 << 0;
const int HAS_BIO_WEAPONS = 0x1 << 1;
const int HAS_CHEM_WEAPONS = 0x1 << 2;
Двоичные литералы являются частью языка C++, начиная с C++14. Это литералы, которые начинаются с
0b
или же
0B
. Ссылка
Другой метод:
template<unsigned int N>
class b
{
public:
static unsigned int const x = N;
typedef b_<0> _0000;
typedef b_<1> _0001;
typedef b_<2> _0010;
typedef b_<3> _0011;
typedef b_<4> _0100;
typedef b_<5> _0101;
typedef b_<6> _0110;
typedef b_<7> _0111;
typedef b_<8> _1000;
typedef b_<9> _1001;
typedef b_<10> _1010;
typedef b_<11> _1011;
typedef b_<12> _1100;
typedef b_<13> _1101;
typedef b_<14> _1110;
typedef b_<15> _1111;
private:
template<unsigned int N2>
struct b_: public b<N << 4 | N2> {};
};
typedef b<0> _0000;
typedef b<1> _0001;
typedef b<2> _0010;
typedef b<3> _0011;
typedef b<4> _0100;
typedef b<5> _0101;
typedef b<6> _0110;
typedef b<7> _0111;
typedef b<8> _1000;
typedef b<9> _1001;
typedef b<10> _1010;
typedef b<11> _1011;
typedef b<12> _1100;
typedef b<13> _1101;
typedef b<14> _1110;
typedef b<15> _1111;
Использование:
std::cout << _1101::_1001::_1101::_1101::x;
Реализовано в CityLizard ++ (citylizard / binary / b.hpp).
Я согласен, что полезно иметь опцию для двоичных литералов, и они присутствуют во многих языках программирования. В C я решил использовать макрос, как это:
#define bitseq(a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
(a31|a30<< 1|a29<< 2|a28<< 3|a27<< 4|a26<< 5|a25<< 6|a24<< 7| \
a23<< 8|a22<< 9|a21<<10|a20<<11|a19<<12|a18<<13|a17<<14|a16<<15| \
a15<<16|a14<<17|a13<<18|a12<<19|a11<<20|a10<<21|a09<<22|a08<<23| \
a07<<24|a06<<25|a05<<26|a04<<27|a03<<28|a02<<29|a01<<30|(unsigned)a00<<31)
Использование довольно простое =)
Один, немного ужасный способ сделать это - сгенерировать файл.h с большим количеством #defines...
#define b00000000 0
#define b00000001 1
#define b00000010 2
#define b00000011 3
#define b00000100 4
и т.д. Это может иметь смысл для 8-битных чисел, но, вероятно, не для 16-битных и более.
В качестве альтернативы, сделайте это (похоже на ответ Зака Скривены):
#define bit(x) (1<<x)
int HAS_NUKES = bit(HAS_NUKES_OFFSET);
int HAS_BIO_WEAPONS = bit(HAS_BIO_WEAPONS_OFFSET);
Может быть, менее актуально для двоичных литералов, но это выглядит так, как будто это лучше решить с помощью битового поля.
struct DangerCollection : uint32_t {
bool has_nukes : 1;
bool has_bio_weapons : 1;
bool has_chem_weapons : 1;
// .....
};
DangerCollection arsenal{
.has_nukes = true,
.has_bio_weapons = true,
.has_chem_weapons = true,
// ...
};
if(arsenal.has_bio_weapons){
std::cout << "BIO!!"
}
Вы все равно сможете заполнить его двоичными данными, поскольку его двоичный след — это всего лишь uint32. Это часто используется в сочетании с объединением для компактной двоичной сериализации:
union DangerCollectionUnion {
DangerCollection collection;
uint8_t data[sizeof(DangerCollection)];
};
DangerCollectionUnion dc;
std::memcpy(dc.data, bitsIGotFromSomewhere, sizeof(DangerCollection));
if (dc.collection.has_bio_weapons) {
// ....
По моему опыту, менее подвержен ошибкам и легко понимает, что происходит.