2.5 整数
轻松一刻,做个小游戏,猜一猜下面的代码会显示什么结果?
int xPos; xPos = 105.5; NSLog(@"%i", xPos);
105.5吗?错了!
你也许会想,这是为什么呢?明明写的是105.5,怎么就总是显示105呢?问题就在于把xPos变量声明为整数(int)了。
原来int类型不能处理小数呀!这下就对了!
好的,是时候考虑数据的类型了。在Objective-C中,数据类型可以分为两种风格和若干种类型。其中,两种风格是指:
❑ C风格的基本类型,如int、unsingned int、long int、float、double、char等,这些类型与C语言中的数据类型应用是相似的。
❑ 在Foundation框架中也定义了一些数据类型,如NSInteger、NSUInteger、NSString、CGFloat等。实际上,这些数据类型只是C数据类型的别名,在后续的学习中,我们会逐步发现使用这些类型的意义所在。此外,Foundation是在iOS和OS X系统中进行开发的基本框架,在这个框架中,定义了一系列的基本数据类型和开发资源。也许你会想起来,我们的代码中都引用了Foundation.h头文件。
接下来是真正的数据类型,包括整数、浮点数、布尔、字符、字符串等,我们会详细讨论各种数据类型的取值范围、运算、类型转换等应用特点,首先从整数开始。
■2.5.1 取值范围
整数是指没有小数部分的数,按照计算机内部处理方式又分为有符号整数和无符号整数。其中,有符号整数可以处理负数、零和正数,而无符号整数只能处理零和正数。
在C风格数据类型中,int和unsigned int是两种比较常用的整数类型。其中,int是有符号整数(singn int)的简写形式,而unsigned int则是无符号整数。它们都定义为32位整数,这样一来,int类型的取值范围就是-2147483648到2147483647,即-231到231-1,相对应,unsigned int的取值范围则是0到232-1。
在Foundation框架中,常用的整数类型为NSInteger和NSUInteger,如果我们呼叫帮助,就会发现,它们实际使用了typeof关键字定义为C数据类型的别名,在64位环境下,它们分别定义为long和unsinged long,而在32位环境下,它们分别定义为int和unsinged int类型。我们知道,iOS和OS X系统都已进入64位时代,所以,在开发较新平台下的项目时,我们可以认为NSInteger和NSUInteger类型分别是long int和unsigned long int类型的别名。
那么,为什么要使用这些Foundation中定义的数据类型呢?我想,最大的优势在于代码对各种平台的兼容性。
■2.5.2 算术运算
接下来,我们进行基本的算术运算训练,就是加、减、乘、除那些东西,很简单,只是记得要使用Objective-C代码!
第一题:1号机库有30架战斗机,2号机库有29架战斗机,一共有多少架战斗机,在程序中应该怎么写?
答:用加法(+),如下面的代码。
NSInteger hangar1 = 30; NSInteger hangar2 = 29; NSInteger sum = hangar1 + hangar2; NSLog(@"共有%li架战斗机", sum);
第二题:要是飞走了16架,还剩多少架,怎么计算呢?
答:用减法(-)呀!我们接着前面的代码写。
NSInteger overplus = sum -16; NSLog(@"剩余%li架战斗机", overplus);
第三题:一个飞行中队有4架战斗机,那14个中队有多少架呢?
等等,我知道,用乘法(*),小学生都会。
NSInteger squadrons = 14; sum = squadrons * 4; NSLog(@"14个中队共%li架战斗机", sum);
第四题:那2号机库里有几个飞行中队呢?
答:用除法(/),如下面的代码。
squadrons = hangar2 / 4; NSLog(@"2号机库有%li个飞行中队", squadrons);
在Xcode中测试之前,先想一想结果是什么?
第五题:好像2号机库里的战斗机数量不能被4整除,分了7个中队,还剩几架战斗机,这个怎么计算呢?
答:这个在程序中就很简单了,可以直接使用取余数运算符(%)来计算,如下面的代码。
NSInteger remainder = hangar2 % 4; NSLog(@"2号机库分完飞行中队还剩%li架战斗机", remainder);
以上示例,我们使用整数进行了一系列的算术运算,对于加法、减法和乘法运算,其结果和我们习惯上的概念相同,但请注意除法和取余数运算,其中,整数除以整数,结果依然是整数,而取余数运算的结果也是整数。
此外,我们还应该注意,在任何编程语言中,运算符都会有一定的优先级规则,即一系列运算符在表达式中运算的先后顺序。比如,前面的5个算术运算符在一个算式里时,就应该先计算乘、除、取余数,然后再计算加法和减法。
对于运算符的优先级,如果你不能保证百分之百的记忆正确(可是有几十个运算符),我们还是建议使用小括号()来强制指定运算顺序,这样,代码会更安全,可读性也更强,所以,在工作中,我们不会完全依赖默认的运算符优先级。
■2.5.3 NSLog()函数与格式化输出
在开发和调试过程中,NSLog()函数可以用来显示各种信息,其中,非文本的数据需要使用格式化字符。前面的内容中,我们已经多次使用NSLog()函数。
在这里,常用的整数格式化字符包括:
❑ int类型使用“%i”格式化字符,我们还可以使用“%x”将整数显示为十六进制,使用“%o”将整数显示为八进制。
❑ unsigned int类型,无符号整数,使用“%u”格式化字符。
❑ long int类型,长整型数据,使用“%li”格式化字符。
❑ unsigned long int类型,无符号长整型数据,使用“%lu”格式化字符。
❑ long long int类型,长长整型数据,使用“%lli”格式化字符。
❑ unsigned long long int类型,无符号长长整型数据,使用“%llu”格式化字符。
这些整数类型都有相应的十六进制和八进制的格式化字符,基本的变化原则就是将列表中给出的格式化字符中的i或u变为x(十六进制)或o(八进制)。
■2.5.4 组合运算符
组合运算符是将基本的算术运算符与赋值运算符合并使用,如下面的代码。
NSInteger x = 1; x += 2; // 相当于x = x + 2 NSLog(@"%li", x); // 3
在Objective-C中,5种算术运算都有相应的组合运算符,即+=、-+、*=、/=和%=运算符。
■2.5.5 增量与减量运算
在整数操作中,还有一种运算很常用,那就是增量计算,简单的说,就是变量进行加1的运算。增量运算包括两种形式,即前增量和后增量。
前增量运算表达式中,变量会先进行加1的计算,然后返回表达式的值,此时,表达式的值就是变量加1的值。最终的结果,表达式和变量的值都是变量原值加1,如下面的代码。
int i = 1; NSLog(@"%i", ++i); // 2 NSLog(@"%i", i); // 2
后增量运算表达式中,表达式会返回变量原来的值,然后变量再进行加1的计算。最终结果,表达式的值就是变量的原值,变量的值会加1,如下面的代码。
int i = 1; NSLog(@"%i", i++); // 1 NSLog(@"%i", i); // 2
实际应用中,如果我们只需要使用增量计算后变量的值,则前增量和后增量的区别就无所谓了。但是,如果我们需要使用增量运算表达式的值,就必须非常小心前增量和后增量的区别。
此外,与增量运算相对应的减量运算,同样包括前减量和后减量运算,工作方式与增量运算相似,只是减量运算执行的是变量减1的操作。
■2.5.6 二进制与位运算
对于初学者,本部分可以作为选读,这也是本书中为数不多的与计算机软件基本工作原理相关的内容,了解一下不会有坏处。当然,现在不研究,暂时也不会影响我们继续往下学习,以后有需要,也可以随时回来串串门。
了解整数的位运算,我们首先需要清楚整数在计算机中的处理方式,也就是整数的二进制形式。前面,我们说过的32位整数、64位整数,指的就是二进制位的数量。
在二进制计算中,运算数只包括0和1。这个原因很简单,因为计算机所使用的数字电路就只有两种状态,即连通和断开状态,二进制才能最简单地表示数字电路的状态。
1.无符号整数的二进制形式
无论是8位还是64位,它们对于整数处理的基本原理是一样的,所以,接下来,我们就使用简单点的8位二进制形式来讨论相关的应用问题。你一定和我一样,也不想看着64个0或1组成的二进制数发呆,对吧!
二进制数如何转换为十进制整数呢?我们先来看无符号整数及有符号整数中0和正数的转换方法。
如果二进制位上全部是0,那么,这个数就是0了;如果二进制位上是1,就使用2n-1计算出这个二进制位对应的十进制整数,其中,n是从右向左数的第几位(由低位向高位),然后,将所有的数值相加,就得到了这个二进制数所表示的十进制整数。
如图2-4中的00011001,我们可以得到算式:24+23+20=16+8+1=25,最终,我们计算出二进制数00011001表示的十进制整数就是25。
图2-4 二进制数据
那么,十进制正整数如何转换为二进制呢?我们还以25为例,如图2-5所示的计算方法。
图2-5 十进制转二进制计算方法
首先,我们使用25除以2,商是12(写在下方),余数为1(写在左边);然后使用12除2,商是6,余数是0;以此类推,直到商为1时结束计算。然后,我们从下方往上写出1和0,如图中的结果就写成11001,如果位数不够,比如我们需要8位整数,则在前面(高位)补0,最终得到00011001,这就是十进制整数25的二进制形式了。
2.逻辑位运算
下面,我们就来了解Objective-C中的位运算符。
按位与(AND)运算,使用&运算符,包括两个运算数,当两个运算数都是1时,结果为1,否则为0。如图2-6所示。
图2-6 按位与运算
按位或(OR)运算,使用|运算符,包括两个运算数,当两个运算数有一个为1时,结果为1,只有两个数都是0时,结果才为0。如图2-7所示。
图2-7 按位或运算
按位异或(XOR)运算,使用^运算符,包括两个运算数,当两个数一样时,结果为0,只是两个数不同时,结果才为1。如图2-8所示。
图2-8 按位异或运算
按位取反(NOT)运算,使用!运算符,只需要一个运算数,即1变0,0变1。如图2-9所示。
图2-9 按位取反运算
3.补码
一个二进制数的补码是指原数按位取反后加1的值。还以00011001为例,其按位取反的结果是11100110,再加1就是11100111。这样,11100111就是00011001的补码。
那么,在计算机中使用补码有什么用呢?我们在有符号整数的处理中就可以看到了。
4.有符号整数的二进制形式
我们知道,有符号整数可以处理负数、零和正数。那么,问题来了,在二进制中如何处理整数的符号呢?
答案是,有符号整数二进制的最高位就是它的符号位,如图2-10所示。
图2-10 有符号整数的二进制形式
当有符号整数为零或正数时,符号位为0,也就是说,8位有符号整数的最大取值就是01111111,这就是127。
负数的表示就有点意思了,除了符号位为1,其他位上的数据实际上是负数绝对值的补码形式。
比如,-128的二进制就是10000000(这可不是-0)。我们先看128的二进制形式,即10000000,请注意,此时,我们没有考虑符号问题,然后,按位取反就是01111111,再加1得到补码10000000(和原数是一样的)。
不过,我们现在处理的是8位有符号整数,所以,只有后7位才是真正的数据,所以,128补码中的最高位1就直接扔掉了(在计算机中就是这么绝),然后换成另外一个1(其含义是负数),这样,-128的二进制就是10000000。我们可以看到,虽然看上去差不多,但其实际意义是完全不同的。
好的,大家也看到了,-128是8位有符号整数的最小取值,它有点太特殊了,我们换个数试试吧。
比如-25,先看25的二进制数,我们前面已经算过了,就是00011001,它的补码呢?我们也算过了,就是11100111。别忘了有符号整数的最高位是符号位,8位有符号整数-25的二进制就是11100111。
接下来,我们再算一个好玩的,就是11100111的后七位是十进制的多少,算式如下:
26+25+22+21+20= 64+32+4+2+1 = 103
103有什么特别的?它正好等于128-25,隐约中有没有想到点什么呢?
那么,补码有什么特殊用途呢?在计算机内部可以只进行加法操作,而不需要减法操作(真是多一事不如少一事)。下面,我们来看看25-25在计算机中的二进制处理,我们假设数据类型都是8位有符号整数。
首先,25-25可以处理为25+(-25),好的,25的二进制形式是00011001, -25的二进制是11100111。然后,我们进行相加运算,如图2-11所示。
图2-11 有符号整数的加法运算
好的,我们正在处理的是8位有符号整数,所以,最终的结果只有00000000,也就是整数0。
现在,大家应该了解了有符号整数和无符号整数的二进制表现形式,以及取值范围是怎么确定的吧!如果你感兴趣,可以按二进制试着计算一下,加法虽不难,只是在加数有些多时,注意不要加错了。
5.位移运算
关于二进制,最后讨论一下位移运算,在Objective-C中,位移运算包括位左移运算(<<运算符)和位右移运算(>>运算符),对于这两种位移运算,我们同样按有符号和无符号整数分别讨论。
首先是无符号整数的位移操作,以8位无符号整数64为例,它的二进制表示为01000000,那么它右移2位的操作如图2-12所示。
图2-12 无符号整数右移2位
我们可以看到,当一个无符号整数右移操作时,会将低位的数据直接舍弃,然后在高位补零。那么,64右移2位后的二进制就是00010000,我们换算成十进制就是16。实际上,我们可以看到,当一个整数(N)进行右移n位时,就是在进行N÷2n的计算。
反过来,当我们进行整数(N)的左移运算时,高位数据舍弃,在低位补0,实际上,就是在进行N×2n的计算。但请注意,当我们进行整数的位移运算时,应考虑整数的取值范围。如果左移超出了允许的最高位,也就是超出了整数的取值范围,同样会舍弃,这时就会造成整数的溢出。
下面的代码演示了无符号整数的位移运算:
NSLog(@"%ul", 64 >> 2); // 16 NSLog(@"%ul", 16 << 2); // 64
对于有符号整数的位移运算,会有些不同。如进行8位有符号整数-64的右移运算,首先,-64的二进制表示为11000000,那么,它的右移两位计算操作就如图2-13所示。
图2-13 有符号整数右移2位
我们可以看到,在对有符号整数进行移位操作时,只对数据位进行移动,而符号位保持不变;如图中的右移操作,会在数据的高位补上与符号位相同的数据,这样,-64右移2位后的二进制数就是11110000,也就是-16。我们可以看到,同样是在进行N÷2n的操作。
对于有符号整数的左移操作,同样是只移动数据位,符号位保持不变,然后,在低位补0,这实际上也是在执行N×2n的操作。
下面的代码演示了有符号整数的位移运算:
NSLog(@"%ul", -64 >> 2); // -16 NSLog(@"%ul", -16 << 2); // -64