7.7 在方法中分配和返回对象
我们注意到add:方法改变了接收该消息的对象值。创建新版本的add:函数,这个方法创建了一个新的分数来存储相加的结果。在这种情况下,需要向消息的发送者返回新的Fraction。下面是新方法add:的定义:
-(Fraction)add:(Fraction)f
{
//To add two fractions:
//a/b+c/d=((ad)+(bc))/(b*d)
//result will store the result of the addition
Fraction*result=[[Fraction alloc]init];
int resultNum, resultDenom;
resultNum=numerator*f.denominator+
denominator*f.numerator;
resultDenom=denominator*f.denominator;
[result setTo:resultNum over:resultDenom];
[result reduce];
return result;
}
方法定义的第一行是:
-(Fraction)add:(Fraction)f;
这一行说明add:方法将返回一个名为Fraction的对象,并且说明它还使用一个Fraction作为参数。这个参数将与消息的接收者相加。这个接收者同样是Fraction。
这一方法分配并初始化了一个新的Fraction对象,名为result,然后定义两个名为resultNum和resultDenom的局部变量。它们将用于存储程序相加运算产生的分子分母。
和以前一样执行完加法运算之后,将产生的分子和分母复制给局部变量,然后使用以下消息表达式设置result的值:
[result setTo:resultNum over:resultDenom];
约简结果之后,将结果返回给消息的发送者。
注意在add:方法中分配给Fraction result的空间被返回,并且没有被系统释放。不能从add:方法中释放,因为add:方法的调用者需要它。因此这个方法的使用者知道返回的对象是一个新变量,并将被随后释放。用户通过可用的合适文档,可以了解这些信息。
代码清单7-5测试了新方法add:。
代码清单7-5测试文件main.m
import“Fraction.h”
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Fraction*aFraction=[[Fraction alloc]init];
Fraction*bFraction=[[Fraction alloc]init];
Fraction*resultFraction;
[aFraction setTo:1 over:4];//set 1st fraction to 1/4
[bFraction setTo:1 over:2];//set 2nd fraction to 1/2
[aFraction print];
NSLog(@);
[bFraction print];
NSLog(@);
resultFaction=[aFraction add:bFraction];
[resultFraction print];
//This time give the result directly to print
//memory leakage here!
[[aFraction add:bFraction]print];
[aFraction release];
[bFraction release];
[resultFraction release];
[pool drain];
return 0;
}
代码清单7-5输出
1/4
+
1/2
=
3/4
3/4
这里要做一点补充,首先,你定义了两个Fraction类,aFraction和bFraction,并将它们的值分别设为1/4和1/2,然后又定义了一个名为resultFraction的Fraction类(为什么不必为它分配空间并初始化呢)。这个变量将用来存储相加操作的结果。
以下代码
resultFraction=[aFraction add:bFraction];
[resultFraction print];
首先发送add:消息到aFraction类,同时将类Fraction bFraction作为它的参数。该方法返回的结果Fraction存储到resultFraction中,然后,通过向它发送一条print消息,显示这个结果。注意虽然你没有在main例程中为它分配空间,但是在程序的末尾一定要将resultFraction释放。它是由add:方法分配空间的,但是由你负责清除它。消息表达式
[[aFraction add:bFraction]print];
看起来很不错,但是它实际上存在一个问题。因为你使用add:方法返回的Fraction类,并向其发送一条要print的消息,你没有办法释放add:方法创建的Fraction类。这是一个内存泄漏的例子。如果程序中有很多这样的嵌套消息,最终这些分数的存储空间就会累加,这些分数的内存没有被释放。每次,你将添加或者泄漏少许内存(它们不能直接恢复)。
问题的一个解决方案是将print方法返回它的接收者,然后可以将它释放。但这有点兜圈子。一个更好的解决方案是将嵌套的消息分成两个单独的消息,和在程序中以前做的一样。
随便说一句,可以在add:方法中避免使用临时变量resultNum和resultDenom。相反,单条消息调用
[result setTo:numeratorf.denominator+denominatorf.numerator
over:denominator*f.denominator];
将实现这个目的!并不推荐编写如此简明的代码。但是,如果查看其他程序员的编码,可能会发现这种代码,所以了解如何阅读和理解这些强大的表达式很有用。
我们介绍本章最后一个分数。例如,考虑计算以下内容:
Σ符号是总和的缩写。用在这里是表示将1/2i加到一起,i从1到n。即1/2+1/4+1/8……。如果n足够大,这个序列的总和应该接近于1。试验不同的n值,来看看有多接近1。
代码清单7-6提示你提供不同的n值,并执行相应的计算。
代码清单7-6 FractionTest.m
import“Fraction.h”
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Fraction*aFraction=[[Fraction alloc]init];
Fractionsum=[[Fraction alloc]init],sum2;
int i, n,pow2;
[sum setTo:0 over:1];//set 1st fraction to 0
NSLog(@“Enter your value for n:);
scanf(“i”,&n);
pow2=2;
for(i=1;i<=n;++i){
[aFraction setTo:1 over:pow2];
sum2=[sum add:aFraction];
[sum release];//release previous sum
sum=sum2;
pow2*=2;
}
NSLog(@“After%i iterations, the sum is%g”,n,[sum convertToNum]);
[aFraction release];
[sum release];
[pool drain];
return 0;
}
代码清单7-6输出
Enter your value for n:
5
After 5 iterations, the sum is 0.96875
代码清单7-6输出(重新运行)
Enter your value for n:
10
After 10 iterations, the sum is 0.999023
代码清单7-6输出(重新运行)
Enter your value for n:
15
After 15 iterations, the sum is 0.999969
当分子为0分母为1时Fraction Sum被设为0(如果你将分子分母都设为0会怎样?)。然后程序提示用户输入n值,并使用scanf读取它。然后进入for循环来计算这个序列的总和。首先,将变量pow2初始化为2。这个变量用来储存2i的值。所以,每循环一次它的值被乘以2。
for循环从1开始到n结束,每循环一次,将aFraction设为1/pow2或1/2i。然后这个值通过前面定义的add:方法与累积的sum变量相加。Add:的结果被赋值给sum2,而不是sum以免内存漏洞问题(如果你直接赋值给sum会怎样)。然后释放旧的sum, sum的新值sum2将赋值给sum,以便进行下一轮迭代。学习在代码中释放分数的方式,就可以熟悉用来避免内存泄漏的策略。如果这是一个执行成百上千次的for循环,而你不懂得如何释放分数,就会迅速积累很多浪费的内存空间。
for循环结束时,使用convertToNum方法将结果以十进制值的方式显示。那时只有两个对象要释放,aFraction和最后存储在sum中的Fraction对象。然后程序执行结束。
输出展示了在MacBook Air中独立运行该程序三次后所发生的情况。第一次,计算出这个序列的和,显示结果值0.96875。第三次使用n值为15运行该程序,计算结果非常接近1。
扩展类的定义和接口文件
现在,你已经开发了处理分数的一个小方法库。下面是接口文件,它是完整地列出的,所以可以看到该类实现的所有方法:
import<Foundation/Foundation.h>
//Define the Fraction class
@interface Fraction:NSObject
{
int numerator;
int denominator;
}
@property int numerator, denominator;
-(void)print;
-(void)setTo:(int)n over:(int)d;
-(double)convertToNum;
-(Fraction)add:(Fraction)f;
-(void)reduce;
@end
你可能不需要处理分数,但是这些例子告诉你如何通过加入新方法来定义和扩展一个类。其他处理分数的人将使用这个接口文件,并且使用这个文件足够编写他自己的处理分数的程序。如果他需要添加新方法,通过直接扩展类定义或者定义自己的子类并添加自己的新方法直接地扩展该类,可以实现该目的。下一章将学习如何实现。