首页 > 其他分享 >02 线性结构——数组(特性、优缺点、基本使用、可变长的动态数组)

02 线性结构——数组(特性、优缺点、基本使用、可变长的动态数组)

时间:2024-10-13 14:20:33浏览次数:3  
标签:02 int 元素 优缺点 内存 数组 array size

目录

1 数组基础知识

1.1 认识数组

1.2 数组的声明

1.3 数组的特性

2 数组的优缺点

2.1 优点

2.1.1 查找容易

2.1.2 高效的访问和修改

2.2 缺点

2.2.1 插入和删除效率低

2.2.2 扩展相对繁琐

3 数组的基本使用

3.1 遍历数组

3.2 修改数组元素

4 可变长的动态数组

4.1 实现原理

4.1.1 分配内存(malloc)

 4.1.2 分配内存并初始化为零(calloc)

4.1.3 重新分配内存(realloc)

4.1.4 释放内存(free)

4.2 功能定义

4.3 功能实现


1 数组基础知识

1.1 认识数组

        数组是一种最常见、最简单的数据结构,由相同类型的数据元素组成数组需要先声明才能使用,且其长度一旦确定就不能改变数组的下标从零开始,即第一个元素的下标为 0,最后一个元素的下标为数组长度减 1。

1.2 数组的声明

        在不同的编程语言中,数组的声明和初始化方式有所不同。以下是一些常见编程语言的示例:

java:

int[] array = new int[10];  // 声明一个长度为 10 的整数数组
array[0] = 1;               // 第一个元素赋值为 1
array[9] = 10;              // 最后一个元素赋值为 10
System.out.println(array[0]);  // 输出第一个元素,结果为 1
System.out.println(array[9]);  // 输出最后一个元素,结果为 10

C:

int array[10];  // 声明一个长度为 10 的整数数组
array[0] = 1;   // 第一个元素赋值为 1
array[9] = 10;  // 最后一个元素赋值为 10
printf("%d\n", array[0]);  // 输出第一个元素,结果为 1
printf("%d\n", array[9]);  // 输出最后一个元素,结果为 10

1.3 数组的特性

        固定长度:数组的长度在声明时确定,之后不能改变。例如,声明一个长度为 10 的数组后,不能再增加或减少数组的长度。

        相同类型数组中的所有元素必须是相同的数据类型。例如,一个整数数组的所有元素都是整数。

        下标从零开始数组的下标从 0 开始计数。对于长度为 10 的数组,第一个元素的下标是 0,最后一个元素的下标是 9。

问题:为什么数组的下标从 0 开始计数?

        数组下标从 0 开始的主要原因是为了与指针的偏移量保持一致。在内存中,数组元素是连续存储的,下标 0 代表数组起始位置(即第一个元素),没有偏移。从 0 开始计数可以简化内存地址的计算,使得通过指针访问数组元素时,只需将下标值乘以元素大小并加到数组起始地址上即可得到目标元素的地址。


2 数组的优缺点

2.1 优点

2.1.1 查找容易

        时间复杂度为O(1):通过下标可以直接访问数组中的任意元素,查找操作非常高效。

        不需要额外申请或删除空间:数组的存储空间是预先分配好的,访问和修改元素时不需要额外的内存操作。

2.1.2 高效的访问和修改

        使用下标索引:通过下标位置索引可以非常高效地访问和修改任意元素,操作速度快。

2.2 缺点

2.2.1 插入和删除效率低

        插入操作:在数组中插入元素时,需要移动大量元素以保持元素空间的连续性。平均情况下,插入操作需要移动 n/2 个元素,时间复杂度为 O(n)。

问题:为什么平均情况下,插入操作需要移动 n/2 个元素?

        假设我们在一个长度为 n 的数组中随机选择一个位置插入一个新元素。我们可以考虑所有可能的插入位置,并计算每种情况下需要移动的元素数量,然后取平均值。

1. 插入位置和移动元素的数量

  • 如果在位置 0 插入,需要移动 n 个元素。
  • 如果在位置 1 插入,需要移动 n−1 个元素。
  • 如果在位置 2 插入,需要移动 n−2 个元素。
  • ...
  • 如果在位置 n−1 插入,需要移动 1 个元素。
  • 如果在位置 n 插入,需要移动 0 个元素。

