8.2.3 具有对象的类
能够解释代码清单8-5的输出结果吗?
代码清单8-5
import“Rectangle.h”
import“XYPoint.h”
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
Rectangle*myRect=[[Rectangle alloc]init];
XYPoint*myPoint=[[XYPoint alloc]init];
[myPoint setX:100 andY:200];
[myRect setWidth:5 andHeight:8];
myRect.origin=myPoint;
NSLog(@“Origin at(%i,%i)”,
myRect.origin.x, myRect.origin.y);
[myPoint setX:50 andY:50];
NSLog(@“Origin at(%i,%i)”,
myRect.origin.x, myRect.origin.y);
[myRect release];
[myPoint release];
[pool drain];
return 0;
}
代码清单8-5输出结果
Origin at(100,200)
Origin at(50,50)
将XYPoint myPoint从原程序中的(100,200)改为(50,50),显然也改变了矩形的原点!但是为什么出现这种情况?你并没有显式地使用另一个setOrigin:方法调用重新设置矩形的原点,所以,为什么矩形的原点会改变?如果回顾setOrigin:方法的定义,也许就会明白:
-(void)setOrigin:(XYPoint*)pt
{
origin=pt;
}
当使用以下表达式调用setOrigin:方法时,
myRect.Origin=myPoint;
myPoint的值做为参数传递给该方法。这个值指向存储XYPoint对象的内存,如图8-5所示。
存储在myPoint(它是一个指向内存的指针)中的值被复制到在该方法中定义的本地变量pt中。现在pt与myPoint都引用内存中相同的数据。如图8-6所示。
在方法中将origin变量设置为pt时,pt中存储的指针被复制到实例变量origin,如图8-7所示。
图 8-5 内存中的XYPoint myPoint
图 8-6 将矩形原点传递给该方法
图 8-7 设置矩形原点
因为myPoint和存储在myRect中的origin变量引用内存中的同一区域(与局部变量pt相同),所以随后将myPoint的值改为(50,50)时,矩形的原点也被更改。
避免这一问题的办法就是修改setOrigin:方法,以便分配自己的点并将原点设置为该点,如下所示:
-(void)setOrigin:(XYPoint*)pt
{
origin=[[XYPoint alloc]init];
[origin setX:pt.x andY:pt.y];
}
这个方法首次分配并初始化一个新XYPoint。消息表达式
[origin setX:pt x andY:pt.y];
将新分配的XYPoint设置为该方法参数的x、y坐标。研究该消息表达式,直到完全了解它的工作原理。
setOrigin:方法的改变意味着每个Rectangle实例现在都有它的origin XYPoint实例。既然它负责为XYPoint分配内存,所以还应该负责释放该内存。通常,当一个类包含其他对象时,有时你希望拥有部分或全部对象。以矩形为例,它有自己的原点很合理,因为这是矩形的基本属性。
但是,如何释放origin占用的内存呢?释放矩形内存,并不同时释放为原点分配的内存。释放内存的一种方式是在main中插入以下一行:
[[myRect origin]release];
这样可以释放由原点方法返回的XYPoint对象。但必须在Rectangle对象释放自己的内存之前释放XYPoint对象,因为在对象释放内存后它包含的所有实例都是无效的。因此,正确的代码顺序应该如下所示:
[[myRect origin]release];//Release the origins memory
[myRect release];//Release the rectangles memory
要记住释放原点的内存是个负担。毕竟,不是你为原点分配的内存,而是由Rectangle类分配的。在下一节“重载方法”中,将学习如何释放Rectangle的内存。
使用修改后的方法,重新编辑和运行代码清单8-5,生成以下出错消息,如图8-8所示。
图 8-8 编译器出错消息
糟了,这么多问题!这里的问题是由于在修改后的方法中使用了XYPoint类的一些方法,所以,现在编译器需要的信息多于@class指令提供的。在这个例子中,返回并用import代替这个指令,如:
import“XYPoint.h”
代码清单8-5输出结果
Origin at(100,200)
Origin at(100,200)
这样就好多了!这次在main例程中将myPoint的值更改为(50,50)对矩形原点没有影响,因为在矩形的setOrigin:方法中创建了该点的副本。
顺便说一下,这里我们没有合成origin方法,因为合成的setter setOrigin:方法的行为将与你之前编写的那个方法一样。也就是说,在默认情况下,合成setter只是简单地复制对象指针而不是对象本身。
你可以合成另一种setter方法,而不是制作对象的副本。但是,如果那样做,就需要学习如何编写特殊的复制方法。第17章“内存管理”将再次讨论这一主题。