C语言学习——指针与数组,指针与函数,指针与堆空间

目录

一、指针与数组

1.导入问题

[2.深入理解数组地址(int a[] = {1,2,3,4,5};)](#2.深入理解数组地址(int a[] = {1,2,3,4,5};))

3.指针与数组的等价用法

4.字符串拾遗

[5.指针移动组合拳:int v = *p++](#5.指针移动组合拳:int v = *p++)

6.小结

二、指针与函数

1.导入问题

2.深入函数之旅

[3.函数指针(Type func(Type1 a,Type2 b))](#3.函数指针(Type func(Type1 a,Type2 b)))

4.函数指针参数

5.再论数组参数

6.小结

三、指针与堆空间

1.再论内存空间

2.堆空间的本质

3.预备知识------void*

4.堆空间的使用

5.堆空间的使用原则

6.小结


一、指针与数组

1.导入问题

我们来思考一个问题,数组的本质是一片连续的内存,那么,数组的地址是什么?要如何获取?

先来了解一些事实

---使用取地址操作符 & 可以获取数组的地址

---数组名可看作一个指针,代表数组中0元素的地址

---当指针指向数组元素时,可进行指针运算(指针移动)

cs 复制代码
int a[] = {1,2,3,4,5};
int* p = a;
p = p + 1;

2.深入理解数组地址(int a[] = {1,2,3,4,5};)

---&a与a在数值上相同,但是意义上不同

---&a代表数组地址,类型为:int (*)[5]

---a代表数组 0号元素地址,类型为: int*

---指向数组的指针:int (*pName)[5] = &a;

代码示例:

cs 复制代码
#include <stdio.h>

int main()
{
    int a[] = {1, 2, 3, 4, 0};
    int* p = a;  //&a[0] ==> int*
    int (*pa) [5] = &a;

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

    p++;

    *p = 100;  // a[1] = 100;

    printf("%d, %d\n", *p, a[1]);
    printf("%p, %p\n", &a, a);

    p = pa;   // WARNING  !!!!

    p = a;

    while( *p )
    {
        printf("%d\n", *p);

        p++;
    }

    return 0;
}

注意:数组名并不是指针,只是代表0号元素的地址,因此可以当做指针使用

3.指针与数组的等价用法

int a[] = {1,2,3, 4,5};

int* p = a;

a + i 即数组中第i个元素的地址

故 a[i] <--> *(a +i) <--> *(p +i) <--> p[i]

代码示例:

cs 复制代码
#include <stdio.h>

int main()
{
    int a[] = {1, 2, 3, 4, 5};
    int* p = a;
    int i = 0;

    // a[i] <==> *(a+i) <==> *(p+i) <==> p[i]

    while(i<3)
    {
        printf("a + %d = %p, a[%d] = %d\n", i, a+i, i, a[i]);
        printf("p + %d = %p, p[%d] = %d\n", i, p+i, i, p[i]);
    }
    printf("&a = %p, &p = %p\n", &a, &p);

    return 0;
}

4.字符串拾遗

---问:C语言中的字符串常量是什么类型

---答:char * 类型,是一种指针类型

#include <stdio.h>

int main()

{

printf("%p\n", "D.T.Software");

printf("%p\n", "D.T.Software");

return0;

}

5.指针移动组合拳:int v = *p++

---解读·

指针访问操作符(*)和自增运算操作符(++)优先级相同

所以,先从p指向的内存中取值,然后p进行移动

即int v = *p++等价于

int v= *p;

p++;

代码示例:

cs 复制代码
#include <stdio.h>

int main()
{

    char* s = NULL;

    printf("First = %c\n", *"D.T.Software");

    s = "D.T.Software";

    while( *s ) printf("%c", *s++);

    printf("\n");

    return 0;
}

运行结果:

6.小结

---数组名可看作一个指针,代表数组中 0元素的地址

---&a 与a在数值上相同,但是意义上不同

---C语言中的字符串常量的类型是 char*

---当指针指向数组元素时,才能进行指针运算

二、指针与函数

1.导入问题

我们来思考一个问题,函数调用时会跳转到函数体对应的代码处执行,那么,如何知道函数体代码的具体位置?

所以,我们接下来深入学习指针与函数之间的关系。

2.深入函数之旅

---函数的本质是一段内存中的代码(占用一片连续内存)

---函数拥有类型,函数类型由返回类型和参数类型列表组成的

例如:

|--------------------------------|---------------------|
| 函数声明 | 类型 |
| int sum(int n); | int (int) |
| void swap(int* pa, int* pb); | void (int*, int*) |
| void g(void); | void (void) |

又一些事实

---函数名就是函数体代码的起始地址(函数入口地址)

---通过函数名调用函数,本质为指定具体地址的跳转执行

---因此,可定义指针,保存函数入口地址

3.函数指针(Type func(Type1 a,Type2 b))

---函数名即函数入口地址,类型为Type(*)(Type1,Type2)

---对于 func 的函数,&funcfunc 数值相同,意义相同

---指向函数的指针:Type(*pFunc)(Type1,Type2)= func;

代码示例:

cs 复制代码
#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int mul(int a, int b)
{
    return a * b;
}


int main()
{
    int (*pFunc) (int, int) = NULL;

    pFunc = add;

    printf("%d\n", pFunc(1, 2));
    printf("%d\n", (*pFunc)(3, 4));

    pFunc = &mul;

    printf("%d\n", pFunc(5, 6));
    printf("%d\n", (*pFunc)(7, 8));

    return 0;
}

4.函数指针参数

函数指针的本质还是指针(变量,保存内存地址)

可定义函数指针参数,使用相同代码实现不同功能

例如以下:

int calculate(int all, int len, int(*cal)(int, int))

{

int ret = a[0];

int i = 0;

for(i=l; i<len; i++)

ret = cal(ret, a[i]);

return ret;

}

注意

---函数指针只是单纯的保存函数的入口地址

---因此,只能通过函数指针调用目标函数,不能进行指针移动(指针运算)

5.再论数组参数

函数的数组形参退化为指针!因此,不包含数组实参的长度信息。

使用数组名调用时,传递的是0号元素的地址。

void func(int a[])<-->void func(int* a )

<--> void func( int a[1])

<--> void func(int a[10])

<--> void func(int a[100])

<--> ......

代码示例:

cs 复制代码
#include <stdio.h>

int demo(int arr[], int len)  // int demo(int* arr, int len)
{
    int ret = 0;
    int i = 0;

    printf("demo: sizeof(arr) = %d\n", sizeof(arr));

    while( i < len )
    {
        ret += *arr++;

        i++;
    }

    return ret;
}

一般来说ret += *arr++;是存在语法错误的,但因为arr[]数组作为函数参数传入函数,函数的数组形参退化为指针,故可以通过对数组名加减来移动该指针对应的数组。

6.小结

---函数名的本质是函数体的入口地址

---函数类型由 返回类型参数类型列表 组成

---可定义指向函数的指针:Type(*pFunc)(Type1,Type2);

---函数指针只是单纯的保存函数的入口地址(不能进行指针运算)

三、指针与堆空间

1.再论内存空间

内存区域不同,用途不同

---全局数据区:存放全局变量,静态变量

---栈空间:存放函数参数,局部变量

---堆空间:用于动态创建变量(数组)

在内存空间中,我们之前学过了全局数据区,栈空间,所以我们接下来学习新的内容------堆空间。

2.堆空间的本质

---备用的"内存仓库",以字节为单位预留的可用内存

---程序可在需要时从"仓库"中申请使用内存(动态借)

---当不需要再使用申请的内存时,需要及时归还(动态还)

3.预备知识------void*

所以我们接下来的问题就是如何从堆空间申请内存?如何归还?

在回答这个问题之前,我们还需要学习预备知识------void*

---void 类型是基础类型 ,对应的指针类型为 void*

---void*是指针类型,其指针变量能够保存地址

---通过 void*指针无法获取内存中的数据(无长度信息)

代码示例:

cs 复制代码
#include <stdio.h>

int main()
{
    char c = 0;
    int i = 0;
    float f = 2.0f;
    double d = 3.0;

    void* p = NULL;

    double* pd = NULL;
    int* pi = NULL;

    /* void* 指针可以保存任意类型的地址 */
    p = &c;
    p = &i;
    p = &f;
    p = &d;

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

    // void* 类型的指针无法访问内存中的数据
    // printf("%f\n", *p);

    /* void* 类型的变量可以直接合法的赋值给其他具体数据类型的指针变量 */
    pd = p;
    pi = p;

    // void* 是例外,其他指针类型的变量不能相互赋值
    // pd = pi;

    return 0;
}

"void* 总结

---不可使用 void* 指针直接获取内存数据。

---void*指针可与其它数据指针相互赋值。

4.堆空间的使用

接下来,我们开始学习堆空间的使用。

---工具箱: stdlib.h

---申请:void* malloc(unsigned bytes )

---归还:void free( void*p)

{

int* p = malloc(4);

*p = 100;

printf("号d\n",*p);

free(p);

}

代码示例:

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* p = malloc(4); // 从堆空间申请 4 个字节当作 int 类型的变量使用

    if( p != NULL )  // 如果申请失败 p 为 0 ,即:空值
    {
        *p = 100;

        printf("%d\n", *p);

        free(p);
    }

    p = malloc(4 * sizeof(int));

    if( p != NULL )
    {
        int i = 0;

        for(i=0; i<4; i++)
        {
            p[i] = i * 10;
        }

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

        free(p);
    }

    return 0;
}

5.堆空间的使用原则

---有借有还,再借不难(杜绝只申请,不归还)

---malloc 申请内存后,应该判断是否申请成功

---free 只能释放申请到的内存,且不可多次释放

6.小结

---堆空间是程序中预留且可用的内存区域

---void*指针只能能够保存地址,但无法获取内存数据

---·void*指针可与其它数据指针相互赋值

---"malloc 申请内存后,应该判断是否申请成功

---free 只能释放申请到的内存,且不可多次释放

相关推荐
勤劳的进取家38 分钟前
XML、HTML 和 JSON 的区别与联系
前端·python·算法
星雨流星天的笔记本1 小时前
英语外刊写作积累(2024.09)
学习
诚丞成1 小时前
栈算法篇——LIFO后进先出,数据与思想的层叠乐章(下)
c++·算法
wangqiaowq2 小时前
Apache PAIMON 学习
学习
羊小猪~~2 小时前
MYSQL学习笔记(二):基本的SELECT语句使用(基本、条件、聚合函数查询)
数据库·笔记·sql·学习·mysql·考研·数据分析
然然阿然然2 小时前
2025.1.15——二、字符型注入
网络·数据库·sql·学习·网络安全
清风~徐~来2 小时前
【算法】枚举
算法
qingy_20463 小时前
【算法】图解二叉树的前中后序遍历
java·开发语言·算法
大大菜鸟一枚3 小时前
arm使用ubi系统
linux·arm开发·学习
Catherinemin3 小时前
剑指Offer|LCR 031. LRU 缓存
javascript·算法·缓存