写了一个模板函数,用于查看各种类型的数值的二进制编码。主要利用了C++的union。
2 string parseBinary( Type target,
3 const DisplayFormat &format = DisplayFormat(),
4 string format_separator = "," )
5 {
6 const size_t BYTES = sizeof(Type);
7
8 union {
9 Type value;
10 unsigned char ch[BYTES];
11 } u;
12 u.value = target;
13
14 bitset<BYTES*8> binary;
15 unsigned char mask;
16 for(size_t i=0; i!=BYTES; ++i) {
17 mask = 0x01;
18 for(size_t j=0; j!=8; mask<<=1, ++j) {
19 if((u.ch[i] & mask) == mask)
20 binary.set(8*i+j);
21 }
22 }
23
24 return format(binary.to_string(), format_separator);
25 }
第2行的DisplayFormat是一个仿函数,第3行是分隔符,都是格式化输出用的,暂时忽略不计,所以parseBinary的功能是对任意类型的输入target,获取并返回它的二进制编码。
第8~11行是整个函数的主要内容,把目标数值分解成一个一个的字节(unsigned char型占1字节)。这里会遇到“低位低地址,高位高地址”的问题,以int型数(4字节)1023为例:
------------------------------------------------- → union
u.ch[3] u.ch[2] u.ch[1] u.ch[0] ( u.ch )
binary[31] … … … … binary[0] → bitset<32>
如图中展示的,u.ch[0]应该是0xFF,而u.ch[3]是0x00。注意了这一点就一切好办,第14~22行就是把各位的0和1原样复制到binary(bitset<32>类型)中,最低位binary[0]=1,最高位binary[31]=0。
第24行输出。这里bitset<*>的一个特性比较好:它的to_string()成员函数会将bitset<*>对象逆序转化成string,即高位在前低位在后,这样得到的string更符合我们的直观。
过程中之所以使用bitset<*>是出于空间的考虑。bitset<*>的一个元素只占1位,而且和其他STL容器的空间分配策略类似,bitset<*>以4字节为单位追加空间,所以最坏的情况下也只会浪费3个字节。bitset<*>当然可以被int数组替代,但是int数组的空间代价是bitset<*>的32倍。另外,用int数组代替bitset<*>时需要手动处理逆序问题。
对于string类型以及字符串常量的输入,parseBinary有下列两个重载版本。
2 const DisplayFormat &format = DisplayFormat(),
3 string format_separator = ",")
4 {
5 string result = "";
6 size_t target_size = target.size();
7 for(size_t i=0; i!=target_size; ++i)
8 result += parseBinary(target[i], format, format_separator);
9 return result;
10 }
11
12 string parseBinary(const char *ptarget,
13 const DisplayFormat &format = DisplayFormat(),
14 string format_separator = ",")
15 {
16 return parseBinary(string(ptarget), format, format_separator);
17 }
默认情况下,输出是一个未经分割的0-1序列。不过有时候插入一些分隔符会有助于阅读。parseBinary的第2、3个参数提供这样的自定义途径。基类格式DisplayFormat定义如下,这是一个无格式的返回。
2 virtual string operator() (string s, string separator = ",") const { return s; }
3 };
2 string operator() (string s, string separator = ",") const {
3 size_t s_size = s.size();
4 if(s_size%8 != 0) return s;
5
6 string result = "";
7 for(size_t i=0; i!=s_size; i+=8)
8 result += (s.substr(i, 8) + separator);
9
10 return result.substr(0, result.size()-1);
11 }
12 };
2 string operator() (string s, string separator = ",") const {
3 if(s.size() != 32) return s;
4 return (s.substr(0,1) + separator + s.substr(1,8) + separator + s.substr(9));
5 }
6 };
7
8 struct IEEE754_Double: public DisplayFormat {
9 string operator() (string s, string separator = ",") const {
10 if(s.size() != 64) return s;
11 return (s.substr(0,1) + separator + s.substr(1,11) + separator + s.substr(12));
12 }
13 };
下面是一些测试。
2 cout << parseBinary('a') << endl;
3 cout << parseBinary(3.0f, IEEE754_Float()) << endl;
4 cout << parseBinary(-3.0, IEEE754_Double(), "-") << endl;
5 cout << parseBinary(wchar_t('ab')) << endl;
6 cout << parseBinary("wxyz") << endl;
7 cout << parseBinary("中国", Bytewise()) << endl;
对应的结果如下:
2 01100001
3 0,10000000,10000000000000000000000
4 1-10000000000-1000000000000000000000000000000000000000000000000000
5 0110000101100010
6 01110111011110000111100101111010
7 11010110,11010000,10111001,11111010
遇到的主要问题:
error C2620:“union有用户定义的构造函数或非平凡默认构造函数。”
这个问题是试图直接将bitset<*>放进union造成的。我的初步理解是构造函数的自动调用会不受用户控制地修改union数据。
error C2765:“函数模板的显式专用化不能有任何默认参数。”
这个问题是试图把处理字符串的两个parseBinary实现为函数模板的专用化版本时出现的。我对这个error的深层次原因比较感兴趣,等有空再慢慢查找资料。作为替代方案,我把针对字符串的两个parseBinary做成重载而非专用化。
warning C4239:“使用了非标准的扩展。”
如果不把parseBinary的第二个参数声明为const则会产生这样的问题。最终调用parseBinary的时候传进DisplayFormat或其子类的临时对象是比较整洁的做法,但C++不鼓励把非常量引用绑定到临时对象。第二个参数声明为const后,DisplayFormat及其子类的operator()也要连带着声明为const,否则会引发error C3848,这一点不难理解:非const的operator()有可能改变对象值,从而对象整体的const性质得不到保证。