第2章 Objective-C语言基础

在最近几年,从众多编程语言中脱颖而出,这颗耀眼的新星就是本章的主角——Objective-C。在本章的内容中,将带领大家初步认识Objective-C语言,为读者步入本书后面知识的学习打下基础。

2.1 最耀眼的新星

在过去的两年中,Objective-C的占有率连续攀升,截止到2012年1月,成为了仅次于Java、C、C#和C++之后的一门编程语言。在本节将带领大家一起探寻Objective-C如此火爆的秘密。

2.1.1 看一份统计数据

开始之前,先看一下表2-1的统计数据。

表2-1 编程语言排行榜(截止到2014年5月)

TIOBE于2014年5月公布了2014年4月编程语言排行榜,如表2-1所示。和以前月份的统计数据相比,前三的位置有所变动,例如Objective-C取代了C++的第三名位置。作为2011年还在十名开外的Objective-C来说,在短时间内取得如此骄人的战绩是十分可贵的。这主要归功于iPhone和iPad的持续成功,这两种设备上的程序主要都由Objective-C实现。

提示:TIOBE编程语言社区排行榜是编程语言流行趋势的一个指标,每月更新。这份排行榜排名基于互联网上有经验的程序员、课程和第三方厂商的数量。排名使用著名的搜索引擎(诸如Google、MSN、雅虎)以及Wikipedia和YouTube进行计算。请注意这个排行榜只是反映某个编程语言的热门程度,并不能说明一门编程语言好不好,或者一门语言所编写的代码数量多少。这个排行榜可以用来考查你的编程技能是否与时俱进,也可以作为在开始开发新系统时对选择的语言进行策略性的决策依据。

2.1.2 究竟何为Objective-C

Objective-C是苹果Mac OS X系统上开发的首选语言。Mac OS X技术来源自NextStep的OpenStep操作系统,而OPENSTEP的软件架构都是用Objetive-C语言编写的。这样,Objective-C就理所当然地成为了Mac OS X上的最佳语言。

Objective-C诞生于1986年,Brad Cox在第一个纯面向对象语言Smalltalk的基础上写成了Objective-C语言。后来Brad Cox创立了StepStone公司,专门负责Objective-C语言的推广。

1988年,Steve Jobs的NextStep采用Objective-C作为开发语言。

1992年,在GNU GCC编译器中包含了对Objective-C的支持。在这以后相当长的时间内,Objective-C语言得到了很多程序员的认可,并且很多是编程界的鼻祖和大碗,例如Richard Stallman、Dennis Glating等人。

Objective-C通常被写为ObjC、Objective C或Obj-C,是一门扩充了C语言的面向对象的编程语言。Objective-C语言推出后,主要被用在如下两个使用OpenStep标准的平台上面。

Mac OS X。

GNUstep。

除此之外,在NeXTSTEP和OpenStep中,Objective-C语言也是被作为基本语言来使用的。在gcc运作的系统中,可以实现Objective-C的编写和编译操作,因为,gcc包含Objective-C的编译器。

2.1.3 为什么选择Objective-C

iOS选择Objective-C作为开发语言,有许多方面的原因,具体来说有以下4点。

1.面向对象

Objective-C语言是一门面向对象的语言,功能十分强大。在Cocoa框架中的很多功能,只能通过面向对象的技术来呈现,所以,Objective-C一开始就是为了满足面向对象而设计的。

2.融合性好

从严格意义讲,Objective-C语言是标准C语言的一个超集。当前使用的C程序无须重新开发就可以使用Cocoa软件框架,并且开发者可以在Objective-C中使用C的所有特性。

3.简单易用

Objective-C是一种简洁的语言,它的语法简单,易于学习。但是另一方面,因为易于混淆的术语以及抽象设计的重要性,对于初学者来说可能学习面向对象编程的过程比较漫长。要想学好Objective-C这种结构良好的语言,需要付出很多汗水和精力。

4.动态机制支持

Objective-C和其他的基于标准C语言的面向对象语言相比,对动态的机制支持更为彻底。专业的编译器为运行环境保留了很多对象本身的数据信息,所以,在编译某些程序时可以将选择推迟到运行时来决定。正是基于此特性,使得基于Objective-C的程序非常灵活和强大。例如,Objective-C的动态机制提供了如下两个比普通面向对象语言的优点。

Objective-C语言支持开放式的动态绑定,这有助于交互式用户接口架构的简单化。例如,在Objective-C程序中发送消息时,不但无须考虑消息接收者的类,而且也无须考虑方法的名字。这样可以允许用户在运行时再做出决定,也给开发人员带来了极大的设计自由。

Objective-C语言的动态机制成就了各种复杂的开发工具。运行环境提供了访问运行中程序数据的接口,所以使得开发工具监控Objective-C程序成为可能。

2.2 Objective-C的优点及缺点

Objective-C是一门非常“实际”的编程语言,它使用一个用C写成的很小的运行库,只会令应用程序的大小增加很小,这和大部分OO(面向对象)系统那样使用极大的VM(虚拟机)执行时间来取代了整个系统的运作相反。Objective-C写成的程序通常不会比其原始码大很多。

Objective-C的最初版本并不支持垃圾回收。在当时这是人们争论的焦点之一,很多人考虑到Smalltalk回收会产生漫长的“死亡时间”,从而令整个系统失去功能。Objective-C为避免这个问题,所以不再拥有这个功能。虽然在某些第三方版本已加入这个功能(尤是GNUstep),但是Apple在其Mac OS X中仍未引入这个功能。不过令人欣慰的是,在Apple发布的xCode 4中开始支持自动释放,虽然不敢冒昧地说那是垃圾回收,因为毕竟两者机制不同。在xCode 4中的自动释放,也就是ARC(Automatic Reference Counting)机制,是不需要用户手动去Release(释放)一个对象,而是在编译期间,编译器会自动帮我们添加那些以前经常写的[NSObject release]。

还有另外一个问题,Objective-C不包括命名空间机制,取而代之的是程序设计师必须在其类别名称加上前缀,这样会经常导致冲突。在2004年,在Cocoa编程环境中,所有Mac OS X类别和函式均有“NS”作为前缀,例如NSObject或NSButton来清楚分别它们属于Mac OS X核心;使用“NS”是由于这些类别的名称在NeXTSTEP开发时定下。

虽然Objective-C是C语言的母集,但它也不视C语言的基本型别为第一级的对象。和C++不同,Objective-C不支援运算子多载(它不支持ad-hoc多型)。虽然与C++不同,但是和Java相同,Objective-C只允许对象继承一个类别(不设多重继承)。Categories和protocols不但可以提供很多多重继承的好处,而且没有很多缺点,例如额外执行时间过重和二进制不兼容。

由于Objective-C使用动态运行时类型,而且所有的方法都是函数调用,有时甚至连系统调用“syscalls”也是如此,所以很多常见的编译时性能优化方法都不能应用于Objective-C,例如内联函数、常数传播、交互式优化、纯量取代与聚集等。这使得Objective-C性能劣于类似的对象抽象语言,例如C++。不过Objective-C拥护者认为,既然Objective-C运行时消耗较大,Objective-C本来就不应该应用于C++或Java常见的底层抽象。

2.3 一个简单的例子

在本节的内容中,将首先举一个十分简单的例子,编写一段Objective-C程序,这段简单程序能够在屏幕上显示短语“first Programming!”。整个代码十分简单,下面是完成这个任务的Objective-C程序。

//显示短语

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[l)

{

.NSAutoreleasePool ' pool=[ [NSAutoreleasePool alloc] init];

NSLog ( @ "first Programming!" ) ;

[pool drain] ;

return 0;

}

对于上述程序,我们可以使用Xcode编译并运行程序,或者使用GNU Objective-C编译器在Terminal窗口中编译并运行程序。Objective-C程序最常用的扩展名是“.m”,我们将上述程序保存为“prog1.m”,然后可以使用Xcode打开。

注意:在Objective-C中,小写字母和大写字母是有区别的。Objective-C并不关心从程序行的何处开始输入,程序行的任何位置都能输入语句。基于此,我们可以开发容易阅读的程序。

2.3.1 使用Xcode编辑代码

Xcode是一款功能全面的应用程序,通过此工具可以输入、编译、调试并执行Objective-C程序。如果想在Mac上快速开发Objective-C应用程序,则必须学会使用这个强大的工具的方法。在本章前面的章节中,已经介绍了安装并搭建Xcode工具的流程,接下来将简单介绍使用Xcode编辑Objective-C代码的基本方法。

