用 C语言 写一个 Objective-C 的数组对象(链式存储结构)
-
分析 Objective-C 中 NSMutableArray 的属性与方法,规划线性表对象
1.属性方面:容量(capacity)、长度(length)、指向存储线性表元素的堆内存的指针(value)
2.方法方面:创建线性表、销毁线性表、清空线性表、获取线性表的属性(capacity、length)、对线性表的增、删、改、查、删除特定值,打印线性表对象内存图如下所示:

-
实现线性表对象
HZPLinearList.h 文件代码如下:#ifndef LinearList_h #define LinearList_h #include <stdio.h> #pragma mark - 宏定义(条件编译) #ifndef LINEARLIST_STRUCT // 线性表节点数据 typedef void* LinearListNodeValue; // 线性表 typedef void LinearList; #endif #pragma mark - 创建 销毁 清空 // 创建线性表 // return.线性表指针 LinearList* listCreat(); // 销毁线性表 // param0.线性表指针 void listRelease(LinearList* list); // 清空线性表 // param0.线性表指针 void listClear(LinearList* list); #pragma mark - 属性获取 // 获取线性表的长度 // param0.线性表指针 // return.线性表长度 int listLength(LinearList* list); #pragma mark - 增 // 往线性表中插入数据 // param0.线性表指针 // param1.要插入的位置的索引 // param2.要插入的值 void listInsert(LinearList* list, int index, LinearListNodeValue value); // 往线性表中添加数据(添加在表尾) void listAdd(LinearList* list, LinearListNodeValue value); #pragma mark - 删 // 删除线性表中指定索引位置的元素 // param0.线性表指针 // param1.索引 void listRemove(LinearList* list, int index); #pragma mark - 改 // 修改线性表中指定位置的元素为指定的值 // param0.线性表指针 // param1.索引 // param2.值 void listSet(LinearList* list, int index, LinearListNodeValue value); #pragma mark - 查 // 获取线性表指定索引处元素的值 // param0.线性表指针 // param1.索引 // return.元素的值 LinearListNodeValue listGet(LinearList* list, int index); #pragma mark - 特殊功能 // 删除线性表中具有指定值的所有元素 // param0.线性表指针 // param1.要删除的值 void listRemoveValue(LinearList* list, LinearListNodeValue value); // 打印线性表 // param0.线性表指针 void listPrint(LinearList* list); #endif /* LinearList_h */HZPLinearList.c 代码如下:
#pragma mark - 定义线性表结构体 // 节点数据指针 typedef void* LinearListNodeValue; // 节点结构体 typedef struct _LinearListNode LinearListNode; struct _LinearListNode { LinearListNodeValue value; // 节点数据 LinearListNode* next; // 指向下个节点的指针 }; // 线性表结构体 typedef struct { int length; // 长度 LinearListNode* header; // 头节点 } LinearList; /* 注意:宏 LINEARLIST_STRUCT 的定义,一定要在导入 HZPLinearList.h 之前 这是为了防止上面的 LinearListNodeValue 指针和 LinearList 结构体被重复定义 **/ // #define LINEARLIST_STRUCT #pragma mark - 导入头文件 #include "HZPLinearList.h" // 使用 mallc函数 需要导入 #include <stdlib.h> #pragma mark - 创建 销毁 清空 // 创建线性表 LinearList* listCreat() { // 分配线性表结构体和表头的内存空间(在堆区) // malloc函数如果遇到内存资源紧张,给不了这么多字节,可能会返回空 LinearList* list = malloc(sizeof(LinearList) + sizeof(LinearListNode)); if (list) { list->length = 0; list->header = (LinearListNode *)(list + 1); list->header->value = NULL; list->header->next = NULL; } return list; } // 销毁线性表 void listRelease(LinearList* list) { if (NULL == list) { return; } listClear(list); free(list); } // 清空线性表 void listClear(LinearList* list) { if (NULL == list) { return; } // while 循环中,headerNode 会一直有值,while 循环通过 break 跳出 LinearListNode* headerNode = list->header; while (headerNode) { LinearListNode* removeNode = headerNode->next; if (NULL == removeNode) { break; } headerNode->next = removeNode->next; free(removeNode); } // 将线性表的长度置为 0 list->length = 0; } #pragma mark - 属性获取 // 获取线性表的长度 int listLength(LinearList* list) { if (NULL == list) { return 0; } return list->length; } #pragma mark - 增 // 往线性表中插入数据 void listInsert(LinearList* list, int index, LinearListNodeValue value) { if (NULL == list) { return; } // 可以在表尾进行插入,因此这里的条件是 index > list->length,而不是 index >= list->length if (index < 0 || index > list->length) { return; } // 获取 index - 1 节点 LinearListNode* currentNode = list->header; for (int i = 0; i < index; i++) { currentNode = currentNode->next; } // 创建新增节点 LinearListNode* tempNode = malloc(sizeof(LinearListNode)); if (NULL == tempNode) { return; } tempNode->value = value; // 交换 新增节点 与 index - 1节点 的 next 指针 tempNode->next = currentNode->next; currentNode->next = tempNode; // 线性表的长度 + 1 list->length++; } // 往线性表中添加数据(添加在表尾) void listAdd(LinearList* list, LinearListNodeValue value) { if (NULL == list) { return; } listInsert(list, list->length, value); } #pragma mark - 删 // 删除线性表中指定索引位置的元素 void listRemove(LinearList* list, int index) { if (NULL == list) { return; } if (index < 0 || index > list->length - 1) { return; } // 获取 index - 1 节点 LinearListNode* currentNode = list->header; for (int i = 0; i < index; i++) { currentNode = currentNode->next; } // 获取 index 节点 LinearListNode* removeNode = currentNode->next; // 删除 index 节点 currentNode->next = removeNode->next; free(removeNode); // 线性表的长度 - 1 list->length--; } #pragma mark - 改 // 修改线性表中指定位置的元素为指定的值 void listSet(LinearList* list, int index, LinearListNodeValue value) { if (NULL == list) { return; } if (index < 0 || index > list->length - 1) { return; } // 获取 index 节点,并修改 index 节点的值 LinearListNode* currentNode = list->header; for (int i = 0; i <= index; i++) { currentNode = currentNode->next; } currentNode->value = value; } #pragma mark - 查 // 获取线性表指定索引处元素的值 LinearListNodeValue listGet(LinearList* list, int index) { if (NULL == list) { return 0; } if (index < 0 || index > list->length - 1) { return 0; } // 获取 index 节点,并获取 index 节点的值 LinearListNode* currentNode = list->header; for (int i = 0; i <= index; i++) { currentNode = currentNode->next; } return currentNode->value; } #pragma mark - 特殊功能 // 删除线性表中具有指定值的所有元素 - 效率较高 void listRemoveValue(LinearList* list, LinearListNodeValue value) { if (NULL == list) { return; } // 遍历所有元素 // 注意: // 1.for 循环的循环次数依赖于集合的大小,如果在循环过程中集合的大小发生改变,会变得比较难处理。因此这里用 while 循环 // 2.这里的 while 循环通过 return 跳出 LinearListNode* currentNode = list->header; while (currentNode) { LinearListNode* nextNode = currentNode->next; if (NULL == nextNode) { return; } if (nextNode->value == value) { currentNode->next = nextNode->next; free(nextNode); list->length--; } else { currentNode = nextNode; } } } // 打印线性表 void listPrint(LinearList* list) { if (NULL == list) { return; } printf("list{\n"); printf("\tlength = %d;\n", list->length); printf("\tvalue = ["); LinearListNode* currentNode = list->header; for (int i = 0; i <= list->length - 1; i++) { currentNode = currentNode->next; printf("%p", currentNode->value); if (i <= list->length - 2) { printf(","); } } printf("];\n\t}\n\n"); }main.m 代码如下
#import <Foundation/Foundation.h> #import "HZPLinearList.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 创建线性表 LinearList* list = listCreat(); // 添加数据 for (int i = 0; i < 5; i++) { listAdd(list, (LinearListNodeValue)i); } listInsert(list, 0, (LinearListNodeValue *)3); listInsert(list, 1, (LinearListNodeValue *)4); listPrint(list); /* list{ length = 7; value = [0x3,0x4,0x0,0x1,0x2,0x3,0x4]; } **/ // 删除数据 listRemove(list, 2); listPrint(list); /* list{ length = 6; value = [0x3,0x4,0x1,0x2,0x3,0x4]; } **/ // 修改数据 listSet(list, 0, (LinearListNodeValue)4); listPrint(list); /* list{ length = 6; value = [0x4,0x4,0x1,0x2,0x3,0x4]; } **/ // 查询数据 LinearListNodeValue value = listGet(list, 0); NSLog(@"value = %p", value); /* value = 0x4 **/ // 删除指定数据 listRemoveValue(list, (LinearListNodeValue)4); listPrint(list); /* list{ length = 3; value = [0x1,0x2,0x3]; } **/ // 清空线性表 int length = listLength(list); NSLog(@"length = %d", length); listClear(list); listPrint(list); /* length = 3 list{ length = 0; value = []; } **/ // 销毁线性表 listRelease(list); listPrint(list); /* list{ length = 0; value = []; } **/ } return 0; }
线性表 HCGLinearList && HZPLinearList 函数执行效率分析
-
线性表 HCGLinearList(顺序存储结构)和 HZPLinearList(链式存储结构)各个函数的时间复杂度如下:
函数 HCGLinearList(顺序存储结构) HZPLinearList(链式存储结构) LinearList* listCreat() O(1) O(1) void listRelease(LinearList* list) O(1) O(n) void listClear(LinearList* list) O(1) O(n) int listLength(LinearList* list) O(1) O(1) int listCapacity(LinearList* list) O(1) - - - - void listInsert(LinearList* list, int index, LinearListNodeValue value) O(n) O(n) void listAdd(LinearList* list, LinearListNodeValue value) O(n) O(n) void listRemove(LinearList* list, int index) O(n) O(n) void listSet(LinearList* list, int index, LinearListNodeValue value) O(1) O(n) LinearListNodeValue listGet(LinearList* list, int index) O(1) O(n) void listRemoveValue(LinearList* list, LinearListNodeValue value) O(n) O(n) void listPrint(LinearList* list) O(n) O(n) -
函数执行效率分析
对比 HCGLinearList(顺序存储结构) 与 HZPLinearList(链式存储结构)各个函数的时间复杂度,我们发现:- HCGLinearList(顺序存储结构)中,除了 listInsert、listAdd、listRemove、listRemoveValue、listPrint 的时间复杂度为 O(n) 外,其余函数的时间复杂度均为 O(1)
- HZPLinearList(链式存储结构)中,除了 listCreat、listLength 的时间复杂度为 O(1) 外,其余函数的时间复杂度均为 O(n)
虽然 HCGLinearList 的总体执行效率明显高于 HZPLinearList,但是这并不能说明 顺序存储结构 优于 链式存储结构。顺序存储结构与链式存储结构,各有优缺点:
- 顺序存储结构:存储单元的内存地址是连续的。适用于:对数据遍历和查找频繁 && 对数据插入和删除较少 的场合。
- 链式存储结构:存储单元的内存地址可以是连续的也可以是不连续的,它不要求逻辑上相邻的元素在物理地址上也相邻。适用于:对数据遍历和查找较少 && 对数据插入和删除频繁 的场合。
Question:
那是什么原因造成 HZPLinearList(链式存储结构)在插入数据和删除数据的性能上没有高于 HCGLinearList(顺序存储结构)呢?
Answer:
问题出在函数调用方法的设计上。
HCGLinearList 为 顺序存储结构,存储单元的内存地址是连续的,操作数据可以通过索引(index)。
HZPLinearList 为 链式存储结构,存储单元的内存地址是不连续的,如果通过索引操作数据,则必然会进行遍历,这样就体现不出链式存储的优势。因此,链式存储结构 操作数据应该通过节点(LinearListNode),而不是通过索引(index)。
XCode 使用汇编
在 XCode 下使用汇编的方式有两种:
- 内联汇编:汇编代码与高级语言代码混合使用
- 外联汇编:用专门的文件编写汇编代码,高级语言通过函数调用汇编代码。
-
内联汇编
int main(int argc, const char * argv[]) { @autoreleasepool { int num1 = 10; int num2 = 20; int result = num1 + num2; asm ( "pushq %rax\n" "pushq %rdi\n" "pushq %rsi\n" "movq $0x1122, %rdi\n" "movq $0x3344, %rsi\n" "movq %rdi, %rax\n" "addq %rsi, %rax\n" "popq %rsi\n" "popq %rdi\n" "popq %rax\n" ); NSLog(@"result = %d", result); } return 0; } -
外联汇编
外联汇编使用步骤:
- 新建汇编代码文件,使用汇编代码实现相应的函数功能
- 在高级语言中导入相应的函数,然后调用
HcgFunc.h 文件如下:
#ifndef Sum_h #define Sum_h int sum(int a, int b); #endif /* Sum_h */HcgFunc.s 文件如下:
/* <<新建汇编代码文件>> 新建汇编代码文件的步骤: New File ... - macOS - Assembly File 汇编代码在 macOS/Unix 下后缀为 .s,在 Windows 下后缀为 .asm 注意: 汇编代码文件(.s 文件),也是代码文件,并非真正的 以 0 和 1 表示的汇编代码 .s 文件也需要经过编译器编译才能生成对应的汇编代码 我们可以在 Build Phases - Compile Sources 下看到,我们新建的汇编文件也会参与编译, 因为 .s 文件也是源文件,并非目标二进制代码文件 <<编写汇编代码>> .text : 表示代码段 .data : 表示数据段 .global 标号 : 在.s文件里面定义的函数,默认是私有函数,外界调用不到,使用(.global 标号)将函数声明为全局函数 _sum : 汇编代码里面的标号为 _函数名,高级语言调用汇编代码的功能时,直接使用函数名( 在汇编代码里面,如果是函数的实现,则需要在函数名前面加一个下划线 _ ) 通过编译器生成的汇编代码,在调用函数进行传参时,存储参数的寄存器是有顺序的: di si dx cx 8d 9d MacBook 的 CPU 为 64 位的 CPU,其专门用来存储函数形参的寄存器有6个 当源函数传递给目标函数的形参超过6个,则会将剩余的形参 push 到源函数的栈空间中 esi : e 表示 32 位寄存器 rsi : r 表示 64 位寄存器 movl : l 表示操作 32 位 movq : q 表示操作 64 位 <<导入汇编函数并使用>> 高级语言中导入汇编函数的方式有两种: 1.为汇编文件 HcgFunc.s 新建一个相应的头文件 HcgFunc.h,在 HcgFunc.h 中声明 HcgFunc.s 里面实现的函数,然后在高级语言中导入 HcgFunc.h,高级语言通过 HcgFunc.h 声明的函数调用相应的汇编代码。 2.在高级语言中,通过 extern 导入相应的函数,进行使用 extern int sum(int a, int b); */ .text .global _sum _sum: movq %rdi, %rax addq %rsi, %rax retqmain.m 文件如下:
#import <Foundation/Foundation.h> //#import "HcgFunc.h" extern int sum(int num1, int num2); int main(int argc, const char * argv[]) { @autoreleasepool { /* 关于如何传参数 sum(10, 20) 必然生成如下汇编指令 movq $10, %rdi movq $20, %rsi call _sum */ int result = sum(10, 20); NSLog(@"result = %d", result); } return 0; }

本文对比分析了C语言实现的Objective-C数组对象(链式存储结构)与顺序存储结构线性表的函数执行效率,探讨了不同存储结构在插入、删除等操作上的性能差异,并介绍了XCode下的汇编语言使用方法。
&spm=1001.2101.3001.5002&articleId=106549402&d=1&t=3&u=5d6cb33c20a74aa08da5fdf404e1e1e6)
2352

被折叠的 条评论
为什么被折叠?



