二进制字面量?

70

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

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里面行得通吗?


3
我很好奇为什么十六进制符号对你无效?数字就是数字。二进制表示法更容易出现打字错误,对于较大的数字来说也会变得很繁琐。 - EBGreen
30
二进制更好用,因为整个关键在于“与”和“或”运算符的二进制格式操作,并且我想能够“看到”位模式。直接可见哪些位被设置了。即使是初学者也可以阅读代码而不必诉诸计算器。 - Frank
13
当你编写微控制器程序时,使用二进制符号非常有用。甚至有些微控制器C编译器可以接受类似0b00101010这样的数字形式。 - Rocketmagnet
1
如果这是微控制器代码,那当然可以。但我认为它不是。 - EBGreen
20
小心处理 "arsenal &= has_bio_weapons" 这段代码。我认为你的意思是 "(arsenal & has_bio_weapons) == has_bio_weapons"。请注意不要改变原有的含义,并将其翻译成通俗易懂的语言。 - Mr Fooz
显示剩余6条评论
19个回答

116

在C++14中,您可以使用以下语法来使用二进制字面量:

0b010101010 /* more zeros and ones */

这个特性已经在最新版本的 clanggcc 中实现了。如果你使用带有 -std=c++1y 选项的编译器,可以尝试使用它。


现在它可以与clang-3.4一起使用(请参见https://llvm.org/svn/llvm-project/cfe/trunk@194194);刚编译过,确实返回了3: int main(int argc, char** argv) { int a = 0b00000011; return a; } - daminetreg
@daminetreg,是的,它确实有这个功能。实际上,我在帖子中正好谈到了clang 4.8 trunk,但没有提到版本号。 - sasha.sochka
4.8 不是 gcc 的版本吗?还是我错过了什么? - daminetreg
哎呀,我的错误,我在谈到clang时想到了gcc。当然,你是正确的。 - sasha.sochka
关于GCC和Clang,它们都支持这种语法作为C和C++的扩展,并且早在C++1y提出之前就已经存在了(自GCC 4.3起)。 - Jonathan Baldwin
显示剩余3条评论

72

我会使用位移运算符:

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更好。


4
我猜旧编译器可能太笨了,会直接移动那个“1”,而不是将表达式转换成整型字面量。 - Calyth
我建议使用枚举而不是常量。然而,存在一个问题,即您无法对枚举进行OR运算。您可以创建一个覆盖这些的类,但会失去编译时性能!唉,这就是生活。 - strager
3
在使用这种语法时需要注意一点,如果您将类型更改为更宽的整数类型(例如unsigned long long),则您将不得不将所有的 1<<N 更改为 1ULL<<N,至少对于较大的 N ,否则可能会发生静默不可预测的行为(如果您很幸运,您将得到编译器警告)!(与十六进制语法相比,您无需添加特殊后缀,因为编译器将选择足够大的整数类型。) - ndkrempel
1
@strager,除了被效率较低的编译器内联之外,enum还有什么好处?不管怎样,自从C++11添加了constexpr,它总是更可取的。普通的const也可以作为字面值内联,但constexpr更好地表明了意图并打开了许多其他可能性。 - underscore_d
请注意,只有当所有位掩码都设置时,is_dangerous值才为true。如果要逻辑上或条件,则应检查二进制AND的结果是否非零:bool is_dangerous = (country->flags&dangerous_mask)!= 0; - Tyler Kropp

38

顺便说一句,下一个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主干版本已经实现了可变参数模板和静态断言。希望它很快将支持其他两个功能。我认为C++1x将会非常强大。


1
非常好的例子,这正是我在我的简短回答中所想的,但你把它描述得非常好! - Motti
根据最后一个链接,应该是 constexpr int operator"_b"() 吧? - NikiC
4
“下一个 C++ 版本”是指 C++ 语言的下一个主要版本。我在2009年回答的问题可能指的是 C++11 版本。 - Wolf
2
我发现用户字面量被集成到C++11中:用户定义字面量(自C++11起)- cppreference.com - Wolf
1
在2009年,下一个C++版本是C++11。如果我说“C++11将会...”,我会面临英语语法的问题,并用“C++11已经...”来替换它。然后重写所有其他答案并将未来改为过去。我太累了,希望你能理解 :) 不过,欢迎您编辑和修正我的答案 :) - Johannes Schaub - litb
显示剩余5条评论

16
C++ 标准库是你的朋友:
#include <bitset>

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

3
哈,这很不错。唯一的缺点似乎是对于我们中的纯粹主义者来说,它必须在运行时解析字符串才能赋值。使用BOOST_BINARY,正如这里有人指出的那样,这是不必要的。 - Frank
5
或者,你也可以使用 const int has_nukes = bitset<32>("10101101").to_ulong(); 进行替代。 - Johannes Schaub - litb

14

从4.3版本开始,GCC通过扩展支持二进制常量。请参阅公告(查看“新语言和语言特定改进”部分)。


+1 为什么没有人意识到这一点?他们的损失——这太棒了!支持GCC。 - Engineer
1
如果你的代码将由除gcc(或某些兼容gcc的实现)之外的其他编译器编译,那么这并不有用。 - Keith Thompson
它在clang中也可以工作(尽管您会收到-pedantic的警告)。 - Joe the Person

13
你可以使用<<(双小于号)如果你喜欢。
int hasNukes = 1;
int hasBioWeapons = 1 << 1;
int hasChemWeapons = 1 << 2;

2
谢谢,这比0b0000...选项更好。 - Frank

9

这个讨论可能会很有趣... 可惜链接已经失效了。它描述了一种基于模板的方法,类似于其他答案中的方法。

还有一个叫做BOOST_BINARY的东西。


1
讨论链接已经失效了。您能否在这里进行总结呢? - Rob Kennedy
你的回答中有一个没有上下文的死链接,所以现在它已经无用了,至少你的第二个链接可以在谷歌上搜索到... - Troyseph

9
你需要的术语是“二进制字面量”。 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
 */

4

我这样编写二进制字面量:

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. I think you meant 0b100000000000000 - hlscalon

4

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

int operator "" _B(int i);

assert( 1010_B == 10);

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