OC中类可以分为两种: 根类(超类) 和 子类
比如:类Fraction 从类NSObject 派生来的,也就是继承NSObject 类
NSObject 是根类。
为什么需要创建一个子类?
1.希望继承一个类的函数,也许加入一些新的方法和/或实例变量
2.希望创建一个类的特别版本,
3.希望通过覆写一个或者多个方法来改变类的默认行为。
只要定义一个新类(不是一个新的根类),都会继承一些属性,例如,父类的非私有实例变量和方法都会成为新类定义的一部分,子类可以直接访问这些方法和实例变量,就像直接在类定义中定义了这些子类一样。
需要注意的是,
要在子类中直接使用实例变量,必须现在接口部分声明。
通过继承来扩展:添加新方法
做一个处理矩形的类接口部分 Rectangle.h@interface Rectangle: NSObject @property int width, height; -(int) area; //求面积 -(int) perimeter; //求周长 -(void) setWidth: (int) w andHeight: (int) h; @end
实现部分 Rectangle.m#import "Rectangle.h" @implementation Rectangle @synthesize width, height; -(void) setWidth: (int) w andHeight: (int) h { width = w; height = h; } -(int) area { return width * height; } -(int) perimeter { return (width + height) * 2; } @end
测试main 函数#import "Rectangle.h" int main() { @autoreleasepool { Rectangle *myRect = [[Rectangle alloc] int]; [myRect setWidth: 5 andHeight: 8]; //设置长宽 NSLog(@"Rectangle: w = %i , h = %i",myRect.width, myRect.height); //输出长宽 NSLog(@"Area = %i , Perimeter = %i", [myRect.area], [myRect.perimeter]); //计算面积和周长 } return 0; }
做完处理长方形的类之后,发现需要做一个处理正方形的类。我们知道正方形是特殊长方形,所以我们可以通过继承来完成正方形的类(Spuare)接口部分为Square.h#import "Rectangle.h" //包含 Rectangle.h文件 @interface Square: Rectangle //Square 的父类是Rectangle -(void) setSide: (int) s; -(int) side; @end
实现部分为Square.m@import "Square.h" @implementation Square: Rectangle -(void) setSide: (int) s { [self setWidth: s andHeight: s]; } -(int) side { return self.width; } @end
测试main函数
#import "Square.h" int main() { @autoreleasepool { Square *mySquare = [[Square alloc] int]; [mySquare setSide: 5]; //设置边长 NSLog(@"Square: s = %i ",[mySquare side]); //输出边长 NSLog(@"Area = %i , Perimeter = %i", [mySquare area], [mySquare perimeter]); //计算面积和周长 } return 0; }
----Square类继承了Rectangle类,并根据sqare类的自身需要,扩展父类Rectangle类的方法。
@class 命令
主要是用于声明一个类,告诉编译器它后面的名字是一个类的名字,而这个类的定义实现是暂时不用知道的,后面会告诉你.也是因为在@class仅仅只是声明一个类,所以在后面的实现文件里面是需要去#import这个类,这时候才包含了这个被引用的类的所有信息。
这样的好处是提高效率,因为使用@class命令,编译器不需要引入和处理整个类的.h文件,只需要知道@class命令后面是个类名就可以。
接着上面的例子。我们可以再添加一个关于坐标的类,XYPoint类,通过这个类来储存原点坐标。接口部分 XYPoint.h 如下:#import <Foundation/Foundation.h> @interface XYPoint: NSObject @property int x, y; -(void) setX: (int) xVal andY: (int) yVal; @end
实现部分 XYPoint.m 如下:
#impoint "XYPoint.h" @implementation XYPoint @synthesize x, y; -(void) setX: (int) xVal andY: (int) yVal { x = xVal; y = yVal; } @end
我们在Rectangle类的接口部分 Rectangle.h 可以这样定义#import <Foundation/Foundation.h> @class XYPoint; //使用@class 告诉编译器XYPoint 是一个类,其他的先不用管 @interface Rectangle: NSObject -(XYPoint *)origin; -(void) setOrigin: (XYPoint *) pt; ...... @end
在 实现文件Rectangle.m 中:#impoint "Rectangle.h" @implementation Rectangle { XYPoint *origin; } @synthesize width, height; -(void) setOrigin: (XYPoint *) pt { origin = pt; } -(XYPoint *) origin { return origin; } .... @end
.....origin 为rectangle类的一个实例变量,其实是可以使用@property 和 @ synthesizer 来定义存取方法,这里为了突出重点,自己编写了 -(void) setOrigin: (XYPoint *) pt 和 -(XYPoint *) origin
测试main函数:
#import "Rectangle.h" #import "XYPoint.h" int main (...) { @autoreleasepool { Rectangle *myRect = [[Rectangle alloc] init]; XYPoint *myPoint = [[XYPoint alloc] init]; [myPoint setX: 100 andY: 200]; //设置x,y坐标 [myRect setWidth: 5 andHeight: 8]; //设置矩形的长宽 myRect.origin = myRect; // 设置矩形的坐标 NSLog(@"Rectangle w = %i , h = %i",myRect.width, myRect.height); NSLog(@"origin x = %i, y = %i ", myRect.origin.x, myRect.origin.y); NSLog(@"area = %i perimeter = %i" , [myRect area], [myRect perimeter]); } return 0; }
注意: myRect.origin = myRect; 这行代码将对象myRect当做参数给了myRect.origin方法,之前有说过,当对象作为参数时,在方法中是可以对对象中的实例参数做更改的,反过来说,如果对象中的实例变量改动,也会影响方法中对对象的操作。这是因为一个对象其实是一个内存的引用。当对象作为参数传过去时,也就是把内存的引用作为参数传过去,所以可以对内存操作。比如,在 myRect.origin 方法中: origin = pt; 参数pt的更改肯定会影响 origin 对象。如果在main 函数中做如下处理:
[myPoint setX: 100 andY: 200]; //设置x,y坐标 [myRect setWidth: 5 andHeight: 8]; //设置矩形的长宽 myRect.origin = myRect; // 设置矩形的坐标 NSLog(@"origin x = %i, y = %i ", myRect.origin.x, myRect.origin.y); [myPoint setX: 200 andY: 300]; //设置x,y坐标 NSLog(@"origin x = %i, y = %i ", myRect.origin.x, myRect.origin.y);
则输出结果为:origin x = 100 y = 200origin x = 200 y = 300虽然重新设置坐标后,并没有改动矩形的坐标,但输出结果显示还是改动了矩形的坐标所以我们应该对setOrigin方法做一些处理:
-(void) setOrigin: (XYPoint *)pt { if (! origin) { origin = [[XYPoint alloc] init]; } origin.x = pt.x; origin.y = pt.y; }
这样做使得每一个Rectangle 实例都有自己的XYPoint实例但是该方法更改后编译会出现一些问题,主要原因是在该方法中引用了XYPoint 类的一些实例变量,所以现在编译器需要的信息多于@class指令提供的。所以要在Rectangle.h 中使用 #import "XYPoint.h" 替换@class命令
覆写方法
覆写方法使用和父类相同的名称定义的方法代替或覆写了集成的定义,新的方法必须具有相同的返回类型,并且参数的数目与覆写的方法相同
抽象类
创建抽象类是为了更容易创建子类,在该类中定义方法和实例变量,但不期望任何人从该类创建实例。