2. 总移动次数

  • 总移动次数是所有可能插入位置上移动次数的总和:n+(n−1)+(n−2)+⋯+1+0
  • 这是一个等差数列的求和公式,总和为:

3. 平均移动次数:

  • 由于有 n+1 个可能的插入位置(从 0 到 n),平均移动次数为:

        因此,平均情况下,插入操作需要移动 n/2​ 个元素。这表明在数组中插入一个元素的平均时间复杂度为 O(n)。

        删除操作:在数组中删除元素时,同样需要移动大量元素以填补空缺。平均情况下,删除操作需要移动 (n−1)/2 个元素,时间复杂度为 O(n)。

问题:为什么平均情况下,删除操作需要移动 (n−1)/2 个元素?

        假设我们在一个长度为 n 的数组中随机选择一个位置删除一个元素。我们可以考虑所有可能的删除位置,并计算每种情况下需要移动的元素数量,然后取平均值。

1. 删除位置和移动元素的数量:

  • 如果在位置 0 删除,需要移动 n−1 个元素。
  • 如果在位置 1 删除,需要移动 n−2 个元素。
  • 如果在位置 2 删除,需要移动 n−3 个元素。
  • ...
  • 如果在位置 n−2 删除,需要移动 1 个元素。
  • 如果在位置 n−1 删除,需要移动 0 个元素。

2. 总移动次数

  • 总移动次数是所有可能删除位置上移动次数的总和:(n−1)+(n−2)+(n−3)+⋯+1+0
  • 这是一个等差数列的求和公式,总和为:

3. 平均移动次数:

  • 由于有 n 个可能的删除位置(从 0 到 n−1),平均移动次数为:

        因此,平均情况下,删除操作需要移动 (n−1)/2 个元素。这表明在数组中删除一个元素的平均时间复杂度为 O(n)。

2.2.2 扩展相对繁琐

        需要连续内存空间:扩展数组时,需要确保能够提供更大区域的连续内存空间。如果内存碎片较多,可能难以找到足够大的连续内存块。

        数据复制:扩展数组时,需要将原有数据复制到新的数组中,这会带来额外的时间和空间开销。


3 数组的基本使用

3.1 遍历数组

#include <stdio.h>

// 定义数组的大小
#define N 10

int main()
{
    // 初始化一个包含10个整数的数组
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 正向遍历数组
    puts("正向遍历数组");
    for (int i = 0; i < N; i++)
    {
        printf("%d  ", arr[i]);
    }
    printf("\n"); // 换行

    // 逆向遍历数组
    puts("逆向遍历数组");
    for (int i = N - 1; i >= 0; i--)
    {
        printf("%d  ", arr[i]);
    }
    printf("\n"); // 换行

    return 0;
}

        输出结果如下所示:

3.2 修改数组元素

        定义一个整型数组并赋初值,遍历数组让每个索引位置的数组元素都除以首元素,得到的结果作为该索引位置的新值。最后输出处理后的数组。

#include <stdio.h>
#define N 7

int main()
{
    // 定义并初始化一个包含 7 个整数的数组
    int arr[] = {12, 43, 65, 53, -38, 64, 2};

    // 获取数组的首元素
    int firstElement = arr[0];

    // 遍历并打印处理前的数组
    puts("处理前的数组:");
    for (int i = 0; i < N; i++)
    {
        printf("%d  ", arr[i]);
    }
    printf("\n");

    // 遍历数组,将每个元素除以首元素
    // 这种方法是错误的!
    // 这个循环遍历数组,并将每个元素除以 arr[0]。在每次迭代中,arr[0] 都会被重新读取。
    // 这意味着如果 arr[0] 在循环中被修改,后续的除法操作将使用新的 arr[0] 值(即 1)。
    // for (int i = 0; i < N; i++)
    // {
    //     arr[i] = arr[i] / arr[0];  // 错误行为 !!!
    // }

    // 解决方案 1
    // 使用临时变量先将首元素数据保存起来
    // 后面直接除以临时变量即可
    // for (int i = 0; i < N; i++)
    // {
    //     arr[i] = arr[i] / firstElement;
    // }

    // 解决方案 2
    // 倒着遍历数组
    for (int i = N - 1; i >= 0; i--)
    {
        arr[i] = arr[i] / arr[0];
    }

    // 遍历并打印处理后的数组
    puts("将每个元素除以首元素,处理后的数组:");
    for (int i = 0; i < N; i++)
    {
        printf("%d  ", arr[i]);
    }

    printf("\n");

    return 0;
}

        输出结果如下所示:


