/* 轉載請注明出處:http://www.cnblogs.com/Martinium/p/binary_literal.html */
二進制的語法
C/C++ 默認數字使用十進制,八進制使用前綴 0, 十六進制使用前綴 0x 或 0X,二進制常數的提議被否決(引用
C 語言程序原理國際標准 的 6.4.4.1 章節字段 "A proposal to add binary constants was rejected due to lack of precedent and insufficient utility."),一直沒有二進制的表示方法,
GCC 使用 0b/0B 前綴作為擴展,其實很多編譯器都有這個擴展的,只是標准委員會一直沒采納。直到
C++14 才引進,可謂姍姍來遲啊。這種二進制語義
Java 7 也早些時候引入,
Python 2.6 以及后來的 3 也引入,Python 為了避免十進制與八進制的混淆,一律用字母前綴,不分大小寫,0b 為二進制,0o 為八進制,ox 為十六進制。Python2 升到 Python3 時果斷丟棄了原來的八進制語義(也就是在 C/C++/Java 里面有 0 前綴的八進制的語義)。
表示方法
1. 下面每行語句表達語義相同,只是選用不同的進制數表示。
int i = 0b101010; // binary
int i = 052; // octal
int i = 42; // decimal
int i = 0x2a; // hexadecimal
2. 使用 strtol/strtoll/strtoq 函數,可以將一個字符串轉換成整數,base 取 2 表示轉換成二進制數。
#include <stdlib.h>
long int
strtol(const char *nptr, char **endptr, int base);
long long int
strtoll(const char *nptr, char **endptr, int base);
我在解密一些二進制數據的時候,用到過這個函數。先用正則表達式每八個數字一拆分 echo A_LONG_BINARY_STRING | sed -r 's/([01]{8})/\1 /g',然后利用空格作間隔符提取數字所代表的 ASCII 碼,當然,你也可以用移位運算自己實現每八個數字一讀的功能。
#include <stdio.h> #include <ctype.h> #include <stdlib.h>
int main() { const char* raw = "01110101 01110011 01100101 00100000 01110111 01100101 01100100 01101110 01100101 01110011 01100100 01100001 01111001 00100000 01100110 01101111 01110010 00100000 01110100 01101000 01100101 00100000 01100001 01101110 01110011 01110111 01100101 01110010"; const char* begin; char* end = const_cast<char*>(raw); do { begin = end; long int byte = strtol(begin, &end, 2/*base*/); putchar(static_cast<int>(byte)); }while(begin != end); return 0; }
3. 也可以使用 STL 里的
bitset 完成,不僅可以用數字,也可以用其他字符表示 0/1,這個比方法 2 更強大。這種方法我在做
培根密碼解密題用到過,將 A/B 字符映射成 0/1 數字。
// unsigned long long constructor
std::bitset<8> (42); // string constructor
std::string bits = "110010"; std::bitset<8> binary(bits); // [0,0,1,1,0,0,1,0] // string constructor using custom zero/one digits
std::string bacon = "ABABB"; std::bitset<5> m(bacon, 0, std::string::npos, 'A', 'B'); // [0,1,0,1,1]
4. 可以使用 boost 庫的
BOOST_BINARY, 可以每隔開幾個數字一寫,在表示很大的數時很方便閱讀。
int value1 = BOOST_BINARY( 100 111000 01 1 110 ); unsigned long value2 = BOOST_BINARY_UL( 100 001 ); // unsigned long
long long value3 = BOOST_BINARY_LL( 11 000 ); // long long if supported
提到方便閱讀,C++14 加入了數字分隔符這個想法, 於是可以寫成 double e = 2.718'281'828'459'045'; 方便閱讀。
5. 如果不是一項工程,而是簡短的程序,可以利用 C++ 的模板模擬二進制語義。
template<unsigned long long N>
struct binary { enum { value = (N%10) + binary<N/10>::value*2 }; }; template<>
struct binary { enum { value = 0 }; }
int value = static_cast<int>(binary<101010>::value);
上面的代碼中, enum 是為了讓變量在編譯期獲取結果,沒有用整型。還有一個問題是如果數的開頭有 0,結果可不妙。整個數被當成了八進制而不是十進制數解析,很容易滋生 bug。比較好的解決方法是數字經常以 0 開頭(或添加斷言強制),將十進制換成八進制。
// need to enable C++11 flag
template<unsigned long long>
struct binary { constexpr static int value = binary<N/8>::value + N%8; }; template<>
struct binary<0> { constexpr static int value = 0; }
6. 采用用戶自定字面值(
User-defined literals),注意這個是 C++11 新加的功能。之前的 C++03 內置一些類似的語義(比方說在浮點數后加字符 "f" 表示聲明一個 float 而不是 double),但是用戶不能自己定義。
C++11 開放用戶定義新的字面修飾符(literal modifier),利用自定義的修飾符完成由字面值構造對象。所有的自定字面值必須是后置,前置是不允許的。C++11 開放用戶定義新的字面修飾符(literal modifier),利用自定義的修飾符完成由字面值構造對象。除了下划線,所有的后置被標准保留。所以,用戶定義時需要以下划線開頭。
int operator "" _B(int i); static_assert( 101010_B == 42);