类的继承
继承是面向对象的三大特征(封装,多态,继承)之一,也是实现软件复用的重要手段。objective-c 继承是单继承,每个子类只有一个父类。
objective-c 里子类继承父类的语法格式:
@interface SubClass :SuperClass
{
// 成员变量定义
}
//方法定义部分
@end
被继承的类被称为父类。实现继承的类被称为子类。比如水果个橘子的关系,橘子继承了水果,水果是橘子的父类,橘子是水果的子类。子类是一种特殊的父类,也就是说父类包含的范围比子类包含的范围更大更宽泛。
继承关系,本质上是一种”有一般到特殊”的关系。所以,用继承来理解子类和父类的关系,其实是有些偏颇的。我个人感觉用延展来描述更合适,比如上面的例子,橘子类延展(延伸扩展)了水果类。
在子类延展父类时,子类可以继承得到的父类的如下东西:
1.全部成员变量。
2.全部方法(包括初始化方法)。
示例代码 :
FKFruit.h
#import <Foundation/Foundation.h>
@interface FKFruit : NSObject
@property (nonatomic , assign) double weight;
- (void) info;
@end
FKFruit.m
#import "FKFruit.h"
@implementation FKFruit
@synthesize weight;
- (void) info
{
NSLog(@"我是一个水果!重%gg!" , weight);
}
@end
FKApple.h
#import <Foundation/Foundation.h>
#import "FKFruit.h"
@interface FKApple : FKFruit
@end
FKApple.m
#import "FKApple.h"
@implementation FKApple
@end
FKAppleTest.m
#import "FKApple.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建FKApple的对象
FKApple* a = [[FKApple alloc] init];
// FKApple对象本身没有weight属性
// 因为FKApple的父类有weight属性,也可以访问Apple对象的weight属性
a.weight = 56;
// 调用FKApple对象的info方法
[a info];
}
}
编译运行结果:
2015-09-24 15:12:00.302 923[6965:357763] 我是一个水果!重56g!
类继承说法的纠偏
有的资料里介绍objective-c 的单继承时,可能会说 objective-c 类只有一个父类。这种说法不严谨。
应该说:objective-c 类只能有一个直接父类。但可以有无限个间接父类。比如:
@interface HXYFruit :HXYPlant {...} @end
@interface HXYApple :HXYFruit {...} @end
这其中,HXYApple的直接父类是HXYFruit,间接父类是HXYPlant。
NSObject 类 是所有类的父类
定义任何 OC 的类都需要指定一个直接父类,默认情况下会让自己的OC 类继承 NSObject 类,因此,NSObject 类是所有类的父类——直接父类或者间接父类。也是因为这样,所有 OC 对象都可以调用NSObject 类所定义的实例方法。这也是 KVC,KVO 机制能正常调用那些方法的原因。
重写父类的方法
多数情况下,子类都是以父类为基础进行延展,额外增加内容。但有种特殊情况:子类需要重写父类的方法。
比如,鸵鸟是鸟,但却不会飞,那么鸵鸟就需要重写鸟类中的方法。
示例程序:
FKBird.h
#import <Foundation/Foundation.h>
@interface FKBird: NSObject
- (void) fly;
@end
FKBird.m
#import <Foundation/Foundation.h>
#import "FKBird.h"
@implementation FKBird
// FKBird类的fly方法
- (void) fly
{
NSLog(@"我在天空里自由自在地飞翔...");
}
@end
FKOstrich.h
#import <Foundation/Foundation.h>
#import "FKBird.h"
@interface FKOstrich: FKBird
- (void) callOverridedMethod;
@end
FKOstrich.m
#import <Foundation/Foundation.h>
#import "FKOstrich.h"
@implementation FKOstrich
// 重写父类的fly方法
- (void) fly
{
NSLog(@"我只能在地上奔跑...");
}
- (void) callOverridedMethod
{
// 在子类方法中通过super显式调用父类被覆盖的实例方法。
[super fly];
}
@end
FKOstrichTest.m
#import "FKOstrich.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建FKOstrich对象
FKOstrich* os = [[FKOstrich alloc] init];
// 执行FKOstrich对象的fly方法,将输出"我只能在地上奔跑..."
[os fly];
[os callOverridedMethod];
}
}
编译运行结果:
2015-09-24 15:50:37.276 923[7359:376538] 我只能在地上奔跑...
2015-09-24 15:50:37.280 923[7359:376538] 我在天空里自由自在地飞翔...
这里就能看出,[os fly];
不再执行父类中的 fly 方法,而是执行FKOstrich类中的方法。
方法重写/方法覆盖:
这种子类包含与父类同名方法的情况就被称为方法重写或方法覆盖。注意:方法重写一定要方法签名关键字完全相同,也就是说方法名和方法签名中形参标签都需要完全相同,否则就不算是方法重写。
★super关键字
如果需要在子类方法中调用父类被覆盖的实例方法,可以使用 super 关键字调用父类被覆盖的实例方法。比如上面代码中,
- (void) callOverridedMethod
{
// 在子类方法中通过super显式调用父类被覆盖的实例方法。
[super fly];
}
★**super关键字:**super是 objective-c提供的一个关键字,super 用于限定该对象调用它从父类继承得掉的属性或方法。
类方法的调用者只能是类本身,而不是对象,因此 super 关键字在类方法中也就失去了意义。 super 和 self 关键字一样,都不能出现在类方法中。
当子类继承父类的时,子类可以获得父类中定义的成员变量,因此子类接口与父类接口部分的成员变量不能重名,不管父类中用什么访问控制符都不能定义重名变量。
需要特别指出的是,父类在类实现部分定义的成员变量对子类没有影响——因为类实现部分定义的成员变量限制在该类内部。反过来说也同样,子类中定义在类实现部分的成员变量,也不受父类接口部分定义的成员变量的影响。
当子类实现部分定义了与父类(接口部分)重名的成员变量,子类的成员变量就会隐藏父类的成员变量。因此,子类方法很难直接访问到父类的成员变量,此时可以通过调用父类的方法来访问父类中被隐藏的成员变量。
示例代码:
FKParent.h
#import <Foundation/Foundation.h>
@interface FKParent: NSObject
{
int _a;
}
@property (nonatomic , assign) int a;
@end
FKParent.m
#import <Foundation/Foundation.h>
#import "FKParent.h"
@implementation FKParent
@synthesize a = _a;
- (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.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];
}
}
编译运行结果为:
2015-09-24 17:42:13.881 923[7977:415250] 子类中_a成员变量:7
2015-09-24 17:42:13.882 923[7977:415250] 父类中被隐藏的_a成员变量:5
说明:可以看到子类实现部分定义与父类同名的成员变量,只是隐藏父类的成员变量,虽然程序只创建了一个 FKSub 对象,但该对象内部仍然有2块内存来保存_a的成员变量,一块保存父类中被隐藏的_a变量——可以通过父类中定义的方法访问;一块保存子类实现部分定义的_a成员变量,可以在子类方法中访问。