九、指针和内存管理

文章目录

      • 指针和内存管理
        • 1. 指针
          • 1.1 函数指针【重点】
          • 1.2 函数指针案例
        • 2. 内存管理
          • 2.1 内存申请和释放
          • 2.2 常用函数接口
          • 2.3 malloc 案例
          • 2.4 calloc 案例
          • 2.5 realloc 案例
          • 2.6 内存管理案例数组扩容
          • 2.6 内存管理案例数组截取

指针和内存管理

1. 指针
1.1 函数指针【重点】

函数得分类:

  • 有参数有返回值、无参数有返回值、有参数无返回值、无参数无返回值

区分函数的特征是根据【返回值类型】【参数类型】来确定的

函数名是调用当前函数的重要名称,同时【函数名是当前指针的指针常量】,存储当前函数在内存【方法区/函数区】的空间首地址。

#include 

/*
计算得到两个 int 类型数据之和

@param  n1 用户提供的 int 类型数据
@param  n2 用户提供的 int 类型数据
@return 两个 int 类型数据之和,返回值数据类型为 int 类型
*/
int add(int n1, int n2);

int main(int argc, char const *argv[])
{
    /*
    【重点1】
        函数关注的点是函数的【返回值数据类型】【形式参数列表数据类型】
    【重点2】
        函数名指针变量,存储当前函数在内存【方法去/函数区】首地址
    */
    printf("add = %p\n", add);

    /*
    根据当前函数的【类型】,自定函数指针,存储函数空间首地址
    自定义函数指针的格式:
    返回值类型:
        int
    形式参数列表数据以及顺序
        (int, int) 参数名称不重要
    函数指针的名称和固定格式
        (* 函数指针变量名称)
        小括号包含 * 指针变量标记是为了优化组合关系,
        如果没有对应函数返回值类型从 int 转换 int *

    可以存储 add 函数空间首地址函数指针变量
    */
    int (*pf) (int, int) = add;

    /*
    pf 就是函数指针
    要求:
        1. 对应函数的返回值数据类型必须是 int 类型
        2. 对应的函数形式参数列表必须是(int, int)
    */
   // 函数指针,可以当作对应函数使用
    int ret = pf(10, 20);
    printf("ret = %d\n", ret);
    
    return 0;
}

int add(int n1, int n2)
{
    return n1 + n2;
}

九、指针和内存管理_第1张图片

1.2 函数指针案例

接口思维

  • 规范限制
  • 接口可以做什么,取决于接口对应的连接目标是什么
  • 对应 USB 接口概念
#include 

int add(int n1, int n2);
int sub(int n1, int n2);
int mul(int n1, int n2);
int div(int n1, int n2);

/*
多功能函数,可以处理用户提供的两个 int 类型数据,
处理方式由函数指针 pf 对应的函数决定

@param n1 用户提供的 int 类型数据
@param n2 用户提供的 int 类型数据
@param pf 处理两个 int 类型数据对应的函数指针,决定当前函数最终功能
@return 处理数据之后的最终反馈结果
*/
int opreation(int n1, int n2, int (*pf)(int, int));

int main(int argc, char const *argv[])
{
    int (*pf)(int, int) = NULL;

    // pf 函数指针指向不同的函数,处理结果的结果不同,执行操作一致
    pf = add;
    printf("ret = %d\n", pf(10, 20));// 30

    pf = sub;
    printf("ret = %d\n", pf(30, 10));// 20

    pf = mul;
    printf("ret = %d\n", pf(30, 30));// 900

    pf = div;
    printf("ret = %d\n", pf(200, 40));// 5
    printf("--------------------------------\n");

    // 调用函数指针作为函数参数的 operation 函数
    /*
    函数指针作为函数的参数,可以直接提供符合函数指针要求的函数名称
    作为实际参数,提供的数据本质是函数在内存【方法区/函数区】的空间
    首地址,CPU 可以根据函数指针对应的地址,访问对应的二进制可执行
    内存空间内容,执行目标函数
    */
    int ret = opreation(10, 20, add);// 30
    printf("ret = %d\n", ret);
    ret = opreation(50, 30, sub);// 20
    printf("ret = %d\n", ret);
    ret = opreation(30, 40, mul);// 1200
    printf("ret = %d\n", ret);
    ret = opreation(40, 10, div);// 4
    printf("ret = %d\n", ret);


    return 0;
}

int opreation(int n1, int n2, int (*pf)(int, int))
{
    // 函数指针可以直接当前函数使用
    return pf(n1, n2);
}

int add(int n1, int n2)
{
    return n1 + n2;
}

int sub(int n1, int n2)
{
    return n1 - n2;
}

int mul(int n1, int n2)
{
    return n1 * n2;
}

int div(int n1, int n2)
{
    return n1 / n2;
}
2. 内存管理
2.1 内存申请和释放

