OC类与对象(下)

news2025/2/22 6:53:14

OC类与对象(下)

文章目录

  • OC类与对象(下)
    • 不是包装类的
    • NSValue 和NSNumber
    • 处理对象
      • 打印对象和description方法
      • == 和 isEqual方法
    • 类别
      • 类别语法部分
      • 利用类别进行模块化设计
      • 使用类别来调用私有方法
    • 类的扩展
    • 协议与委托
      • 规范,协议与接口
      • 使用类别实现非正式协议
      • 正式协议的定义
      • 遵守协议
      • 正式与非正式的差异
    • 协议与委托

不是包装类的

  • NSInterger: 大致相当于long型整数
  • NSUInterger: 大致相当于 unsigned long型整数
  • CGFLoat:在64位平台上相当于double

它们仍然是一个基本类型。

NSValue 和NSNumber

这两者都是一个包装类。NSValueNSNumber的一个父类,它可以用于包装单个short,int,long,指针,对象id等数据项,通过这些包装类,可以把它添加到NSArray等集合中

NSNumber是更具体的包装类,主要用于包装C语言的各种数值类型,下面主要介绍三类方法

  • +numberWithXxx:这个类方法直接将特定类型的值包装成NSNumber。
  • -initWithXxx:这个实例方法先要创建一个NSNumber对象,再用一个基本类型值来初始化NSnumber
  • -xxxValue:这个实例方法返回该NSNumber对象包装的基本类型的值。

下面给出一段代码,下面是用不同的方法来对基本类型进行一个包装

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSNumber* num1 = [NSNumber numberWithInt:32];
        NSNumber* num2 = [NSNumber numberWithDouble:3.14];
        NSLog(@"%d", [num1 intValue]);
        NSLog(@"%g", [num2 doubleValue]);
        NSNumber* ch = [[NSNumber alloc] initWithChar:'A'];
        NSLog(@"%@", ch); // 用来输出
        NSLog(@"%c", [ch charValue]); //用来输出字符 
    }
    return 0;
}

处理对象

打印对象和description方法

OC中的对象都是NSObject子类的实例,都可以直接调用该类中定义的方法,这些方法提供了处理OC对象的通用方法。

我们先定义个类

接口部分

@interface FKPerson : NSObject {
    @private
    int _age;
    NSString* _name;
}
@property (nonatomic) int age;
@property (nonatomic) NSString* name;
- (id) initWithName: (NSString*) name andAge: (int) age;
@end

实现部分

@implementation FKPerson
@synthesize age = _age;
@synthesize name = _name;
- (id) initWithName:(NSString *)name andAge:(int)age {
    if (self = [super init]) {
        self.name = name;
        self.age = age;
    }
    return self;
}
@end

主函数部分

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKPerson* p1 = [[FKPerson alloc] initWithName:@"messi" andAge: 30];
        NSLog(@"%@", [p1 description]); //打印一个对象
    }
    return 0;
}

打印结果为:

请添加图片描述

这个结果会随着每一次运行而改变,这个description方法是NSObject类的一个实例方法,所有对象具有description方法。这个类提供的description方法总是返回**<FKPerson: 16进制的首地址>**,这个返回值并不能实现一个自我描述的功能,所以我们大部分时候按下面的格式重写如下格式的字符串:

<类名 [实例变量 1 = 值 1, 实例变量 2 = 值 2]>

下面我们将对这个description方法进行一个重写,让他可以描述自己的一些特性。

- (NSString*) description {
    return [NSString stringWithFormat:@"<FKPerson[_name = %@, _age = %d]>", self.name, self.age];
}

然后在运行我们的主函数

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKPerson* p1 = [[FKPerson alloc] initWithName:@"messi" andAge: 30];
        NSLog(@"%@", p1); //直接对对象进行一个打印
    }
    return 0;
}

这样我们就可以实现一个对于对象的一个打印

请添加图片描述

== 和 isEqual方法

