OC类的继承

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OC类的继承相关的知识,希望对你有一定的参考价值。

继承也是面向对象的三大特征之一,也是实现软件复用的重要手段,OC的继承具有单继承的特点,每个子类只有一个直接父类。

继承的特点

OC的继承通过“ : 父类”语法来实现,实现继承的类被称为子类,被继承的类被称为父类,也被称为基类、超类。
子类是一种特殊的父类,因此,父类包含的范围总比子类包含的范围要大,可以认为父类是大类,而子类是小类。
没有父类的类位于类次结构的最顶层,称为根类。OC中允许定义自己的根类,但通常不这么做。比如,我们现在定义的类都属于 NSObject 根类的派生类。
OC里子类继承父类·的语法格式如下:

@interface SubClass : SuperClass {
	//成员变量定义
}
//方法部分定义
@end

当子类扩展父类时,子类可以继承得到父类的:

  • 全部成员变量
  • 全部方法(包括初始化方法)

注意:子类中使用示例变量,必须先在接口部分声明。在实现部分声明和合成(synthesize)的实例变量是私有的,子类中不能直接访问,需要明确定义或合成取值方法,才能访问实例变量的值。
示例:

//  FKFruit.h

#import <Foundation/Foundation.h>

@interface FKFruit : NSObject
@property (nonatomic ,assign) double weight;
-(void) info;
@end
//  FKFruit.m

#import "FKFruit.h"

@implementation FKFruit
- (void) info {
    NSLog(@"我是一个水果!重%g!", self.weight);
}
@end
//  FKApple.h

#import "FKFruit.h"

@interface FKApple : FKFruit
//一个空类,没有定义任何的属性和方法
@end
//  main.m

#import "FKApple.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKApple* a = [[FKApple alloc] init];
        a.weight = 56;
        [a info];
    }
    return 0;
}

FKApple可以使用FKFruit的所有方法,其实weight这个变量也是继承的。其实,类的每个实例都拥有自己的实例变量,即使这些实例变量是继承来的。
OC类只能有一个直接父类,但可以拥有无限个间接父类。

通过继承来扩展:添加新方法

继承通常用于扩展一个类,例如,你要处理一些几何图形(矩形为例)。
你要知道他们一个顶点(比如左上方顶点)的坐标,边长,进而求出它们的面积和周长。
根据数学知识可知:正方形是特殊的矩形,所以我们可以先去考虑长方形。先给出关于xy坐标的代码:

//  XYPoint.h

#import <Foundation/Foundation.h>

@interface XYPoint : NSObject

@property int x, y;
-(void) setX: (int) xVal andY: (int) yVal;
//定义x,y与设置值的方法
@end
//  XYPoint.m

#import "XYPoint.h"

@implementation XYPoint

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

@end

下面给出长方形的代码:

//  Rectangle.h

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

@interface Rectangle : NSObject

@property int width, height;
-(XYPoint*) origin;
//定义返回坐标的方法
-(void) setOrigin: (XYPoint*) pt;
//传入一个对象来设置坐标
-(int) area;
-(int) perimeter;
-(void) setWidth: (int) w andHeight: (int) h;

@end
//  Rectangle.m

#import "Rectangle.h"

@implementation Rectangle {
    XYPoint *origin;
}

@synthesize width, height;
-(void) setWidth: (int) w andHeight: (int) h {
    width = w;
    height = h;
}
-(void) setOrigin: (XYPoint*) pt {
    if (!origin) {
        origin = [[XYPoint alloc] init];
    }//每一次创建自己的点,并为origin设置这个点,避免直接传地址。
    origin.x = pt.x;
    origin.y = pt.y;
}
-(int) area {
    return width * height;
}
-(int) perimeter {
    return (width + height) * 2;
}
-(XYPoint*) origin {
    return origin;
}

@end

其实,我们不难理解,正方形其实就是长和宽相等的矩形,所以其实求周长与面积的方法可以复用。下面给出正方形的代码:

//  Square.h

#import "Rectangle.h"

@interface Square : Rectangle

-(void) setSide: (int) s;
-(int) side;
//正方形需要设置边长,返回边长,这是与长方形不同的地方
@end
//  Square.m

#import "Square.h"

@implementation Square
-(void) setSide:(int)s {
    [self setWidth: s andHeight: s];
}
//正方形的两边一样长,相当于给矩形的长和宽同时设置成s
-(int) side {
    return self.width;
}
@end

看过了代码,我们应该就理解了如何通过添加子类来扩展。
下面给出实现部分:

//  main.m

#import "Square.h"
#import "XYPoint.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Rectangle *myRect = [[Rectangle alloc] init];
        id myPoint = [[XYPoint alloc] init];
        [myPoint setX: 100 andY: 200];
        [myRect setWidth: 5 andHeight: 8];
        myRect.origin = myPoint;
        NSLog(@"Rectangle: w = %i, h = %i", myRect.width, myRect.height);
        NSLog(@"Origin at (%i, %i)",myRect.origin.x, myRect.origin.y);
        [myPoint setX: 50 andY: 50];
        //处理后再对坐标进行set,不会改变。
        //最后输出的坐标还会是 100,200
        //如果设置坐标时不是新创建属于自己的坐标
        //而是直接使用了地址,则会导致更改x与y的值时改变实例变量的坐标
        NSLog(@"Area = %i,Perimeter = %i", [myRect area], [myRect perimeter]);
        Square *mySquare = [[Square alloc] init];
        [mySquare setSide: 5];
        NSLog(@"Square s = %i", [mySquare side]);
        NSLog(@"Area = %i, Perimeter = %i", [mySquare area], [mySquare perimeter]);
    }
    return 0;
}

重写父类的方法