(1)Xcode位于“Developer”文件夹内中的“Applications”子文件夹中,快捷图标如图2-1所示。

图2-1 Xcode图标

(2)启动Xcode,在File菜单下选择“New Project”,如图2-2所示。

图2-2 启动一个新项目

(3)此时出现一个窗口,如图2-3所示。

图2-3 启动一个新项目:选择应用程序类型

(4)在New Project窗口的左侧,显示了可供选择的模板类别,因为我们的重点是类别iOS Application,所以在此需要确保选择了它。而在右侧显示了当前类别中的模板以及当前选定模板的描述。就这里而言,请单击模板“Empty Application(空应用程序)”,再单击Next(下一步)按钮。窗口界面效果如图2-4所示。

图2-4 单击模板“Empty Application(空应用程序)”

(5)单击“Choose”按钮打开一个新窗口,如图2-5所示。

图2-5 Xcode文件列表窗口

(6)在此将前面的程序命名为prog1,保存在本地机器后,在Xcode中的编辑界面如图2-6所示。

图2-6 Xcode prog1项目窗口

(7)此时可以打开前面创建的第一段Obiective-C代码。不要担心屏幕上为文本显示的各种颜色。Xcode使用不同的颜色指示值、保留字等内容。

现在应该编译并运行第一个程序了,但是首先需要保存程序,方法是从File菜单中选择Save。如果在未保存文件的情况下尝试编译并运行程序,Xcode会询问您是否需要保存。在Build菜单下,可以选择Build或Build and Run。我们选择后者,因为如果构建时不会出现任何错误,则会自动运行此程序。也可单击工具栏中出现的Build and Go图标。

Build and Go意味着“构建,然后执行上次最后完成的操作”,这可能是Run、Debug、Run with Shark或Instruments等。首次为项目使用此图标时,Build and Go意味着构建并运行程序,所以此时使用这个操作没有问题。但是一定要知道“Build and Go”与“Build and Run”之间的区别。

如果程序中有错误,在此步骤期间会看到列出的错误消息。如果情况如此,可回到程序中解决错误问题,然后再次重复此过程。解决程序中的所有错误之后,会出现一个新窗口,其中显示prog1 – DebuggerConsole。如果该窗口没有自动出现,可进入主菜单栏并从Run菜单中选择Console,这样就能显示了。

2.3.2 基本元素介绍

1.注释

接下来开始分析文件first.m,看程序的第一行。

//显示短语

上述代码表示一段注释,在程序中使用的注释语句用于说明程序并增强程序的可读性。注释负责告诉该程序的读者,不管是程序员还是其他负责维护该程序的人,这只是程序员在编写特定程序和特定语句序列时的想法。一般首行注释用来描述整个程序的功能。

在Objective-C程序中,有如下两种插入注释的方式。

第一种:使用两个连续的斜杠“//”,在双斜杠后直到这行结尾的任何字符都将被编译器忽略。

第二种:使用“/*…*/”注释的形式,在中间不能插入任何空格。“/*”表示开始,“*/”表示结束,在两者之间的所有字符都被看作注释语句的一部分,从而被Objective-C编译器忽略。

当注释需要跨越很多程序行时,通常使用这种注释格式,例如下面的代码:

/*

这是注释,因为很长很长很长很长很长很长的,

所以得换行,

功能是显示一行文本。

如果不明白可以联系作者:

xxxx@yahoo.com

*/

在编写程序或者将其键入到计算机上时,应该养成在程序中插入注释的习惯。使用注释有如下两个好处。

(1)当特殊的程序逻辑在您的大脑中出现时就说明程序,要比程序完成后再回来重新思考这个逻辑简单得多。

(2)通过在工作的早期阶段把注释插入程序中,可在调试阶段隔离和调试程序逻辑错误时受益非浅。注释不仅可以帮助您(或者其他人)通读程序,而且还有助于指出逻辑错误的根源。

2.#import指令

我们继续分析程序,看接下来的代码。

#import <Foundation/Foundation.h>

#import指令的功能是,告诉编译器找到并处理名为Foundation.h的文件,这是一个系统文件,表示这个文件不是我们创建的。#import表示将该文件的信息导入或包含到程序中,这个功能像把此文件的内容键入到程序中。例如上述代码可以导入文件Foundation.h。

在Objective-C语言中,编译器指令以@符号开始,这个符号经常用在使用类和对象的情况。在表2-2中,对Objective-C语言中的指令进行了总结。

表2-2 编译器指令

3.主函数

接下来看如下剩余的代码:

int main (int argc, const char * argv[l)

{

NSAutoreleasePool ' pool = [ [NSAutoreleasePool alloc] init] ;

NSLog ( @ "first Programming!" ) ;

[pool drain] ;

return 0;

}

上述代码都被包含在函数main()中,此函数和C语言中的同名函数类似,是整个程序的入口函数。上述代码功能是指定程序的名称为main,这是一个特殊的名称,功能是准确地表示程序将要在何处开始执行。在main前面的保留关键字int用于指定main返回值的类型,此处用int表示该值为整型(在本书后面的章节中将更加详细地讨论类型问题)。

在上述main代码块中包含了多条语句。我们可以把程序的所有语句放入到一对花括号中,最简单的情形是:一条语句是一个以分号结束的表达式。系统将把位于花括号中间的所有程序语句看作main例程的组成部分。首先看如下第一条语句:

NSAutoreleasePool ' pool = [ [NSAutoreleasePool alloc] init] ;

上述语句为自动释放池在内存中保留了空间(会在本书后面的“内存管理”章节讨论这方面的内容)。作为模板的一部分,Xcode会将这行内容自动放入程序中。

接下来的一条语句用于指定要调用名为NSLog的例程,传递或传送给NSLog例程的参数或实参是如下字符串。

@ "first Programming!"

此处的符号@位于一对双引号的字符串前面,这被称为常量NSString对象。NSString例程是Objective-C库中的一个函数,它只能显示或记录其参数。但是之前它会显示该例程的执行日期和时间、程序名以及其他在此不会介绍的数值。在本书的后面的内容中,不会列出NSLog在输出前面插入的这些文本。

在Objective-C中,所有的程序语句必须使用分号“;”结束,这也是为什么分号在NSLog调用的结束圆括号之后立即出现的原因。在退出Objective-C程序之前,应该立即释放已分配的内存池和与程序相关联的对象,例如使用类似于下面的语句可以实现:

[pool drain];

注意:Xcode会在程序中自动插入此行内容。

在函数main中的最后一条语句是:

return 0;

上述语句的功能是终止main的运行,并且返回状态值0。在Objective-C程序中规定,0表示程序正常结束。任何非零值通常表示程序出现了一些问题,例如无法找到程序所需要的文件。

如果使用Xcode进行调试,会在Debug Console窗口中发现在NSLog输出的行后显示下面的提示。

The Debugger has exited with status 0.

假如修改上面的程序,修改后能够同时显示文本“Objective-C OK”。要想实现这个功能,可以通过简单添加另一个对NSLog例程的调用的方法来实现,例如使用下面的代码实现:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[l)

{

. NSAutoreleasePool ' pool = [ [NSAutoreleasePool alloc] init] ;

NSLog ( @ "first Programming!" ) ;

NSLog (@" Objective-C OK!");

[pool drain] ;

return 0;

}

在编写上述代码时,必须使用分号结束每个Objective-C程序语句。执行后会输出:

first Programming!

Objective-C OK!

而在下面的代码中可以看到,无需为每行输出单独调用NSLog例程:

#import <Foundation/Foundation.h>

int main (int argc, const char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

NSLog (@"look...\n..1\n...2\n....3");

[pool drain];

return 0;

}

在上述代码中,首先看看特殊的两字符序列。“\n”中的反斜杠和字母是一个整体,合起来表示换行符。换行符的功能是通知系统要准确完成其名称所暗示的转到一个新行的工作。任何要在换行符之后输出的字符随后将出现在显示器的下一行。其实换行符非常类于HTML标记中的换行标记<br>。执行上述代码后会输出:

look...

..1

...2

....3

4.显示变量的值

在Objective-C程序中,通过NSLog不仅可以显示简单的短语,而且还能显示定义的变量值并计算结果。例如在下面的代码中,使用NSLog显示了数字“10+20相”的结果:

#import <Foundation/Foundation.h>

int main (int argc, const char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

int sum;

sum = 10 + 20;

NSLog (@"The sum of 10 and 20 is %i", sum);

[pool drain];

return 0;

}

对于上述代码的具体说明如下所示。

(1)函数main会自动释放池后面的第一条程序语句,将变量sum定义为整型。在Objective-C程序中,在使用所有程序变量前必须先定义它们。定义变量的目的是告诉Objective-C编译器程序将如何使用这些变量。编译器需要确保这些信息生成正确的指令,便于将值存储到变量中或者从变量中检索值。被定义成int类型的变量只能够存储整型值,例如3、4、−10和0都是整型值,也就是说没有小数位的值是整型值。而带有小数位的数字,例如2.34、2.456和27.0等被称为浮点数,它们都是实数。

(2)整型变量sum的功能是存储整数10和20的和。在编写上述代码时,故意在定义这个变量的下方预留了一个空行,这样做的目的是在视觉上区分例程的变量定义和程序语句。(注意:这种做法是一个良好的风格,在很多时候,在程序中添加单个空白行可使程序的可读性更强。)

(3)代码“sum = 10 + 20;”表示数字10和数字20相加,并把结果存储(如赋值运算符,或者等号所示)到变量sum中。

(4)NSLog语句也调用了圆括号中有两个参数,这些参数用逗号隔开。NSLog语句的第一个参数总是要显示的字符串。然而在显示字符串的同时,通常还希望要显示某些程序变量的值。在上述代码中,希望在显示字符之后还要显示变量sum的值:

The sum of 50 and 25 is

第一个参数中的百分号是一个特殊字符,它可以被函数NSLog识别。紧跟在百分号后的字符指定在这种情况下将要显示的值类型。在前一个程序中,字母i被NSLog例程识别,它表示将要显示的是一个整数。只要NSLog例程在字符串中发现字符“%i”,它都将自动显示例程第二个参数的值。因为sum是NSLog的下一个参数,所以它的值将在显示字符The sum of 10 and 20 is之后自动显示。

上述代码执行后会输出:

The sum of 10 and 20 is 30

2.4 数据类型和常量

其实在本章前面的第一段代码中已经接触过Objective-C的基本数据类型int,例如声明为int类型的变量只能用于保存整型值,也就是说没有小数位的值。其实除了int类型之外,在Objective-C中还有另外3种基本数据类型,分别是float、double和char,具体说明如下所示。

float:用于存储浮点数(即包含小数位的值)。

double:和float类型一样,但是前者的精度约是后者精度的两倍而已。

char:可以存储单个字符,例如字母a,数字字符100,或者一个分号“;”。

在Objective-C程序中,任何数字、单个字符或者字符串通通常被称为常量。例如,数字88表示一个常量整数值。字符串@“Programming in Objective-C”表示一个常量字符串对象。在Objective-C程序中,完全由常量值组成的表达式被称为常量表达式。例如下面的表达式就是一个常量表达式,因为此表达式的每一项都是常量值:

128 + 1 - 2

如果将i声明为整型变量,那么下面的表达式就不是一个常量表达式:

128 + 1 – i

在Objective-C中定义了多个简单(或基本)的数据类型,例如int表示整数类型,这就是一种简单的数据类型,而不是复杂的对象。

注意:虽然Objective-C是一门面向对象的语言,但是简单数据类型并不是面向对象的。它们类似于其他大多数非面向对象语言(比如C语言)的简单数据类型。在Objective-C中提供简单数据类型的原因是出于效率方面的考虑,另外与Java语言不同,Objective-C的整数大小是根据执行环境的规定而变化。

2.4.1 int类型

在Objective-C程序中,整数常量由一个或多个数字的序列组成。序列前的负号表示该值是一个负数,例如值88、−10和100都是合法的整数常量。Objective-C规定,在数字中间不能插入空格,并且不能用逗号来表示大于999的值。所以数值“12,00”就是一个非法的整数常量,如果写成“1200”就是正确的。

在Objective-C中有两种特殊的格式,它们用一种非十进数(基数10)的基数来表示整数常量。如果整型值的第一位是0,那么这个整数将用八进制计数法来表示,就是说用基数8来表示。在这种情况下,该值的其余位必须是合法的8进制数字,因此必须是0到7之间的数字。因此,在Objective-C中以8进制表示的值50(等价于10进制的值40),表示方式为050。与此类似,八进制的常量0177表示十进制的值127 (1×64+7×8+7)。通过在NSLog调用的格式字符串中使用格式符号%o,可以在终端上用八进制显示整型值。在这种情况下,使用八进制显示的值不带有前导0。而格式符号%#o将在八进制值的前面显示前导0。

如果整型常量以0和字母x(无论是小写字母还是大写字母)开头,那么这个值都将用十六进制(以16为基数)计数法来表示。紧跟在字母x后的是十六进制值的数字,它可以由0到9之间的数字和a到f(或A到F)之间的字母组成。字母表示的数字分别为10到15。假如要给名为RGBColor的整型常量指派十六进制的值FFEF0D,则可以使用如下代码实现。

RGBColor = 0xFFEF0D;

在上述代码中,符号“%x”用十六进制格式显示一个值,该值不带前导的0x,并用a到f之间的小写字符表示十六进制数字。要使用前导0x显示这个值,需要使用格式字符%#x的帮助,例如下面的代码。

NSlog("Color is %#x\n",RGBColor);

在上述代码中,通过“%X”或“%#X”中的大写字母X可以显示前导的x,然后用大写字母表示十六进制数字。无论是字符、整数还是浮点数字,每个值都有与其对应的值域。此值域与存储特定类型的值而分配的内存量有关。在大多数情况下,在Objective-C中没有规定这个量,因为它通常依赖于所运行的计算机,所以叫做设备或机器相关量。例如,一个整数不但可以在计算机上占用32位空间,而且也可以使用64位空间来存储。

另外,在任何编程语言中,都预留了一定数量的标识符,这些标识符是不能被定义变量和常量的。表2-3中列出了Objective-C程序中具有特殊含义的标识符。

表2-3 特殊的预定义标识符

2.4.2 float类型

在Objective-C程序中,float类型变量可以存储小数位的值。由此可见,通过查看是否包含小数点的方法可以区分出是否是一个浮点常量。在Objective-C程序中,不但可以省略小数点之前的数字,而且也可以省略之后的数字,但是不能将它们全部省略。例如3.、125.8及-.0001等都是合法的浮点常量。要想显示浮点值,可用NSLog转换字符——%f。

另外,在Objective-C程序中也能使用科学计数法来表示浮点常量。例如“1.5e4”就是使用这种计数法来表示的浮点值,它表示值1.5×10-4。位于字母e前的值称为尾数,而之后的值称为指数。指数前面可以放置正号或负号,指数表示将与尾数相乘的10的幂。因此,在常量2.85e-3中,2.85是尾数值,而−3是指数值。该常量表示值2.85×10-3或0.00285。另外,在Objective-C程序中,不但可用大写字母书写用于分隔尾数和指数的字母e,而且也可以用小写字母来书写。

在Objective-C程序中,建议在NSLog格式字符串中指定格式字符%e。使用NSLog格式字符串%g 时,允许NSLog确定使用常用的浮点计数法还是使用科学计数法来显示浮点值。当该值小于-4或大于5时,采用%e(科学计数法)表示,否则采用%f(浮点计数法)。

十六进制的浮点常量包含前导的0x或0X,在后面紧跟一个或多个十进制或十六进制数字,然后紧接着是p或P,最后是可以带符号的二进制指数。例如,0x0.3p10表示的值为3/16×210=192。

2.4.3 double类型

在Objective-C程序中,类型double与类型float类似。Objective-C规定,当在float变量中所提供的值域不能满足要求时,需要使用double变量来实现需求。声明为double类型的变量可以存储的位数大概是float变量所存储的两倍多。在现实应用中,大多数计算机使用64位来表示double值。除非另有特殊说明,否则Objective-C编译器将全部浮点常量当作double值来对待。要想清楚地表示float常量,需要在数字的尾部添加字符f或F,例如:

12.4f

要想显示double的值,可以使用格式符号%f、%e或%g来辅助实现,它们与显示float值所用的格式符号是相同的。其实double类型和float类型可以被称为实型。在Objective-C语言中,实型数据分为实型常量和实型变量。

1.实型常量

实型常量也称为实数或者浮点数。在Objective-C语言中,它有两种形式:小数形式和指数形式。

小数形式:由数字0~9 和小数点组成。例如:0.0、25.0、5.789、0.13、5.0、300.、-267.8230 等均为合法的实数。注意,必须有小数点。在NSLog 上,使用%f 格式来输出小数形式的实数。

指数形式:由十进制数,加阶码标志“e”或“E”以及阶码(只能为整数,可以带符号)组成。其一般形式为:a E n(a为十进制数,n为十进制整数)。其值为 a×10n。在NSLog上,使用%e格式来输出指数形式的实数。例如下面是一些合法的实数:

2.1E5(等于2.1×105)

3.7E-2(等于3.7×10-2)

而下面是不合法的实数:

345(无小数点)

E7(阶码标志E 之前无数字)

-5(无阶码标志)

53.-E3(负号位置不对)

2.7E(无阶码)

Objective-C 允许浮点数使用后缀,后缀为“f”或“F”即表示该数为浮点数。如356f 和356F是等价的。

2.实型变量

(1)实型数据在内存中的存放形式。

实型数据一般占4 个字节(32 位)内存空间,按指数形式存储。小数部分占的位(bit)数越多,数的有效数字越多,精度越高。指数部分占的位数越多,则能表示的数值范围越大。

(2)实型变量的分类。

实型变量分为单精度(float型)、双精度(double型)和长双精度(long double型)3类。在大多数机器上,单精度型占4 个字节(32 位)内存空间,其数值范围为3.4E-38~3.4E+38,只能提供7 位有效数字。双精度型占8 个字节(64 位)内存空间,其数值范围为1.7E-308~1.7E+308,可提供16位有效数字。

2.4.4 char类型

在Objective-C程序中,char类型变量的功能是存储单个字符,只要将字符放到一对单引号中就能得到字符常量。例如‘a’、‘;’和‘0’都是合法的字符常量。其中‘a’表示字母a,‘;’表示分号,‘0’表示字符0(并不等同于数字0)。

在Objective-C程序中,不能把字符常量和C风格的字符串混为一谈,字符常量是放在单引号中的单个字符,而字符串则是放在双引号中任意个数的字符。不但要求在前面有@字符,而且要求放在双引号中的字符串才是NSString字符串对象。

另外,字符常量‘\n’(即换行符)是一个合法的字符常量,虽然这看似与前面提到的规则相矛盾。出现这种情况的原因是,反斜杠符号是Objective-C中的一个特殊符号,而其实并不把它看成一个字符。也就是说,Objective-C编译器仅仅将‘\n’看作是单个字符,尽管它实际上由两个字符组成,而其他的特殊字符由反斜杠字符开头。要想了解在Objective-C中所有的特殊字符,读者可以参阅本书附录。

在NSLog调用中,可以使用格式字符%c来显示char变量的值。例如在下面的中,使用了基本的Objective-C数据类型:

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

int integerVar = 50;

float floatingVar = 331.79;

double doubleVar = 8.44e+11;

char charVar = 'W';

NSLog (@"integerVar = %i", integerVar);

NSLog (@"floatingVar = %f", floatingVar);

NSLog (@"doubleVar = %e", doubleVar);

NSLog (@"doubleVar = %g", doubleVar);

NSLog (@"charVar = %c", charVar);

[pool drain];

return 0;

}