我们用来判断两个变量是否相等时,如果两个变亮是数值类型的数值相等(并不要求数据类型严格),如果是指针类型的话,那么就是判断两者的内存值是否相同,当使用比较类型snag没有继承关洗的两个指针变量的时候,编译器会提示警告。下面程序示例使用==比较类型变量是否相等的结果。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSInteger it = 65;
        CGFloat it1 = 65.0;
        char ch = 'A';
        NSLog(@"%d", it == ch); //返回1
        NSLog(@"%d", it == it1); // 返回1
        NSString* str3 = @"hello"; //常量池
        NSString* str4 = @"hello";
        NSString* str1 = [NSString stringWithFormat:@"hello"];
        NSString* str2 = [NSString stringWithFormat:@"hello"];
        NSString* str5 = [NSString stringWithFormat:@"梅西"];
        NSString* str6 = [NSString stringWithFormat:@"梅西"];
        NSLog(@"%p %p %p %p", str1, str2, str3, str4);
        NSLog(@"%d", (str1 == str2)); // 返回1
        NSLog(@"%d", [str1 isEqual: str2]); // 返回1
        NSLog(@"%d", (str5 == str6)); // 返回0
        NSLog(@"%p %p", str5, str6);
    }
    return 0;
}

下面是他的一个打印结果

请添加图片描述

tips

这里我们发现了个事情就是我们在用stringWithFormat这个函数去写英文字符串的时候,发现它们的地址居然相同,这是因为在英文字符串较短的时候,OC会创建一种 NSTaggedPointerString的类型,这种类型虽然也是在堆区创建一块内存,但是会建立两个值相同的字符串,所以我们创建的str1, str2会指向同一块内存。

但是我们用stringWithFormat方法创建中文字符串或者是创建一个长英文字符串的时候,他还是正常在堆区开辟两块内存来保存相同的字符串,用不同的指针指向不同的内存块。(这时候创建的字符串为一个**_NSCFString类型)。而当我们用一个直接给字符串赋值的方法的话,OC会把他存储在一个常量池**中,然后所有值相同的字符串只会创建一次。

需要注意的是一个isEqual方法在NSObject类中与**==没有任何区别,所以我们在NSString**中间看到的是这个子类重写他的父类的方法,只要两个字符串所包含的字符序列相同,通过isEqual比较就要返回一个真

我们在大部分时候需要的isEuqal方法一般希望的是两个相同类型的对象中的关键标识符是相等的,才可以认为这两个对象相同。所以我们需要重写isEqual方法才可以实现我们的结果。

下面我们给出一个FKUser类,用下面的程序来测试它。

接口部分

@interface FKUser : NSObject
@property NSString* idstr;
@property NSString* name;
- (BOOL) isEqual: (id) object;
- (id) initWithId: (NSString*) idstr andName: (NSString*) name;
@end

实现部分

@implementation FKUser
@synthesize  idstr;
@synthesize name;
- (id) initWithId:(NSString *)idstr andName:(NSString *)name { // 重写初始化方法
    if (self = [super init]) {
        self.idstr = idstr;
        self.name = name;
    }
    return self;
}
- (BOOL) isEqual: (id) object { // 重写isEqual方法
    if (self == object) {
        return YES;
    } else if (object != nil && [object isMemberOfClass: FKUser.class]) {
        FKUser* target = (FKUser*) object;
        return [self.idstr isEqual: target.idstr];
    }
    return NO;
}
@end

主函数部分

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id preson = [[FKUser alloc] initWithId:@"10" andName: @"梅西" ];
        id p2 = [[FKUser alloc] initWithId:@"10" andName: @"球王"];
        NSLog(@"%d", [preson isEqual: p2]);
        NSLog(@"%@", [preson description]);
    }
    return 0;
}

输出结果如下:

请添加图片描述

这样我们就实现了一个自己重写的isEqual方法。

注意,我们重写的isEqual方法要满足下面的要求

请添加图片描述

类别

类别语法部分

