【IOS 开发学习总结-OC-17】★★objective-c面向对象之——多态

示例程序

示例程序:
FKBase.h

#import <Foundation/Foundation.h>

@interface FKBase : NSObject
- (void) base;
- (void) test;
@end

FKBase.m

#import <Foundation/Foundation.h>
#import "FKBase.h"

@implementation FKBase
- (void) base
{
    NSLog(@"父类的普通base方法");
}
- (void) test
{
    NSLog(@"父类的将被覆盖的test方法");
}
@end

FKSubclass.h

#import <Foundation/Foundation.h>
#import "FKBase.h"

@interface FKSubclass : FKBase
- (void) sub;
@end

FKSubclass.m

#import <Foundation/Foundation.h>
#import "FKSubclass.h"

@implementation FKSubclass
- (void) test
{
    NSLog(@"子类的覆盖父类的test方法");
}
- (void) sub
{
    NSLog(@"子类的sub方法");
}
@end

FKSubclassTest.m

#import <Foundation/Foundation.h>
#import "FKSubclass.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 下面编译时类型和运行时类型完全一样,因此不存在多态
        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 方法,但由于编译类型为 FKBase,因此,编译时无法调用 sub 方法
//      [ploymophicBc sub];
        // 可将任何类型的指针变量赋值给id类型的变量
        id dyna = ploymophicBc;
        [dyna sub];
    }
}

上面程序的 main() 函数中第三个指针变量 ploymophicBc 比较特殊,它编译时是 FKBase, 而运行时类型是 FKSubclass。当调用该指针变量的 test方法时(FKBase类中定义了该方法,子类FKSubclass重写了父类的该方法),实际执行FKSubclass类中覆盖后的 test方法,这就可能出现多态。

向上转型-upcasting

objective-c 允许把一个子类对象直接赋给一个父类指针变量——因为子类其实是一种特殊的父类,无须任何类型转换。也被称为向上转型,由系统自动完成。

多态

当把一个子类对象直接赋给父类指针变量,当运行时调用该指针变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征。这就可能出现这样的情况——相同类型的变量调用同一个方法时,呈现出多种不同的行为特征,这就是多态。

这里用一个比喻:假如要编写哺乳动物对象发声的代码,但哺乳动物的特定实例其发声方式会有很大不同,在你调用发声方法时,它会判断你处理的是什么类型的哺乳动物,然后调用响应的方法。如:狗是吠,猫会喵。

特别说明

指针变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。所以,在编写 OC 代码时,指针变量只能调用声明该变量时所用类中包含的方法,例如:NSObject*p =[[FKPerson alloc]init]; 代码定义了一个变量 p,则这个 p 只能调用 NSObject 类的方法,而不能调用FKPerson类里定义的方法。

id 类型

为了解决编译时类型检查的问题。objective-c 提供了 id 类型,程序可以对任何对象或任何类型的指针变量赋值为 id 类型的变量,而且使用 id 类型的变量可以调用该变量实际所指对象的方法。比如上面示例程序中,

id dyna = ploymophicBc;
[dyna sub];

这样就可以调用FKSubclass类中定义的 sub 方法。

指针变量的强制类型转换

编写 OC 程序中,出了 id 类型的变量外,指针变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法——即使它实际所指对象确实包含该方法。
如果要让这个指针变量调用它运行时类型的方法,则必须强制类型转换成运行时类型,强制类型转换需要借助类型转换运算符——圆括号()
类型转换运算符的用法:(type*)variable, 可以将variable变量转换为一个 type 类型的变量。除此之外,类型转换运算符还可以将一个指针类型变量转换成其子类类型。

特别提示:这种强制类型转换只是改变了该指针变量编译时的类型,但该变量所指向对象的实际类型并不会发生任何改变。。如果开发者不加判断呢地进行转换,转换出来的变量在调用方法时就会引发错误。示例程序:
ConversionTest.m

#import <Foundation/Foundation.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指向同一个对象
        NSDate* date = (NSDate*)obj2;
        // 程序调用date的isEqualToDate:方法。
        // 由于date的编译时类型是NSDate,因此编译时没有任何问题
        // 由于date实际指向的对象是NSString,因此程序执行时就会引发错误,
        NSLog(@"%d" , [date isEqualToDate:[NSDate date]]);
    }
}

编译运行结果:

2015-09-24 21:33:07.638 923[9401:494443] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString isEqualToDate:]: unrecognized selector sent to instance 0x3f048'

这段程序,编译没有问题,可以通过。但是运行会出现错误。因为 date 指针变量编译时虽是 NSDate, 但实际上指向的对象依然是 NSString。
注意:
当把子类对象赋给父类指针变量时,称为向上转型,这种转型总是可以成功的(自动完成),这从侧面证实了子类是一种特殊的父类。但把一个父类对象赋给子类指针变量时,就需要进行强制类型转换,而且还很可能在运行时产生错误。

判断指针变量的实际类型

为确保程序正常运行,一般建议在执行强制转换之前先判断该对象是否为该类,或其子类的实例。判断指针变量是否为某个类,或其子类的实例,可用以下几个方法:
1.-(BOOL)isKindOfClass:(Class)aClass
作用:判定该对象(receiver)是否为aClass或其子类的实例。
2.-(BOOL)isMemberOfClass:(Class)aClass
作用:判定该对象(receiver)是否为aClass的实例
3.+(BOOL)isSubclassOfClass:(Class)aClass
作用:判定是否为aClass的子类。只有类方法
示例代码:

#import <Foundation/Foundation.h>
//#import "FKSubclass.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 声明hello时使用NSObject类,则hello的编译时类型是NSObject,
        // NSObject是所有类的父类 , 但hello变量的实际类型是NSString
        NSObject* hello = @"Hello";
        // 使用isKindOfClass判断该变量所指的对象是否为指定类、及其子类的实例
        NSLog(@"字符串是否是NSObject类的实例:%d"
            , ([hello isKindOfClass:[NSObject class]]));
        // 返回true。
        NSLog(@"字符串是否是NSString类的实例:%d"
            , ([hello isKindOfClass:[NSString class]]));
        // 返回false。
        NSLog(@"字符串是否是NSDate类的实例:%d"
            , ([hello isKindOfClass:[NSDate class]]));
        NSString* a = @"Hello";
        // 返回false
        NSLog(@"a是否是NSDate类的实例:%d"
            , ([a isKindOfClass:[NSDate class]]));
    }
}

编译运行结果:

2015-09-24 21:54:31.471 923[9707:510357] 字符串是否是NSObject类的实例:1
2015-09-24 21:54:31.476 923[9707:510357] 字符串是否是NSString类的实例:1
2015-09-24 21:54:31.477 923[9707:510357] 字符串是否是NSDate类的实例:0
2015-09-24 21:54:31.477 923[9707:510357] a是否是NSDate类的实例:0

上面的代码可以看出:-(BOOL)isKindOfClass:(Class)aClass 方法主要作用:在执行强制类型转换前,首先判断前一个对象是否是该类的实例,是否可以成功转换,从而保证代码更加健壮。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值