4 可变长的动态数组

4.1 实现原理

        “可变长的动态数组” 是一种数据结构,它允许在运行时根据需要动态地调整数组的大小,而不需要提前指定固定的大小。这种动态数组通常被称为动态数组动态分配数组动态增长数组动态内存数组

        在 C 语言中,动态数组是通过使用指针和内存分配函数来实现的,常见的内存分配函数包括 malloc、realloc 和 free。以下是一些相关的概念和操作:

4.1.1 分配内存(malloc)

函数原型:

        malloc() 函数用于在程序运行时动态分配一块连续的内存空间。这是 C 语言中常用的动态内存分配函数之一,通常与 free() 函数一起使用,以确保内存的正确管理和释放。

#include <stdlib.h>
void *malloc(size_t size);
  • size:要分配的内存块的大小,以字节为单位。
  • 如果内存分配成功,返回一个 void 指针,指向新分配内存块的起始地址
  • 如果内存分配失败(例如内存不足),返回一个空指针 NULL

动态分配整型数据的空间:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    // 在栈区直接创建局部变量
    int num = 120;
 
    int *p = NULL;
    // 动态分配整型数据的空间
    // malloc(sizeof(int)) 请求分配一个 int 类型大小的内存块
    // (int *) 是显式类型转换,将 void 指针转换为 int 指针
    // p 指向新分配内存块的起始地址
    p = (int *)malloc(sizeof(int));
 
    // 检查内存是否分配成功
    if (p == NULL)
    {
        printf("内存分配失败\n");
        return 1; // 退出程序
    }
 
    // p = &num; 不要这样操作,这相当于修改了指针 p 的指向,就没有用到上面动态分配的空间
 
    // 使用解引用赋值并输出
    *p = num;
    printf("p指向的地址(堆区):%p\n", (void *)p);
    printf("局部变量num的地址(栈区):%p\n", (void *)&num);
    printf("p指向的值:%d\n", *p); // 120
 
    // 释放分配的内存,避免内存泄漏
    free(p);
    p = NULL; // 释放后将指针设为 NULL,避免悬挂指针
 
    return 0;
}

        输出结果如下所示:

        错误做法:

动态分配数组空间:
        在 C 语言中,malloc() 函数不仅可用于分配单个变量的内存,还可以用于动态分配数组的内存(指针的偏移)。以下是一个示例,展示了如何使用 malloc() 函数动态分配整型数组的内存,并对其进行操作。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *p = NULL; // 定义整型指针
    int n = 5;     // 定义数组长度
    // int array[n];  错误,表达式必须含有常量值

    // 动态分配内存,将地址赋给指针 p
    // malloc(n * sizeof(int)) 请求分配一个大小为 n * sizeof(int) 的内存块,即 n 个 int 类型的内存
    // (int *) 是显式类型转换,将 void 指针转换为 int 指针
    // p 指向新分配内存块的起始地址
    p = (int *)malloc(n * sizeof(int));

    // 判断是否分配成功
    if (p == NULL)
    {
        printf("内存分配失败\n");
        return 1; // 退出程序
    }

    // 给数组元素赋值
    for (int i = 0; i < n; i++)
    {
        // p[i] = i * 10;  // p[i] 等价于 *(p + i)
        *(p + i) = i * 10;
    }

    // 输出数组的元素
    for (int i = 0; i < n; i++)
    {
        // printf("p[%d] = %d\n", i, p[i]); // p[i] 等价于 *(p + i)
        printf("p[%d] = %d\n", i, *(p + i));
    }

    // 释放分配的内存,避免内存泄漏
    free(p);
    p = NULL; // 释放后将指针设为 NULL,避免悬挂指针

    return 0;
}

        输出结果如下所示:

 4.1.2 分配内存并初始化为零(calloc)

        calloc() 函数用于在程序运行时动态分配内存,并将分配的内存初始化为零。这是 C 语言中常用的动态内存分配函数之一,通常与 free() 函数一起使用,以确保内存的正确管理和释放。