OC的动态特征允许使用类别为现有的类添加方法,并且不需要创建子类,不需要访问原有类的源代码,通过使用类别即可动态地为现有的类添加新方法,而且可以将类定义模块化的分布到多个相关文件中。

类别同样是接口和实现部分组成,接口部分的语法格式如下:

@interface 已有类 (类别名)
...
@end

这个语法格式和定义类的语法存在如下差异:

  • 定义类时使用类名必须时该类中不存在的类,而定义类别时使用的类名必须是已有的类
  • 定义类别是必须用圆括号来包括类别名
  • 类别中通常只定义方法

实现部分

@implmentation 已有类 (类别名)
...
@end

下面我们给出类别的一个实现,接口部分

@interface NSNumber (fk)
- (NSNumber*) add: (double) num1;
- (NSNumber*) substract: (double) num2;
- (NSNumber*) multiply: (double) num2;
- (NSNumber*) divide: (double) num2;

@end

实现部分

@implementation NSNumber (fk)
- (NSNumber*) add: (double) num1 {
    return [NSNumber numberWithDouble: ([self doubleValue] + num1)];
}
- (NSNumber*) substract:(double)num2 {
    return  [NSNumber numberWithDouble: ([self doubleValue] - num2)];
}
- (NSNumber*) multiply:(double)num2 {
    return  [NSNumber numberWithDouble: ([self doubleValue] * num2)];
}
- (NSNumber*) divide:(double)num2 {
    return [NSNumber numberWithDouble: ([self doubleValue] / num2)];
}
@end

主函数部分

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSNumber* p1 = [NSNumber numberWithInt: 3];
        NSNumber* num1 = [p1 add: 2.4];
        NSLog(@"%@", num1);
        NSNumber* num2 = [p1 substract: 2.4];
        NSLog(@"%@", num2);
        NSNumber* num3 = [p1 divide: 2.4];
        NSLog(@"%@", num3);
    }
    return 0;
}

请添加图片描述

利用类别进行模块化设计

类的实现部分不能分布到多个.m文件中去,因此当某个类非常大时,会导致那个类实现所在的文件非常大,以至于维护起来非常困难。因此如果需要将一个较大的类分模块设计,使用类别是一个不错的选择。通过类别可以对类实现按模块分布到不同的*.m文件中,从而提高项目后期的可维护性。

使用类别来调用私有方法

在我们定义类的部分有提到,没有在接口部分定义而是在类的实现部分定义的方法相当于私有方法,通常不允许调用。但后面也说过OC实际上不存在真正的一个私有方法

我们定义一个类的接口部分

请添加图片描述

类的实现部分,这一块定义两个内容一个是在实现部分定义的,一个是在实现部分定义的方法

请添加图片描述

我们在主函数里面调用这两个方法

请添加图片描述

这时候我们调用这个方法,我们发现调用不到这个方法,为了让我们的方法可以在主函数中调用,所以我们需要进行增加一个类别。

@interface FKItem (fk)
- (double) calDiscount: (double) discount;
@end

类的扩展

扩展与类别类似,扩展相当于匿名类别,定义里扩展的语法格式如下

@interafce 已有类 (){ //没有类别名
  实例变量
}
//方法定义
@end

扩展和类别的语法类似,但是类的扩展用于临时对于类的莫哥接口进行一个临时扩展,类实现部分同时实现接口部分定义的方法和扩展中的。

tips:定义类的扩展的时候,可以额外增加实例变量,也可以用@property,@synathesize来合成我们的setter,getter方法,但是定义类的类别,但定义类的列表,则不允许额外定义实例变量。

然后我们就编写一下扩展部分的代码

接口部分

@interface FKCar : NSObject
@property (nonatomic, copy) NSString* brand;
@property (nonatomic, copy) NSString* model;
- (void) drive;
@end

扩展部分

@interface FKCar ()
@property (nonatomic, copy) NSString* color;
- (void) drive: (NSString*) owner;

@end

实现部分

