`
sarin
  • 浏览: 1757003 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
E3b14d1f-4cc5-37dd-b820-b6af951740bc
Spring数据库访问系列...
浏览量:173669
C2083dc5-6474-39e2-993e-263652d27795
Android学习笔记
浏览量:368140
5f40a095-b33c-3e8e-8891-606fcf3b8d27
iBatis开发详解
浏览量:189272
B272a31d-e7bd-3eff-8cc4-c0624ee75fee
Objective-C学习...
浏览量:99846
社区版块
存档分类
最新评论

Objective-C学习笔记11:多态和动态类型

阅读更多
    接上文
    多态是一个典型的面向对象概念。Objective-C中的多态可以使得来自不同类的对象定义同名方法。
    我们来看下面的示例,分数类Fraction我们已经多次涉及到了,我们来回顾一下我们分数类的定义:
#import <Foundation/Foundation.h>

@interface Fraction : NSObject

@property int numerator,denominator;

-(void) print;
-(double) convertToNum;
-(void) setTo:(int) n over: (int) d;
-(Fraction *)add:(Fraction *)f;
-(void) reduce;

@end

    方法convertToNum是将分数转化为小数形式,setTo...over方法是设置分数的,reduce是约分方法,这是对于分数处理所特有的方法和命名。那么print和add方法则是两个比较普通的方法。那么我们定义其它类也可以包含print和add方法来实现其它效果。
    比如数学概念中有复数的概念,复数由实部和虚部构成,那么我们创建复数类Complex的定义,代码可以是这样的:
#import <Foundation/Foundation.h>

@interface Complex : NSObject

@property double real,imaginary;

-(void) print;
-(void) setReal:(double)r andImaginary:(double)i;
-(Complex *) add:(Complex *) c;
@end

    复数类的定义比较简单,仅仅是为了说明问题,定义了实部和虚部的两个double类型变量, print和add方法的命名是和分数类Fraction一致的,只是设置复数的方法命名和setTo...over不同,那么它的实现如下:
#import "Complex.h"

@implementation Complex

@synthesize real,imaginary;

-(void) print
{
    NSLog(@"%g + %gi",real,imaginary);
}

-(void) setReal:(double)r andImaginary:(double)i
{
    real=r;
    imaginary=i;
}

-(Complex *) add:(Complex *)c
{
    Complex *result=[Complex new];
    result.real=real+c.real;
    result.imaginary=imaginary+c.imaginary;
    return result;
}
@end

    对定义的三个方法进行实现,代码很简单,意思就不过多解释了,那么来看主函数:
#import "Fraction.h"
#import "Complex.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        Fraction *fractionA = [Fraction new];
        Fraction *fractionB = [Fraction new];
        Fraction *resultFraction;
        
        [fractionA setTo:1 over:3];
        [fractionB setTo:2 over:5];
        
        [fractionA print];NSLog(@"  +");[fractionB print];
        NSLog(@"---");
        resultFraction=[fractionA add:fractionB];
        [resultFraction print];
        
        Complex *complexA=[Complex new];
        Complex *complexB=[Complex new];
        Complex *resultComplex;
        
        [complexA setReal:10.0 andImaginary:2.3];
        [complexB setReal:-5.0 andImaginary:3.6];
        
        [complexA print];NSLog(@"        +");[complexB print];
        NSLog(@"---------");
        resultComplex=[complexA add:complexB];
        [resultComplex print];
    }
    return 0;
}

    我们分别定义了两个分数和两个复数,分别对其使用add方法进行相加,然后使用print方法进行打印。编译运行这个程序,我们得到如下的结果:




    那么可以看到,我们分别得到了正确的结果。在Objective-C中不同的类使用相同的方法名的能力称为多态。那么我们在开发一组类时,它们可以使用相同的方法名而每个类又有不同的实现代码。那么多态还允许你创建心类时,这些新类也使用相同的方法名。
    我们继续来深入说明。在Objective-C中有一种数据类型比较特殊,就是id类型。id表示一种通用的对象类型,也就是说id可以用来存储任何类的对象。那么我们使用这种方式来存储不同类型的对象时,在程序运行期间,这种数据类型的优势就体现出来了,我们修改上面的主函数,使用id类型来看一下:
#import "Fraction.h"
#import "Complex.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        id result;
        Fraction *fractionA = [Fraction new];
        Fraction *fractionB = [Fraction new];
        
        [fractionA setTo:1 over:3];
        [fractionB setTo:2 over:5];
        
        [fractionA print];NSLog(@"  +");[fractionB print];
        NSLog(@"---");
        result=[fractionA add:fractionB];
        [result print];
        
        Complex *complexA=[Complex new];
        Complex *complexB=[Complex new];
        
        [complexA setReal:10.0 andImaginary:2.3];
        [complexB setReal:-5.0 andImaginary:3.6];
        
        [complexA print];NSLog(@"        +");[complexB print];
        NSLog(@"---------");
        result=[complexA add:complexB];
        [result print];
    }
    return 0;
}

    和之前的程序相比,这里我们定义了id类型的result变量,并且删除了分数类和复数类各自的result对象,统一使用id类型的变量作为替代,那么运行程序,我们先来看一下结果:




    可以看到,程序依然正确运行,我们得到了想要的结果。这里我们将result定义成id类型,那么它就可以表示任何类型的对象,请特别注意,这个声明不是指针类型,不加星号。那么在做分数运算时,result首先被赋值为Fraction类型,这是Objective-C程序运行时的动态类型和动态绑定机制,这种动态机制就是在运行时先判断对象所属的类,然后调用对应的方法,而在编译时是不检查的。依照这个理论,那么在执行复数类的print时,result对象已经赋值为了复数类型的结果,那么自然打印复数。
    想想我们之前的矩形和正方形的例子,如果加入了draw方法,那么对于矩形和正方形的实现肯定是不同的。而若在程序中我们可能还会有圆形,三角形等图案,那么若定义了id类型的shape变量,它就可以表示任何图形了,在运行时动态确定结果,动态选择要执行的所属类的draw方法就可以得到不同的图案了,这就是动态类型和动态绑定的实现。
    看下面的代码段:
Fraction *f = [Fraction new];
[f setReal:10.0 addImaginary:3.6];

    这里我们定义了分数类型的对象f,对f调用了复数类的setReal...addImaginary方法,这显然是无法通过编译的,因为编译器知道f的类型,Fraction类中没有setReal...addImaginary方法的定义或者继承这个方法,那么在编译阶段就会报出错误。这很好理解,接着来看:
id data=[Fraction new];
[data setReal:10.0 addImaginary:3.6];

    这样的代码在编译时不会发生错误,因为data可以表示任意类型,编译器在编译时不会检查对象的具体类型。而当程序运行时,显然会出现问题,因为data是Fraction类型的,而Fraction类型没有定义/继承setReal...addImaginary方法。那么就会出错,这就是运行时报错。
    以上两种情况就说明了编译时检查和运行时检查机制。也可以看出如果滥用id类型会埋下的潜在隐患。因为id类型可以看做是动态类型,即编译器不会检查对象的类型,而是在运行时动态确定的,如果对象和要执行的方法不匹配,就会出错。而将变量定义为特定类型的对象时,就是静态类型,静态类型是会接受编译器检查的。很显然静态类型可以在编译阶段就指出错误而且提高程序的可读性。
    同时,使用动态类型调用方法时,如果在多个类中定义有同名方法,那么这个方法的各个参数类型和返回值类型要一致。
    引入动态类型后,问题随之而来。比如说在运行时如何判断一个对象是某种类型,一个对象是否支持某方法,一个对象是否是某类或是其子类的成员。幸运的是,NSObject类为我们提供了一些方法用于处理这类问题,要使用这些方法,我们需要一些铺垫,首先介绍class方法,看下面的示例代码:
#import "Square.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Rectangle *rect=[Rectangle new];
        Class cls=[rect class];
        NSLog(@"%@",cls);
        NSLog(@"%@",[Square class]);
    }
    return 0;
}

    首先我们创建了一个Rectangle对象rect,之后对rect调用class方法返回一个Class类型的对象,然后打印这个cls对象的内容。而下面我们对Square类直接调用class方法,也能返回Class对象,那么编译运行,我们得到如下结果:




    我们可以看到通过class方法我们可以获取当前对象的类型,使用类名来调用通常也是为了返回这个类型的对象,他们都是Class类型的对象。那么我们知道class方法的用处后就可以用于判断了,比如下面的代码:
#import "Square.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Rectangle *rect=[Rectangle new];
        Square *square=[Square new];
        Rectangle *rectangle=[Rectangle new];
        
        if([rect class]==[rectangle class]){
            NSLog(@"rect & rectangle is the same class");
        }
        if([square class]==[rect class]){
            NSLog(@"square & rect is the same class");
        }
    }
    return 0;
}

    那么我们很容易就能与基础该程序的效果,请看下图所示:




    下面介绍@selector指令,该指令用于一个方法名,比如@selector (alloc),它的返回值是SEL类型,这个指令所获取的SEL类型结果也是用于动态类型的一些方法的,这里我们仅仅有个感性认识即可,那么看下面的代码:
#import "Square.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        SEL sel=@selector(setSide);
        
        NSLog(@"SEL=%@",NSStringFromSelector(sel));
    }
    return 0;
}

    这里我们需要将SEL结果转换成NSString类型,那么编译运行,我们会得到如下结果:




    那么在后面处理动态类型的方法中,我们会看到@selector指令的具体用途。参考下面的程序:
#import "Square.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Square *square=[Square new];
        
        NSLog(@"%@",[square isMemberOfClass:[Square class]]?@"YES":@"NO");
        NSLog(@"%@",[square isMemberOfClass:[Rectangle class]]?@"YES":@"NO");
        NSLog(@"%@",[square isMemberOfClass:[NSObject class]]?@"YES":@"NO");
        
        NSLog(@"%@",[square isKindOfClass:[Square class]]?@"YES":@"NO");
        NSLog(@"%@",[square isKindOfClass:[Rectangle class]]?@"YES":@"NO");
        NSLog(@"%@",[square isKindOfClass:[NSObject class]]?@"YES":@"NO");
        
        NSLog(@"%@",[square respondsToSelector:@selector(setSide:)]?@"YES":@"NO");
        NSLog(@"%@",[square respondsToSelector:@selector(setWidth:andHeight:)]?@"YES":@"NO");
        NSLog(@"%@",[Square respondsToSelector:@selector(alloc)]?@"YES":@"NO");
        
        NSLog(@"%@",[Rectangle instanceRespondToSelector:@selector(setSide:)]?@"YES":@"NO");
        NSLog(@"%@",[Square instanceRespondToSelector:@selector(setSide:)]?@"YES":@"NO");
        
        NSLog(@"%@",[Square isSubclassOfClass:[Rectangle class]]?@"YES":@"NO");
    }
    return 0;
}

    我们先来看运行结果:




    我们逐行来解释一下这个程序,isMemberOfClass : class方法用于判断对象是不是class的成员,返回值为BOOL类型,那么这里我们使用三目运算符来获取BOOL表达式的结果,NSString也是id类型的一种,那么我们使用%@来表示。很显然square对象是Square类的一个成员,而不是Rectangle类和NSObject类的成员。
    isKindOfClass: class方法用于判断对象是不是class类或其子类的成员。显然square对象是Square类的成员,是Rectangle类和NSObject类的子类成员。
    respondsToSelector : selector方法用于判断对象是否可以响应@selector提供的方法,那么直接看上面的结果即可,不用多说了。instanceRespondToSelector : selector方法用于判断指定的实例能否响应@selector提供的方法,那么直接看结果就行了。
    这些方法我们仅仅有个感性认识即可,等后续内容使用到了再做深入了解。
    我们知道运用动态类型时在程序执行过程中可能会出现异常,那么如何来处理异常呢?Objective-C中引入@try块儿来处理异常,异常处理在@catch块中进行。我们来看下面的代码:
#import "Fraction.h"
#import "Complex.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        id result;
        
        id fractionA = [Fraction new];
        
        [fractionA setTo:1 over:3];

        id complexA=[Complex new];
        
        [complexA setReal:10.0 andImaginary:2.3];
        
        @try {
            result=[fractionA add:complexA];
        }
        @catch (NSException *exception) {
            NSLog(@"Caught: %@%@",exception.name,exception.reason);
        }
        @finally {
            NSLog(@"In finally");
        }

    }
    return 0;
}

    代码使用的是我们分数和复数的项目,那么为了引发异常,我们将分数类和复数类都使用id类型来表示,显然编译器不会报错,然后我们对两个不同的对象使用add方法,这显然是不可以的,那么将代码放入@try块中,在@catch块中进行异常处理,这里我们仅仅是打印一些异常信息。在@finally块中编写不论异常是否发生,都要执行的代码,那么编译运行后,我们得到如下结果:




    之后我们修改代码,不会出现异常时是这样的情况:
#import "Fraction.h"
#import "Complex.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        id result;
        
        id fractionA = [Fraction new];
        
        [fractionA setTo:1 over:3];

        id fractionB=[Fraction new];
        
        [fractionB setTo:10 over:18];
        
        @try {
            result=[fractionA add:fractionB];
        }
        @catch (NSException *exception) {
            NSLog(@"Caught: %@%@",exception.name,exception.reason);
        }
        @finally {
            NSLog(@"In finally");
        }

    }
    return 0;
}

    此时代码不会出错了,那么编译运行就得到了如下结果:




    可以看到@finally块中的语句无论异常是否发生,都会执行。所以它常用来进行连接的关闭等操作。如果想主动抛异常,还可以使用@throw指令来进行,这就是更高级内容了,暂时先不涉及。因为异常处理的开销很大,特别是移动设备对资源占用很敏感,所以非必要,请不要使用异常处理。
    接下文
  • 大小: 79.3 KB
  • 大小: 109.7 KB
  • 大小: 56.5 KB
  • 大小: 66 KB
  • 大小: 50.5 KB
  • 大小: 150.2 KB
  • 大小: 82.4 KB
  • 大小: 59.2 KB
2
1
分享到:
评论

相关推荐

    PHP语言基础知识详解及常见功能应用.docx

    本文详细介绍了PHP的基本语法、变量类型、运算符号以及文件上传和发邮件功能的实现方法,适合初学者了解和掌握PHP的基础知识。

    公司金融课程期末考试题目

    公司金融整理的word文档

    适用于 Python 应用程序的 Prometheus 检测库.zip

    Prometheus Python客户端Prometheus的官方 Python 客户端。安装pip install prometheus-client这个包可以在PyPI上找到。文档文档可在https://prometheus.github.io/client_python上找到。链接发布发布页面显示项目的历史记录并充当变更日志。吡啶甲酸

    DFC力控系统维护及使用

    DFC力控系统维护及使用

    Spring Data的书籍项目,含多数据库相关内容.zip

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。

    2019-2023GESP,CSP,NOIP真题.zip

    2019-2023GESP,CSP,NOIP真题.zip

    基于 Gin + Element 实现的春联生成平台

    博文链接 https://blog.csdn.net/weixin_47560078/article/details/127712877?spm=1001.2014.3001.5502

    zetero7实测可用插件

    包含: 1、jasminum茉莉花 2、zotero-style 3、greenfrog 4、zotero-reference 5、translate-for-zotero 用法参考:https://zhuanlan.zhihu.com/p/674602898

    简单的 WSN 动画制作器 matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。 替换数据可以直接使用,注释清楚,适合新手

    毕业设计&课设_仿知乎社区问答类 App 项目:吉林大学毕业设计,含代码、截图及相关说明.zip

    1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。

    python技巧学习.zip

    python技巧学习.zip

    2023 年“泰迪杯”数据分析技能赛 A 题 档案数字化加工流程数据分析

    2023 年“泰迪杯”数据分析技能赛 A 题 档案数字化加工流程数据分析 完整代码

    life-expectancy-table.json

    echarts 折线图数据源文件

    此扩展现在由 Microsoft fork 维护 .zip

    Visual Studio Code 的 Python 扩展Visual Studio Code 扩展对Python 语言提供了丰富的支持(针对所有积极支持的 Python 版本),为扩展提供了访问点,以无缝集成并提供对 IntelliSense(Pylance)、调试(Python 调试器)、格式化、linting、代码导航、重构、变量资源管理器、测试资源管理器等的支持!支持vscode.devPython 扩展在vscode.dev (包括github.dev )上运行时确实提供了一些支持。这包括编辑器中打开文件的部分 IntelliSense。已安装的扩展Python 扩展将默认自动安装以下扩展,以在 VS Code 中提供最佳的 Python 开发体验Pylance - 提供高性能 Python 语言支持Python 调试器- 使用 debugpy 提供无缝调试体验这些扩展是可选依赖项,这意味着如果无法安装,Python 扩展仍将保持完全功能。可以禁用或卸载这些扩展中的任何一个或全部,但会牺牲一些功能。通过市场安装的扩展受市场使用条款的约束。可

    Centos6.x通过RPM包升级OpenSSH9.7最新版 升级有风险,前务必做好快照,以免升级后出现异常影响业务

    Centos6.x通过RPM包升级OpenSSH9.7最新版 升级有风险,前务必做好快照,以免升级后出现异常影响业务

    5 总体设计.pptx

    5 总体设计.pptx

    用于执行 RPA 的 Python 包.zip

    Python 版 RPAv1.50  • 使用案例•  API  参考 • 关于 和制作人员 • 试用云 •  PyCon 视频 •  Telegram 聊天 • 中文 •  हिन्दी  • 西班牙语 • 法语 •  বাংলা  •  Русский  • 葡萄牙语 • 印尼语 • 德语 • 更多..要为 RPA(机器人流程自动化)安装此 Python 包 -pip install rpa要在 Jupyter 笔记本、Python 脚本或交互式 shell 中使用它 -import rpa as r有关操作系统和可选可视化自动化模式的说明 -️‍ Windows -如果视觉自动化有故障,请尝试将显示缩放级别设置为推荐的 % 或 100% macOS -由于安全性更加严格,请手动安装 PHP并查看PhantomJS和Java 弹出窗口的解决方案 Linux -视觉自动化模式需要在 Linux 上进行特殊设置,请参阅如何安装 OpenCV 和 Tesseract Raspberry Pi - 使用此设置指南在 Raspberry Pies(低成本自

    原生js识别手机端或电脑端访问代码.zip

    原生js识别手机端或电脑端访问代码.zip

    极速浏览器(超快速运行)

    浏览器

    基于SpringBoot和Vue的旅游可视化系统设计与实现

    内容概要:本文介绍了基于Spring Boot和Vue开发的旅游可视化系统的设计与实现。该系统集成了用户管理、景点信息、路线规划、酒店预订等功能,通过智能算法根据用户偏好推荐景点和路线,提供旅游攻略和管理员后台,支持B/S架构,使用Java语言和MySQL数据库,提高了系统的扩展性和维护性。 适合人群:具有一定编程基础的技术人员,特别是熟悉Spring Boot和Vue框架的研发人员。 使用场景及目标:适用于旅游行业,为企业提供一个高效的旅游推荐平台,帮助用户快速找到合适的旅游信息和推荐路线,提升用户旅游体验。系统的智能化设计能够满足用户多样化的需求,提高旅游企业的客户满意度和市场竞争力。 其他说明:系统采用现代化的前后端分离架构,具备良好的可扩展性和维护性,适合在旅游行业中推广应用。开发过程中需要注意系统的安全性、稳定性和用户体验。

Global site tag (gtag.js) - Google Analytics