最近在我工作的一个项目中,我想用Object-C以二进制字符串的形式显示一个整数。虽然我写出了我认为是相当好的实现,我还是好奇想知道其他开发者是如何着手处理相同问题的。
我问了Nick Lockwood是否有时间编写相似的一些东西。记住几乎没有提供限制性的需求。解决方法可以是C函数、一个方法或者一个范畴,调用的名字并没有定义。
读一下看看四个不同的方法来将一个整数转换为一个二进制的NSString对象。
方法一:将整数作为NSString转换为二进制数(John)
以下展示出的内容只是为了调试方便, 性能并不是最主要的考虑因素。但是,我依旧在寻找是否有其他独一无二并且高效的办法。
作为我的第一次尝试,我决定使用一个基于C语言的数组并且在每个数组元素的最右的bit做上记号,之后 .我把新的值右移一位,然后接着循环,这就是这个方法的代码看起来的样子。
- (NSString *)intToBinary:(int)intValue{
int byteBlock = 8, // 8 bits per byte
totalBits = (sizeof(int)) * byteBlock, // Total bits
binaryDigit = totalBits; // Which digit are we processing // C array - storage plus one for null
char ndigit[totalBits + 1];
while (binaryDigit-- > 0)
{
// Set digit in array based on rightmost bit
ndigit[binaryDigit] = (intValue & 1) ? '1' : '0';
// Shift incoming value one to right
intValue >>= 1; } // Append null
ndigit[totalBits] = 0;
// Return the binary string
return [NSString stringWithUTF8String:ndigit];}
按下列方式运行以上代码。
int intValue = 16;
NSLog(@"int: %d", intValue);
NSLog(@"res: %@", [self intToBinary:intValue]);
intValue = -1;
NSLog(@"int: %d", intValue);
NSLog(@"res: %@\n\n", [self intToBinary:intValue]);
intValue = 2147483647;
NSLog(@"int: %d", intValue);
NSLog(@"res: %@\n\n", [self intToBinary:intValue]);
intValue = -2147483648;
NSLog(@"int: %d", intValue);
NSLog(@"bin: %@\n\n", [self intToBinary:intValue]);
现在,那些数据看起来是这样的:
int: 16
bin: 00000000000000000000000000010000
int: -1
bin: 11111111111111111111111111111111
int: 2147483647
bin: 01111111111111111111111111111111
int: -2147483648
bin: 10000000000000000000000000000000
方法二: 用NSString转换整数为二进制字串(作者:John)
不管高效与否,我不能抱着抛出来的没有区分构成整数字节边界的一大堆1或0发晕啊。可以使用C数组的方法输出“漂亮的打印”吗,这是肯定的。这会搞乱代码吗?这是更为肯定的。所以我决定用另一种方式实现:
- (NSString *)intToBinary:(int)intValue{
int byteBlock = 8, // 每个字节8位
totalBits = sizeof(int) * byteBlock, // 总位数(不写死,可以适应变化)
binaryDigit = 1; // 当前掩(masked)位
NSMutableString *binaryStr = [[NSMutableString alloc] init]; // 二进制字串
do
{
// 检出下一位,然后向左移位,附加 0 或 1
[binaryStr insertString:((intValue & binaryDigit) ? @"1" : @"0" ) atIndex:0];
// 若还有待处理的位(目的是为避免在最后加上分界符),且正处于字节边界,则加入分界符|
if (--totalBits && !(totalBits % byteBlock))
[binaryStr insertString:@"|" atIndex:0];
// 移到下一位
binaryDigit <<= 1;
} while (totalBits);
// 返回二进制字串
return binaryStr;}
这种方法稍有不同,首先要提的就是使用了基于C的Objective-C可变字符串的数组。同时,不再对传入的值移位以获得每一位,这一次我用移位从最低到最高位进行检查。我希望创造一个更可读的输出,在代码中判断是否已到一个字节的边界(一个字节8位),若是则输出一个竖杠|,除了最末尾。
我通常都会把代码写得冗长(以避免可能的BUG),对这个小练手也一样。例如,在3–4两行的值可以用硬编码(指定固定的位数)和在do循环内分配。然而,我通常愿意在便携性和/或可读性上做出这样的权衡。
第二种方法的输出如下:
int: 16
bin: 00000000|00000000|00000000|00010000
int: -1
bin: 11111111|11111111|11111111|11111111
int: 2147483647
bin: 01111111|11111111|11111111|11111111
int: -2147483648
bin: 10000000|00000000|00000000|00000000
方法三:用NSString转换整数为二进制字符串(作者:Nick)
当 John 让我创建一个从整数输出二进制字符串的方法时,我第一个本能反应当然就是去 Stack Overflow 找现成答案。
虽然我是一个程序员而非抄袭者,而且我喜欢解决编程的麻烦问题,但我一直认为,好钢应该用在刀刃上,辛苦最好用于解决新的问题,而不是那些已被解决的。我期待能够有一个内置的解决方案,比如使用[NSString stringWithFormat:] 方法或者 NSNumberFormatter 就可以做到,但我很惊讶地发现居然没有。
我从这个问题帖(http://stackoverflow.com/questions/111928/is-there-a-printf-converter-to-print-in-binary-format)获悉,一些C编译器在它们对printf函数的实现中包括了一个 %b 选项,用于打印二进制字符串,但它不是一个标准特性,并没有包括在Mac或iOS的实现中。
在解决这个实际生成字符的串问题之前,我先思考代码接口该是什么样子。当把数字作为字符串打印时,我通常使用 stringWithFormat: 或 NSNumberFormatter,所以我的想法是把我的二进制格式化程序绑定到这些机制之一。
NSNumberFormatter似乎不支持任何手段来扩展其格式选项。当然,我可以使用一个类别(category)来添加额外的格式属性,但是为了使用它们我不得不滥用 stringFromNumber: 方法,这过于粗暴,非我所爱之调调。
stringWithFormat: 方法同样不能扩展。你可以通过重写Objective-C类的描述方法来创建它们的自定义描述,但滥用NSNumber的描述方法来返回二进制字符串显然是个坏主意(它将破坏那些希望它返回一个十进制数字字符串的代码),而创建一个自定义的类来只是封装一个整数以便打印它,有点像一个荒谬的过度设计。
我找到了一些内容,提及glib里有个很有前途的特性叫 register_printf_function(),它允许你指定自定义的 printf 格式字串。它的一个使用例子在 http://codingrelic.geekhold.com/2008/12/printf-acular.html。不幸的是,register_printf_function()似乎没有在Mac或iOS上实现–大概是被认为有安全风险,或只是并非需要优先考虑实现的事情。
我放弃了,看来没有容易的方式把这个二进制字符串格式化整合到现有的Objective-C格式化机制中,于是回到问题本身:
我之前在Stack Overflow上发现有许多实际生成二进制字符串的解决方案,但感觉它们太像是“C”的解决方案。我决定从无到有写点东西,更多使用Cocoa方法和类型以提高可读性。它牺牲了处理中的一些未加工的性能,但如果对性能不作要求,我更喜欢把代码优化得更清晰。
这就是我的解决方案:
- (NSString *)binaryStringWithInteger:(NSInteger)value{
NSMutableString *string = [NSMutableString string]; while (value)
{
[string insertString:(value & 1)? @"1": @"0" atIndex:0];
value /= 2; }
return string;}
我想这是我见过的最短的解决方案,而且相当简单易懂。其基本原理和我碰到的大多数其他解决方案一样:在一个循环中,我让这个值与1进行AND运算以得到其最后一位的内容,然后通过除以2来移动字节的位。使它如此简洁的原因是,它只是简单地对一个 NSMutableString 预置了位的值,而不是更复杂的东西。
我可以使用 >> 移位算子代替除号 /,但我个人发现整数除法的概念更容易掌握,我永远都无法记得那些>或<都代表数据的位发生怎样的变化!(随着时间,你的记忆和理解可能发生变化。而且对编译器来说,移位和乘除法没什么区别)
这个方法的输出与 John 的解决方案有所不同。明显的是,我没有用零填充空位,所以如果输入4会生成“100″,输入1就会生成“1″,输入0则生成一个空字串。
接下来的问题是如何使用这些代码,它可以作为 NSString 的类别(category), 也可以作为公共的工具类。我可能更愿意把它实现为 NSNumber 的 category,因为它能够使接口更简洁,而只需要付出把输出包装为 NSNumber 这样一个很小的代价。看起来类似这样:
@interface NSNumber (Binary) - (NSString *)binaryString;
@end @implementation NSNumber (Binary) - (NSString *)binaryString{
NSMutableString *string = [NSMutableString string];
NSInteger value = [self integerValue]; while (value)
{
[string insertString:(value & 1)? @"1": @"0" atIndex:0];
value /= 2; }
return string;} @end
然后你就可以这样来调用它:
NSInteger input = 99;NSString *output = [@(input) binaryString]; //1100011
方法4:采用NSString将整数转换成二进制(Nick)
独立于John的解决方法,在此我写下我的解决方法,没有输出格式的指导,也并不比他的解法优越。看完John的解法之后,我好奇我是否需要以固定宽度和8比特分割的格式输出。John的方法是没有牺牲解决方法的简洁性的。下面是我想出来的方法用于填充比特:
- (NSString *)paddedBinaryString{
NSMutableString *string = [NSMutableString string];
NSInteger value = [self integerValue]; for (NSInteger i = 0; i < sizeof(value) * 8; i++)
{
[string insertString:(value & 1)? @"1": @"0" atIndex:0];
value /= 2; }
return string;}
方法很简单---我仅仅采用for循环来代替while循环,目的是计算出比特的个数(这个值在当代的系统中应该是64)。如果采用对每个字节添加分割的方法,又会如何呢?
- (NSString *)delimitedBinaryString{
NSMutableString *string = [NSMutableString string];
NSInteger value = [self integerValue]; for (NSInteger i = 0; i < sizeof(value) * 8; i++)
{
if (i && i % 8 == 0) [string insertString:@"|" atIndex:0]; [string insertString:(value & 1)? @"1": @"0" atIndex:0];
value /= 2; }
return string;}
在此我们添加了额外的一行,去简单地说明如果循环计数器不是0,但是已经可以被8整除,然后如何插入管道符。
我很高兴去谈论这些,但是当我仔细去查看的时候才意识到如果把幻数放入变量中,然后把for循环和do/while循环交换,这样的解决方法基本上是一样的。