Objective-C学习系列之一 继承中的指针部分

这个例子来源于《Programmin In Objective-C(6 th)》,直入主题。
我们先写一个矩形类,其属性为长height,宽width,具有简单的设置长宽,计算周长面积的方法,相关代码如下:

//Rectangle.h
@interface Rectangle : NSObject

@property int width,height;

- (void) setWidth: (int) w andHeight: (int) h;
- (int) area;
- (int) perimeter;

@end
//Rectangle.m
#import "Rectangle.h"

@implementation Rectangle

@synthesize width,height;
- (int) area
{
    return width * height;
}
- (int) perimeter
{
    return 2 * (width + height);
}

- (void) setWidth: (int) w andHeight: (int) h
{
    width = w;
    height = h;
}
@end

测试代码先省略,随后我们加入一个XYPoint类,用于存储矩形的原点。我们先创建一个XYPoint类,其属性为x,y及基本的设置x,y的方法,相关代码如下:

//XYPoint.h
@interface XYPoint : NSObject

@property int x, y;
- (void) setX: (int) xVal andY: (int) yVal;
@end
@implementation XYPoint

@synthesize x, y;

- (void) setX:(int)xVal andY:(int)yVal
{
    x = xVal;
    y = yVal;
}

@end

之后我们在Rectangle.h中加上*@class XYPoint;标识符用于让编译器遇到XYPoint时知道它是什么,当然还有一种方法是直接导入它的头文件#import “XYPoint.h”;*。但使用@class指令更有效,因为编译器不需要导入并因此处理整个XYPoint.h文件(即使它非常小);它只需要知道XYPoint是一个类的名称。如果你需要引用一个XYPoint类的方法(例如在实现部分中),那么@class指令就不够了,因为编译器需要更多信息;它需要知道方法采用了多少个论证,它们的类型是什么,以及方法的返回类型是什么。改过后的Rectangle.h为:

#import <Foundation/Foundation.h>

@class XYPoint;

@interface Rectangle : NSObject

@property int width,height;

- (XYPoint *) origin;
- (void) setOrigin: (XYPoint *) pt;
- (void) setWidth: (int) w andHeight: (int) h;
- (int) area;
- (int) perimeter;

@end

相应的Rectangle.m为:

import "Rectangle.h"
#import "XYPoint.h"

@implementation Rectangle
{
    XYPoint *origin;
}

@synthesize width,height;
- (XYPoint *) origin
{
    return origin;
}
- (void) setOrigin: (XYPoint *) pt
{
	origin = pt;
}
- (int) area
{
    return width * height;
}
- (int) perimeter
{
    return 2 * (width + height);
}

- (void) setWidth: (int) w andHeight: (int) h
{
    width = w;
    height = h;
}
@end

测试文件 main.m为

#import <Foundation/Foundation.h>
#import "Rectangle.h"
#import "XYPoint.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Rectangle *myRect = [[Rectangle alloc] init];
        XYPoint *myPoint = [[XYPoint alloc] init];
        
        [myPoint setX:100 andY:200];
        
        [myRect setWidth:5 andHeight:8];
        myRect.origin = myPoint; //调用setOrigin
        
        NSLog(@"Rectangle w = %i, h = %i", myRect.width, myRect.height);
        NSLog(@"Origin at (%i, %i)", myRect.origin.x, myRect.origin.y);
        NSLog(@"Area = %i, Perimeter = %i", [myRect area], [myRect perimeter]);
    }
    return 0;
}

测试结果为:
在这里插入图片描述
接下来我们改一下测试文件

#import <Foundation/Foundation.h>
#import "Rectangle.h"
#import "XYPoint.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Rectangle *myRect = [[Rectangle alloc] init];
        XYPoint *myPoint = [[XYPoint alloc] init];
        
        [myPoint setX:100 andY:200];
        
        [myRect setWidth:5 andHeight:8];
        myRect.origin = myPoint; //调用setOrigin
        
        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);
    }
    return 0;
}

结果为:
在这里插入图片描述
我们发现:通过调用 myPoint 的setX:andY:方法,我们改了矩形原来的原点的值,这显然是不合理的。那为什么会造成这样的结果呢,我们看下代码。

- (void) setOrigin: (XYPoint *) pt
{
    origin = pt;
}

当使用表达式myRect.origin = myPoint调用setOrigin:方法时;myPoint的值作为参数传递给方法。 该值指向此XYPoint对象存储在内存中的位置,如图1所示。
图图1 内存中myPoint对象的引用

存储在myPoint中的值(它是指向内存的指针)将被复制到方法内定义的局部变量pt中。 现在,pt和myPoint都引用了存储在内存中的相同数据。 图2说明了这一点。
在这里插入图片描述图2 将矩形的原点传递给方法

当在方法内将origin变量设置为pt时,将复制存储在pt中的指针进入实例变量origin,如图3所示。
在这里插入图片描述图3 设置矩形的原点

这样一来,那个问题就非常明显了,pt和myPoint指针指向的是同一个地址,只要改变其中之一的值就会改变其内容。

我们能够在setOrigin:方法中避免该问题的发生,改变后的代码如下。

- (void) setOrigin: (XYPoint *) pt
{
    if (!origin)
        origin = [[XYPoint alloc] init];

    origin.x = pt.x;
    origin.y = pt.y;
}

该方法首先测试实例变量origin是否为非零。 (确保您了解该测试以及使用的逻辑否定运算符!)回想一下,所有实例变量最初都设置为零。因此,当分配新的Rectangle对象时,其实例变量(包括origin)将设置为零。如果原点为零,则setOrigin:方法将分配并初始化一个新的XYPoint对象,并在原点中存储对它的引用。然后,该方法将新分配的XYPoint对象设置为方法的参数的x,y坐标。研究这种方法,直到你完全理解它是如何工作的。对setOrigin:方法的更改意味着每个Rectangle实例现在都拥有它的XYPoint实例。即使它现在负责为该XYPoint分配内存,它现在也应该负责释放该内存。通常,当一个类包含其他对象时,有时您会希望它拥有这些对象中的部分或全部。在矩形的情况下,Rectangle类拥有其原点是有意义的,因为这是矩形的基本属性。只有类的访问器方法应该能够设置或检索该原点。这与数据封装的概念一致。这时的输出结果为
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_38126805/article/details/86537879