【OC】集合类


前言

OC的集合类是一种特别有用的工具类

一、集合类的概述

他可以存储多个对象,并且实现我们常用的数据结构,例如栈与队列。除此之外OC集合还可以用于保存具有映射关系的关联数组。
OC的集合大致可以分为三种:NSArray、NSSet、NSDictionary
NSArray代表有序,可重复的集合
NSSet代表无序,不可重复的集合
NSDictionary代表具有隐身关系的集合
OC集合像一种容器,我们可以把多个对象丢进容器(实际上是对象的指针

因为在编程中我们常常需要集中存放多个数据,而为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组,例如我们的成绩表:语文–87就可以说他们具有映射关系。)我们的OC提供了集合类,集合类主要负责保存其他数据,因此也叫做容器类。
在这里插入图片描述
在这里我们需要先明白三种集合类的特征
在这里插入图片描述

二、数组NSArray与NSMutableArray

OC中的NSArray与c语言的数组类似

1.NSArray的功能以及用法

NSArray类提供了两种方法来创建我们的数组,一种是类方法,以array开始,另一种是实例方法,以init开头。
在这里插入图片描述

NSArray类中一些方法的用法:
在这里插入图片描述

输出结果:
在这里插入图片描述

我们上面的程序最开始创建了一个NSArray对象,我们创建的对象可以直接传入多个参数,然后我们用nil标识结束。

当我们程序调用点语法来访问数组中的元素时,实际上是调用NSArray类中的objectAtIndexSubscrit:方法来进行访问的。

同时我们可以看到如果我们查找的元素不在数组范围中,将会返回922… 这是常量NSNotFound的值
在这里插入图片描述

然后我们采用了两种方式对数组后面追加了元素

  1. 使用arrayByAddingObject,追加单个元素
  2. 使用arrayWithObjects,将另一个数组中的所有元素追加到原数组的后面。
    不管用上述哪种方法,都不能对原来的NSArray对象进行修改(因为集合本身是不能修改的),程序只是返回一个新的NSArray对象

在这里插入图片描述

在这里插入图片描述

我们在上面的代码中大量调用了判断元素在数组中索引的方法,那么我们这里引申出一个问题:
我们数组中的元素实际上是对象,但是我们输入的对象和数组中的对象肯定不是一个对象,那么为什么我们还能返回我们的索引的正确位置呢?

这里就引出了我们集合中包含指定元素的标准:
在这里插入图片描述

2.对集合元素整体调用方法

NSArray允许集合中所有元素部分元素调用方法,如果只是简单地调用集合元素,可通过一下两种方法:

如果希望对集合中的所有元素进行隐式遍历,并以此使用集合元素来执行某一段代码,可通过如下方法来完成:
在这里插入图片描述
接下来我们可以给出代码:
在这里插入图片描述
在这里插入图片描述
这里用到了整体调用的方法。

3.对NSArray进行排序

NSArray中有大量的方法对集合元素进行排序,例如:

  1. sortedArrayUsingFunction:context:该方法使用排序函数对集合元素进行排序,该排序函数必须返回NSOrdererDescending、NSOrderedAscending、NSOrderedSame这些枚举值,用于代表集合元素的大小。该方法返回一个排好序的新NSArray对象
  2. sortedArrayUsingSelector:该方法使用集合元素自身的方法对集合元素进行排序、必须返回一系列枚举值用于代表集合元素的大小。该方法也返回一个排好序的新对象
  3. sortedArrayUsingComparator:该方法使用代码块对集合元素进行排序,也返回一个排好序的新对象。
    要注意这些都是实例方法,是用我们设置的数组来调用的。

方法调用代码:
在这里插入图片描述
编译结果:
在这里插入图片描述
上面的程序中我们实现了三种对NSArray进行排序的方法。
第一种:NSString是用自身的compare:方法进行了排序。这是因为NSString自身已经实现了compare:方法,这意味着NSString对象本身就可以比较大小。–NSString比较大小的方法是根据字符编码来进行的。
后两种通过调用函数或代码块来比较大小,代码块相当于一个匿名函数,因此后面两种方式的本质是一样的。都可以通过自定义的比较规则来比较集合元素的大小。
在这里插入图片描述

4.使用枚举器遍历NSArray集合元素

对于NSArray对象,我们除了可以根据索引来遍历集合中的元素。还可以调用如下两个方法来返回枚举器:
objectEnumerator: 返回NSArray集合的顺序枚举器
reverseObjectEnumerator: 返回NSArray集合的逆序枚举器
在这里插入图片描述