@implementation FKCar
@synthesize brand;
@synthesize model;
@synthesize color;
- (void) drive {
    NSLog(@"%@汽车飞驰", self);
}
- (void) drive: (NSString *) owner {
    NSLog(@"%@开着%@汽车上飞驰", owner, self);
}
- (NSString*) description {
    return [NSString stringWithFormat:@"<FKCar[brand = %@, model = %@, color = %@]>", self.brand, self.model, self.color];
}
@end

在上面的程序中间,我们可以调用点语法来访问color属性,也可以调用drive方法。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKCar* c = [[FKCar alloc] init];
        c.color = @"black";
        c.model = @"e300L";
        c.brand = @"ben-z";
        [c drive];
        [c drive: @"krystal"];
    }
    return 0;
}

他的结果如下所示:
请添加图片描述

协议与委托

规范,协议与接口

类是一种具体实现体,而协议则是定义了一种规范,定义了某一批类所需要遵守的规范。协议不提供任何实现,它体现的是规范和实现分离的设计哲学。让规范和实现分离正是协议的好处,是一种松耦合的设计。

协议定义的是多个类共同的公共行为规范,协议里通常定义的是一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去实现

使用类别实现非正式协议

这正是类别的作用之一:当某个类实现NSObject的该类别时,就需要实现该类别下所有方法,这种基于NSObject定义的类别即可认为是非正式协议。

@interface NSObject (Eatable)
- (void) taste;
@end

这里我们给NSObject提供了一个类别,这个类别可以理解为一个声非正式的一个协议,因为剩余的所有子类都会自动继承这个方法,我们可以根据我们的子类是否需要来决定是否要实现这个方法。

然后我们给他派生一个子类FKApple,(接口部分过于简单就不给出了)。

#import "FKApple.h"
#import "NSObject+Eatable.h"
@implementation FKApple
- (void) taste {
    NSLog(@"apple is tasty");
}
@end

这时候我们在主函数中调用taste方法,这样我们就可以理解为他遵守了这个(Eatable)协议。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKApple* a = [[FKApple alloc] init];
        [a taste];
    }
    return 0;
}

下面是代码的结果。

请添加图片描述

tips:我们对于非正式协议的类而言,OC中并不强制要求我们要实现该协议中的所有方法,也就是我们的FKApple类可以不实现该方法,但如果FKApple类不实现该方法的话,我们的程序就会报错。

请添加图片描述

正式协议的定义

和定义类不同,我们不用@interface这类关键字而是用@protocol关键字,基本语法如下

@protocol 协议名 <父协议1, 父协议2> {
  //方法的定义。
}

上面语法的详细说明如下

  • 协议名和类名采用相同的命名规则(遵守我们的一个可读性的规范)

  • 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类

  • 协议中定义的方法只有方法签名,但没有方法实现,协议中可以是类方法也可以是实例方法。

  • 协议中的所有方法都具有公开的访问权限

下面定义两个协议。

FKOutput

@protocol FKOutput
- (void) output;
- (void) addDate (String mag);
@end

FKPrintable

@protocol FKPrintable
- (NSDate*) getProdouceTime;
@end

上面我们用一个新的协议,该协议同时继承两个上面两个协议。

FKProdutable

@protocol FKProdutable <FKOutput, FKPrintable>
- (NSString*) printColor;
@end

遵守协议

下面我们为了这个FKProdutable协议提供一个实现类:FKPrinter

请添加图片描述

这个类实现了我们的两个协议的要求。

如果我们没有实现协议中规定的函数那么我们就会报错。

请添加图片描述

主函数部分我们定义了两个通过协议定义的两个变量。

NSObject<协议名> * 变量

id<协议名> * 变量

这两个编译类型仅仅只是所遵循的协议类型,因此只能调用该协议中所定义的方法。

正式与非正式的差异

1、非正式协议通过为NSObject创建类别来实现,而正式协议直接使用@protocol创建;