内存申请是为了解决程序所需变量占用内存空间较大时,像数组、链表······,如果存储在栈区,对于整个内存空间不友好,再者是栈区得内存空间较小。针对于这个问题,可以利用申请【内存堆区】空间得方式,存储对应的目标数据内容,利用指针存储对应申请空间得首地址,方便之后的操作。

通过内存申请函数在内存【堆区】做开辟的空间,需要通过释放函数告知系统:当前内存已经归还!

2.2 常用函数接口

在 C 语言中,申请内存的常用函数时 malloc、calloc 和 realloc。

在使用这些函数的时候,需要在代码前导入头文件 stdlib.h

1. malloc 函数用于在堆区上分配指定大小得内存空间

malloc 的声明如下:

void* malloc(size_t size);
/*
malloc 函数接受一个参数 size,表示需要分配的字节数。
如果分配成功,它会返回一个指针分配内存的指针;
如果分配失败,返回 NULL。
*/

2. calloc 函数用于在堆区上分配指定数量的指定大小的内存空间,并将分配的内存全部设置为零

calloc 的声明如下:

void* calloc(size_t num, size_t size);
/*
calloc 函数接受两个参数 num 和 size,分别表示需要分配的元素个数和每个元素的大小。
如果分配成功,它会返回一个指针分配内存的指针;
如果分配失败,返回 NULL。
*/

3. realloc 函数用于重新分配已经申请的内存空间大小

realloc 的声明如下:

void* realloc(void* ptr, size_t size);
/*
realloc 函数接受两个参数 ptr 和 size,其中 ptr 时先前使用
malloc calloc 或 realloc 返回的指针,size 表示需要重新分配的字节数。
如果分配成功,它会返回一个指针分配内存的指针;
如果分配失败,返回 NULL。
【注意】
realloc 函数可能会在原地或者重新分配内存。
*/

4. free 函数释放内存

以上这些函数使用后应该通过调用 free 函数来释放内存,以免内存泄漏。

free 函数的声明如下:

void free(void* ptr);
// free 函数接受一个参数 ptr,表示要释放的内存指针
2.3 malloc 案例

九、指针和内存管理_第2张图片

#include 
#include 

int main(int argc, char const *argv[])
{
    /*
    void *malloc(size_t size);
        size_t ==> unsigned long 无符号长整型,仅支持正数 long 类型
        malloc 函数接受一个参数 size,表示需要分配的字节数。
        如果分配成功,它会返回一个指向分配内存的指针;如果分配失败,返回 NULL。

    void * 万能指针,可以指向任何一个空间首地址,到那时系统要求,不可以通过
    void * 访问读取目标空间中数据内容,在使用时需要进行【强制类型转换】,
    明确告知当前指针对应数据空间,按照哪一个类型进行处理。
    */
    // 当前操作只是向 CPU 申请内存堆区空间,要求空间字节数为 40 个字节
    void *p = malloc(40);

    /*
    【小缺失】使用 malloc 申请的内存空间,需要明确告知空间存储数据类型
    或者说申请的内存空间,按照哪一个类型进行分配
        (int *)p 强制转换类型,原本 p 是 void * ,强制转换为 int *
        告知 CPU 当前 p 对应地址的内存,按照 int 类型数据方式处理
    */
    int *p2 = (int *)p;

    /*
    可以通过 p2 操作申请的内存空间,CPU 通过 p2 操作对应的内存空间,
    空间每一个冤死都是 int 类型
    */
    for (int i = 0; i < 40 / sizeof(int); i++)
    {
        // 方式一:可以按照指针形式进行操作
        *(p2 + i) = i * 2;
    }
    
    for (int i = 0; i < 40 / sizeof(int); i++)
    {
        // 可以按照指针形式进行操作
        printf("*(p2 + %d) = %d\n", i, *(p2 + i));
    }

    printf("---------------------------------------\n");

    for (int i = 0; i < 40 / sizeof(int); i++)
    {
        // 方式二:可以按照数组形式进行操作,当前指针形式,数组的存储数据类型为 int 类型
        p2[i] = i * 2;
    }
    for (int i = 0; i < 40 / sizeof(int); i++)
    {
        // 方式二:可以按照数组形式进行操作,当前指针形式,数组的存储数据类型为 int 类型
        printf("p2[%d] = %d\n", i, p2[i]);
    }
    
    /*
    void free(void* ptr);
        free 函数是用于释放 malloc reallc calloc 申请的内存空间,需要提供给
        当前函数申请的空间的【首地址】,所需参数是 void * 类型,在实际使用中,
        可以提供任意类型指针。

        free 操作将 p2 指向内存空间进行释放操作,告知系统当前内存使用完毕,
        可以其他程序申请
    */
    free(p2);

    /*
    p 和 p2 原本都存储申请空间首地址,但是对应空间已经通过 free 释放
    归还给系统,为了安全操作不可以通过 p 和 p2 访问原本的数据空间,
    重新赋值为 NULL,防止后期使用。
    */
    p = NULL;
    p2 = NULL;
    return 0;
}
2.4 calloc 案例
#include 
#include 

