原文鏈接:http://www.cnblogs.com/yaski/
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的使用方法以及使用的時機。
- 超類和子類的方法的執行。
我們在下一章將要講述selector等等的一些其他的重要的概念。