C语言指针(五)

大家好,我是小张同学,今天继续来学习指针,废话不多说,开干!

目录

1. 结构体指针

1.1 定义一个结构体指针

1.2 结构体指针作为函数参数

2. 函数和指针

2.1 函数指针

2.1 指针变量作为函数参数

2.2 指针变量作为函数返回值


1. 结构体指针

1.1 定义一个结构体指针

当一个变量指向结构体时,就称它为结构体指针。举个例子:

typedef struct
{
    uint8_t age;
    const char *name;
}StudentInfo;

int main()
{
    StudentInfo Info;            //结构体变量
    StudentInfo *pInfo = &Info;  //结构体指针
    Info.age = 15;
    Info.name = "liangchen";
    ......
    return 0;
}

需要注意的是,结构体变量名和数组名不一样,数组名在表达式中会被转换为一个指针常量,结构体变量名不会,在任何地方结构体变量名都表示整个集合本身,要想取得结构体的地址,必须在前面加 & 才行。

结构体和结构体变量是两个不同的概念。

结构体是一种数据类型,这种数据类型有我们自己来定义,编译器不会为它分配内存空间,就像下面这样,我们定义了一种 StudentInfo 的数据类型,就和 int 、float、char一样,是一种数据类型。

typedef struct
{
    uint8_t age;
    const char *name;
}StudentInfo;

结构体变量才是实实在在的数据,需要内存存储数据,比如下面创建了一个 StudentInfo 结构体变量。

StudentInfo g_Info;

1.2 结构体指针作为函数参数

结构体变量名称代表的是整个集合本身,作为函数参数时传递的是整个集合,不会像数组名那样被转换为一个指针。我们来验证一下,比如下面这段代码:

#include <stdio.h>

typedef struct
{
    int age;
    const char *name;
}StudentInfo;

void Function(StudentInfo Info)
{
    printf("Function struct Size %d\n", sizeof(Info));
}

int main()
{
    StudentInfo Info;
    StudentInfo *pInfo = &Info;
    Info.age = 15;
    Info.name = "liangchen";

    printf("Main struct Size %d\n", sizeof(Info));
    Function(Info);
    return 0;
}

 执行结果如下:

Main Size 16
Function Size 16

可见结构体名称作为函数参数时,会将整个结构体传进来。如果结构体成员较多,直接传递结构体本身,时间和空间开销就会很大,所以最好的方法就是使用结构体指针。

2. 函数和指针

2.1 函数指针

函数指针可以看之前的文章C语言指针(四),已经很详细了。

2.1 指针变量作为函数参数

指针变量可以作为函数参数将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而销毁。

着重说一下将数组指针作为函数参数的情况。

数组名作为函数参数时会被转化为一个指针,我们来验证一下,在下面这段代码中定义了两个函数 SumOfArray 和 SumOfArrayNoSize,用来计算数组元素的和,其中 SumOfArray 会传入数组名以及数组大小,而SumOfArrayNoSize会传入数组名,数组大小在函数内部计算。

#include <stdio.h>

int SumOfArray(int array[], int size)
{
    int sum = 0;
    for(int i = 0; i < size; i++)
    {
        sum += array[i]; 
    }
    return sum;
}

int SumOfArrayNoSize(int array[])
{
    int sum = 0;
    int size = sizeof(array)/sizeof(int);
    printf("sizeof(array) %d, sizeof(int) %d\n", sizeof(array), sizeof(int));
    for(int i = 0; i < size; i++)
    {
        sum += array[i]; 
    }
    return sum;
}

int main()
{
    int array[] = {0, 1, 2, 3, 4};
    int size = sizeof(array)/sizeof(int);
    printf("Main sizeof(array) %d, sizeof(int) %d\n", sizeof(array), sizeof(int));
    int sum = SumOfArray(array, size);
    int sum1 = SumOfArrayNoSize(array);
    printf("sum %d, sum1 %d\n", sum, sum1);
}

 编译执行上面这个程序,会出现一个警告,意思是说在 SumOfArrayNoSize 函数里面,sizeof(array) 将返回 sizeof(int*) 的值,即在这里面,array 被隐式地转为了int*

warning: 'sizeof' on array function parameter 'array' will return size of 'int *' [-Wsizeof-array-argument] 
printf("sizeof(array) %d, sizeof(int) %d\n", sizeof(array), sizeof(int));
                                                        ^

并且执行结果如下,显然在函数内部获取数组大小是有问题的。

Main sizeof(array) 20, sizeof(int) 4
sizeof(array) 8, sizeof(int) 4     
sum 10, sum1 1

那么,计算机是怎么解释数组作为函数参数的呢?

当程序开始执行的时候,首先调用main函数,栈上的一部分内存分配给main函数使用,在main函数中的局部变量,都会存入栈帧中,其中数组 array 应该占据了20个字节。

当调用 SumOfArray 函数的时候,main 函数会暂停,并且会为 SumOfArray 分配一部分内存使用,当调用 SumOfArray 函数,并且将数组名作为函数形参传入的时候,我们可能会认为主调函数中的 array 会被复制到被调函数中

传参的本质就是对内存进行复制,即将一块内存上的数据复制到另一块内存上

但事实是,当编译器看到数组作为函数参数的时候,不会拷贝整个数组,而是在被调函数的栈帧中创建一个同名的指针,这个指针指向了数组,所以编译器将数组名转换为了指针。

虽然 SumOfArray 函数形参的类型为 int array[ ],表明传入的是一个数组,但这都是假象,实际上被转换为了指针,因此将函数形参写为数组指针更符合实际情况。

也正是因为这样,在函数内部没办法求得数组长度,所以必须额外增加一个参数来传递数组长度。

2.2 指针变量作为函数返回值

当函数的返回值是一个指针时,这个函数被称为指针函数

需要注意的是,函数运行结束后会销毁在它内部定义的所有局部数据,函数返回的指针尽量不要指向这些数据。

int *func()
{
    int n = 100;
    int *p = &n;
    return &n;
}

int main()
{
    int *p = func();
    int n = *p;
    printf("n = %d\n", n);
    return 0;
}

在上面的代码中,函数 func 返回一个局部变量的指针,在main函数中打印出n仍然等于100,好像没什么问题。

实际不是这样,在上面的例子中,虽然得出的结果还是正确的,但是并不代表程序是安全的。

我们说函数运行结束后会销毁所有的局部变量,这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是说,后面的代码可以随意使用这块内存,上面的例子中,func运行结束后 n 的值还保持原样,是因为这块内存还没有被覆盖掉,那个位置的值只是暂时还没有变化,所以是有风险的。

这篇文章就到这里了,下一篇文章学习如何理解复杂指针。

  • 28
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值