我们给出枚举器的示例程序:
在这里插入图片描述
在这里插入图片描述

5.快速枚举(for…in)

我们的OC还提供了一种快速枚举的方法来遍历集合(几乎包括所有集合)。
我们使用快速枚举时,无需获得集合的长度,也无需根据索引来访问集合元素,即可快速枚举集合中的每个元素。
在这里插入图片描述
在这里插入图片描述
给出示例程序:
在这里插入图片描述
在这里插入图片描述
输出结果:
在这里插入图片描述

6.可变数组(NSMutableArray)

NSArray代表集合元素不可变的集合,这里我们就该像前文一样引出我们的可变数组(NSMutableArray)
在这里插入图片描述
我们的NSMutableArray与我们前面的可变字符串一样,可以调用NSArray的所有方法,因为NSArray是他的爸爸。同时,他代表的是一个集合元素可变的集合。因此我们的程序可以向集合中添加新的元素,也可以进行删除与替换。
在这里插入图片描述
在这里插入图片描述

示例程序:

#import <Foundation/Foundation.h>
//定义一个函数,这个函数用于把NSArray集合转换为字符串
NSString *NSCollectionToString(NSArray *array) {
    
    
    NSMutableString *result = [NSMutableString stringWithString:@"["];
    for (id obj in array) {
    
    
        [result appendString:[obj description]];//默认打印输出为<类名:内存地址>
        [result appendString:@", "];
    }
    NSUInteger len = [result length];//获取字符串长度
    //去除字符串的最后两个字符
    [result deleteCharactersInRange:NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
    
        NSMutableArray *array = [NSMutableArray arrayWithObjects:@"aaa", @"bbb", @"cccc", @"小欣姐姐", nil];
        [array addObject:@"疯狂3g"];//向集合最后添加一个元素
        NSLog(@"最后追加一个元素后:%@", NSCollectionToString(array));
        //使用NSArray向集合尾部添加多个元素
        [array addObjectsFromArray:[NSArray arrayWithObjects:@"张飞", @"关羽", @"刘备", nil]];
        NSLog(@"%@", NSCollectionToString(array));
        //向集合的指定位置插入一个元素后
        [array insertObject:@"疯狂iOS" atIndex:2];
        NSLog(@"插入第二个元素之后%@", NSCollectionToString(array));
        //使用NSArray向集合指定位置插入多个元素
        [array insertObjects:[NSArray arrayWithObjects:@"武松", @"林冲", nil] atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(3, 2)]];
        //当我们要插入多个元素时,一定要记得调用方法来设置多个对象
        NSLog(@"%@", NSCollectionToString(array));
        [array removeLastObject];
        NSLog(@"%@", NSCollectionToString(array));
        [array removeObjectAtIndex:1];//删除下标为1的元素
        NSLog(@"%@", NSCollectionToString(array));
        [array removeObjectsInRange:NSMakeRange(2, 2)];//删除索引为2-4的元素
        NSLog(@"%@", NSCollectionToString(array));
        //替换索引出为2的元素;
        [array replaceObjectAtIndex:2 withObject:@"疯狂Andriod"];
        NSLog(@"%@", NSCollectionToString(array));

        

    }
    return 0;
}

编译结果:
在这里插入图片描述

三、集合(NSSet与NSMutableSet)

1.NSSet功能与用法

前面我们的图中已经介绍过了我们的集合类似于一个罐子,我们只需要把东西丢进罐子,而不用考虑它们的顺序。另外集合中不允许包含相同的元素
同时我们的NSSet不能根据索引操作元素,因为里面的元素是无序的。
但我们的NSSet与NSArray有一些相似的地方
在这里插入图片描述
在这里插入图片描述

接下来让我们了解一下NSSet中的一些方法
在这里插入图片描述
在这里插入图片描述
下面我们给出NSSet方法的使用实例:


#import <Foundation/Foundation.h>
//一个将set集合转换为字符串的函数
NSString *NSCollectionToString(id collection) {
    
    
    NSMutableString *result = [NSMutableString stringWithString:@"["];
    for (id obj in collection) {
    
    
        [result appendString:[obj description]];//默认打印输出为<类名:内存地址>
        [result appendString:@", "];
    }
    NSUInteger len = [result length];//获取字符串长度
    //去除字符串的最后两个字符
    [result deleteCharactersInRange:NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
    
        //用四个元素初始化set集合
        //然后我们故意传入两个相等的元素,集合只会保留一个元素
        NSSet *set1 = [NSSet setWithObjects:@"fk3g", @"fkiOS", @"fkiOS", @"fkc++", nil];
        //然后输出我们集合中元素的个数
        NSLog(@"set1集合中的元素个数为%ld", [set1 count]);
        NSLog(@"%@", NSCollectionToString(set1));
        NSSet *set2 = [NSSet setWithObjects:@"孙悟空", @"猪八戒", @"疯狂Android", nil];
        NSLog(@"%@", NSCollectionToString(set2));
        //向集合set1中添加单个元素,并将田间元素后生成的集合赋给set1
        [set1 setByAddingObject:@"fk605"];
        //因为我们调用方法后返回的是集合,然后集合
        NSLog(@"%@", NSCollectionToString(set1));
        NSLog(@"%@", [set1 setByAddingObject:@"fk605"]);
        set1 = [set1 setByAddingObject:@"fk605"];
        NSLog(@"%@", NSCollectionToString(set1));
        //然后我们添加多个集合
        NSSet *s = [set1 setByAddingObjectsFromSet:set2];
        NSLog(@"%@", NSCollectionToString(s));//计算两个集合的并集
        BOOL b = [set1 intersectsSet:set2];//判断两个集合是否有相同元素
        BOOL bo = [set2 isSubsetOfSet:set1];//判断set2是否为set1的子集
        BOOL bb = [set1 containsObject:@"fkiOS"];//判断是否包含指定元素
        NSLog(@"%d", b);
        NSLog(@"%d", bo);
        NSLog(@"%d", bb);
        
        NSLog(@"set1中随机取出一个元素:%@", [set1 anyObject]);
        NSLog(@"set1中随机取出一个元素;%@", [set1 anyObject]);
        
        
        
    }
    
    return 0;
}

编译结果:
在这里插入图片描述
在这里我们需要注意一下:因为我们调用方法后返回的是集合,然后所以如果要修改我们的原集合一定要对在调用方法后对原集合进行重新赋值

2.NSSet判断集合元素重复的标准

当向NSSet添加我们的元素时,我们的程序会调用两个方法来判断这个元素是否在set中具有重复,一种是isEqual方法,另一种是hash方法。

我们的hash方法可以用来得到对象的hashcode值,然后根据该hashcode的值决定我们的对象在底层hash表中的存储位置

如果我们的两个元素的hashcode相同,那么又会调用isEqual方法来判断两个元素是否相等。如果返回NO,那么我们的set依然认为他们不相等,两个元素会在同一个位置,但是会形成链。返回YES则添加失败。
在这里插入图片描述
我们通过以下程序来讲解我们的hash的具体原理:
在这里插入图片描述
然后我们重写我们的hash方法:
在这里插入图片描述
最后我们可以得到我们的结果:
在这里插入图片描述
笔者在学习的过程中又回忆起了以前的一点知识,那就是当我们使用NSLog打印我们的对象时,系统默认调用description方法来打印。所以当我们的对象有多个成员变量时,我们要重写我们的description方法来打印我们的对象

我们这里有一个问题需要注意:如果我们需要把一个对象放进我们的NSSet类中,**如果重写该对象对应类的isEqual:方法,也应该重写其Hash方法,**其规则是:如果两个对象通过isEqual:方法比较返回YES时,这两个对象的Hash方法返回值也应该相同。

如果需要某个类的对象保存到NSSet集合中,重写这个类的isEqual:方法和Hash方法时,应该尽量保证两个对象通过isEqual:比较返回YES时,它们的Hash方法返回值也相等。

重写Hash方法的基本规则

  • 程序运行过程中,同一个对象多次调用Hash应该返回相同的值。
  • 当两个对象通过isEqual:方法比较返回YES时,这两个对象的Hash应返回相等的值。
  • 对象中作为isEqual:比较标准的实例变量,都应该用来计算hashCode值。

重写我们的hash方法的一般步骤:

  1. 把对象的每个有意义的实例变量都能计算出一个int类型的hashCode值
  2. 将多个hashcode的组合计算出一个总和返回。例如:return [f1 hash] + [f2 hash];

同时为了避免我们的偶然性,我们一般会将某些实例变量乘上质数后相加。

3.NSMutableSet的功能与用法

与前面的相同,他也继承了爸爸的所有方法,同时有新家了一些方法。
由于我们的NSMutableSet可以动态添加集合元素,所以我们创建set集合时可以指定底层hash表的初始容量。
在这里插入图片描述
接下来我们用实例来讲解我们的NSMutableSet
在这里插入图片描述
输出结果:
在这里插入图片描述

4.NSCountedSet的用法

