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

目录

一、指针与数组

1.导入问题

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

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

4.字符串拾遗

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

6.小结

二、指针与函数

1.导入问题

2.深入函数之旅

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

4.函数指针参数

5.再论数组参数

6.小结

三、指针与堆空间

1.再论内存空间

2.堆空间的本质

3.预备知识——void*

4.堆空间的使用

5.堆空间的使用原则

6.小结


一、指针与数组

1.导入问题

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

先来了解一些事实

—使用取地址操作符 & 可以获取数组的地址
—数组名可看作一个指针,代表数组中0元素的地址
—当指针指向数组元素时,可进行指针运算(指针移动)

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;

代码示例:

#include 

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]

代码示例:

#include 

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
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++;

代码示例:

#include 

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;
}

运行结果:

C语言学习——指针与数组,指针与函数,指针与堆空间_第1张图片

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 的函数,&func func 数值相同,意义相同
—指向函数的指针:Type(*pFunc)(Type1,Type2)= func;

代码示例:

#include 

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         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])
       <--> ......

代码示例:

#include 

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*指针无法获取内存中的数据(无长度信息)

代码示例:

#include 

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);
}

代码示例:

#include 
#include 

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 只能释放申请到的内存,且不可多次释放

你可能感兴趣的:(c语言,学习,算法)