在上述代码中,第二行floatingVar的值是331.79,但是实际显示为331.790009。这是因为,实际显示的值是由使用的特定计算机系统决定的。出现这种不准确值的原因是计算机内部使用特殊的方式表示数字。当使用计算器处理数字时,很可能遇到相同的不准确性。如果用计算器计算1除以3,将得到结果.33333333,很可能结尾带有一些附加的3。这串3是计算器计算1/3的近似值。理论上,应该存在无限个3。然而该计算器只能保存这些位的数字,这就是计算机的不确定性。此处应用了相同类型的不确定性:在计算机内存中不能精确地表示一些浮点值。

执行上述代码后会输出:

integerVar = 50

floatingVar = 331.790009

doubleVar = 8.440000e+11

doubleVar = 8.44e+11

charVar = ‘W’

另外,使用char也可以表示字符变量。字符变量类型定义的格式和书写规则都与整型变量相同,例如下面的代码:

char a,b;

每个字符变量被分配一个字节的内存空间,因此只能存放一个字符。字符值是以ASCII码的形式存放在变量的内存单元之中的。如x的十进制ASCII 码是120,y 的十进制ASCII码是121。下面的例子是把字符变量a、b分别赋予'x'和'y':

a='x';

b='y';

实际上是在a、b两个内存单元内存放120和121的二进制代码。我们可以把字符值看成是整型值。Objective-C 语言允许对整型变量赋以字符值,也允许对字符变量赋以整型值。在输出时,允许把字符变量按整型量输出,也允许把整型量按字符量输出。整型量为多字节量,字符量为单字节量,当整型量按字符型量处理时,只有低8位字节参与处理。

2.4.5 字符常量

在Objective-C程序中,字符常量是用单引号括起来的一个字符,例如下面列出的都是合法字符常量:

'a'、'b'、'='、'+'、'?'

Objective-C中的字符常量有如下4个特点。

(1)字符常量只能用单引号括起来,不能用双引号或其他括号。

(2)字符常量只能是单个字符,不能是字符串,转义字符除外。

(3)字符可以是字符集中任意字符。但数字被定义为字符型之后就不能参与数值运算。如'5'和5是不同的。'5'是字符常量,不能参与运算。

(4)Objective-C中的字符串不是"abc",而是@"abc"。

转义字符是一种特殊的字符常量。转义字符以反斜线“\”开头,后面紧跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。例如,“\n”就是一个转义字符,表示“换行”。转义字符主要用来表示那些用一般字符不便于表示的控制代码。常用的转义字符及其含义如表2-4所示。

表2-4 常用的转义字符及其含义

在大多数情况下,Objective-C字符集中的任何一个字符都可以使用转义字符来表示。在表2-3中,ddd和hh分别为八进制和十六进制的ASCII代码,表中的\ddd和\xhh正是为此而提出的。例如\101表示字母A,\102表示字母B,\134表示反斜线,\XOA表示换行等:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

char a=120;

char b=121;

NSLog(@"%c,%c",a,b);

NSLog(@"%i,%i",a,b);

[pool drain];

return 0;

}

在上述代码中,定义a、b为字符型,但在赋值语句中赋以整型值。从结果看,输出a和b值的形式取决于NSLog 函数格式串中的格式符。当格式符为“%c”时,对应输出的变量值为字符,当格式符为“%i”时,对应输出的变量值为整数。执行上述代码后输出:

x,y

120,121

2.4.6 id类型

在Objective-C程序中,id是一般对象类型,id数据类型可以存储任何类型的对象。例如在下面的代码中,将number声明为了id类型的变量:

id number;

我们可以声明一个方法,使其具有id类型的返回值。例如在下面的代码中,声明了一个名为newOb的实例方法,它不但具有名为type的单个整型参数,而且还具有id类型的返回值。在此需要注意,对返回值和参数类型声明来说,id是默认的类型:

-(id) newOb: (int) type;

再例如在下面的代码中,声明了一个返回id类型值的类方法:

+allocInit;

id数据类型是本书经常使用的一种重要数据类型,是Objective-C中的一个十分重要的特性。在表2-5中列出了基本数据类型和限定词。

表2-5 Objective-C的基本数据类型

在Objective-C程序中,id 类型是一个独特的数据类型。在概念上和Java语言中的类Object相似,可以被转换为任何数据类型。也就是说,在id类型变量中可以存放任何数据类型的对象。在内部处理上,这种类型被定义为指向对象的指针,实际上是一个指向这种对象的实例变量的指针。例如下面定义了一个id类型的变量和返回一个id类型的方法:

id anObject;

- (id) new: (int) type;

id 和void *并非完全一样,下面是id在objc.h中的定义:

typedef struct objc_object {

class isa;

} *id;

由此可以看出,id是指向struct objc_object 的一个指针。也就是说,id 是一个指向任何一个继承了Object或NSObject类的对象。因为id 是一个指针,所以在使用id的时候不需要加星号,例如下面的代码:

id foo=renhe;

上述代码定义了一个renhe指针,这个指针指向NSObject 的任意一个子类。而“id*foo= renhe;”则定义了一个指针,这个指针指向另一个指针,被指向的这个指针指向NSObject的一个子类。