#include <stdlib.h>
void *calloc(size_t numElements, size_t sizeOfElement);
  • numElements:要分配的元素的数量。
  • sizeOfElement:每个元素的大小(以字节为单位)。
  • 如果内存分配成功,返回一个 void 指针,指向新分配内存块的起始地址。
  • 如果内存分配失败(例如内存不足),返回一个空指针 NULL。

基本用法:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int *p = NULL; // 定义整型指针
    int n = 5;     // 定义数组长度
 
    // 动态分配内存并初始化为零,将地址赋给指针 p
    // calloc(n, sizeof(int)) 请求分配一个大小为 n * sizeof(int) 的内存块,并将每个字节初始化为零
    // (int *) 是显式类型转换,将 void 指针转换为 int 指针
    p = (int *)calloc(n, sizeof(int));
 
    // 判断是否分配成功
    if (p == NULL)
    {
        printf("内存分配失败\n");
        return 1; // 退出程序
    }
 
    // 输出数组的元素的值
    for (int i = 0; i < n; i++)
    {
        printf("p[%d] = %d\n", i, p[i]); // 全是 0
    }
 
    // 给数组元素赋值
    for (int i = 0; i < n; i++)
    {
        p[i] = i * 10;
    }
 
    // 输出数组的元素
    for (int i = 0; i < n; i++)
    {
        printf("p[%d] = %d\n", i, p[i]); // 0 10 20 30 40
    }
 
    // 释放分配的内存,避免内存泄漏
    free(p); // 简单处理
    p = NULL; // 释放后将指针设为 NULL,避免悬挂指针
 
    return 0;
}

        输出结果如下所示:

4.1.3 重新分配内存(realloc)

        realloc() 函数用于重新分配 malloc() 或 calloc() 函数所获得的内存块的大小。这在需要动态调整内存大小时非常有用。

#include <stdlib.h>
void *realloc(void *ptr, size_t size);
  • ptr:要重新分配的内存块的指针。
  • size:新的内存块的大小(以字节为单位)。
  • 返回一个指向重新分配内存块的指针。如果内存重新分配成功,返回的指针可能与原始指针相同,也可能不同。
  • 如果内存分配失败,返回一个空指针 NULL。
  • 如果在原内存块上进行缩减,通常返回的地址与原来的地址相同。

基本用法:

        以下是一个示例代码,展示了如何使用 realloc() 函数动态调整内存大小,并使用 _msize() 函数获取指定内存块的大小:

        _msize() 函数用于获取指定内存块的大小,但请注意,这个函数不是标准 C 库的一部分,而是特定于某些平台(如 Windows)。在其他平台上,可能需要使用其他方法来获取内存块的大小。

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
 
int main()
{
    // 声明指针
    int *p = NULL;
 
    // 分配内存
    // 使用 malloc() 函数分配初始内存,大小为 100 * sizeof(int)
    p = (int *)malloc(sizeof(int) * 100);
    if (p == NULL)
    {
        printf("初始内存分配失败\n");
        return 1;
    }
    // 使用 _msize() 函数获取分配的内存大小,并输出指针地址和内存大小
    printf("p=%p, size:%zu 字节\n", p, _msize(p)); // 400
 
    // 调整内存大小
    p = (int *)realloc(p, sizeof(int) * 2000);
    if (p == NULL)
    {
        printf("内存重新分配失败\n");
        return 1;
    }
    // 使用 _msize() 函数获取分配的内存大小,并输出指针地址和内存大小
    printf("p=%p, size:%zu 字节\n", p, _msize(p)); // 8000
 
    // 再次调整内存大小
    // 如果在原内存块上进行缩减,通常返回的地址与原来的地址相同
    p = (int *)realloc(p, sizeof(int) * 200);
    if (p == NULL)
    {
        printf("内存重新分配失败\n");
        return 1;
    }
    // 使用 _msize() 函数获取分配的内存大小,并输出指针地址和内存大小
    printf("p=%p, size:%zu 字节\n", p, _msize(p)); // 800
 
    // 释放分配的内存,避免内存泄漏
    free(p); // 简单处理
    p = NULL; // 释放后将指针设为 NULL,避免悬挂指针
 
    return 0;
}

        输出结果如下所示:

4.1.4 释放内存(free)

        内存泄漏是指在程序运行过程中,动态分配的内存空间没有被正确释放,导致系统中的可用内存逐渐减少,直到耗尽系统可用的内存资源。内存泄漏不仅会影响程序的性能,还可能导致程序崩溃或系统不稳定。

        free() 函数用于释放动态分配的内存,以便将内存返回给操作系统,防止内存泄漏

#include <stdlib.h>
void free(void *ptr);
  • ptr:指向要释放的内存块的指针。ptr 必须是通过 malloc()、calloc() 或 realloc() 动态分配的内存块地址。

基本用法:

        以下是一个示例代码,展示了如何正确使用 malloc() 和 free() 函数:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = NULL;  // 定义整型指针

    // 动态分配内存
    p = (int *)malloc(sizeof(int));
    if (p == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 使用分配的内存
    *p = 120;
    printf("p指向的地址:%p\n", (void *)p);
    printf("p指向的值:%d\n", *p);

    free(p);
    p = NULL;  // 释放后将指针设为 NULL

    return 0;
}

4.2 功能定义

        动态数组允许在运行时根据需要动态地调整数组的大小,而不需要提前指定固定的大小。以下是需要实现的主要函数:

功能函数签名描述
初始化动态数组void initDynamicArray(DynamicArray *array, size_t initialCapacity)初始化动态数组,设置初始容量。
释放动态数组内存void destroyDynamicArray(DynamicArray *array)释放动态数组占用的内存。
调整动态数组内存大小void resizeDynamicArray(DynamicArray *array, size_t newCapacity)调整动态数组的容量。
获取动态数组长度size_t getLength(const DynamicArray *array)获取动态数组中元素的个数。
在指定位置插入新元素void insertAt(DynamicArray *array, size_t index, int element)在指定位置插入新元素(假设是 int 类型)。
在末尾插入新元素void insertEnd(DynamicArray *array, int element)在动态数组的末尾插入新元素(假设是 int 类型)。
删除指定位置的元素int deleteAt(DynamicArray *array, size_t index)删除指定位置的元素并返回被删除的元素假设是 int 类型)。
删除末尾的元素int deleteEnd(DynamicArray *array)删除动态数组末尾的元素并返回被删除的元素假设是 int 类型)。
遍历所有元素void print(DynamicArray *array)遍历并打印动态数组中的所有元素。
  • size_t 是 C 标准库中定义的一种无符号整数类型,通常用于表示对象的大小或数组的索引。
  • 上述功能是针对元素类型为 int 的数组定义的。在实际编程中,可以根据具体需求对元素类型进行调整。

4.3 功能实现

#include <stdio.h>
#include <stdlib.h>

// 动态数组结构体
typedef struct
{
    int *data;       // 指向动态数组的指针,以 int 类型数组为例
    size_t size;     // 当前数组中的元素个数
    size_t capacity; // 当前数组的容量(可以容纳的最大元素个数)
} DynamicArray;

// 初始化动态数组
void initDynamicArray(DynamicArray *array, size_t initialCapacity)
{
    array->data = (int *)malloc(initialCapacity * sizeof(int)); // 分配初始内存
    array->size = 0;                                            // 初始化元素个数为 0
    array->capacity = initialCapacity;                          // 设置初始容量
    printf("成功初始化动态数组^_^容量为:%zu\n", initialCapacity);
}

// 释放动态数组内存
void destroyDynamicArray(DynamicArray *array)
{
    free(array->data);   // 释放动态数组内存
    array->data = NULL;  // 将指针设为 NULL,避免悬挂指针
    array->size = 0;     // 重置元素个数为 0
    array->capacity = 0; // 重置容量为 0
    puts("成功释放动态数组内存^_^");
}