2、遵守非正式协议通过继承带特定类别的NSObject来实现,而遵守正式协议则有专门的OC语法来实现;

3、遵守非正式协议不要求实现协议中定义的所有方法;而遵守正式协议则必须实现协议中定义的所方法

在OC中为了增加灵活性,新增了两个关键字。

@optional:实现类可以选择是否实现这些方法

@require:位于该关键字之后、@optional或@end之前声明的方法是必需的,实现类必需实现这些方法。

协议与委托

协议体现的是一种规范,定义协议的类可以把协议定义的方法委托给实现协议的类,这样可以让类定义具有更好的通用性,因为具体的动作将由该协议的实现类去完成。无论是基于Mac的Cococa应用开发还是iOS开发,各种应用程序大量依赖委托这个概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1656075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BLIP和BLIP2 论文讲解

文章目录 BLIPIntroductionMethod模型架构预训练目标字幕和过滤&#xff08;Capfilt&#xff09; BLIP2IntroductionMethod模型结构Q-Former预训练第一阶段Q-Former预训练第二阶段 BLIP 论文&#xff1a; 《BLIP: Bootstrapping Language-Image Pre-training for Unified Visio…

Unity打开安卓设备不同的设置面板

1&#xff0c;打开安卓设备不同的设置面板&#xff0c;我还贴心的把Android官网的链接放下面了 2&#xff0c;使用也很方便&#xff1a;unity按钮事件上拖这个脚本&#xff0c;注册MyOpenAndroidSettings方法&#xff0c;参数 填 和枚举值相应的数字 // 功能&#xff1a;打开…

【c++】线程池的原理及实现

&#x1f4bb;文章目录 &#x1f4c4;前言线程池的原理概念工作原理 线程池的实现线程池的基础结构任务队列的实现工作线程的实现 线程池的应用与拓展线程池的拓展 &#x1f4d3;总结 &#x1f4c4;前言 不知道各位是否有试过点进限时抽奖网站、抢票网站呢&#xff1f;你是否好…

静态分析-RIPS-源码解析记录-02

这部分主要分析scanner.php的逻辑&#xff0c;在token流重构完成后&#xff0c;此时ini_get是否包含auto_prepend_file或者auto_append_file 取出的文件路径将和tokens数组结合&#xff0c;每一个文件都为一个包含require文件名的token数组 接着回到main.php中&#xff0c;此时…

一款功能强大的网络安全综合工具-PotatoTool

一、 简介 这款工具是一款功能强大的网络安全综合工具&#xff0c;旨在为安全从业者、红蓝对抗人员和网络安全爱好者提供全面的网络安全解决方案。它集成了多种实用功能&#xff0c;包括解密、分析、扫描、溯源等&#xff0c;为用户提供了便捷的操作界面和丰富的功能选择。 二…

《Fundamentals of Power Electronics》——状态空间平均法

文献中出现了许多交流变换器建模技术&#xff0c;包括电流注入法、电路平均法和状态空间平均法。尽管给定方法的支持者可能更喜欢用特定形式表示最终结果&#xff0c;但几乎所有方法的最终结果都是等效的。所有人都会赞同&#xff0c;平均和小信号线性化是PWM变换器建模的关键步…

厚德提问大佬答4:AI绘画生成的心得

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德云替你问&#xff0c;你解…

Element-plus修改input的placeholder文字颜色