2.4.7 限定词

在Objective-C程序中的限定词有:long、long long、short、unsigned及signed。

1.long

如果直接把限定词long放在声明int之前,那么所声明的整型变量在某些计算机上具有扩展的值域。例如下面是一个上述情况的例子:

long int factorial;

通过上述代码,将变量fractorial声明为long的整型变量。这就像float和double变量一样,long变量的具体精度也是由具体的计算机系统决定。在许多系统上,int与long int具有相同的值域,而且任何一个都能存储32位宽(231-1,或2,147,483,647)的整型值。

在Objective-C程序中,long int类型的常量值可以通过在整型常量末尾添加字母L(大小写均可)来形成,此时在数字和L之间不允许有空格出现。根据此要求,我们可以声明为如下格式:

long int numberOfPoints = 138881100L;

通过上述代码,将变量numberOfPoints声明为long int类型,而且初值为138,881,100。

要想使用NSLog显示long int的值,需要使用字母 l 作为修饰符,并且将其放在整型格式符号i、o和x之前。这意味着格式符号%li用十进制格式显示long int的值,符号%lo用八进制格式显示值,而符号%lx则用十六进制格式显示值。

2.long long

例如在下面的代码中,使用了long long的整型数据类型。

long long int maxnum;

通过上述代码,将指定的变量声明为具有特定扩展精度的变量,通过此该扩展精度,保证了变量至少具有64位的宽度。NSLog字符串不使用单个字母l,而使用两个l来显示long long的整数,例如“%lli”的形式。我们同样可以将long标识符放在double声明之前,例如下面的代码:

long double CN_NB_2012;

可以long double常量写成其尾部带有字母l或L的浮点常量的形式,例如:

1.234e+5L

要想显示long double的值,需要使用修饰符L来帮助实现。例如通过%Lf用浮点计数法显示long double的值,通过%Le用科学计数法显示同样的值,使用%Lg告诉NSLog在%Lf和%Le之间任选一个使用。

3.short

如果把限定词short放在int声明之前,意思是告诉Objective-C编译器要声明的特定变量用来存储相当小的整数。使用short变量的主要好处是节约内存空间,当程序员需要大量内存,而可用的内存量又十分有限时,可以使用short变量来解决内存不足的问题。

在很多计算机设备上,short int所占用的内存空间是常规int变量的一半。在任何情况下,需要确保分配给short int的空间数量不少于16位。

在Objective-C程序中,没有其他方法可显式编写short int型常量。要想显示short int变量,可以将字母h放在任何普通的整型转换符号之前,例如%hi、%ho或%hx。也就是说,可以用任何整型转换符号来显示short int,原因是当它作为参数传递给NSLog例程时,可以转换成整数。

4.unsigned

在Objective-C程序中,unsigned是一种最终限定符,当整数变量只用来存储正数时可以使用最终限定符。例如通过下面的代码向编译器声明,变量counter只用于保存正值。使用限制符的整型变量可以专门存储正整数,也可以扩展整型变量的精度:

unsigned int counter;

将字母u(或U)放在常量之后,可以产生unsigned int常量,例如下面的代码:

0x00ffU

在编写整型常量时,可以组合使用字母u(或U)和l(或L),例如下面的代码可以告诉编译器将常量10000看作unsigned long:

10000UL

如果整型常量之后不带有字母u、U、l或L中的任何一个,而且因为太大,所以不适合用普通大小的int表示,那么编译器将把它看作是unsigned int值。如果太小则不适合用unsigned int来表示,那么此时编译器将把它看作long int。如果仍然不适合用long int表示,编译器会把它作为unsigned long int来处理。

在Objective-C程序中,当将变量声明为long int、short int或unsigned int类型时,可以省略关键字int,为此变量unsigned counter和如下声明格式等价:

unsigned counter;

同样也可以将变量char声明为unsigned。

5.signed

在Objective-C程序中,限定词signed能够明确地告诉编译器特定变量是有符号的。signed主要用在char声明之前。

2.4.8 总结基本数据类型

在Objective-C程序中,可以使用以下格式将变量声明为特定的数据类型:

type name = initial_value;

在表2-6中,总结了Objective-C中的基本数据类型。

表2-6 Objective-C中的基本数据类型

2.5 字符串

在Objective-C程序中,字符串常量是由@和一对双引号括起的字符序列。比如,@"CHINA"、@"program"、@"$12.5"等都是合法的字符串常量。它与C语言的区别是有无“@”。

字符串常量和字符常量是不同的量,主要有如下两点区别。

(1)字符常量由单引号括起来,字符串常量由双引号括起来。

(2)字符常量只能是单个字符,字符串常量则可以含一个或多个字符。

在Objective-C 语言中,字符串不是作为字符的数组被实现。在Objective-C 中的字符串类型是NSString,它不是一个简单数据类型,而是一个对象类型,这是与C++ 语言不同的。我们会在后面的章节中详细介绍NSString,例如下面是一个简单的NSString例子:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSLog (@"Programming is fun!") ;

[pool drain];

return 0;

}

上述代码和本章的第一段Objective-C程序类似,运行后会输出:

Programming is fun!

2.6 算数表达式

在Objective-C语言中,在两个数相加时使用加号(+),在两个数相减时使用减号(-),在两个数相乘时使用乘号(*),在两个数相除时使用除号(/)。因为它们运算两个值或项,所以这些运算符称为二元算术运算符。

2.6.1 运算符的优先级

运算符的优先级是指运算符的运算顺序,例如数学中的先乘除后加减就是一种运算顺序。算数优先级用于确定拥有多个运算符的表达式如何求值。在Objective-C中规定,优先级较高的运算符首先求值。如果表达式包含优先级相同的运算符,可以按照从左到右或从右到左的方向来求值,运算符决定了具体按哪个方向求值。上述描述就是通常所说的运算符结合性。

例如,下面的代码演示了减法、乘法和除法的运算优先级。在程序中执行的最后两个运算引入了一个运算符比另一个运算符有更高优先级的概念。事实上,Objective-C中的每一个运算符都有与之相关的优先级:

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

int a = 100;

int b = 2;

int c = 20;

int d = 4;

int result;

result = a - b; //subtraction

NSLog (@"a - b = %i", result);

result = b * c; //multiplication

NSLog (@"b * c = %i", result);

result = a / c; //division

NSLog (@"a / c = %i", result);

result = a + b * c; //precedence

NSLog (@"a + b * c = %i", result);

NSLog (@"a * b + c * d = %i", a * b + c * d);

[pool drain];

return 0;

}

对于上述代码的具体说明如下所示。

(1)在声明整型变量a、b、c、d及result之后,程序将“a-b”的结果赋值给result,然后用恰当的NSLog调用来显示它的值。

(2)语句“result = b*c;”的功能是将b的值和c的值相乘并将其结果存储到result中。然后用NSLog调用来显示这个乘法的结果。

(3)开始除法运算。Objective-C中的除法运算符是“/”。执行100除以25得到结果4,可以用NSLog语句在a除以c之后立即显示。在某些计算机系统上,如果将一个数除以0将导致程序异常终止或出现异常,即使程序没有异常终止,执行这样的除法所得的结果也毫无意义。其实可以在执行除法运算之前检验除数是否为0。如果除数为0,可采用适当的操作来避免除法运算。

(4)表达式“a + b * c”不会产生结果2040(102×20);相反,相应的NSLog语句显示的结果为140。这是因为Objective-C与其他大多数程序设计语言一样,对于表达式中多重运算或项的顺序有自己的规则。通常情况下,表达式的计算按从左到右的顺序执行。然而,为乘法和除法运算指定的优先级比加法和加法的优先级要高。因此,Objective-C将表达式“a + b * c”等价于“a + (b * c)”。如果采用基本的代数规则,那么该表达式的计算方式是相同的。如果要改变表达式中项的计算顺序,可使用圆括号。事实上,前面列出的表达式是相当合法的Objective-C表达式。这样,可以使用表达式“result = a + (b * c);”来替换上述代码中的表达式,也可以获得同样的结果。然而,如果用表达式“result = (a + b) * c;”来替换,则指派给result的值将是2040,因为要首先将a的值(100)和b的值(2)相加,然后再将结果与c的值(20)相乘。圆括号也可以嵌套,在这种情况下,表达式的计算要从最里面的一对圆括号依次向外进行。只要确保结束圆括号和开始圆括号数目相等即可。

