目录
将数组开辟到堆区,实现动态扩展。
问题:
① 用户的数据类型无法确定;
② 用户的数据无法确定创建在堆区还是栈区;
③ 不管数据在堆区还是栈上,都是在内存中,就会有地址,只需维护数据的地址就行。eg:如果数据类型是 int,则使用 int* 来指向该数据地址。
所以,这里使用万能指针 void* 来指向用户的数据地址。
第二点,我们是在堆区创建的一个数组,每一个元素都是 void* 类型的。如果要操纵这个数组,则可以使用二级指针 void** 来指向该数组的地址。
- typedef struct DynamicArray
- {
- void** pAddr; //维护真实在堆区开辟的数组的二级指针
- int m_Capacity; //数组容量
- int m_Size; //数组大小
- }dynamicArray;
- //初始化数组,参数:初始数组的容量。返回值:数组指针
- dynamicArray* init_dynamicArray(int capacity)
- {
- if (capacity <= 0)
- {
- return NULL;
- }
- //给数组分配内存
- dynamicArray* arr = malloc(sizeof(dynamicArray));
- if (arr == NULL)
- return NULL;
- //数组属性初始化
- arr->pAddr = malloc(sizeof(void*) * capacity); //我们维护的数据类型是void*,
- //这里是capacity个void*类型的数据大小,又malloc返回的是void**类型,pAddr同样是void**类型
- arr->m_Capacity = capacity;
- arr->m_Size = 0;
- }
- //数组元素插入
- void insert_dynamicArray(dynamicArray* arr, void* data, int pos)
- {
- if (arr == NULL)
- return;
- if (data == NULL)
- return;
- if (pos<0 || pos>arr->m_Size)
- pos = arr->m_Size;
- //判断数组是否满了
- if (arr->m_Size == arr->m_Capacity)
- {
- //计算新的空间大小
- int newCapacity = arr->m_Capacity * 2;
- //开辟新空间
- void** newSpace = malloc(sizeof(void*) * newCapacity);
- //将原空间下数据拷贝到新空间下
- memcpy(newSpace, arr->pAddr, sizeof(void*) * arr->m_Capacity);
- //释放原空间
- free(arr->pAddr);
- //更改指向
- arr->pAddr = newSpace;
- //更新容量
- arr->m_Capacity = newCapacity;
- }
- //将新元素插入到指定位置
- for (int i = arr->m_Size - 1; i >= pos; i--) //从最后边的数据开始移
- {
- arr->pAddr[i + 1] = arr->pAddr[i];
- }
- arr->pAddr[pos] = data;
- //更新数组大小
- arr->m_Size++;
- }
- //遍历
- void foreach_dynamicArray(dynamicArray* arr,void(*myPrint)(void*))
- {
- if (arr == NULL)
- return;
- for (int i = 0; i < arr->m_Size; i++)
- {
- myPrint(arr->pAddr[i]);
- }
- }
- //删除数组中元素
- //按照位置删除
- void removeByPos_dynamicArray(dynamicArray* arr, int pos)
- {
- if (arr == NULL)
- return;
- if (pos<0 || pos>arr->m_Size - 1)
- return;
- for (int i = pos; i < arr->m_Size; i++)
- {
- arr->pAddr[i] = arr->pAddr[i + 1];
- }
- arr->m_Size--;
- }
- //按照数值删除
- void removeByVal_dynamicArray(dynamicArray* arr, void* data, int(*myCompare)(void*,void*))
- {
- if (arr == NULL)
- return;
- if (data == NULL)
- return;
- for (int i = 0; i < arr->m_Size; i++)
- {
- if (myCompare(arr->pAddr[i], data))//利用回调函数让用户告诉我们如何对比数据
- {
- removeByPos_dynamicArray(arr,i);
- break;
- }
- }
- }
-
- void destory_dynamicArray(dynamicArray* arr)
- {
- if (arr == NULL)
- return;
- //内部维护在堆区数组指针先释放
- if (arr->pAddr != NULL)
- {
- free(arr->pAddr);
- arr->pAddr = NULL;
- }
- free(arr);
- arr = NULL;
- }
- typedef struct Person //用户的数据类型
- {
- char name[32];
- int age;
- }person;
- //回调函数,打印用户数据
- void printPerson(void* data)
- {
- person* person = data;
- printf("Name:%s Age:%d\n", person->name, person->age);
- }
- int comparePerson(void* data1, void* data2)
- {
- person* p1 = data1;
- person* p2 = data2;
- return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
- }
-
-
- void test01()
- {
- dynamicArray* arr = init_dynamicArray(5);
- person p1 = { "sun",18 };
- person p2 = { "yu",19 };
- person p3 = { "hang",17 };
- person p4 = { "li",20 };
- person p5 = { "hai",21 };
-
- printf("插入数据前:容量 = %d,大小 = %d\n", arr->m_Capacity, arr->m_Size);
- insert_dynamicArray(arr, &p1, 0);
- insert_dynamicArray(arr, &p2, 1);
- insert_dynamicArray(arr, &p3, -1);
- insert_dynamicArray(arr, &p4, 0);
- insert_dynamicArray(arr, &p5, 2);
-
- foreach_dynamicArray(arr, printPerson);
- printf("插入数据后:容量 = %d,大小 = %d\n", arr->m_Capacity, arr->m_Size);
- printf("-------------------------------\n");
- //按位置删除
- removeByPos_dynamicArray(arr,0);
- foreach_dynamicArray(arr, printPerson);
- printf("-------------------------------\n");
- //按值删除
- removeByVal_dynamicArray(arr, &p5, comparePerson);
- foreach_dynamicArray(arr, printPerson);
- printf("删除数据后:容量 = %d,大小 = %d\n", arr->m_Capacity, arr->m_Size);
-
- destory_dynamicArray(arr);
- arr = NULL;
- }
- 插入数据前:容量 = 5,大小 = 0
- Name:li Age:20
- Name:sun Age:18
- Name:hai Age:21
- Name:yu Age:19
- Name:hang Age:17
- 插入数据后:容量 = 5,大小 = 5
- -------------------------------
- Name:sun Age:18
- Name:hai Age:21
- Name:yu Age:19
- Name:hang Age:17
- -------------------------------
- Name:sun Age:18
- Name:yu Age:19
- Name:hang Age:17
- 删除数据后:容量 = 5,大小 = 3
设计一个可以存不同类型数据的链表。
首先来看一个存储void*类型数据的普通链表。
其中的一个节点只是一个独立的个体,无法代表整个链表。
整个链表用一个新的结构体来维护,如下:
- struct LList
- {
- struct LinkNode pHeader; //链表的头结点,拿到链表的头结点即可还原整个链表
- int m_Size; //记录链表的节点个数
- }
在动态数组的实现中,我们可以随时访问动态数组的容量arr->m_Capacity,但由于整个数组的结构体暴露给了用户,导致用户也可以随意修改。
链表的实现中,我们就不直接让用户拿到链表的结构体 LList,防止其被篡改。进行如下操作:
typedef void* LinkList; //给 void* 起别名
则用户拿到的数据永远都是 void* 类型的,我们在操作的时候,再把void*转为struct LList类型。
- //链表的节点结构体
- typedef struct LinkNode
- {
- void* data;
- struct LinkNode* next;
- }LinkNode;
-
- //链表结构体
- typedef struct LList
- {
- struct LinkNode pHeader;
- int m_Size;
- }Llist;
-
- //void别名
- typedef void* LinkList;
- //初始化链表
- LinkList init_LinkList()
- {
- //分配内存
- Llist* mylist = malloc(sizeof(Llist));
- if (mylist == NULL)
- return;
- mylist->pHeader.data = NULL;
- mylist->pHeader.next = NULL;
- mylist->m_Size = 0;
-
- return mylist; //返回的是void*类型
- }
- //插入节点
- void insert_LinkList(LinkList list, int pos, void* data)
- {
- if (list == NULL)
- return;
- if (data == NULL)
- return;
- Llist* mylist = list;
- if (pos<0 || pos>mylist->m_Size)
- {
- //如果传进来的位置是无效位置
- pos = mylist->m_Size;
- }
- //创建临时节点
- LinkNode* pCurrent = &mylist->pHeader; //pHeader直接是一个节点,所以用&取到其地址
- for (int i = 0; i < pos; i++)
- {
- pCurrent = pCurrent->next;
- }
- //此时pCurrent就是插入位置的前驱节点
- //创建新节点
- LinkNode* newNode = malloc(sizeof(LinkNode));
- newNode->data = data;
- newNode->next = NULL;
- //建立节点之间的关系
- newNode->next = pCurrent->next;
- pCurrent->next = newNode;
- //更新链表长度
- mylist->m_Size++;
- }
- //遍历链表
- void foreach_LinkList(LinkList list,void(*myPrint)(void*))
- {
- if (list == NULL)
- return;
- //还原链表真实结构体
- Llist* mylist = list;
- LinkNode* pCurrent = mylist->pHeader.next;
- for (int i = 0; i < mylist->m_Size; i++)
- {
- myPrint(pCurrent->data); //我们不知道data的类型,所以这里使用用户自己的回调来打印
- pCurrent = pCurrent->next;
- }
- }
- //删除链表节点之按位置删除
- void removeByPos_LinkList(LinkList list, int pos)
- {
- if (list == NULL)
- return;
- Llist* mylist = list;
- if (pos<0 || pos>mylist->m_Size - 1)
- return; //无效位置直接返回
- //找到待删除位置的前驱节点的位置
- LinkNode* pCurrent = &mylist->pHeader;
- for (int i = 0; i < pos; i++)
- {
- pCurrent = pCurrent->next;
- }
- //PCurrent就是待删除节点的前驱节点
- //利用一个指针记录待删除节点
- LinkNode* pDel = pCurrent->next;
- //更改指针的指向
- pCurrent->next = pDel->next;
- //释放待删除节点
- free(pDel);
- pDel = NULL;
- mylist->m_Size--;
- }
- //删除链表节点之按值删除
- void removeByVal_LinkList(LinkList list, void* data, int(*myCompare)(void*))
- {
- if (list == NULL)
- return;
- if (data == NULL)
- return;
- //将list还原为真实链表结构体
- Llist* mylist = list;
- //创建两个辅助指针变量
- LinkNode* pPrev = &mylist->pHeader;
- LinkNode* pCurrent = mylist->pHeader.next;
- //遍历链表,找用户要删的数据
- for (int i = 0; i < mylist->m_Size; i++)
- {
- if (myCompare(data, pCurrent->data))
- {
- //开始删除,更改指针指向
- pPrev->next = pCurrent->next;
- free(pCurrent);
- pCurrent = NULL;
- mylist->m_Size--;
- break;
- }
- //辅助指针向后移动
- pPrev = pCurrent;
- pCurrent = pCurrent->next;
- }
- }
- //请空链表
- void clear_LinkList(LinkList list)
- {
- if (list == NULL)
- return;
- Llist* mylist = list;
- LinkNode* pCurrent = mylist->pHeader.next;
- for (int i = 0; i < mylist->m_Size; i++)
- {
- //记录下一个节点的位置
- LinkNode* pNext = pCurrent->next;
-
- free(pCurrent);
- pCurrent = pNext;
- }
- mylist->pHeader.next = NULL;
- mylist->m_Size = 0;
- }
-
- //销毁链表
- void destory_LinkList(LinkList list)
- {
- if (list == NULL)
- return;
- //先清空链表再销毁头结点
- clear_LinkList(list);
-
- free(list);
- list = NULL;
- }
另外涉及到:打印用户数据,以及比较用户数据。由于我们不知道用户的数据类型,所以需要用户自己提供回调函数。
- //回调函数之打印
- void printPerson(void* data)
- {
- Person* person = data;
- printf("Name:%s,Age:%d\n", person->name, person->age);
- }
- //回调函数之比较
- int ComparePerson(void* data1, void* data2)
- {
- Person* p1 = data1;
- Person* p2 = data2;
- return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
- }
- //给用户提供接口获取链表长度
- int size_LinkList(LinkList list)
- {
- if (list == NULL)
- return -1;
- Llist* mylist = list;
- return mylist->m_Size;
- }
- void test02()
- {
- //初始化链表
- LinkList mylist = init_LinkList(); //mylist本质是一个void*类型的,用来隐藏结构体里的数据属性
- //mylist->m_size = -1; //err,用户访问不到真实链表中的属性
-
- Person p1 = { "sun",18 };
- Person p2 = { "yu",19 };
- Person p3 = { "hang",17 };
- Person p4 = { "li",20 };
- Person p5 = { "hai",21 };
-
- insert_LinkList(mylist, 0, &p1);
- insert_LinkList(mylist, 1, &p2);
- insert_LinkList(mylist, -1, &p3);
- insert_LinkList(mylist, 0, &p4);
- insert_LinkList(mylist, 2, &p5);
- //遍历链表
- foreach_LinkList(mylist, printPerson);
- printf("--------------------\n");
- removeByPos_LinkList(mylist,1);
- foreach_LinkList(mylist, printPerson);
- printf("--------------------\n");
- removeByVal_LinkList(mylist,&p4,ComparePerson);
- foreach_LinkList(mylist, printPerson);
- printf("链表长度为:%d\n", size_LinkList(mylist));
- printf("--------------------\n");
- //清空链表
- clear_LinkList(mylist);
- printf("清空链表后链表长度为:%d\n", size_LinkList(mylist));
- //销毁链表
- destory_LinkList(mylist);
- mylist = NULL;
- }
- Name:li,Age:20
- Name:sun,Age:18
- Name:hai,Age:21
- Name:yu,Age:19
- Name:hang,Age:17
- --------------------
- Name:li,Age:20
- Name:hai,Age:21
- Name:yu,Age:19
- Name:hang,Age:17
- --------------------
- Name:hai,Age:21
- Name:yu,Age:19
- Name:hang,Age:17
- 链表长度为:3
- --------------------
- 清空链表后链表长度为:0