前言
oc指针类型的变量有两个:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明该变量时使用的类型决定,运行时的类型由实际赋给该变量的对象决定,如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
一、多态是什么?
多态一般都要跟继承结合起来说,其本质是子类通过覆盖或重载父类的方法,来使得对同一类对象同一方法的调用产生不同的结果。这里需要辨析的地方在:同一类对象指的是继承层级再上一层的对象,更加泛化。
简单来说,就是父类指针指向子类对象。
二、多态的原理
1.动态绑定
- 动态绑定
- 动态类型能使程序直到执行时才确定对象的真实类型
- 动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法
2.多态条件
- 有继承关系
- 子类重写父类方法
- 父类指针指向子类对象
3.程序中的表现
父类指针指向不同子类对象的时候,通过父类指针调用被重写的方法的时候,会执行该指针指向的那个对象的方法
4.多态的优点
多态的主要好处是简化了编程接口,他允许在类和类之间重用一些习惯性的命名,而不用为每一个新加的函数命名一个新名字,这样,编程接口就是一些抽象的行为集合,从而和实现接口的类区分开来
多态也使得代码可以分散在不同的对象中而不用试图在一个函数中考虑到所有可能的对象,这样使得代码扩展性和复用性特别好,当一个新的情景出现时,只需要增加一个新的类和新的同名方法
三、注意事项
父类能调用子类的方法吗?
1、同名方法一定可以调用
2、不同名方法:
1)父类访问子类特有的方法,需要强制类型转换
例如:父类Animal调用子类Dog特有的方法
Animal* ani = [[dog alloc] init];
[(Dog *)ani run];
2)如果子类并没有这个方法,即使强制类型转换也不能调用。
#import <Foundation/ Foundation.h>
#import "FKSubclass.h"
int main(int argc, char * argv[] ) {
@autoreleasepool l
//下面编译时类型和运行时类型完全一样,因此不存在多态
FKBase*bc = [[FKBase alloc] init] ;
//下面两次调用将执行BaseClass的方法
[bc base] ;
[bc test];
//下面编译时类型和运行时类型完全一样, 因此不存在多态
FKSubclass★sc = [FKSubclass alloc] init];
//下面调用将执行从父类继承到的base方法
[sc base];
//下面调用将执行子类重写的test方法
[sc test] ;
//下面调用将执行子类定义的sub方法
[sc sub] ;
//下面编译时类型和运行时类型不一样,多态发生
FKBase* ploymophicBc = [[FKSubclass alloc] init];
//下面调用将执行从父类继承到的base方法
[ploymophicBc base];
//下面调用将执行子类重写的test方法
[ploymophicBc test];
//因为ploymophicBc的编译类型是FKBase
// FKBase类没有提供sub方法,所以下面代码编译时会出现错误
[ploymophicBc sub];//可将任何类型的指针变量赋值 给id类型的变量
id dyna = ploymophicBc;
[dyna sub];
}
}
上面程序的 main0函数中显式创建了4个指针类型的变量, 对于前两个指针变量 bc和sc,它们编译时类型和运行时类型完全相同,因此,调用它们的方法非常正常,完全没有任何问题。
第三个指针变量ploymophicBc 则比较特殊,它的编译时类型是FKBase, 而运行时类型为FKSubclass, 当调用该指针变量的test 方法时(FKBase 类中定义了该方法,子类FKSubclass重写了父类的该方法),实际执行FKSubclass类中覆盖后的test方法,这就可能出现多态。第四个变量则使用id类型声明变量。
子类是特殊的父类,OC允许把一个子类对象直接赋给一个父类的指针变量,无需类型转换。
当一个子类对象直接赋给父类指针时,例如FKBase * p= [[FKSubclass alloc]init];这个p变量在编译时类型还是FKBase,在运行时变成了FKSubclass,当运行时调用该指针变量的方法时,其方法总是表现子类方法也就是调用子类方法,这就可能出现相同类型的变量调用同一个方法出现不同的行为特征,这就是多态
上面main()函数中注释了ploymophicBc.sub(),这行代码会在编译时引发错误。虽然 ploymophicBc变量实际指向的对象确实包含sub()方法 (例如可以通过performSeletor执行该方法),但因为它的编译类型为FKBase,因此编译时无法调用sub()方法。
注意:
指针变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Objective-C代码时,指针变量只能调用声明该变量时所用类中包含的方法。例如,我们通过NSObject* p= [[FKPerson alloc ]init];代码定义一个变量p,则这个p只能调用NSObject类的方法,而不能 调用FKPerson 类里定义的方法。
四、 指针变量的强制类型转换
1.类型转换运算符是圆括号(),用法:
(type *)variable
将variable转换成一个type类型的变量
2.这种强制转换只是改变了指针变量的编译时类型,但是该变量所指对象的实际类型不会发生改变
#import <Foundation/Foundation.h>
#import "FKSubclass.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject * obj = @"Hello";
//由于obj变量所指的对象是NSString对象,所以运行时也可以通过。
NSString * objStr = (NSString *)obj;
NSLog(@"%@", objStr);
// 定义一个obj2变量,编译类型为NSObject,实际类型为NSString
NSObject * obj2 = @"iOS";
//尝试将obj2强制转换为NSDate,这行代码没有问题
//但程序只是定义了一个NSDate类型的指针,该指针与obj2指向同一个对象
NSData * date = (NSData *) obj2;
// 程序调用date的isEqualToDate:方法
//由于date的编译类型是NSDate,因此编译时没有任何问题
//由于date实际指向的对象时NSString,因此程序执行时就会引发错误
NSLog(@"%d", [date isEqualToDate:[NSDate date]]);
}
}
当把子类赋给父类对象时,被称为向上转型,这种转型总是可以成功的,这也从另一个侧面证实了子类是一种特殊的父类。这种转型只是表明这个指针变量的编译类型是父类,但实际执行它的方法时,依然是子类对象的行为方式
总结
Objective-C语言是面向对象的高级编程语言,因此,它具有面向对象编程所具有的一些特性,即:封装性、继承性和多态性,今天学习了一下Objective-C中的多态性。