(5)开始研究最后一条代码语句,当将NSLog指定的表达式作为参数时,无需将该表达式的结果先指派给一个变量,这种做法是完全合法的。表达式“a * b + c * d”可以根据以上述规则使用“(a * b) + (c * d)”的格式,也就是使用“(100 * 2) + (20 * 4)”格式来计算,得出的结果280将传递给NSLog例程。

运行上述代码后会输出:

a - b = 98

b * c = 40

a / c = 5

a + b * c = 140

a * b + c * d = 280

2.6.2 整数运算和一元负号运算符

例如下面的代码演示了运算符的优先级,并且引入了整数运算的概念:

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

int a = 25;

int b = 2;

int result;

float c = 25.0;

float d = 2.0;

NSLog (@"6 + a / 5 * b = %i", 6 + a / 5 * b);

NSLog (@"a / b * b = %i", a / b * b);

NSLog (@"c / d * d = %f", c / d * d);

NSLog (@"-a = %i", -a);

[pool drain];

return 0;

}

对于上述代码的具体说明如下所示。

(1)第一个NSLog调用中的表达式巩固了运算符优先级的概念。该表达式的计算按以下顺序执行:

因为除法的优先级比加法高,所以先将a的值(25)除以5。该运算将给出中间结果4。

因为乘法的优先级也大于加法,所以随后中间结果(5)将乘以2(即b的值),并获得新的中间结果(10)。

最后计算6加10,并得出最终结果(16)。

(2)第二条NSLog语句会产生一个新误区,我们希望a除以b再乘以b的操作返回a(已经设置为25)。但是此操作并不会产生这一结果,在显示器上输出显示的是24。其实该问题的实际情况是:这个表达式是采用整数运算来求值的。再看变量a和b的声明,它们都是用int类型声明的。当包含两个整数的表达式求值时,Objective-C系统都将使用整数运算来执行这个操作。在这种情况下,数字的所有小数部分将丢失。因此,计算a除以b,即25除以2时,得到的中间结果是12,而不是期望的12.5。这个中间结果乘以2就得到最终结果24,这样,就解释了出现“丢失”数字的情况。

(3)在倒数第2个NSLog语句中,如果用浮点值代替整数来执行同样的运算,就会获得期望的结果。决定到底使用float变量还是int变量的是基于变量的使用目的。如果无需使用任何小数位,可使用整型变量。这将使程序更加高效,也就是说,它可以在大多数计算机上更加快速地执行。另一方面,如果需要精确到小数位,很清楚应该选择什么。此时,惟一必须回答的问题是使用float还是double。对此问题的回答取决于使用数据所需的精度以及它们的量级。

(4)在最后一条NSLog语句中,使用一元负号运算符对变量a的值进行了求反处理。这个一元运算符是用于单个值的运算符,而二元运算符作用于两个值。负号实际上扮演了一个双重角色:作为二元运算符,它执行两个数相减的操作;作为一元运算符,它对一个值求反。

经过以上分析,最终运行上述代码后会输出:

6 + a / 5 * b = 16

a / b * b = 24

c / d * d = 25.000000

-a = -25

由此可见,与其他算术运算符相比,一元负号运算符具有更高的优先级,但一元正号运算符(+)除外,它和算术运算符的优先级相同。所以表达式“c = -a * b;”将执行-a乘以b。

在上述代码的前3条语句中,在int和a、b及result的声明中插入了额外的空格,这样做的目的是对齐每个变量的声明,这种书写语句的方法使程序更加容易阅读。另外我们还需要养成这样一个习惯——每个运算符前后都有空格,这种做法不是必需的,仅仅是出于美观上的考虑。一般来说,在允许单个空格的任何位置都可以插入额外的空格。

2.6.3 模运算符

在Objective-C程序中,使用百分号(%)表示模运算符。为了了解模运算符的工作方式,请读者看下面代码:

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

int a = 25, b = 5, c = 10, d = 7;

NSLog (@"a %% b = %i", a % b);

NSLog (@"a %% c = %i", a % c);

NSLog (@"a %% d = %i", a % d);

NSLog (@"a / d * d + a %% d = %i", a / d * d + a % d);

[pool drain];

return 0;

}

对于上述代码的具体说明如下所示。

(1)在main语句中定义并初始化了4个变量:a、b、c和d,这些工作都是在一条语句内完成的。NSLog使用百分号之后的字符来确定如何输出下一个参数。如果它后面紧跟另一个百分号,那么NSLog例程认为您其实想显示百分号,并在程序输出的适当位置插入一个百分号。

(2)模运算符%的功能是计算第一个值除以第二个值所得的余数,在上述第一个例子中,25除以5所得的余数显示为0。如果用25除以10,会得到余数5,输出中的第二行可以证实。执行25乘以7将得到余数4,它显示在输出的第三行。

(3)最后一条求值表达式语句。Objective-C使用整数运算来执行两个整数间的任何运算,所以两个整数相除所产生的任何余数将被完全丢弃。如果使用表达式a / b表示25除以7,将会得到中间结果3。如果将这个结果乘以d的值(即7),将会产生中间结果21。最后,加上a除以b的余数,该余数由表达式a % d来表示,会产生最终结果25。这个值与变量a的值相同并非巧合。一般来说,表达式“a / b * b + a % b”的值将始终与a的值相等,当然,这是在假定a和b都是整型值的条件下做出的。事实上,定义的模运算符%只用于处理整数。

在Objective-C程序中,模运算符的优先级与乘法和除法的优先级相同。由此而可以得出,表达式“table + value % TABLE_SIZE”等价于表达式“table + (value % TABLE_SIZE)”。

经过上述分析,运行上述代码后会输出:

a % b = 0

a % c = 5

a % d = 4

a / d * d + a % d = 25

2.6.4 整型值和浮点值的相互转换

要想使Objective-C程序实现更复杂的功能,必须掌握浮点值和整型值之间进行隐式转换的规则。例如下面的代码演示了数值数据类型间的一些简单转换:

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

float f1 = 123.125, f2;

int i1, i2 = -150;

i1 = f1; // floating 转换为integer

NSLog (@"%f assigned to an int produces %i", f1, i1);

f1 = i2; // integer 转换为floating

NSLog (@"%i assigned to a float produces %f", i2, f1);

f1 = i2 / 100; // 整除integer类型

NSLog (@"%i divided by 100 produces %f", i2, f1);

f2 = i2 / 100.0; //整除float类型

NSLog (@"%i divided by 100.0 produces %f", i2, f2);

f2 = (float) i2 / 100; //类型转换操作符

NSLog (@"(float) %i divided by 100 produces %f", i2, f2);

[pool drain];

return 0;

}

对于上述代码的具体说明如下所示。

(1)因为在Objective-C中,只要将浮点值赋值给整型变量,数字的小数部分都会被删节。所以在第一个程序中,当把f1的值赋予i1时会删除数字123.125,这意味着只有整数部分(即123)存储到了i1中。

(2)当产生把整型变量指派给浮点变量的操作时,不会引起数字值的任何改变,该值仅由系统转换并存储到浮点变量中。例如上述代码的第二行验证了这一情况——i2的值(-150)进行了正确转换并储到float变量f1中。

执行上述代码后输出:

123.125000 assigned to an int produces 123

-150 assigned to a float produces -150.000000

-150 divided by 100 produces -1.000000

-150 divided by 100.0 produces -1.500000

(float) -150 divided by 100 produces -1.500000

2.6.5 类型转换运算符

在声明和定义方法时,将类型放入圆括号中可以声明返回值和参数的类型。在表达式中使用类型时,括号表示一个特殊的用途。例如在前面程序2-11中的最后一个除法运算:

f2 = (float) i2 / 100;

在上述代码中引入了类型转换运算符。为了求表达式值,类型转换运算符将变量i2的值转换成float类型。该运算符永远不会影响变量i2的值;它是一元运算符,行为和其他一元运算符一样。因为表达式-a永远不会影响a的值,因此表达式(float)a也不会影响a的值。

类型转换运算符的优先级要高于所有的算术运算符,但是一元减号和一元加号运算符除外。如果需要可以经常使用圆括号进行限制,以任何想要的顺序来执行一些项。例如下面的代码是使用类型转换运算符的另一个例子,表达式“(int) 29.55 + (int) 21.99”在Objective-C中等价于“29 + 21”。因为将浮点值转换成整数的后果就是舍弃其中的浮点值。表达式“(float) 6 / (float) 4”得到的结果为1.5,与表达式“(float)6 / 4”的执行效果相同。

