【问题标题】:Binary literals?二进制字面量?
【发布时间】:2010-10-06 22:37:33
【问题描述】:

在代码中,我有时会看到人们像这样以十六进制格式指定常量:

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 等其他语言中使用?

【问题讨论】:

  • 我很好奇为什么十六进制表示法不适合你?一个数字就是一个数字。二进制符号更容易出现拼写错误,并且对于大量数字会变得非常陈旧。
  • 二进制效果更好,因为使用 'and' 和 'or' 运算符的整个技巧适用于二进制格式,我希望能够看到位模式。设置了哪些位是直接可见的。即使是初学者也无需借助计算器即可阅读代码。
  • @EBGreen:当您对微控制器进行编程时,使用二进制表示法非常很有用。以至于一些 uC C 编译器实际上接受 0b00101010 形式的数字。
  • 好吧,如果这是微控制器代码,那么当然可以。我不认为它是。
  • 谨慎使用“arsenal &= has_bio_weapons”。我想你的意思是“(arsenal & has_bio_weapons) == has_bio_weapons”。

标签: c++


【解决方案1】:

在 C++14 中,您将能够使用具有以下语法的二进制文字:

0b010101010 /* more zeros and ones */

此功能已在最新的clanggcc 中实现。如果您使用-std=c++1y 选项运行这些编译器,您可以尝试一下。

【讨论】:

  • 它现在可以与 clang-3.4 一起使用(参见 llvm.org/svn/llvm-project/cfe/trunk@194194);刚刚编译,它确实返回 3 : int main(int argc, char** argv) { int a = 0b00000011;返回一个; }
  • @daminetreg,是的,确实如此。实际上,我在帖子中确切地谈论了clang 4.8 trunk,但没有提及版本。
  • gcc 不是 4.8 的版本吗?还是我错过了什么?
  • 哎呀,我的错,我在写clang时正在考虑gcc。当然,你是对的。
  • WRT GCC 和 Clang,都支持这种语法作为 C 和 C++ 的扩展,并且早在 C++1y 被提出之前(从 GCC 4.3 开始。)
【解决方案2】:

我会使用位移运算符:

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的洪水还要好。

【讨论】:

  • 我的疯狂猜测是,旧的编译器足够愚蠢,实际上将 1 左右移动,而不是将该表达式转换为整数文字。
  • 我建议使用枚举而不是常量。但是,存在不能 OR 枚举的问题。您可以创建一个覆盖这些的类,但您将失去编译时性能!啊,这就是生活。
  • 使用此语法时要注意的一点是,如果您将类型更改为更广泛的整数类型(例如unsigned long long),则必须更改所有1&lt;&lt;N1ULL&lt;&lt;N,至少对于大型N,否则可能会发生无声的不可预测的行为(如果幸运的话,您会收到编译器警告)! (这与十六进制语法相比,您不需要添加特殊后缀,因为编译器将选择足够大的整数类型。)
  • @strager enums 除了被效率较低的编译器内联之外,还有什么好处?无论如何,自从 C++11 添加了constexpr,这总是更可取的。普通的旧 consts 也可以内联为文字,尽管 constexpr 更好地表明意图并开辟了许多其他可能性。
  • 请注意,is_dangerous 值只有在设置了所有位掩码时才为真。如果您想对条件进行逻辑或,您将检查二进制 AND 的结果是否非零:bool is_dangerous = (country-&gt;flags &amp; dangerous_mask) != 0;
【解决方案3】:

顺便说一句,下一个 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 返回类型。我认为甚至可以使它取决于二进制字符串的长度。对于任何感兴趣的人,它使用以下功能:

实际上,当前的 GCC 主干already 实现了可变参数模板和静态断言。让我们希望它能尽快支持其他两个。我认为 C++1x 会震撼人心。

【讨论】:

  • 很好的例子,这是我在简短回答中的想法,但你很好地充实了它!
  • 根据最后一个链接,不应该是constexpr int operator"_b"()吗?
  • the next C++ version 是什么意思?你的答案是从 2009 年开始,是 C++11 吗?
  • 我发现用户文字被集成到 C++11 中:User-defined literals (since C++11) - cppreference.com
  • @ThomasWeller 在 2009 年下一个 C++ 版本是 C++11。如果我说“C++11 will ...”,我会遇到英文语法并将其替换为“C++11 has...”。并以这种方式重写我所有其他答案,并将未来变为过去。我太累了,希望你能理解 :) 欢迎你编辑和修复我的答案 :)
【解决方案4】:

C++ 标准库是你的朋友:

#include <bitset>

const std::bitset <32> has_nukes( "00000000000000000000000000000001" );

【讨论】:

  • 哈,这很好。对于我们中间的纯粹主义者来说,唯一的缺点似乎是,它必须在运行时解析字符串才能分配值。对于这里有人指出的 BOOST_BINARY,这是没有必要的。
  • 或者使用 const int has_nukes = bitset("10101101").to_ulong();
【解决方案5】:

GCC 从 4.3 开始支持二进制常量作为扩展。请参阅announcement(查看“新语言和语言特定改进”部分)。

【讨论】:

  • +1 为什么没有人意识到这一点?他们的损失——太棒了!去 GCC。
  • 如果您的代码将由 gcc(或某些 gcc 兼容的实现)以外的其他东西编译,那将没有用。
  • 它也可以在 clang 中工作(尽管您会收到 -pedantic 的警告)
【解决方案6】:

你可以使用

int hasNukes = 1;
int hasBioWeapons = 1 << 1;
int hasChemWeapons = 1 << 2;

【讨论】:

  • 谢谢,这比 0b0000... 选项更好。
【解决方案7】:

This discussion 可能很有趣... 可能会很有趣,因为不幸的是链接已失效。它描述了一种基于模板的方法,类似于此处的其他答案。

还有一个东西叫BOOST_BINARY

【讨论】:

  • 讨论链接已损坏。你能在这里总结一下吗?
  • 你的答案中有一个没有上下文的死链接,所以它现在没用了,至少你的第二个链接是可搜索的......
【解决方案8】:

你想要的术语是二进制字面量

Ruby has them 使用您提供的语法。

另一种方法是定义帮助宏来为您转换。我在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
 */

【讨论】:

    【解决方案9】:

    C++ 的下一个版本,C++0x,将引入user defined literals。我不确定二进制数是否会成为标准的一部分,但最坏的情况是您可以自己启用它:

    int operator "" _B(int i);
    
    assert( 1010_B == 10);
    

    【讨论】:

      【解决方案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 吗?

      对于人类来说,稍微练习一下十六进制或八进制比二进制更容易。而且,在我看来,使用移位运算符更容易阅读。但我承认,我多年的汇编语言工作可能会让我在这一点上产生偏见。

      【讨论】:

      • 0b0001000000000000000 != 0x04000。我想你的意思是0b100000000000000
      【解决方案11】:

      如果你想使用 bitset、auto、variadic 模板、用户定义的文字、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");
      }
      

      感谢@johannes-schaub-litb

      【讨论】:

        【解决方案12】:

        不幸的是,Java 也不支持二进制文字。但是,它有 enums,可以与 EnumSet 一起使用。一个EnumSet 在内部用位字段表示枚举值,并提供一个Set 接口来操作这些标志。

        或者,您可以在定义值时使用位偏移量(十进制):

        const int HAS_NUKES        = 0x1 << 0;
        const int HAS_BIO_WEAPONS  = 0x1 << 1;
        const int HAS_CHEM_WEAPONS = 0x1 << 2;
        

        【讨论】:

        • Java 现在支持二进制字面量!
        【解决方案13】:

        在 C++ 中没有像十六进制和八进制那样的文字二进制常量语法。看起来你正在尝试做的最接近的事情可能是学习和使用bitset

        【讨论】:

          【解决方案14】:

          顺便说一句:

          特别是如果您正在处理一个大集合,您可以让每个常量依赖于先前定义的常量,而不是通过 [次要] 脑力来编写移位量序列:

          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;
          

          所以,我认为这种级联语法的主要优势在于处理常量的插入和删除。

          【讨论】:

            【解决方案15】:

            另一种方法:

            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)中实现。

            【讨论】:

              【解决方案16】:

              我同意为二进制文字提供一个选项很有用,并且它们存在于许多编程语言中。在 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)
              

              用法非常简单 =)

              【讨论】:

                【解决方案17】:

                一种有点可怕的方法是生成一个包含大量#defines 的.h 文件...

                #define b00000000 0
                #define b00000001 1
                #define b00000010 2
                #define b00000011 3
                #define b00000100 4
                

                等等。 这可能对 8 位数字有意义,但可能不适用于 16 位或更大的数字。

                或者,这样做(类似于 Zach Scrivena 的回答):

                #define bit(x) (1<<x)
                int HAS_NUKES       = bit(HAS_NUKES_OFFSET);
                int HAS_BIO_WEAPONS = bit(HAS_BIO_WEAPONS_OFFSET);
                

                【讨论】:

                【解决方案18】:

                可能与二进制文字不太相关,但这看起来好像可以通过位域更好地解决。

                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 结合使用,用于紧凑的二进制序列化:

                union DangerCollectionUnion {
                  DangerCollection collection;
                  uint8_t data[sizeof(DangerCollection)];
                };
                DangerCollectionUnion dc;
                std::memcpy(dc.data, bitsIGotFromSomewhere, sizeof(DangerCollection));
                if (dc.collection.has_bio_weapons) {
                  // ....
                

                根据我的经验,不易出错且易于理解发生了什么。

                【讨论】:

                  猜你喜欢
                  • 2010-10-10
                  • 2022-01-25
                  • 2010-10-16
                  • 1970-01-01
                  • 2016-02-22
                  • 2012-10-03
                  • 2012-06-25
                  • 2014-02-26
                  • 2012-08-27
                  相关资源
                  最近更新 更多