15.3 数组对象

Foundation数组是有序的对象集合。最常见的情况是,一个数组中的元素都是一个特定类型,但这不是必需的。就像存在可变字符串和不可变字符串,同样也存在可变数组和不可变数组。不可变数组是由NSArray类处理的,而可变数组是由NSMutableArray处理的。后者是前者的子类,就是说后者继承前者的方法。要在你的程序中使用数组对象,包括下面这行代码:

import<Foundation/NSArray. h>

代码清单15-6设置一个数组,用来存储一年中的月份名字,然后显示这些月份。

代码清单15-6


import<Foundation/NSObject.h>

import<Foundation/NSArray.h>

import<Foundation/NSString.h>

import<Foundation/NSAutoreleasePool.h>

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

{

int i;

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

//Create an array to contain the month names

NSArray*monthNames=[NSArray arrayWithObjects:

@“January”,@“February”,@“March”,@“April”,

@“May”,@“June”,@“July”,@“August”,@“September”,

@“October”,@“November”,@“December”,nil];

//Now list all the elements in the array

NSLog(@“Month Name”);

NSLog(@“========”);

for(i=0;i<12;++i)

NSLog(@“2i%@”,i+1,[monthNames objectAtIndex:i]);

[pool drain];

return 0;

}


代码清单15-6输出


Month Name

=========

1 January

2 February

3 March

4 April

5 May

6 June

7 July

8 August

9 September

10 October

11 November

12 December


类方法arrayWithObjects:可以用来创建使用一列对象作为元素的数组。在这种情况下,按顺序列出对象并使用逗号隔开。这是方法使用的特殊语法,这个方法可以接受可变数目的参数。要标记参数列表的结束,必须把该列表的最后一个值指定为nil,它实际上并不存储在数组中。

在代码清单15-7中,monthNames被设置为arrayWithObjects:的参数所指定的12个字符串。

数组中的元素是由它们的索引数确定的。与NSString对象类似,索引从0开始。所以,包含12个元素的数组的有效索引数是0-11。要使用数组索引数来检索其中的元素,用objectAtIndex:方法。

程序仅仅使用objectAtIndex:方法简单地执行了一个for循环来从数组中提取每个元素。每个检索到的元素都使用NSLog进行显示。

代码清单15-7生成了一个素数表。因为要在生成素数时,把它们添加到数组中,所以需要一个可变数组。使用arrayWithCapacity:方法分配NSMutableArray的素数。你给出的参数20指定了数组的初始化大小;在程序运行时,可变数组的容量会根据需要自动增长。

即使素数是整数,也不能直接在数组中存储int值。你的数组只能容纳对象。因此,需要在primes数组中存储NSNumber整数对象。

代码清单15-7


import<Foundation/NSObject.h>

import<Foundation/NSArray.h>

import<Foundation/NSString.h>

import<Foundation/NSAutoreleasePool.h>

import<Foundation/NSValue.h>

define MAXPRIME 50

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

{

int i, p,prevPrime;

BOOL isPrime;

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

//Create an array to store the prime numbers

NSMutableArray*primes=

[NSMutableArray arrayWithCapacity:20];

//Store the first two primes(2 and 3)into the array

[primes addObject:[NSNumber numberWithInteger:2]]

[primes addObject:[NSNumber numberWithInteger:3]];

//Calculate the remaining primes

for(p=5;p<=MAXPRIME;p+=2){

//were testing to see if p is prime

isPrime=YES;

i=1;

do{

prevPrime=[[primes objectAtIndex:i]integerValue];

if(p%prevPrime==0)

isPrime=NO;

++i;

}while(isPrime==YES&&p/prevPrime>=prevPrime);

if(isPrime)

[primes addObject:[NSNumber numberWithInteger:p]];

}

//Display the results

for(i=0;i<[primes count];++i)

NSLog(@“li”,(long)[[primes objectAtIndex:i]integerValue]);

[pool drain];

return 0;

}


代码清单15-7输出


2

3

5

7

11

13

17

19

23

29

31

37

41

43

47


将MAXPRIME定义为希望程序计算的最大素数,在这个例子中是50。

在分配primes数组之后,使用如下语句设置数组开始的两个元素:


[primes addObject:[NSNumber numberWithInteger:2]];

[primes addObject:[NSNumber numberWithInteger:3]];


addObject:方法向数组的末尾添加了一个对象。下面分别添加由整数2和3所创建的NSNumber对象。

然后,该程序进入一个for循环,来查找以5开始,直到MAXPRIME为止的素数,并且跳过(p+=2)之间的的偶数。

对每个可能的素数p,你想要了解它能否被前面找到的素数整除。如果能整除,则它不是素数。作为额外的优化,仅使用前面的素数,直到该数的平方根来测试这个数。这是因为,如果一个数不是素数,则它一定能够被小于或等于其平方根(哈,又回到了高中数学)的素数整除。所以,只要prevPrime小于或等于p的平方根,表达式


p/prevPrime>=prevPrime


总是为真。

如果do-while循环退出,而标志isPrime仍然等于YES时,你就发现了另一个素数。在这种情况下,将p加到primes数组,并且继续执行程序。

这里正好有一个关于程序效率的评论。Foundation类为使用数组提供了许多便利。然而,当使用复杂的运算法则操纵大型数字数组时,学习如何用该语言提供的低级数组构造来执行这种任务可能更加有效,对于内存使用和执行速度来说,都是如此。参考第13章中标题为“数组”的小节,以获取更多信息。