首先我们需要知道NSCountedSet是NSMutableSet的子类,但与它爸爸不同的是它多了一个维护次数的功能。
假如一个元素在集合中已经存在,那么添加相同的元素会将该元素的添加次数加1。当我们需要删除该元素时,只有当该元素次数为0时才能完全删除。
下面给出我们的示例程序
在这里插入图片描述
输出:
在这里插入图片描述

这里可以看到我们删除了111之后111还在集合中,说明我们的集合具有计数功能

四、字典(NSDictionary与NSMutableDictionary)

在前面我们知道了字典是用于保存具有映射关系的数据,因此我们的集合中保存着两组值,一组是key,一组是value。同时我们要知道我们map中的key不允许重复
同时我们的key与value是一对一的关系
在这里插入图片描述
其实这里的字典关系就有点像是我们的链表,不过仍有许多不同

1.NSDictionary的功能与用法

我们OC中提供了类方法与实例方法来创造我们的字典,两种方法传入的参数基本类似,只是类方法以dictionary开始,实例方法以init开头
在这里插入图片描述
在这里插入图片描述

通过key来获取value的语法:

  1. 调用NSDictionary的objectForKey:方法即可根据key来获取对应的value。
  2. 直接使用下标法根据key来获取对应的value。

[dictionary objectForKey:key];
dictionary[key]; 这两个是相同的

当程序调用dictionary[key]下标形式的语法来获取value时,实际上就是调用NSDictionary的objectForKeyedSubscript:方法进行访问。

同时为了能直观的看到我们的key与value的详情,我们向NSDictionary添加一个类别print,在该类别中为NSDictionary扩展一个方法,用于打印key-value对的详情
实现部分:
在这里插入图片描述

接下来我们给出NSDictionary功能的具体实现:
在这里插入图片描述
编译结果:
在这里插入图片描述

2.对NSDIctionary的key排序

在这里插入图片描述
我们给出示例程序:
在这里插入图片描述
结果:
在这里插入图片描述
在这里我们使用了两种排序方法
第一种方法是直接调用所有的value的compare:方法进行排序,其比较大小是通过直接根据字符对应的编码进行大小比较
第二种方法是:根据字符串的长短进行比较

3.对NSDictionary的key进行过滤

我们的字典还提供了一些方法对key进行过滤
在这里插入图片描述
下面给出示例程序:
在这里插入图片描述
编译结果:
在这里插入图片描述
只有当我们的key大于80时我们的key才会被保留下来

4.使用自定义类作为NSDictionary的key

我们前面介绍我们的key时一般都是使用我们的NSString作为我们的key
如果我们的程序打算使用自定义类来作为我们的NSDictionary的key
我们的正确重写分为以下两步:

  1. 我们的自定义类正确重写过我们的我们的isEqual以及hash方法,保证当isEqual判断相等时我们的程序返回的hash值也是相等的
  2. 我们的自定义类必须实现了copyWithZone方法且最好能返回我们的不可变副本

在这里我们需要解释一下我们为什么需要实现这个方法:
因为我们的key在我们的字典中是非常关键的,出于安全性考虑,我们的key一般是不能改变的。如果我们key本身是可变的,且程序可以通过其他变量来修改key,那么这是非常不好的

为了避免这种情况,所以我们要保证只要程序把任何对象作为key放入我们的字典,我们的字典总会先调用该key的copy方法来复制该对象的不可变副本,然后以该副本作为字典的key,这样就保证了我们的key没有办法被修改。

为了让我们的自定义的类作为key,我们还需要让我们的自定义类实现NSCopying协议,并让该类实现copyWithZone方法
在这里插入图片描述

然后我们将FKUser类作为我们的key来写我们的程序:
在这里插入图片描述
结果:
在这里插入图片描述

我们上述的程序按value1,key1的格式来设置我们的字典。我们的程序有多对value-key。然后在前面知道了,当程序尝试使用任何对象来作为我们的key时,我们的key都会先调用copy方法来复制该key的不可变副本,这也是我们需要实现copyWithZone:方法的原因。然后我们的程序实际上是将该副本作为我们NSDictionary的key。因此无论我们怎么修改我们的key,他都不会出现变化

同时我们还看到虽然我们定义了五对value-key,但是只输出了四对,因为有一对是相同的。

5.NSMutableDictionary的功能以及用法

由于该类可以动态的添加key-value对,所以创建该集合时可以指定初始容量。
在这里插入图片描述
示例程序:
在这里插入图片描述
输出结果:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_72437555/article/details/130438698
今日推荐