需求 代码 .el-input__inner::placeholder {color: #666f8d !important; }

图像处理--空域滤波增强(原理)

一、均值滤波 线性滤波算法&#xff0c;采用的主要是邻域平均法。基本思想是使用几个像素灰度的某种平均值来代替一个原来像素的灰度值。可以新建一个MN的窗口以为中心&#xff0c;这个窗口S就是的邻域。假设新的新的像素灰度值为&#xff0c;则计算公式为 1.1 简单平均法 就是…

堆的应用2——TOPK问题

TOPK问题 TOP-K问题&#xff1a;即求数据结合中前K个最大的元素或者最小的元素&#xff0c;一般情况下数据量都比较大。 比如&#xff1a;专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 情况1——数据量小 对于Top-K问题&#xff0c;能想到的最简单直接的方式就…

品鉴中的文化碰撞:如何理解和欣赏不同文化背景下的红酒

红酒作为世界各地广泛生产的产品&#xff0c;具有丰富的文化内涵。不同国家、地区和民族的红酒文化各具特色&#xff0c;反映了当地的历史、传统、习俗和生活方式。在品鉴云仓酒庄雷盛红酒时&#xff0c;理解和欣赏不同文化背景下的红酒是提升品鉴体验的重要一环。 首先&#x…

【Java orm 框架比较】九 新增wood框架对比

【Java orm 框架比较】九 新增wood框架对比 本次新增wood 框架测试 测试数据存储、分页查询&#xff0c;文档及框架比较稳定半天时间加入测试使用 迁移到&#xff08;https://gitee.com/wujiawei1207537021/spring-orm-integration-compare&#xff09; orm框架使用性能比较…

【STM32 |GPIO】GPIO结构、GPIO输出

目录 GPIO简介 GPIO的基本结构 GPIO位结构&#xff08;每一位的具体电路结构&#xff09; 输入 上拉和下拉电阻 斯密特触发器 ​编辑 输出 GPIO模式 ​编辑 浮空输入、上拉输入、下拉输入 模拟输入 开漏输出和推挽输出 复用开漏输出和复用推挽输出 LED和蜂鸣器…

代码随想录算法训练营第36期DAY22

DAY22 654最大二叉树 自己做的时候忽略了&#xff1a;nums.length>1的题给条件。所以每次递归都要判断是否size()>1&#xff0c;不要空的。 /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *rig…

JSpdf,前端下载大量表格数据pdf文件,不创建dom

数据量太大使用dom》canvas》image》pdf.addimage方法弊端是canvas超出 浏览器承受像素会图片损害&#xff0c;只能将其切割转成小块的canvas,每一次调用html2canvas等待时间都很长累积时间更长&#xff0c;虽然最终可以做到抽取最小dom节点转canvas拼接数据&#xff0c;但是死…

[附源码+视频教程]暗黑纪元H5手游_架设搭建_畅玩三网全通西方3D世界_带GM

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 一. 演示视频 暗黑纪…

Android 查看CUP占用率

查看每个进程CUP占用率的几种方式,由于自己充电界面老是导致整机温度过高&#xff0c;后面发现自己的线程一直在跑&#xff0c;相当于死循环&#xff0c;后面加上sleep才得以改善&#xff1b;先看看几种查询方式吧。 1、adb shell top 2、adb shell busybox top 3、adb shell …

C++类和对象中篇

&#x1f407; &#x1f525;博客主页&#xff1a; 云曦 &#x1f4cb;系列专栏&#xff1a;[C] &#x1f4a8;路漫漫其修远兮 吾将而求索 &#x1f49b; 感谢大家&#x1f44d;点赞 &#x1f60b;关注&#x1f4dd;评论 文章目录 &#x1f4d4;前言&#x1f4d4;1、类的六个…

微服务学习笔记

微服务学习笔记 文章目录 微服务学习笔记认识微服务微服务技术栈微服务学习要点微服务远程调用1)注册RestTemplate2) 服务远程调用RestTemplate Eureka注册中心简介操作过程搭建EurekaServer注册user-service在order-service完成服务拉取 Ribbon负载均衡IRule负载均衡策略饥饿加…

在线扭蛋机小程序:商家稳占市场的新突破口

近几年&#xff0c;扭蛋机进入了爆发期&#xff0c;动漫、游戏的发展更是推动了市场的发展&#xff0c;我国扭蛋机正在蓬勃发展中。 不过&#xff0c;在市场规模扩大下&#xff0c;扭蛋机行业的竞争力也在同时加大&#xff0c;企业商家需要在市场竞争中寻求发展新思路&#xf…