Objective-C 2.0 with Cocoa Foundation--- 4,继承
4,继承
本系列讲座有着很强的前后相关性,如果你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里。
上一章笔者介绍了一下在Objective-C里面的类的基本构造和定义以及声明的方法。我们知道在面向对象的程序里面,有一个很重要的需求就是代码的重复使用,代码的重复使用的重要方法之一就是继承。我们在这一章里面,将要仔细的分析一下继承的概念以及使用的方法。有过其他面向对象语言的同学,对这一章的内容应该不会感到陌生。
4.1,本章的程序的执行结果
在本章里面,我们将要重复使用第3章的部分代码。我们在第3章构筑了一个叫做Cattle的类,我们在这一章里面需要使用Cattle类,然后基于Cattle类,我们需要构筑一个子类,叫做Bull类。 Bull类里面,我们追加了一个实例变量,名字叫做skinColor,我们也将要追加2个实例方法,分别getSkinColor还有setSkinColor。我们然后需要更改一下我们的main函数,然后在main函数里面让我们的Bull做一下重要讲话。第4章程序的执行结果如图4-1所示:
图4-1,本章程序的执行结果
4.2,实现步骤
第一步,按照我们在第二章所述的方法,新建一个项目,项目的名字叫做04-Hello Inheritance。如果你是第一次看本篇文章,请到这里参看第二章的内容。
第二步,把鼠标移动到项目浏览器上面的“Source”上面,然后在弹出的菜单上面选择“Add”,然后在子菜单里面选择“Exsiting Files” ,如图4-2所示
图4-2,向项目追加文件
第三步,在文件选择菜单里面,选择第3章的项目文件夹“03-Hello Class”,打开这个文件夹之后,用鼠标和苹果电脑的COMMAND键,选泽文件“Cattle.h”和“Cattle.m”,然后按下“Add”按钮,如图4-3所示。如果你没有下载第3章的代码,请点击这里下载。
图4-3,选择文件
第四步,在追加文件的选项对话框里面,让“Copy items into destination group's folder(if needed)” 的单选框变为被选择的状态。这样就保证了我们在第三步里面选择的文件被拷贝到了本章的项目里面,可以避免我们不小心更改“Cattle.h”和“Cattle.m”对已经生效的第3章程序产生影响,虽然我们在本章里面不更改这2个代码。
第五步,把鼠标移动到项目浏览器上面的“Source”上面,然后在弹出的菜单上面选择“Add”,然后在子菜单里面选择“New Files”,然后在新建文件对话框的左侧选择“Cocoa Touch Classes”,然后在右侧窗口选择“NSObject subclass”,选择“Next”,在“New File”对话框里面的“File Name”栏内输入“Bull.m”。在这里笔者没有给出图例,在这里新建文件的步骤和第3章的第二步到第四步相同,只是文件名字不一样。第一次看到本篇文章的同学可以参照第3章。
第六步,打开Bull.h做出如下修改,并且保存。
#import "Cattle.h"
@interface Bull : Cattle {
NSString *skinColor;
}
- (void)saySomething;
- (NSString*) getSkinColor;
- (void) setSkinColor:(NSString *) color;
@end
第七步,打开Bull.m做出如下修改,并且保存
@implementation Bull
-(void) saySomething
{
NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount);
}
-(NSString*) getSkinColor
{
return skinColor;
}
- (void) setSkinColor:(NSString *) color
{
skinColor = color;
}
@end
第八步,打开04-Hello Inheritance.m文件,做出如下修改,并且保存
#import "Cattle.h"
#import "Bull.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id cattle = [Cattle new];
[cattle setLegsCount:4];
[cattle saySomething];
id redBull = [Bull new];
[redBull setLegsCount:4];
[redBull setSkinColor:@"red"];
[redBull saySomething];
Bull *blackBull = [Bull new];
[blackBull setLegsCount:4];
[blackBull setSkinColor:@"black"];
[blackBull saySomething];
[pool drain];
return 0;
}
第九步,选择屏幕上方菜单里面的“Run”,然后选择“Console”,打开了Console对话框之后,选择对话框上部中央的“Build and Go”,如果不出什么意外的话,那么应该出现入图4-1所示的结果。如果出现了什么意外导致错误的话,那么请仔细检查一下你的代码。如果经过仔细检查发现 还是不能执行的话,可以到这里下载笔者为同学们准备的代码。 如果笔者的代码还是不能执行的话,请告知笔者。
4.3,子类Subclass和超类Superclass
让我们首先回忆一下第3章的Cattle.h,在Cattle.h里面我们有如下的代码片断:
这段代码是在告诉编译器,我们的Cattle是继承的NSObject。在这段代码当中,NSObject是超类,Cattle是子类。通过这样写,我们曾经免费的得到了NSObject里面的一个方法叫做new。
在面向对象的程序设计当中,如果在子类当中继承了超类的话,那么超类当中已经生效的部分代码在子类当中仍然是有效的,这样就大大的提高了代码的效率。基于超类我们可以把我们需要追加的一些功能放到子类里面去,在本章里面,我们决定基于Cattle类,重新生成一个子类Bull:
2 #import "Cattle.h"
3
4 @interface Bull : Cattle {
5 NSString *skinColor;
6 }
7 - (void)saySomething;
8 - (NSString*) getSkinColor;
9 - (void) setSkinColor:(NSString *) color;
10 @end
上段代码里面的第2行,是通知编译器,我们这个类的声明部分需要Cattle.h文件。这个文件我们已经很熟悉了,是我们在第3章曾经构筑过的,在本章里面,我们不会改变里面的任何内容。
第4行,就是在通知编译器,我们需要声明一个类名字叫做Bull,从Cattle里面继承过来。
第5行,我们追加了一个实例变量skinColor,用来保存Bull的颜色。
第7行,我们重载了在Cattle类里面已经有的(void)saySomething实例方法。重载(void)saySomething方法的主要原因是,我们认为Bull说的话应该和Cattle有所区别。
第8行到第9行,我们为Bull类声明了两个新的方法(NSString*) getSkinColor和(void) setSkinColor:(NSString *) color,分别用来设定和读取我们的实例变量skinColor。
好的,我们总结一下继承的时候的子类的格式。
实体变量类型 实体变量名字;
}
- (返回值类型)重载的方法名字;
+ (返回值类型)重载的方法名字;
- (返回值类型)其他的方法名字:(变量类型) 变量名字:(变量类型) 变量名字;
@end
4.4,self和super
我们再来打开“Bull.m”, 在saySomething的定义的部分,我们发现了如下的代码:
我们在这句话当中,发现的第一个新朋友是%@,这是在告诉编译器,需要把%@用一个后面定义的字符串来替换,在这里我们给编译器提供的字符串是[self getSkinColor]。看到这里,同学们又会发现一个新的朋友self。
在类的方法定义域之内,我们有的时候需要访问这个类自己的实例变量,或者是方法。在类被实例化之后,我们就可以使用一个指向这个类本身的一个指针,在Java或者C++里面的名字叫做this,在Objective-C里面,这个名字是self。self本身是一个id类型的一个指针变量。我们在第3章里面讲解过,方法的调用格式如下:
在类的方法定义域里面,当我们需要调用类的其他方法的时候,我们需要指定对象或者类的名字,我们的方法是一个实例方法所以我们需要一个指向自己的对象,在这里我们需要使用self。
我们假设,如果方法声明里面的参数序列里面有一个参数的名字和类的实例变量发生重复的情况下并且由于某种原因我们无法更改参数和实体变量的名字的话,我们应该如何应对呢?答案是使用self,格式如下
通过这样写,我们可以取得到类的变量的数值。当然如果没有名字冲突的话,我们完全可以省略self->,Xcode也足够的聪明能够识别我们的实例变量,并且把我们代码里面的实例变量更改为相应的醒目的颜色。
如果我们在类的方法里面需要访问超类的方法或者变量(当然是访问对子类来说是可视的方法或者变量),我们需要怎样写呢?答案是使用super,super在本质上也是id的指针,所以,使用super访问变量和方法的时候的书写格式,和self是完全一样的。
“Bull.m”里面的其他的代码,没有什么新鲜的东西,所以笔者就不在这里赘述了。
4.5,超类方法和子类方法的执行
我们来看一下04-Hello Inheritance.m的下面的代码片断
2 [redBull setLegsCount:4];
3 [redBull setSkinColor:@"red"];
4 [redBull saySomething];
5
6 Bull *blackBull = [Bull new];
7 [blackBull setLegsCount:4];
8 [blackBull setSkinColor:@"black"];
9 [blackBull saySomething];
第1行的代码在第3章里面讲解过,我们来看看第2行的代码。
第2行的代码实际上是向redBull发送一个setLegsCount消息,参数为4。我们没有在Bull里面定义setLegsCount方法,但是从控制台的输出上来看, setLegsCount明显是得到了执行。在执行的时候,我们给redBull发送setLegsCount消息的时候,runtime会在Bull的映射表当中寻找setLegsCount,由于我们没有定义所以runtime找不到的。runtime没有找到指定的方法的话,会接着需要Bull的超类,也就是Cattle。值得庆幸的是,runtime在Cattle里面找到了setLegsCount,所以就被执行了。由于runtime已经寻找到了目标的方法并且已经执行了,所以它就停止了寻找。我们假设runtime在Cattle里面也没有找到,那么它会接着在Cattle的超类NSObject里面寻找,如果还是找不到的话,由于NSOBject是根类,所以它会报错的。关于具体内部是一个怎样的机制,我们将在后面的章节里面讲解。
第3行的代码,是设定skinColor。
第4行的代码是给redBull发送saySomething的消息。按照第2行的runtime的寻找逻辑,它首先会在Bull类里面寻找saySomething,这一次runtime很幸运,它一次就找到了,所以就立即执行。同时runtime也停止了寻找的过程,所以,Cattle的saySomething不会得到执行的。
在第6行里面,我们定义了一个blackBull,但是这一次我们没有使用id作为blackBull的类型,我们使用了Bull *。从本质上来说,使用id还是Bull *是没有任何区别的。但是,我们来想象,当我们的程序存在很多id类型的变量的话,我们也许就难以区分究竟是什么类型的变量了。所以,在没有特殊的理由的情况之下,我们最好还是显式的写清楚类的名字,这样可以方便其他人阅读。由于Bull从Cattle继承而来,我们也可以把地6行代码改为
4.6,本章总结
感谢大家阅读到这里!我们在本章学习了:
- 超类,子类的概念以及如何定义和声明。
- self和super的使用方法以及使用的时机。
- 超类和子类的方法的执行。