// 调整动态数组内存大小
void resizeDynamicArray(DynamicArray *array, size_t newCapacity)
{
    // 这里不用讨论新容量 newCapacity 的合法性
    // 因为是在扩容时,才会调用此函数,而传递过来的容量都是正数
    array->data = (int *)realloc(array->data, newCapacity * sizeof(int)); // 调整数组内存大小
    array->capacity = newCapacity;                                        // 更新容量

    // 如果新容量小于当前元素个数
    if (newCapacity < array->size)
    {
        // 输出一条错误信息到标准错误流 stderr,这条信息表示新容量小于当前元素个数,数据将被截断
        // fprintf(stderr, "New capacity is less than current size, truncating data\n");

        // 1.可以截断数据
        // 2.或者选择抛出异常
        // 3.或者返回一个错误码,让调用者决定如何处理
        // 4.或者拒绝缩小容量,保持当前容量不变
        // 处理方式视实际情况而定!
        puts("新容量小于当前元素个数,将截断数据!");
        array->size = newCapacity; // 这里我们选择方式:1.数据截断(减少元素个数)
    }
    printf("成功调整动态数组内存大小^_^,新容量为:%zu\n", newCapacity);
}

// 获取动态数组长度(元素个数)
size_t getLength(const DynamicArray *array)
{
    return array->size; // 返回数组中的元素个数
}

// 在指定位置插入新元素(需重点理解 !!!)
void insertAt(DynamicArray *array, size_t index, int element)
{
    // 需要判断指定插入位置的合法性,合法范围:[0,array->size]
    // 注意:在位置 array->size 插入元素,表示末尾插入
    if (index < 0 || index > array->size)
    {
        puts("指定的插入位置不合法,插入失败!");
        return; // 忽略无效的插入位置
    }

    // 如果内部存储数据的数组存满了
    if (array->size >= array->capacity)
    {
        // 容量不足,需要扩容
        size_t newCapacity = array->capacity * 2; // 扩大 1 倍,也可以使用左移运算符: << 1
        puts("数组已满,正在扩容,请稍后重新尝试!");
        // 这里传入 array 时,不要在前面加取地址符 &,因为在这个函数里面,它已然是一个指针
        resizeDynamicArray(array, newCapacity); // 调整动态数组内存大小
    }

    // 插入元素的具体操作
    // 将 index 及其后面的元素依次后移

    // 下面这段代码会报错
    // 因为 array->size 初始值为 0,执行减一操作后为 -1(补码表示很大的数),所以循环会出问题
    // 如果 array->size 初始值不是 0,那么下面的循环就是正确的
    // for (size_t i = array->size - 1; i >= index; i--)
    // {
    //     array->data[i + 1] = array->data[i]; // 后移元素以腾出插入位置
    // }

    // 下面这段代码是正确的
    // 如果 array->size 初始值不是 0,上面那个循环与这个循环等价
    for (size_t i = array->size; i > index; i--)
    {
        array->data[i] = array->data[i - 1]; // 后移元素以腾出插入位置
    }

    // 修改 index 位置数据
    array->data[index] = element; // 在指定位置插入新元素
    array->size++;                // 更新元素个数
    printf("成功在指定位置插入新元素^_^,元素内容:%d\n", element);
}

// 在末尾插入新元素
void insertEnd(DynamicArray *array, int element)
{
    // 这里传入 array 时,不要在前面加取地址符 &,因为在这个函数里面,它已然是一个指针
    // 在尾部插入,应该是最后一个元素后面的一个位置,所以传入索引位置:array->size
    insertAt(array, array->size, element); // 在末尾插入新元素
}

// 删除指定位置的元素并返回被删除的元素(需重点理解 !!!)
int deleteAt(DynamicArray *array, size_t index)
{
    // 需要判断删指定删除位置的合法性,合法范围:[0,array->size -1],不能取到 array->size
    if (index < 0 || index >= array->size)
    {
        return -12345678; // 返回一个指定码
    }

    // 如果数组此时为空,即 array->size = 0
    // 在上面的  if (index < 0 || index >= array->size) 就被拦截了

    int deletedElement = array->data[index]; // 获取被删除的元素

    // 将 index 后面的元素依次前移
    for (size_t i = index + 1; i <= array->size - 1; i++)
    {
        array->data[i - 1] = array->data[i]; // 前移元素以填补删除位置
    }
    // 与上面的循环等价
    // for (size_t i = index; i < array->size - 1; i++)
    // {
    //     array->data[i] = array->data[i + 1]; // 前移元素以填补删除位置
    // }
    array->size--; // 更新元素个数

    puts("成功在指定位置删除元素^_^");
    return deletedElement; // 返回被删除的元素
}