子类很多情况下都是以父类为基础,额外增加新的成员变量和方法,但是有一种情况例外,那就是子类需要重写父类的方法。例如鸟类包括了飞翔的方法,但是鸵鸟是一种特殊的鸟类,飞翔的方法并不适用于它,因此,鸵鸟需要重写鸟类的方法。
先定义一个FKBird类,接口:

#import <Foundation/Foundation.h>

@interface FKBird : NSObject
-(void) fly;
@end

实现:

#import "FKBird.h"

@implementation FKBird
-(void) fly {
    NSLog(@"我在天空里自由自在地飞翔。。。");
}
@end

再定义一个FKOstrich类,这个类扩展了FKBird,重写FKBird类的fly的方法。接口:

#import <Foundation/Foundation.h>
#import "FKBird.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKOstrich : FKBird

@end

子类接口部分并不需要重新声明要重写方法,只要在类实现部分直接重写该方法即可。

#import "FKOstrich.h"

@implementation FKOstrich
-(void) fly {
    NSLog(@"我只能在地上奔跑。。。");
}
@end
#import "FKOstrich.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKOstrich* os = [[FKOstrich alloc] init];
        //执行FKOstrich对象的fly方法,将输出“我只能在地上奔跑。。。”
        [os fly];
    }
    return 0;
}

这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖。子类重写了父类的方法,也可以说子类覆盖了父类的方法。方法的重写必须注意方法签名关键字要完全相同,也就是方法名和方法签名中的形参标签都需要完全相同,否则就不能算方法重写。

super关键字

如果需要在子类方法中调用父类被覆盖的方法,则可以使用super关键字来调用父类被覆盖的方法。
为上面的FKOstrich类添加一个方法,在这个方法中调用FKBird被覆盖的fly方法。

-(void) callOverrideMethod {
	[super fly]
}

这样,FKOstrich对象就既可以调用自己重写的fly方法,也可以调用FKBird类中被覆盖的fly方法(调用callOverrideMethod)。super用于限定该对象调用从它父类继承得到的属性或方法。super既可以出现在类方法中,也可以出现在实例方法中。
当子类继承父类时,子类可以获得父类中定义的成员变量,因此,子类接口部分不允许定义与父类接口部分重名的成员变量。例如:

//FKBase.h
#import <Foundation/Foundation.h>
@interface FKBase: NSObject {
	@private
	int _a;
}
@end
//FKSubclass.h
#import <Foundation/Foundation.h>
#import "FKBase.h"
@interface FKSubclass: FKBase {
	int _a;
}
@end

虽然FKBase中定义的成员变量_a使用了@private限制,但在子类的接口部分依然不能定义与之重名的_a成员变量。编译上面的程序,将出现错误提示。
无论父类接口部分的成员变量使用何种访问控制符限制,子类接口部分定义的成员变量都不允许与父类接口部分定义的成员变量重名。
但是,在类实现部分定义的成员变量将被限制在该类内部,因此,父类在类实现部分定义的成员变量对子类没有任何影响。无论是接口部分还是实现部分,子类定义的成员变量都可以与父类实现部分定义的成员变量同名。
反过来,在子类实现部分定义的成员变量也不受父类接口部分定义的成员变量的影响。也就是说,即使父类的接口部分定义了_a的成员变量,子类的实现部分依然可以定义名为_a的成员变量。
当子类实现部分定义了与父类重名的成员变量时,子类的成员变量就会隐藏父类的成员变量。因此,子类方法很难直接访问到父类的成员变量,此时可以通过调用父类的方法来访问父类中被隐藏的成员变量。
示例:

//FKParent.h
#import <Foundation/Foundation.h>
@interface FKParent: NSObject {
	int _a;
}
@property (nonatomic , assign) int a;
@end

FKParent父类定义了一个_a成员变量,并且合成了存取方法。

//FKParent.m
#import <Foundation/Foundation.h>
#import "FKParent.h"
@implementation FKParent
-(id) init {
	if(self = [super init]) {
		self->_a = 5;
	}
	return self;
}
@end

接下来定义一个子类:

//FKSub.h
#import <Foundation/Foundation.h>
#import "FKParent.h"
@interface FKSub: FKParent
-(void) accessOwner;
@end

下面为FKSub子类定义实现部分,实现部分会定义一个名为_a的成员变量,该成员变量将会隐藏父类的成员变量。

//FKSub.m
#import <Foundation/Foundation.h>
#import "FKSub.h"
@implementation FKSub {
	//该成员变量将会隐藏父类的成员变量
	int _a;
}
-(id) init {
	if (self = [super init]) {
		self->_a = 7;
	}
	return self;
}
-(void) accessOwner {
	//直接访问的是当前类中的成员变量
	NSLog(@"子类中_a成员变量:%d", _a);
	//访问父类中被隐藏的成员变量
	NSLog(@"父类中被隐藏的_a成员变量:%d", super.a);
}
@end

int main (int argc , char * argv[]) {
	@autoreleasepool {
		FKSub* sub = [[FKSub alloc] init];
		[sub accessOwner];
	}
}

程序通过super关键字强制指定调用父类的a属性。两块内存分别保存了两个_a成员变量。

@class指令

示例:

@class XYPoint;

它的作用是告诉编译器XYPoint是一个类的名字。使用@class指令可以提高效率,因为编译器无需引入并处理整个XYPoint.h文件,只需知道XYPoint是一个类名。注意:如果需要引用XYPoint类的方法(在实现部分中),@class指令是不够的,需要导入头文件。

以上是关于OC类的继承的主要内容,如果未能解决你的问题,请参考以下文章

oc知识点 初始化方法

OC类的继承

25-oc类的启动过程

转 OC温故:类的三大特性(封装,继承,多态)

OC的动态继承编译机制

java中的继承与oc中的继承的区别