Objective-C學習篇03—繼承,objective-c03繼承
大綱:繼承的基本概念自訂初始化方法便利構造器方法重寫description方法
一 繼承基本概念
程式裡的對象和"人類"的對象是一樣的,高富帥繼承了父母,自然就擁有了父母所有的資源,子類繼承了父類同樣就擁有了父類所有的屬性和方法,當然,父類私人的除外.
我們在定義一個新的類的時候,常常會遇到要定義的新類是某個類的擴充或者是某個類的修正等這種情況.如果能夠在已有的類的基礎上定義新類,那麼新類的定義將會變得十分簡便.
類似於這種,通過擴充或者修改既有的類來定義新類的方法就叫做繼承(inheritance).在繼承關係中,被繼承的類(原有的類)就成為父類(superclass),通過這種繼承關係得到的新類就叫做子類(subclass).
上面也講到,繼承意味著子類可以得到父類所有的屬性和方法,除此之外,子類還可以為新類:
1. 添加新的執行個體變數
2. 追加新的自己特有的方法
3. 重新定義父類中的方法
當然,如果子類中只追加新的執行個體變數而不變更方法則沒有任何意義.可以說,繼承的目的就是要讓子類能做更多父類做不了的事(擴充方法).子類中重新定義父類的方法叫做重寫.分為完全重寫和不完全重寫,這些在後面都會講到.
繼承的優點:1> 省略了大量重複的代碼 2>建立起類與類之間的關係.
缺點在於類間耦合性太強
二 OC中的繼承關係
1. 在OC中,一旦建立了繼承關係,子類就可以使用父類中所有的執行個體變數和方法.(父類私人的除外)
2. OC中的繼承是單繼承關係,一個類只能有一個父類,但是一個類可以有n個子類.
3. OC中的繼承關係一定是合理的
4. OC中繼承是單根類繼承,所有類的祖宗類都是NSObject,NSObject是所有類的基類或者根類
5. A 繼承自 B 可以說,A是B的子類,或者說A類是由B類衍生的,衍生類
6. 使用繼承的好處:如果一些類在定義的時候,發現他們有很多的執行個體變數或者方法是重複的(共同的特徵),此時就可以把這些執行個體變數或者方法抽象出一個新的類作為這些類的父類,這樣我們在定義一個類的時候,直接將要的定義的類繼承自這個父類,就能省去很多重複的代碼,只需要在子類中添加自己專屬的特徵和方法.
7. 父類中被@public和@protected修飾的執行個體變數,子類中可以直接存取,而被@provate(私人的)修飾的執行個體變數子類不能直接存取,只能通過方法訪問
8. 子類在調用方法時優先去自己的類裡面找,找到了就直接調用,找不到就去父類中找,找到就調用,找不到就直接向上找,直到最終找到NSObject,如果實現就調用,沒有實現就Crash
9. 重寫父類的方法,一種是完全重寫,不完全重寫時需要在方法中使用super調用父類對方法的實現
使用繼承時要注意的問題
代碼示範:
首先,建立一個汽車Car類,讓它繼承自NSObject類
Car.h
@interface Car : NSObject // 汽車的特徵{ @public NSString *_color; //顏色
@protected NSString *_brand; //品牌
@private NSInteger _wheel; //輪子}// 車的行為- (void)run;- (void)carShock;
// 執行個體變數的setter getter方法- (void)setColor:(NSString *)color;- (NSString *)color;- (void)setBrand:(NSString *)brand;- (NSString *)brand;- (void)setWheel:(NSInteger)wheel;- (NSInteger)wheel;@end
Car.m
@implementation Car- (void)run { NSLog(@"車在跑");}- (void)carShock { NSLog(@"車震是什麼感覺,沒試過");}- (void)setColor:(NSString *)color { _color = color;}
- (NSString *)color { return _color;}- (void)setBrand:(NSString *)brand { _brand = brand;}- (NSString *)brand { return _brand;}- (void)setWheel:(NSInteger)wheel { _wheel = wheel;}- (NSInteger)wheel { return _wheel;}@end
上面我們定義了一個汽車Car的類,,使它具有顏色,品牌,輪子的屬性,具有跑和車震兩種行為(方法).現在,我們給卡車Truck增加一個最大載貨量的屬性和輸出自身資訊的方法,同時又包含Car這些屬性和方法,那麼我們就要使用繼承;
這個時候利用繼承的思想,我們只需要建立一個類,讓它繼承自Car這個類,新類中寫自己特有的方法和屬性.
建立一個Truck類,繼承自Car:
Truck.h
@interface Truck : Car{ // NSString *_color; error 子類中不能定義和父類同名的執行個體變數 CGFloat _maxLoad; //最大載貨量}- (void)setMaxLoad:(CGFloat)manLoad;- (CGFloat)maxLoad;// 描述卡車資訊的方法- (void)describeTruck;- (void)run; // 子類中可以定義和父類同名的方法,這種形式叫做方法的重寫//那麼,為什麼要進行方法的重寫呢?因為父類提供的方法不能滿足子類的需求// 重寫父類的方法有兩種,一種是完全重寫父類的方法,另一種是不完全重寫的方法
@end
Truck.m
@implementation Truck// _maxLoad的setter getter方法- (void)setMaxLoad:(CGFloat)manLoad { _maxLoad = manLoad;}- (CGFloat)maxLoad { return _maxLoad;}// 描述卡車資訊的方法- (void)describeTruck { NSLog(@"%@ %@ %ld %.2f", _color, _brand, self.wheel, _maxLoad);}// 注意:由於 _wheel被private修飾,不能在子類中直接存取,需要用self// 重寫父類run的方法// 如果在重寫父類方法時,沒有調用父類的方法實現就叫做完全重寫- (void)run NSLog(@"卡車在奔跑");}// 重寫車震的方法- (void)carShock {
// super 編譯器指令,用於調用父類的方法,適用於想保留父類中某些方法的情況 [super carShock]; NSLog(@"卡車不好震");}@end
回到main中.
// 建立一個大卡車的對象Truck *truc = [[Truck alloc] init];
// color brand wheel是從父類繼承過來的truc.color = @"藍色"; // 顏色truc.brand = @"東風"; // 品牌truc.wheel = 12; // 輪子// maxLoad是子類特有的truc.maxLoad = 120.0;// 載重量 // 調用父類的方法// [truc carShock]; [truc run]; // 調用描述卡車的方法 [truc describeTruck];
// 調用車震的方法 [truc carShock];
列印結果:
2015-11-26 22:46:44.741 OCLessonInherit-03[1759:364404] 卡車在奔跑
2015-11-26 22:46:44.742 OCLessonInherit-03[1759:364404] 藍色東風 12 120.00
2015-11-26 22:46:44.742 OCLessonInherit-03[1759:364404] 沒震過,不知道什麼感覺
2015-11-26 22:46:44.742 OCLessonInherit-03[1759:364404] 卡車不好震
三 初始化方法
- (id)init {// 執行父類中的init方法 self = [super init]
if (self) { // 初始化設定 _taxiMeter = @"計價器";}// 返回初始化完成的對象 return self;}
初始化過程:
初始化時,先調用父類的[super init]方法(向上遞迴到NSObject類中的初始化方法),使用self接收結果,然後根據self中的值判斷是否為nil,決定要不要初始自己的執行個體變數;如果為nil,說明父類的初始化方法失敗了,就不再對自身執行個體變數初始化,如果不為nil,說明父類初始化方法成功了,再對自身執行個體變數進行初始化
2.自訂初始化方法
命名規範:
1. 一定是對象方法,以減號 - 開頭
2. 傳回值類型是 id 類型(最好寫id類型) 或者是當前的類類型
3. 必須以 initWith 開頭
4. 自訂初始化方法分為兩種:完全自訂初始化 與 不完全初始化方法
樣本:
不完全初始化方法:
// 只初始化姓名和年齡- (id)initWithName:(NSString *)name age:(NSInteger)age { if (self = [super init]) { _name = name; _age = age; } return self;}
完全自訂初始化:
// 初始化全部的執行個體變數
- (id)initWithName:(NSString *)name age:(NSInteger)age height:(CGFloat)height {
if (self = [super init]) {
_name = name; _age = age; _height = height; } return self;}
3.便利構造器方法
命名規範
1. 遍曆構造器方法一定是一個類方法,以加號開頭(+)
2. 傳回值類型一定是 id類型
3. 以類名小寫開頭 +With+執行個體變數名去掉底線且首字母大寫
注意:
4. 便利構造器又叫做Factory 方法,用於快捷快速地建立對象
5. 便利構造器方法的實質: 在方法內部把一個建立好的對象作為方法的傳回值
6. 便利構造器方法要和自訂初始化方法配套使用
樣本(對應上面列舉的兩種自訂初始化方法):
+ (id)personWithName:(NSString *)name age:(NSInteger)age {
// 將一個建立好的對象作為便利構造器方法的傳回值 return [[Person alloc] initWithName:name age:age];}
+ (id)personWithName:(NSString *)name age:(NSInteger)age hight:(CGFloat)hight {
return [[Person alloc] initWithName:name age:age height:hight];}@end
main
Person *p = [[Person alloc] init]; NSLog(@"%@ - %ld - %.2f", p.name, p.age, p.height);
//調用自訂初始化方法 Person *p1 = [[Person alloc] initWithName:@"吳邪"]; NSLog(@"%@ - %ld - %.2f", p1.name, p1.age, p1.height); Person *p2 = [[Person alloc] initWithName:@"花千骨" age:18]; NSLog(@"%@ - %ld - %.2f", p2.name, p2.age, p2.height);
// 調用完全初始化方法 Person *p3 = [[Person alloc] initWithName:@"白子畫" age:22 height:70]; NSLog(@"%@ - %ld - %.2f", p3.name, p3.age, p3.height); // 調用便利構造器方法 Person *p4 = [Person personWithName:@"殺阡陌"]; NSLog(@"%@ - %ld - %.2f", p4.name, p4.age, p4.height); Person *p5 = [Person personWithName:@"東方彧卿" age:15]; p5.name = @"糖寶"; p5.height = 0.3; NSLog(@"%@ - %ld - %.2f", p5.name, p5.age, p5.height); Person *p6 = [Person personWithName:@"單春秋" age:26 hight:1.65]; NSLog(@"%@ - %ld - %.2f", p6.name, p6.age, p6.height);
以下是完整的代碼:
Person.h
#import "Car.h"@interface Person : Car{ NSString *_name; NSInteger _age; CGFloat _height;}- (void)setName:(NSString *)name;- (NSString *)name;- (void)setAge:(NSInteger)age;- (NSInteger)age;
- (void)setHeight:(CGFloat)height;- (CGFloat)height;// 自訂初始化方法- (id)initWithName:(NSString *)name;- (id)initWithName:(NSString *)name age:(NSInteger)age;- (id)initWithName:(NSString *)name age:(NSInteger)age height:(CGFloat)height;// 寫一個遍曆構造器的類方法+ (id)personWithName:(NSString *)name;+ (id)personWithName:(NSString *)name age:(NSInteger)age;+ (id)personWithName:(NSString *)name age:(NSInteger)age hight:(CGFloat)hight;@end
#import "Person.h"@implementation Person- (void)setName:(NSString *)name { _name = name;}
- (NSString *)name { return _name;} - (void)setAge:(NSInteger)age { _age = age;}- (NSInteger)age { return _age;}- (void)setHeight:(CGFloat)height { _height = height;}- (CGFloat)height { return _height;}// 自訂初始化方法// 無論是哪種初始化方法,都要首先判斷下父類有沒有有初始化成功- (id)initWithName:(NSString *)name { if (self = [super init]) { _name = name; } return self;}- (id)initWithName:(NSString *)name age:(NSInteger)age { if (self = [super init]) { _name = name; _age = age; } return self;}- (id)initWithName:(NSString *)name age:(NSInteger)age height:(CGFloat)height { if (self = [super init]) {
_name = name; _age = age; _height = height; } return self;}
+ (id)personWithName:(NSString *)name {
// 便利構造器的實質就是在便利構造器方法中建立一個對象,然後把這個對象作為傳回值返回 // 遍曆構造器方法一定要和自訂初始化方法配合使用 Person *p = [[Person alloc] initWithName:name]; return p;}+ (id)personWithName:(NSString *)name age:(NSInteger)age { // 將一個建立好的對象作為便利構造器方法的傳回值 return [[Person alloc] initWithName:name age:age];}+ (id)personWithName:(NSString *)name age:(NSInteger)age hight:(CGFloat)hight {
return [[Person alloc] initWithName:name age:age height:hight];}@end