一、继承
在现实世界中,可以看到很多按层次分类的概念。比如,动物分为哺乳动物、爬行动物等。哺乳动物又分为很多小类。整个分类就组成了一个树状结构。
在面向对象中,上一层称为父类,下一层称为子类。继承实现了子类和父类:子类可以使用父类的所有功能,并可以对这些功能进行拓展。
通过继承创建的心类称为“子类”或“派生类”。被继承的类称为‘基类“、”父类“或”超类“。继承的过程,就是从一般到特殊的过程。
通过使用继承,一个对象就只需定义它所在的所属类的属性即可,因为它可以从父类那里继承所有的通用属性。比如,一个银行账号类的子类可以是定期账号类和活期账号类,他们直接从父类继承开户人、地址等属性和方法。
1、继承的用法
定义一个Animal类
/*******Animal的声明和实现*********/
@interface Animal : NSObject
{
int _age;
double _weight;
}
- (void)setAge:(int)age; // 设置dog的年龄
- (int)age; // 获取dog的年龄
- (void)setWeight:(double)weight; // 设置dog的体重
- (double)weight; // 获取dog的体重
@end
@implementation Animal
- (void)setAge:(int)age // 设置dog的年龄
{
_age = age;
}
- (int)age // 获取dog的年龄
{
return _age;
}
- (void)setWeight:(double)weight // 设置dog的体重
{
_weight = weight;
}
- (double)weight // 获取dog的体重
{
return _weight;
}
@end
它有两个子类:
Dog类:
/******Dog类的声明和实现*******/
@interface Dog : Animal
@end
@implementation Dog
@end
Cat类:
/******Cat类的声明和实现*******/
@interface Cat : Animal
@end
@implementation Cat
@end
2、继承的专业术语
父类\超类 superclass子类 subclass\subclasses
3、继承的优缺点
好处:
(1)不改变原来模型的基础上,拓充方法
(2)建立了类与类之间的联系
(3)抽取了公共代码
坏处:
(1)耦合性强,两个类之间的关系太紧密
4、重写
子类中重新实现父类的某个方法,覆盖了父类的方法
(1)父类必须声明在子类的前面
(2)子类不能拥有和父类相同的成员变量
(3)当调用某个方法时,如果找到了就直接调用,如果没有找到就去父类中查找
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
@private
int _age;
}
- (void)setAge:(int)age;
- (int)age;
- (void)run;
@end
@implementation Person
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
- (void)run
{
NSLog(@"Person --- 跑!");
}
@end
@interface Student : Person
{
int _no;
// 不允许子类与父类拥有相同名称的属性
// int _age;
}
- (void)run;
@end
@implementation Student
// 子类重写了父类的方法
- (void)run
{
NSLog(@"Student --- 跑!");
}
@end
int main(int argc, const char *argv[])
{
Student *stu = [Student new];
[stu run];
return 0;
}
运行结果:
MyMac:0805 rui$ ./a.out
2015-04-02 22:04:34.454 a.out[1782:225658] Student --- 跑!
5、继承的使用场合
1、当两个类具有相同的属性和方法时,可以将相同的部分抽取到一个父类中
2、当一个类中的所有属性和方法包含于另一个类当中,可以考虑将前一个类当做父类,后面的类继承它
6、继承和复合
当A类完全拥有B类中的部分属性和方法时,可以考虑让B类继承A类(考虑),在这种情况下,也可以考虑使用组合。
我们设定一个Student类,它有如下的属性:
(1)int _cScore;C语言分数
(2)int _ocSCore; OC语言的分数
(3)int _age; 学生的成绩
#import <Foundation/Foundation.h>
/************Student类的声明**************/
@interface Student : NSObject
{
int _cScore; // C语言的分数
int _ocScore; // OC语言的分数
int _age; // 学生的年龄
}
- (void)setCScore:(int)csSCore;
- (int)cScore;
- (void)setOCScore:(int)ocSCore;
- (int)ocScore;
- (void)setAge:(int)age;
- (int)age;
@end
然后又有一个Score类,它有如下的属性:
(1)int _cScore;C语言分数
(2)int _ocSCore; OC语言的分数
/*************Score分数类的声明*******************/
@interface Score : NSObject
{
int _cScore; // C语言分数
int _ocScore; // OC语言分数
}
- (void)setCScore:(int)cScore;
- (int)cScore;
- (void)setOCScore:(int)ocScore;
- (int)ocSCore;
@end
这种情况下我们可以看到Score类的全部属性包含在Person类当中,那我们是否要将Person作为Score类的子类呢?很显然,这种想法是很荒唐的!很显然,Student类应该拥有一个指向Score类指针的属性,即应该使用组合!
这里我给出自己的以前学习Java的一点看法:
继承:我们可以想象成“is a"的关系,翻译成,是一种,类似一种。例如,Dog类”is a" Animal类,Student类“is a" Person类。
组合:我们可以想象成”has a“的关系,翻译成,有一个。例如本例中的Student类”has a“Score类的指针属性,还有Car类和Wheel类等等都是应该使用组合关系。
#import <Foundation/Foundation.h>
/*************Score分数类的声明*******************/
@interface Score : NSObject
{
int _cScore; // C语言分数
int _ocScore; // OC语言分数
}
- (void)setCScore:(int)cScore;
- (int)cScore;
- (void)setOCScore:(int)ocScore;
- (int)ocSCore;
@end
/************Student类的声明**************/
@interface Student : NSObject
{
// int _cScore; // C语言的分数
// int _ocScore; // OC语言的分数
Score *_score; // 一个Score类的指针
int _age; // 学生的年龄
}
- (void)setCScore:(int)csSCore;
- (int)cScore;
- (void)setOCScore:(int)ocSCore;
- (int)ocScore;
- (void)setAge:(int)age;
- (int)age;
@end
二、关键字super
Super关键字,在子类中重写方法时,可以让调用者跳过这一层而调用父类中的方法。作用:
(1)直接调用父类中的某一个方法
(2)Super处在对象方法中,那么就会调用父类的对象方法;super处于类方法中,那么就会调用父类的类方法。
使用场景:子类在重写父类方法时,想要保留父类的一些行为。
以植物大战僵尸中的僵尸Zoombie来作为演示:
/*
superclass:僵尸
subclas:跳跃僵尸、舞王僵尸、铁桶僵尸
*/
#import <Foundation/Foundation.h>
// 僵尸
@interface Zoombie : NSObject
- (void)walk;
- (void)test;
+ (void)test;
@end
@implementation Zoombie
- (void)walk
{
NSLog(@"往前挪两步-----");
}
- (void)test
{
NSLog(@"对象方法test()");
}
+ (void)test
{
NSLog(@"类方法test()");
}
@end;
// 跳跃僵尸类
@interface JumpZoombie : Zoombie
- (void)callTest;
+ (void)callTest;
@end
@implementation JumpZoombie
- (void)walk
{
// 首先要跳两下
NSLog(@"跳两下");
// 走两下
// NSLog(@"往前走了两步----");
[super walk];
}
- (void)callTest
{
[super test];
}
+ (void)callTest
{
[super test];
}
@end
int main(int argc, const char *argv[])
{
JumpZoombie *jz = [JumpZoombie new];
[jz walk];
[jz callTest];
[JumpZoombie callTest];
return 0;
}
执行结果为:
MyMac:0805 rui$ ./a.out
2015-04-03 00:07:00.312 a.out[2000:258816] 跳两下
2015-04-03 00:07:00.314 a.out[2000:258816] 往前挪两步-----
2015-04-03 00:07:00.314 a.out[2000:258816] Zoombie的对象方法test2()
2015-04-03 00:07:00.314 a.out[2000:258816] 类方法test()
三、多态
1、基本概念
多态在代码中的体现,即为多种形态,必须要有继承,没有继承就没有多态。
(1)在使用多态是,会进行动态检测,以调用真实的对象方法。
(2)多态在代码中的体现即父类指针指向子类对象。
Animal类的声明和实现
#import <Foundation/Foundation.h>
@interface Animal : NSObject
- (void)eat;
@end
@implementation Animal
- (void)eat
{
NSLog(@"Animal吃东西----");
}
@end
Dog类的声明和实现
@interface Dog : Animal
@end
@implementation Dog
- (void)eat
{
NSLog(@"Dog吃东西----");
}
@end
测试程序
int main(int argc, const char *argv[])
{
// 多态,父类指针指向子类对象
Animal *a = [Dog new];
[a eat];
// 调用方法时会检测对象的真是类型
return 0;
}
编译执行之后:
MyMac:0805 rui$ .//a.out
2015-04-03 00:23:25.703 a.out[2047:267832] Dog吃东西----
2、使用注意
代码分析:
Dog *d=[[Animal alloc] init]; 动物是一条狗?语义正确吗?
NSString *str=[Dog new]; 狗是一个字符串?正确吗?
Oc语言是一门弱语法的语言,编译的时候并不会报错,所以这就要求我们在实际的开发过程中一定要按照既定的规范来写代码,不要出现狗是一个字符串这样的问题。
3、多态的好处
可以节省代码,动态的决定对象的类型
例如,我们要喂Dog和Cat,一般我们的做法是定义两个函数,分别来喂Dog和Cat
// 需要一个新的函数专门用来喂狗
Void feed(Dog *d)
{
[d eat];
}
// 如果这个时候也需要喂猫,那就应该重写新一个新的函数
Void feed(Cat *c)
{
[c eat];
}
但是使用多态,我们可以一个函数就搞定上面两个函数:
void feed(Animal *a)
{
[a eat];
}
测试代码:
int main(int argc, const char *argv[])
{
// 多态,父类指针指向子类对象
Animal *a = [Dog new];
// 调用方法时会检测对象的真是类型
// [a eat];
feed(a);
Animal *c = [Cat new];
feed(c);
return 0;
}
执行结果:
MyMac:0805 rui$ ./a.out
2015-04-03 00:42:11.126 a.out[2099:275566] Dog吃东西----
2015-04-03 00:42:11.128 a.out[2099:275566] Cat 吃东西----
3、多态的局限性
不能访问子类的属性,但可以考虑强制转换。
我们定义如下Animal类
#import <Foundation/Foundation.h>
@interface Animal : NSObject
- (void)eat;
@end
@implementation Animal
- (void)eat
{
NSLog(@"Animal吃东西----");
}
@end
定义如下的Cat类
@interface Cat : Animal
- (void)run;
@end
@implementation Cat
- (void)eat
{
NSLog(@"Cat 吃东西----");
}
- (void)run
{
NSLog(@"Cat 跑起来了!");
}
如果此时在执行以下代码:
// 多态,父类指针指向子类对象
Animal *a = [Cat new];
[a run];
编译器会提示:
15-多态.m:65:8: warning: 'Animal' may not respond to 'run'
[a run];
~ ^
1 warning generated.
但是使用强制转换之后:
// 多态,父类指针指向子类对象
Animal *a = [Cat new];
// 强制转换
[(Cat*)a run];
编译连接,编译器没报错,执行程序:
MyMac:0805 rui$ ./a.out
2015-04-03 01:01:47.874 a.out[2183:284585] Cat 跑起来了!