// 删除末尾的元素并返回被删除的元素
int deleteEnd(DynamicArray *array)
{
    // 这里传入 array 时,不要在前面加取地址符 &,因为在这个函数里面,它已然是一个指针
    // 删除末尾的元素,所以传入索引位置:array->size - 1
    return deleteAt(array, array->size - 1); // 删除末尾的元素
}

// 遍历所有的元素
void print(DynamicArray *array)
{
    puts("成功遍历数组,数组内容如下所示:");
    for (int i = 0; i < array->size; i++)
    {
        printf("%d  ", array->data[i]);
    }
    printf("\n");
}

int main()
{
    DynamicArray myArray; // 声明动态数组(结构体变量)

    // 初始化动态数组
    initDynamicArray(&myArray, 2);

    // 向动态数组尾部插入元素
    insertEnd(&myArray, 1); // 插入整数 1
    insertEnd(&myArray, 2); // 插入整数 2
    // 打印动态数组当前长度
    printf("动态数组当前长度(元素个数):%zu\n", getLength(&myArray));
    // 打印动态数组的内容
    print(&myArray);
    puts("------------------------------------");

    // 在索引 0 的位置插入元素 3
    insertAt(&myArray, 0, 3);
    // 再次打印动态数组当前长度
    printf("动态数组当前长度:%zu\n", getLength(&myArray));
    // 打印动态数组的内容
    print(&myArray);
    puts("------------------------------------");

    // 在索引 100 的位置插入元素 999(错误插入)
    insertAt(&myArray, 100, 999); // 错误插入
    puts("------------------------------------");

    // 删除索引 1 的元素
    int returnCode = deleteAt(&myArray, 1);
    if (returnCode = -12345678)
    {
        puts("指定的删除位置不合法,删除失败!");
    }
    else
    {
        printf("删除索引 1 的元素,该元素是:%d\n", returnCode);
    }
    // 再次打印动态数组当前长度
    printf("动态数组当前长度:%zu\n", getLength(&myArray));
    // 打印动态数组的内容
    print(&myArray);
    puts("------------------------------------");

    // 删除动态数组末尾元素
    printf("删除动态数组末尾元素,该元素是%d\n", deleteEnd(&myArray));
    // 再次打印动态数组当前长度
    printf("动态数组当前长度:%zu\n", getLength(&myArray));
    // 打印动态数组的内容
    print(&myArray);
    puts("------------------------------------");

    // 删除索引 666 的元素(错误删除)
    returnCode = deleteAt(&myArray, 666); // 错误删除
    if (returnCode = -12345678)
    {
        puts("指定的删除位置不合法,删除失败!");
    }
    else
    {
        printf("删除索引 666 的元素,该元素是:%d\n", returnCode);
    }
    puts("------------------------------------");

    // 释放动态数组内存
    destroyDynamicArray(&myArray);
    printf("动态数组内存释放完成\n");

    return 0;
}

4.3.1 注意事项

        在插入元素时后移元素和在删除元素时前移元素的循环需要特别注意,如下所示:

插入元素时后移元素

// 插入元素的具体操作
// 将 index 及其后面的元素依次后移

// 下面这段代码会报错
// 因为 array->size 初始值为 0,执行减一操作后为 -1(补码表示很大的数),所以循环会出问题
// 如果 array->size 初始值不是 0,那么下面的循环就是正确的
// for (size_t i = array->size - 1; i >= index; i--)
// {
//     array->data[i + 1] = array->data[i]; // 后移元素以腾出插入位置
// }

// 下面这段代码是正确的
// 如果 array->size 初始值不是 0,上面那个循环与这个循环等价
for (size_t i = array->size; i > index; i--)
{
    array->data[i] = array->data[i - 1]; // 后移元素以腾出插入位置
}

// 修改 index 位置数据
array->data[index] = element; // 在指定位置插入新元素
array->size++;                // 更新元素个数