类型转换运算符通常用于将一般id类型的对象转换成特定类的对象,例如在下面的代码中,将id变量myNumber的值转换成一个Fraction对象。转换结果将指派给Fraction变量myFraction:

id myNumber;

Fraction *myFraction;

myFraction = (Fraction *) myNumber;

2.7 表达式

在Objective-C程序中,联合使用表达式和运算符可以构成功能强大的程序语句。在本节将详细讲解表达式的基本知识,为读者步入本书后面知识的学习打下坚实的基础。

2.7.1 常量表达式

在Objective-C程序中,常量表达式是指每一项都是常量值的表达式。其中在下列情况中必须使用常量表达式。

作为switch语句中case之后的值。

指定数组的大小。

为枚举标识符指派值。

在结构定义中,指定位域的大小。

为外部或静态变量指派初始值。

为全局变量指派初始值。

在#if预处理程序语句中,作为#if之后的表达式。

其中在上述前4种情况中,常量表达式必须由整数常量、字符常量、枚举常量和sizeof表达式组成。在此只能使用以下运算符:算术运算符、按位运算符、关系运算符、条件表达式运算符和类型强制转换运算符。

在上述第5和第6种情况中,除了上面提到的规则之外,还可以显式地或隐式地使用取地址运算符。然而,它只能应用于外部或静态变量或函数。因此,假设x是一个外部或静态变量,表达式“&x + 10”将是合法的常量表达式。此外,表达式“&a[10] – 5”在a是外部或静态数组时将是合法的常量表达式。最后,因为&a[0]等价于表达式a,所以“a + sizeof (char) * 100”也是一个合法的常量表达式。

在上述最后一种需要常量表达式(在#if之后)情况下,除了不能使用sizeof运算符、枚举常量和类型强制转换运算符以外,其余规则与前4种情况的规则相同。然而,它允许使用特殊的defined运算符。

2.7.2 条件运算符

Objective-C中的条件运算符也被称为条件表达式,其条件表达式由3个子表达式组成,其语法格式如下所示:

expression1 ? expression2 : expression3

对于上述格式有如下两点说明:

(1)当计算条件表达式时,先计算expression1的值,如果值为真则执行expression2,并且整个表达式的值就是expression2的值。不会执行expression3。

(2)如果expression1为假,则执行expression3,并且条件表达式的值是expression3的值。不会执行expression2。

在Objective-C程序中,条件表达式通常用作一条简单的if语句的一种缩写形式。例如下面的代码:

a = ( b > 0 ) ? c : d;

等价于下面的代码:

if ( b > 0 )

a = c;

else

a = d;

假设a、b、c为表达式,则表达式“a ? b : c”在a为非0时,值为b;否则为c。只有表达式b或c其中之一被求值。

表达式b和c必须具有相同的数据类型。如果它们的类型不同,但都是算术数据类型,就要对其执行常见的算术转换以使其类型相同。如果一个是指针,另一个为0,则后者将被看作是与前者具有相同类型的空指针。如果一个是指向void的指针,另一个是指向其他类型的指针,则后者将被转换成指向void的指针并作为结果类型。

2.7.3 sizeof运算符

在Objective-C程序中,sizeof运算符能够获取某种类型变量的数据长度,例如下面列出了sizeof 运算符在如下表达式中的作用。

sizeof(type) //包含特定类型值所需的字节数

sizeof a  //保存a的求值结果所必需的字节数

在上述表达式中,如果type为char,则结果将被定义为1。如果a是(显式地或者通过初始化隐式地)维数确定的数组名称,而不是形参或未确定维数的数组名称,那么sizeof a会给出将元素存储到a中必需的位数。

如果a是一个类名,则sizeof (a)会给出保存a的实例所必需的数据结构大小。通过sizeof运算符产生的整数类型是size_t,它在标准头文件<stddef.h>中定义。

如果a是长度可变的数组,那么在运行时对表达式求值;否则在编译时求值,因此它可以用在常量表达式中。

虽然不应该假设程序中数据类型的大小,但是有时候需要知道这些信息。在Objective-C程序中,可以使用库例程(如malloc)实现动态内存分配功能,或者对文件读出或写入数据时,可能需要这些信息。

在Objective-C语言中,提供了sizeof运算符来确定数据类型或对象的大小。sizeof运算符返回的是指定项的字节大小,sizeof运算符的参数可以是变量、数组名称、基本数据类型名称、对象、派生数据类型名称或表达式。例如下面的代码给出了存储整型数据所需的字节数,在笔者机器上运行后的结果是4 (或32位)。

sizeof (int)

假如将x声明为包含100个int数据的数组,则下面的表达式将给出存储x中的100个整数所需要的存储空间。

sizeof (x)

假设myFract是一个Fraction对象,它包含两个int实例变量(分子和分母),那么下面的表达式在任何使用4字节表示指针的系统中都会产生值4。

sizeof (myFract)

其实这是sizeof对任何对象产生的值,因为这里询问的是指向对象数据的指针大小。要获得实际存储Fraction对象实例的数据结构大小,可以编写下面的代码语句实现。

sizeof (*myFract)

上述表达式在笔者机器上输出的结果为12,即分子和分母分别用4个字节,加上另外的4个字节存储继承来的isa成员。

而下面的表达式值将能够存储结构data_entry所需的空间总数。

sizeof (struct data_entry)

如果将data定义为包含struct data_entry元素的数组,则下面的表达式将给出包含在data(data必须是前面定义的,并且不是形参也不是外部引用的数组)中的元素个数。

sizeof (data) / sizeof (struct data_entry)

下面的表达式也会产生同样的结果。

sizeof (data) / sizeof (data[0])

在Objective-C程序中,建议读者尽可能地使用sizeof运算符,这样避免必须在程序中计算和硬编码数据大小。

2.7.4 关系运算符

关系运算符用于比较运算,包括大于(>)、小于(<)、等于(==)、 大于等于(>=)、小于等于(<=)和不等于(!=)六种,而关系运算符的结果是BOOL 类型的数值。当运算符成立时,结果为YES(1),当不成立时,结果为NO(0)。例如下面的代码演示了关系运算符的用法:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSLog (@"%i",3>5) ;

NSLog (@"%i",3<5) ;

NSLog (@"%i",3!=5) ;

[pool drain];

return 0;

}

在上述代码中,根据程序中的判断我们得知,3>5 是不成立的,所以结果是0;3<5 是成立的,所以结果是1;3!=5的结果也同样成立,所以结果为1。运行上述代码后会输出:

0

1

1

2.7.5 强制类型转换运算符

使用强制类型转换的语法格式如下所示。

(类型说明符) (表达式)

功能是把表达式的运算结果强制转换成类型说明符所表示的类型。

例如:

(float)//a 把a 转换为实型

(int)(x+y)//把x+y 的结果转换为整型

例如下面的代码演示强制类型转换运算符的基本用法。

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

float f1=123.125,f2;

int i1,i2=-150;

i1=f1;

NSLog (@"%f 转换为整型为%i",f1,i1) ;

f1=i2;

NSLog (@"%i 转换为浮点型为%f",i2,f1) ;

f1=i2/100;

NSLog (@"%i 除以100 为 %f",i2,f1) ;

f2=i2/100.0;

NSLog (@"%i 除以100.0 为 %f",i2,f2) ;

f2= (float) i2/100;

NSLog (@"%i 除以100 转换为浮点型为%f",i2,f2) ;

[pool drain];

return 0;

}

执行上述代码后将输出:

123.125000 转换为整型为123

-150 转换为浮点形为-150.000000

-150 除以100 为 -1.000000

-150 除以100.0 为 -1.500000

-150 除以100 转换为浮点型为-1.500000

2.8 位运算符

在Objective-C语言中,通过位运算符可处理数字中的位处理。常用的位运算符如下所示。

&:按位与

|:按位或

^:按位异或

~:一次求反

<<:向左移位

>>:向右移位

在上述列出的所有运算符中,除了一次求反运算符(~)外都是二元运算符,因此需要两个运算数。位运算符可处理任何类型的整型值,但不能处理浮点值。在本节将详细讲解Objective-C中位运算符的基本知识,为读者步入本书后面知识的学习打好基础。