制作地址簿

看一个例子,它结合目前为止学到的知识,生成地址簿。你的地址簿将包含地址卡。为简单起见,地址卡中仅包含某人的姓名和email地址[1]将这个概念扩展到其他信息,如地址和电话号码很简单,但是这作为本章末尾的练习题留给你来完成。

生成一个地址卡片

你打算从定义一个名为AddressCard的新类来开始。你需要以下的功能:创建一个新的地址卡片、设置卡片的姓名字段和email字段、检索这些字段的内容,并打印卡片。在图形化的环境下,可以使用一些友好的例程(比如Application Kit框架所提供的例程),在屏幕上绘制卡片。但在这里,继续使用简单的终端界面来显示地址卡片。

代码清单15-8显示了新的AddressCard类的接口文件。这里不会介绍访问器方法,你可以自己编写并从中学习更多的知识。

代码清单15-8接口文件AddressCard.h


import<Foundation/NSObject.h>

import<Foundation/NSString.h>


@interface AddressCard:NSObject

{

NSString*name;

NSString*email;

}

-(void)setName:(NSString*)theName;

-(void)setEmail:(NSString*)theEmail;

-(NSString*)name;

-(NSString*)email;

-(void)print;

@end


这与代码清单15-8中的实现文件一样简单直观。

代码清单15-8实现文件AddressCard.m


import“AddressCard.h”

@implementation AddressCard

-(void)setName:(NSString*)theName

{

name=[[NSString alloc]initWithString:theName];

}

-(void)setEmail:(NSString*)theEmail

{

email=[[NSString alloc]initWithString:theEmail];

}

-(NSString*)name

{

return name;

}

-(NSString*)email

{

return email;

}

-(void)print

{

NSLog(@“===================================”);

NSLog(@);

NSLog(@“%-31s|”,[name UTF8String]);

NSLog(@“%-31s|”,[email UTF8String]);

NSLog(@);

NSLog(@);

NSLog(@);

NSLog(@“O O|”);

NSLog(@“===================================”);

}

@end


可以使用如下方法定义,使setName和setEmail方法直接将这些对象存储在各自的实例变量中:


-(void)setName:(NSString*)theName

{

name=theName;

}

-(void)setEmail:(NSString*)theEmail

{

email=theEmail;

}


但是,AddressCard对象并不拥有它自己的成员对象。在第8章“继承”中,通过拥有自己的origin对象的Rectangle类,讨论了对象要获得所有权的动机。

用以下方式定义的两个方法:


-(void)setName:(NSString*)theName

{

name=[NSString stringWithString:theName];

}

-(void)setEmail:(NSString*)theEmail

{

email=[NSString stringWithString:theEmail];

}


仍然是不正确的途径,因为AddressCard方法仍然没有获得它们的姓名和email对象—而NSString将拥有这些对象。

回到代码清单15-8,print方法尝试用一种类似于Rolodex卡片(还记得它们吗?)的格式向用户显示出具有良好展示效果的地址卡片。NSLog中的“-31s”字符表明要用31个字符的字段宽度且左对齐的方式打印UTF8 C-字符串,这确保输出时地址卡片的右边缘是整齐的。

使用AddressCard类,可以编写一个测试程序来创建一个地址卡片、设置卡片的值以及显示卡片(参见代码清单15-8)。

代码清单15-8测试程序


import“AddressCard.h”

import<Foundation/NSAutoreleasePool.h>

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

{

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

NSString*aName=@“Julia Kochan”;

NSString*aEmail=@“jewls337@axlc.com”;

AddressCard*card1=[[AddressCard alloc]init];

[card1 setName:aName];

[card1 setEmail:aEmail];

[card1 print];

[card1 release];

[pool drain];

return 0;

}


代码清单15-8输出

====================================

| |

|Julia Kochan|

|jewls337@axlc. com|

| |

| |

| |

|O O|

====================================

代码清单15-8中使用的程序行


[card1 release];


用于释放地址卡片占用的内存。从先前的讨论中,你应该认识到用这种方法释放AddressCard对象的同时并没有释放分配给它的name和email成员的内存。为了使AddressCard无漏洞,需要重载名为dealloc的方法,这样,无论何时释放AddressCard对象的内存,其成员的内存都会一并释放。

下面是为AddressCard类的dealloc方法:


-(void)dealloc

{

[name release];

[email release];

[super dealloc];

}


在用super销毁对象本身之前,dealloc方法必须先释放自己的实例变量。这是因为释放对象之后,它就不再有效了。

要使AddressCard无漏洞,还必须修改setName:和setEmail:方法以释放存储在相应实例变量中的对象所占用的内存。如果某人更改了卡片上的姓名,就需要在使用新姓名代替旧姓名之前,释放旧姓名所占用的内存。email地址与此类似,在使用新地址代替旧地址之前,也必须释放旧email地址占用的内存。

下面是新的setName:、setEmail:方法,它们将确保拥有可以正确地进行内存管理的类:


-(void)setName:(NSString*)theName

{

[name release];

name=[[NSString alloc]initWithString:theName];

}

-(void)setEmail:(NSString*)theEmail

{

[email release];

email=[[NSString alloc]initWithString:theEmail];

}


你可以给空对象发送消息,因此,即使没有预先设置name和email,消息表达式


[name release];



[email release];


都是正确的。

[1]Mac OSX提供了完整的地址簿框架,该框架提供了极强的处理地址簿的功能。