上一讲链接:动态数组初始化实现— 2021-08-08
动态数组的插入:
首先我们需要知道设计动态数组的初衷是防止当数组中的元素大小达到了该数组的最大容量值时,也就意味着此时再想往该数组中插入新元素时是不能满足的,所以动态数组设计到堆区就是这个原因。
那在上一讲中,我们只是进行了动态数组的初始化,并没有向该数组中插入新元素,所以接下来开始分析代码:
void insert_dynamicArray(struct dynamicArray * arr, int pos, void * data)
{
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 insert_dynamicArray(struct dynamicArray * arr, int pos, void * data)
首先先解释下该插入函数的几个入口参数:
struct dynamicArray * arr
:相当于是被插入新元素的数组,通过指针arr进行调用。int pos
:相当于是新元素插入到数组中的具体位置。void * data
相当于插入数组中的新元素。
if (arr == NULL)
{
return;
}
if (data == NULL)
{
return;
}
同理,为了算法的严谨性,我们需要判断该函数的入口参数是否有效,如果有效,程序则继续运行,如果无效,则会提前退出该函数。
if (pos < 0 || pos > arr->m_Size)
{
pos = arr->m_Size;
}
我们在进行插入新元素时,需要考虑插入的位置是否与数组当前状态下的元素分布冲突,众所周知,数组元素都是从0
开始进行计数的,如果插入的位置小于0
肯定是不允许出现这种情况的,另外,我们一般在进行放置元素或者插入元素时,元素在数组中的分布一般都是连续分布的,不能出现此时数组中的元素大小为5
,我们插入新元素时,插入的位置在7
,这是不允许的,一般就在数组元素大小的后一位依次类推。所以上述代码就是为了防止出现这两种情况,如果出现了,则会自动将该元素放置在当前数组元素的后一位,即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;
}
此时如果我们数组中的元素大小正好达到了数组所能承受的最大容量值时,意味着我们需要进行动态扩展了,这也是动态数组的设计的意义所在。先判断arr->m_Size
和arr->m_Capacity
这两者之间的值,如果符合上述条件,则会进行动态扩展。具体过程在上一讲中有提到,在这一讲中只分析代码部分。
- 首先我们需要计算下申请新的动态数组的内存空间大小,也就是最终数组拓展到多大。在代码中我们只是扩大了原来的
2
倍,同样也可以扩大为原来的3
倍,甚至于更多,看实际应用场景决定。 - 同样我们需要创建一个指针可以访问该动态数组的各个元素,也就是首元素的地址。由上一讲可知,每个元素的数据类型时
void *
,且实际容量为newCapacity
,那最终在堆区申请的内存大小为sizeof (void *)* newCapacity
,并且将其地址传送给二级指针newSpace
,其数据类型为void **
。 - 成功创建完新的动态数组后,我们就可以将原来的数据拷贝到新空间下,通过
memcpy
函数进行拷贝的。具体该函数的函数原型可以自行去百度,百度一大堆。 - 成功拷贝后,我们为了防止内存泄漏,需要释放掉原有的空间。
- 同时,将结构体中的地址重新指向到新创建的动态数组的首元素的地址,即第二步创建的二级指针
newSpace
。 - 同时更新新数组的容量大小,并最终赋值给结构体中的
m_Capacity
。
for (int i = arr->m_Size - 1; i >= pos; i--)
{
arr->pAddr[i + 1] = arr->pAddr[i];
}
成功进行数组扩展后,接下来就涉及到元素的插入了,在这里可以举个例子方便大家理解,如果我们想要插入的元素位置是当前数组的第3
位,前提是原始数组中的数据已经成功拷贝到当前数组中,也就是arr->m_Size
不为0
。可是如果直接插入的话,当前数组的原始第3位数据就有可能被覆盖,造成了数据的丢失,一般情况下我们这样操作,首先先将当前数组中的arr->m_Size
所在的数向后移动一位,也就是当前数组中的已存在元素的最后一个往后移动一位。然后倒数第二个元素也往后移动一位,其他位以此类推,直到原始第三位移动到第4
位截止。
arr->pAddr[pos] = data;
arr->m_Size++;
成功移位后,便开始将新元素插入到数组中,并且当前数组的大小累加一次。
动态数组的遍历:
void foreach_dynamicArray(struct dynamicArray * arr, void(*myPrint)(void *))
{
if (arr == NULL)
{
return;
}
if (myPrint == NULL)
{
return;
}
for (int i = 0; i < arr->m_Size; i++)
{
myPrint(arr->pAddr[i]);
}
}
成功插入元素后,我们需要验证下最终插入的结果是否符合我们的预期,所以我们需要对该数组进行遍历操作,这个函数里面涉及到了回调函数的概念,为了让大家更好的理解,特意为大家找了一篇讲的不错的文章供大家观看:C 语言回调函数详解
同理,为了算法的严谨性,我们需要判断该函数的入口参数是否有效,如果有效,程序则继续运行,如果无效,则会提前退出该函数。
然后通过回调函数myPrint
对该数组的每一个元素进行打印,从而验证效果。
结束语
如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!