删除元素时前移元素

// 将 index 后面的元素依次前移
// for (size_t i = index + 1; i <= array->size - 1; i++)
// {
//     array->data[i - 1] = array->data[i]; // 前移元素以填补删除位置
// }

// 与上面的循环等价
for (size_t i = index; i < array->size - 1; i++)
{
    array->data[i] = array->data[i + 1]; // 前移元素以填补删除位置
}
array->size--; // 更新元素个数

标签:02,int,元素,优缺点,内存,数组,array,size
From: https://blog.csdn.net/qq_53139964/article/details/142877424

相关文章

  • 2024-2025-1 20241322 《计算机基础与程序设计》第3周学习总结
    2024-2025-120241322《计算机基础与程序设计》第3周学习总结作业信息这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标<数字分类与计数法......
  • 2024.10.13 1332版
    起于《海奥华预言》的思考◆地球管理结构和参考持续更新中...... 英文地址:https://github.com/zhuyongzhe/Earth/tags中文地址:https://www.cnblogs.com/zhuyongzhe85作者:朱永哲 ---------------------------------------------------------------------------------......
  • 洛谷P8818 [CSP-S 2022] 策略游戏
    Problem给出两个数组A,B,进行q次询问,每次分别给出这两个数组的某个区间l1,r1,l2,r2,也就是\(A_{l1\simr1}\)与\(B_{l2\simr2}\),有两位同学小L和小Q分别从A,B的以上区间中选一个数,而两数乘积为此次操作的分数,小L希望分数大,小Q希望分数小,请问他们每次以最优策略进行游戏,分数将会......
  • 2024-2025-1 20241421 《计算机基础与程序设计》第三周学习总结
    这个作业属于哪个课程2024-2025-1-计算机基础与程序设计)这个作业要求在哪里https://www.cnblogs.com/rocedu/p/9577842.html#WEEK03(https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homework/13276))这个作业的目标1、数字分类与计数法位置计数法,2、进制转......
  • 2024-2025-1 学号:20241303 《计算机基础与程序设计》第三周学习总结
    作业信息这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如[2024-2025-1计算机基础与程序设计第三周作业]这个作业的目标<写上具体方面>加入云班课,参考本周学习资源;自学教材;计算机科学概论(第七版)第2章,第3......
  • 2024-2025-1学期 20241427 《计算机基础与程序设计》第3周学习总结
    |这个作业属于哪个课程|<班级的链接>(如2024-2025-1-计算机基础与程序设计)||这个作业要求在哪里|<作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)||这个作业的目标|学会数字分类与计数法,以及进制转化,数字化和门的应用||作业正文||教材学习内容总结《计算机科......
  • CSP-S 2024 前总结与反思
    做题过于依赖题解与讨论区,缺少行之有效的方法。积累较少,trick大多都不会。现状是思维题对于偏思维难度的想不出正解,偏分讨难度的不会实现;码力题是确实还少点劲头,规划、逻辑较为混乱,没有使用草稿纸的习惯。想把去年的大模拟补了。模拟赛忽高忽低。原因在于策略以及码力问题......
  • 2024-2025-1学期 20241427 《计算机基础与程序设计》第3周学习总结
    作业信息这个作业属于哪个课程https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP这个作业要求在哪里(https://www.cnblogs.com/rocedu/p/9577842.html#WEEK03)这个作业的目标学会数字分类与计数法,以及进制转化,数字化和门的应用作业正文https://i.cnblogs......
  • 『模拟赛』多校A层冲刺NOIP2024模拟赛06
    Rank比较还行A.小Z的手套(gloves)签。最大值最小,一眼二分答案。双指针check一下就完了,复杂度\(\mathcal{O(n\logn)}\)。点击查看代码#include<bits/stdc++.h>#definefo(x,y,z)for(registerint(x)=(y);(x)<=(z);(x)++)#definefu(x,y,z)for(regis......
  • 2024-2025-3-计算机基础与程序设计
    学期(如2024-2025-3)学号(20241404)《计算机基础与程序设计》第3周学习总结作业信息这个作业属于哪个课程https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homework/13265这个作业要求在哪里<作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)......