2.8.1 按位与运算符

当对两个值执行与运算时,会逐位比较两个值的二进制表示。当第一个值与第二个值的对应位都是1时,在结果的对应位上就会得到1,其他的组合在结果中都得到0。假如m1和m2表示两个运算数的对应位,那么下面就显示了在m1和m2所有可能值下对m1和m2执行与操作的结果。

假如n1和n2都定义为short int,n1等于十六进制的15,n2等于十六进制的0c,那么下面的语句能够将值0x04指派给n3:

n3 = n1 & n2;

在将n1、n2和n3都表示为二进制后,可以更加清楚地看到此过程。假设所处理的short int大小为16位:

在Objective-C程序中,按位与运算的最常用功能是实现屏蔽运算。也就是说,此运算符可以将数据项的特定位设置为0。例如通过下面的代码,可以将n1与常量3按位与所得的值指派给n3。它的作用是将n3中的全部位(而非最右边的两位)设置为0,并保留n1中最左边的两位。

n3 = n1 & 3;

与Objective-C中使用的所有二元运算符相同,通过添加等号,二元位运算符可同样用作赋值运算符。所以语句“mm &= 15;”与语句“mm = mm & 15;”执行相同的功能,并且它还能将mm的全部位设置为0,最右边的四位除外。

2.8.2 按位或运算符

在Objective-C程序中,当对两个值执行按位或运算时,会逐位比较两个值的二进制表示。这时只要第一个值或者第二个值的相应位是1,那么结果的对应位就是1。按位或进行运算操作的过程如下所示。

此时假如n1是short int,等于十六进制的19,n2也是short int,等于十六进制的6a,那么对n1和n2执行按位或会得到十六进制的7b,具体运算过程如下所示。

按位或操作通常就称为按位OR,用于将某个词的特定位设为1。例如下面的代码将n1最右边的三位设为1,而无论这些位操作前的状态是什么都是如此:

n1 = n1 | 07;

另外,也可以在语句中使用特殊的赋值运算符,例如下面的代码:

n1 |= 07;

2.8.3 按位异或运算符

在Objective-C程序中,按位异或运算符也被称为XOR运算符。使用此种运算时需要遵守以下两个规则。

(1)对于两个运算数的相应位,如果任何一个位是1,但不是两者全为1,那么结果的对应位将是1;否则是0。

例如下面演示了按位异或运算的过程。

(2)如果n1和n2分别等于十六进制的5e和d6,那么n1与n2执行异或运算后的结果是十六进制值e8,例如下面的运算过程:

2.8.4 一次求反运算符

在Objective-C程序中,一次求反运算符是一种一元运算符,功能是对运算数的位进行“翻转”处理。将运算数的每个是1的位翻转为0,而将每个是0的位翻转为1。此处提供真值表只是为了保持内容的完整性。例如下面演示了一次求反运算符的运算过程。

在此假设n1是short int,16位长,等于十六进制值a52f,那么对该值执行一次求反运算会得到十六进制值5ab0:

n1 1010 0101 0010 1111 0xa52f

~n1 0101 1010 1101 0000 0x5ab0

如果不知道运算中数值的准确位大小,那么一次求反运算符非常有用,使用它可让程序不会依赖于整数数据类型的特定大小。例如,要将类型为int的n1的最低位设为0,可将一个所有位都是1,但最右边的位是0的int值与n1进行与运算。所以像下面这样的C语句在用32位表示整数的机器上可正常工作:

n1 &= 0xFFFFFFFE;

如果用“n1 &= ~1;”替换上面的代码,那么在任何机器上n1都会同正确的值进行与运算。这是因为这条语句会对1求反,然后在左侧会加入足够的1,以满足int的大小要求(在32位机器上,会在左侧的31个位上加入1)。

请读者看下面的代码,下面的代码演示了各种位运算符的具体作用:

#import <Foundation/Foundation.h>

int main (int argc, char *argv[])

{

NSAutoreleasePool*pool=[[NSAutoreleasePool alloc] init];

unsigned int w1 = 0xA0A0A0A0, w2 = 0xFFFF0000,

w3 = 0x00007777;

NSLog (@"%x %x %x", w1 & w2, w1 | w2, w1 ^ w2);

NSLog (@"%x %x %x", ~w1, ~w2, ~w3);

NSLog (@"%x %x %x", w1 ^ w1, w1 & ~w2, w1 | w2 | w3);

NSLog (@"%x %x", w1 | w2 & w3, w1 | w2 & ~w3);

NSLog (@"%x %x", ~(~w1 & ~w2), ~(~w1 | ~w2));

[pool drain];

return 0;

}

在上述代码的第四个NSLog调用中,需要注意“按位与运算符的优先级要高于按位或运算符”这一结论,因为这会实际影响表达式的最终结果值。而第五个NSLog调用展示了DeMorgan的规则:~(~a & ~b)等于a | b,~(~a | ~b)等于a & b。

运行上述代码后会输出:

a0a00000 ffffa0a0 5f5fa0a0

5f5f5f5f ffff ffff8888

0 a0a0 fffff7f7

a0a0a0a0 ffffa0a0

ffffa0a0 a0a00000

2.8.5 向左移位运算符

在Objective-C语言中,当对值执行向左移位运算时,会将值中包含的位将向左移动。与该操作关联的是该值要移动的位置(或位)数目。超出数据项的高位的位将丢失,而从低位移入的值总为0。所以如果n1等于3,那么表达式“n1 = n1 << 1;”可以表示成“n1 <<= 1;”,运算此表达式的结果就是3向左移一位,这样产生的6将赋值给n1。具体运算过程如下所示。

n1  ... 0000 0011 0x03

n1 << 1 ... 0000 0110 0x06

运算符<<左侧的运算数表示将要移动的值,而右侧的运算数表示该值所需移动的位数。如果将n1在向左移动一次,那么会得到十六进制值0c:

n1  ... 0000 0110 0x06

n1 << 1 ... 0000 1100  0x0c

2.8.6 向右移位运算符

同样的道理,向右移位运算符(>>)的功能是把值的位向右移动。从值的低位移出的位将丢失。把无符号的值向右移位总是左侧(就是高位)移入0。对于有符号值而言,左侧移入1还是0依赖于被移动数字的符号,还取决于该操作在计算机上的实现方式。如果符号位是0(表示该值是正的),不管哪种机器都将移入0。然而,如果符号位是1,那么在一些计算机上将移入1,而其他计算机上则移入0。前一类型的运算符通常称为算术右移,而后者通常称为逻辑右移。

在Objective-C语言中,当选择使用算术右移还是逻辑右移时,千万不要进行猜测。如果进行此类的假设,那么在一个系统上可正确进行有符号右移运算的程序,有可能在其他系统上运行失败。

如果n1是unsigned int,用32位表示它并且它等于十六进制的F777EE22,那么使用语句“n1 >>= 1;”将n1右移一位后,n1等于十六进制的7BBBF711,具体过程如下所示。

n1  1111 0111 0111 0111 1110 1110 0010 0010 0xF777EE22

n1 >> 1 0111 1011 1011 1011 1111 0111 0001 0001 0x7BBBF711

如果将n1声明为(有符号)的short int,在某些计算机上会得到相同的结果;而在其他计算机上,如果将该运算作为算术右移来执行,结果将会是FBBBF711。

如果试图用大于或等于该数据项的位数将值向左或向右移位,那么该Objective-C语言并不会产生规定的结果。因此,例如计算机用32位表示整数,那么把一个整数向左或向右移动32位或更多位时,并不会在计算机上产生规定的结果。还注意到,如果使用负数对值移位时,结果将同样是未定义的。

注意:在Objective-C语言中还有其他3种类型,分别是用于处理Boolean(即,0或1)值的_Bool;以及分别用于处理复数和抽象数字的_Complex和_Imaginary。

Objective-C程序员倾向于在程序中使用BOOL数据类型替代_Bool来处理Boolean值。这种“数据类型”本身实际上并不是真正的数据类型,它事实上只是char数据类型的别名。这是通过使用该语言的特殊关键字typedef实现的,而typedef将在第10章“变量和数据类型”中讲解。

2.8.7 总结Objective-C的运算符

在表2-7中,总结了Objective-C语言中的各种运算符。这些运算符按其优先级降序列出,组合在一起的运算符具有相同的优先级。

表2-7 Objective-C的运算符