一、简单分析
最近学习了下这方面的知识,所以这边就进行总结分析下,其实在ios中的NSArray其实就是一个结构体,而我们所谓的NSArray * array的这个array对象其实就是一个结构体指针,在下面经过编译之后的文件中其实就可以看出来了。所以我们可以自己去设置结构体去写一个,这里我们采用的时候线性表的顺序存储结构
二、具体实践
接下来我们就自己可以去模仿下NSArray去自己去创建一个数组,首先先去创建一个LinearList.h/.c文件,.h文件代码如下所示
//防止重复导入头文件
#ifndef LinearList_h
#define LinearList_h
#include <stdio.h>
#ifndef LINEARLIST_STRUCT
typedef void * LinearListNodeValue;
typedef void LinearList;
#endif
/**定义一个函数创建线性表的函数*/
LinearList * listCreat(int capacity);
/**销毁线性表*/
void listRelease(LinearList * list);
/**清空线性表*/
void listClear(LinearList *list);
/**获取线性表的长度*/
int listLength(LinearList * list);
/** 获取index对应的数据*/
LinearListNodeValue listGet(LinearList * list,int index);
/**插入数据*/
void listInsert(LinearList *list,int index,LinearListNodeValue value);
/**添加数据*/
void listAdd(LinearList * list,LinearListNodeValue value);
/**设置表中的元素*/
void listSet(LinearList * list,int index,LinearListNodeValue value);
/**删除元素*/
void listRemove(LinearList * list,int index);
/**删除某个值的所有数据*/
void listRemoveValue(LinearList * list,LinearListNodeValue value);
/**打印当前线性表*/
void listPrint(LinearList *list);
#endif /* LinearList_h */
上面的#ifndef LinearList_h其实就是为了防止重复导入头文件,如果当第一次包含LinearList.h文件时,由于没有定义LinearList_h,条件为真,这样就会包含(执行)#ifndef LinearList_h和#endif之间的代码,当第二次包含LinearList.h时前面一次已经定义了LinearList_h,条件为假,#ifndef LinearList_h和#endif之间的代码也就不会再次被包含,这样就避免了重定义了。
以及为了满足把结构体定义在.c文件中,而不是暴露在.h文件中给外界访问,我们这里采用了#ifndef LINEARLIST_STRUCT 这个一个宏定义,外界引用的.h文件的时候,会因为LINEARLIST_STRUCT没有定义而会去走下面的这几条语句
如果是在.c文件中的话我们就会在最上面先去定义#define LINEARLIST_STRUCT 这样的话我们再去导入LinearList.h文件就不会去重复定义LinearListNodeValu和LinearList了。如下所示
下面就是具体实现了,其实都比较简单,唯一有一个函数需要说一下的就是根据外界提供的某个值去把数组中的所有元素给删除的函数,这里做下介绍,共有两种方法,第一种方法的效率不高,第二种方法的效率比第二种的高
/**删除某个值的所有数据*/
void listRemoveValue(LinearList * list,LinearListNodeValue value)
{
if(list==NULL)
{
return;
}
// //方法一
// //遍历所有的元素
// for(int i=0;i<list->length;i++)
// {
// /*为了防止我们比如说有1 2 2 3 4这五个元素,如果我们要删除所有值为2的元素,我们直接删除肯定是不行的,
比如说我们删除了下标为1的2的是这个时候数组中的元素变成了1 2 3 4,而下一轮for循环则是从下标为2开始了,
也就是说直接从3开始了,所有我们需要在下面做特殊处理,还有个条件就是i < list->length是因为如果删除的是最后一个元素,
最后一个元素还是可以取到的,也就是说会进入死循环*/
// while (list->values[i]==value &&i < list->length) {
// //删除元素
// listRemove(list, i);
// }
// }
//方法二
//定义一个初始化记录 简单来说就是如果发现和value值一样的就让removeCount增加,如果不一样就进行移动
int removeCount=0;
//遍历所有的元素
for(int i=0;i<list->length;i++)
{
if(list->values[i]==value)
{
//记录加1
removeCount++;
}
else
{
//否则就进行移动
list->values[i-removeCount] = list->values[i];
}
}
//再进行长度的减短
list->length -= removeCount;
}
下面就是具体的实现了
#define LINEARLIST_STRUCT
typedef void * LinearListNodeValue;
typedef struct {
int capacity;//容量
int length;//长度
LinearListNodeValue * values; //一个指向指针的指针,我们的数据就是指针类型的,而value就是指向首地址的指针
}LinearList;
#include "LinearList.h"
#include <stdlib.h>
//创建线性表
LinearList * listCreat(int capacity)
{
//判断容量的值,如果小于0,直接返回NULL
if(capacity<0)
{
return NULL;
}
//返回的是malloc申请的连续的空间的首地址,虚拟地址是连续的
LinearList * list = malloc(sizeof(LinearList));
//malloc函数有可能会出错的,返回为空,所以要判断
if(list)
{
list->length = 0;
list->capacity = capacity;
//分配空间存放数据 capacity如果为0就返回NULL
list->values = capacity? malloc(capacity*sizeof(LinearListNodeValue)):NULL;
}
return list;
}
/**销毁线性表*/
void listRelease(LinearList * list)
{
//先去判断list是不是为空,如果为空就返回
if(list == NULL)
{
return;
}
free(list->values);
free(list);
list=NULL;
}
/**清空线性表*/
void listClear(LinearList *list)
{
//判断list是否为NULL
if(list==NULL)
return;
list->length=0;
}
/**获取线性表的长度*/
int listLength(LinearList * list)
{
//如果list为空,就直接返回0
if(list==NULL)
{
return 0;
}
return list->length;
}
/** 获取index对应的数据*/
LinearListNodeValue listGet(LinearList * list,int index)
{
if(list==NULL || index>=list->length || index<0)
{
return 0;
}
return list->values[index];
}
/**插入数据*/
void listInsert(LinearList *list,int index,LinearListNodeValue value)
{
if(list==NULL||index < 0||index > list->length||list->length == list->capacity)
{
return;
}
//从index开始到后面所有的数据都要进行挪动
for(int i=list->length-1;i>=index;i--)
{
list->values[i+1] = list->values[i];
}
//设置新的值到index的位置
list->values[index] = value;
//数组的数量增加
list->length++;
}
/**添加数据*/
void listAdd(LinearList * list,LinearListNodeValue value)
{
if(list==NULL)
{
return;
}
listInsert(list, list->length, value);
}
/**设置表中的元素*/
void listSet(LinearList * list,int index,LinearListNodeValue value)
{
if(list==NULL || index<0 || index>=list->length)
{
return;
}
list->values[index]=value;
}
/**删除元素*/
void listRemove(LinearList * list,int index)
{
if(list==NULL || index<0 || index>=list->length)
{
return;
}
//遍历数组,在这个元素后面的值全部往前移动一格
for(int i=index+1;i<list->length;i++)
{
list->values[i-1] = list->values[i];
}
//数组当中的数量减少1个
list->length--;
}
/**删除某个值的所有数据*/
void listRemoveValue(LinearList * list,LinearListNodeValue value)
{
if(list==NULL)
{
return;
}
// //方法一
// //遍历所有的元素
// for(int i=0;i<list->length;i++)
// {
// //为了防止我们比如说有1 2 2 3 4这五个元素,如果我们要删除所有值为2的元素,我们直接删除肯定是不行的,比如说我们删除了下标为1的2的是这个
时候数组中的元素变成了1 2 3 4,而下一轮for循环则是从下标为2开始了,也就是说直接从3开始了,所有我们需要在下面做特殊处理,还有个条件就是
i < list->length是因为如果删除的是最后一个元素,最后一个元素还是可以取到的,也就是说会进入死循环
// while (list->values[i]==value &&i < list->length) {
// //删除元素
// listRemove(list, i);
// }
// }
//方法二
//定义一个初始化记录
int removeCount=0;
//遍历所有的元素
for(int i=0;i<list->length;i++)
{
if(list->values[i]==value)
{
//记录加1
removeCount++;
}
else
{
//否则就进行移动
list->values[i-removeCount] = list->values[i];
}
}
//再进行长度的减短
list->length -= removeCount;
}
/**打印当前线性表*/
void listPrint(LinearList *list)
{
if(list == NULL)
{
return;
}
printf("list{\n");
printf("\tlength = %d;\n",list->length);
printf("\tcapacity = %d;\n",list->capacity);
printf("\tvalue = [");
for (int i=0; i<list->length; i++) {
printf("%p",list->values[i]);
if(i<list->length-1)
{
printf(",");
}
}
printf("];\n}\n");
}
紧接着我们再去创建Person类进行测试,看看能不能把Person对象放到这个数组中去,然后再去获取到,再去调用相应的方法,Person类内部就一个run方法
@implementation Person
-(void)run
{
NSLog(@"啊哈,你好");
}
@end
下面我们进行测试
//创建结构体在堆区开辟了空间,只需要把指针给它
LinearList * list = listCreat(10);
Person * person1 = [[Person alloc]init];
Person * person2 = [[Person alloc]init];
Person * person3 = [[Person alloc]init];
//__bridge就是桥接一下,将OC对象桥接成C语言的void * 的类型
listAdd(list, (__bridge LinearListNodeValue)(person1));
listAdd(list, (__bridge LinearListNodeValue)(person2));
listAdd(list, (__bridge LinearListNodeValue)(person3));
//桥接一下,C语言的void * 转换为 OC 对象
Person * p = (__bridge Person *)(listGet(list, 1));
[p run];
listPrint(list);
listRelease(list);
输出结果
三、和NSArray相似的地方
之前有篇文章已经说了下NSArray数组的一些内存结构,其实我们这个模仿其实现的也差不多是那样的,先来看下打印输出的内存信息,在这里就简单的举个例子,比如说我们写了这样的测试代码
LinearList * list = listCreat(10);
listAdd(list,(LinearListNodeValue *)10);
listAdd(list,(LinearListNodeValue *)20);
listAdd(list, (LinearListNodeValue *)30);
listAdd(list,(LinearListNodeValue *)10);
listAdd(list, (LinearListNodeValue *)40);
listAdd(list,(LinearListNodeValue *)50);
listRemove(list, 3);
listAdd(list,(LinearListNodeValue *)60);
listPrint(list);
listRelease(list);
我们去看看自己通过lldb打印的信息,下面看到的就是结构体中的容量属性
再去看看结构体中的长度属性
下面就是关于结构体中的LinearListNodeValue * values属性
紧接着去读取其values的首地址之后的100个字节的信息
有了上面的铺垫,我们再去看看数组当中的内存信息,下面可以看到哈哈字符串常量的地址信息,其实它的下一行应该就是字符串常量"1"的地址。
我们下面去通过po 命令去根据地址信息去打印变量,就会发现其实哈哈地址下面的那个地址也就是常量@"1"的。
经过上面的观察我们可以发现其内部的属性的地址空间都是连续的,其创建线性表结构有可能是这样的
LinearList * listCreat(int capacity)
{
//判断容量的值,如果小于0,直接返回NULL
if(capacity<0)
{
return NULL;
}
//返回的是malloc申请的连续的空间的首地址,虚拟地址是连续的,一下子创建好
LinearList * list = malloc(sizeof(LinearList)+capacity*sizeof(LinearListNodeValue));
//malloc函数有可能会出错的,返回为空,所以要判断
if(list)
{
list->length = 0;
list->capacity = capacity;
//分配空间存放数据
list->values = list+1;
}
return list;
}