int main(int argc, char const *argv[])
{
    /*
    calloc 案例
        void* calloc(size_t num, size_t size);
        通过 calloc 函数申请内存空间【堆区空间】,要求申请 10 个元素,
        每一个元素占用内存 4 个字节,总计内存空间 40 个字节

        同时直接将当前申请内存空间首地址,强制转为 int * ,CPU 通过 p 操作
        对应内存空间,每一个元素都是 int 类型。

    */
    int *p = (int *)calloc(10, sizeof(int));

    for (int i = 0; i < 10; i++)
    {
        p[i] = 777;
    }
    
    for (int i = 0; i < 10; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
    
    free(p);
    p = NULL;

    return 0;
}
2.5 realloc 案例

九、指针和内存管理_第3张图片
九、指针和内存管理_第4张图片

#include 
#include 

int main(int argc, char const *argv[])
{
    // malloc 申请 40 个直接数据空间
    int *p = (int *)malloc(10 *sizeof(int));

    printf("p = %p\n", p);
    for (int i = 0; i < 10; i++)
    {
        p[i] = i * 2;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }

    /*
    void* realloc(void* ptr, size_t size);
        ptr 是需要给当前函数的通过 malloc calloc realloc
        申请的内存空间首地址
        size 是当前重新分配空间对应的空间字节数

        返回值是新空间首地址
            1. 地址不变
            2. 地址改变
            【必须使用原本存储空间首地址的指针变量,接受 realloc 返回值】
    */
    p = (int *)realloc(p, 20);

    printf("p = %p\n", p);

    for (int i = 0; i < 5; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
    
    free(p);
    p = NULL;    
    
    return 0;
}
2.6 内存管理案例数组扩容
#include 
#include 

int *grow(int *arr, int capacity);

int main(int argc, char const *argv[])
{
    int *p = (int*)malloc(10 *sizeof(int));
    for (int i = 0; i < 10; i++)
    {
        p[i] = i * 2;
    }
    
    p = grow(p, 20);

    for (int i = 0; i < 10; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }
    printf("----------------------------\n");

    for (int i = 0; i < 20; i++)
    {
        p[i] = i * 2;
    }
    
    for (int i = 0; i < 20; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }

    free(p);
    p = NULL;
    
    return 0;
}

// 扩容到原本空间的 2 倍
int *grow(int *arr, int capacity)
{
    return (int *)realloc(arr, capacity * 2 * sizeof(int));
}
2.6 内存管理案例数组截取
#include 
#include 

int *sub_int_array(int *arr, int capacity, int begin, int end);

int main(int argc, char const *argv[])
{
    // 1. 创建一个数组,数组内存空间在内存的堆区
    int *arr = (int *)calloc(10, sizeof(int));

    // 2. 数据赋值
    for (int i = 0; i < 10; i++)
    {
        arr[i] = 2 * i;
    }

    // 3. 用户指定的起始和终止下标位置
    int begin = 3;
    int end = 6;

    // 4. 调用函数
    int *new_arr = sub_int_array(arr, 10, begin, end);
    
    for (int i = 0; i < end - begin; i++)
    {
        printf("new_arr[%d] = %d\n", i, new_arr[i]);
    }
    
    // 5. 释放内存
    free(new_arr);
    free(arr);
    
    new_arr = NULL;
    arr = NULL;

    return 0;
}

int *sub_int_array(int *arr, int capacity, int begin, int end)
{
    // 1. 判断用户提供的参数的合法性
    if (NULL == arr || capacity <= 0)
    {
        printf("用户提供数组相关数据不合法!\n");
        return NULL;
    }

    if (begin > end || begin < 0 || end > capacity)
    {
        printf("用户提供的下标不合法!\n");
    }

    // 2. 计算新数组容量,同时根据容量直接创建新的数组,同样占用内存堆区
    int *new_arr = (int *)calloc((end - begin), sizeof(int));

    if (NULL == new_arr)
    {
        // malloc calloc realloc 申请内存堆区空间,都有可能因为种种原因申请失败
        // 返回值 NULL
        return NULL;
    }

    // 3. 拷贝数据到新数组中
    int count = 0;
    for (int i = begin; i < end; i++)
    {
        new_arr[count++] = arr[i];
    }
    
    // 4. 返回新数组空间首地址
    return new_arr;  
}

九、指针和内存管理_第5张图片

你可能感兴趣的:(开发语言,c语言,学习,学习方法,笔记,经验分享)