大家好,我是小张同学,今天继续来学习指针,废话不多说,开干!
目录
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 的值还保持原样,是因为这块内存还没有被覆盖掉,那个位置的值只是暂时还没有变化,所以是有风险的。
这篇文章就到这里了,下一篇文章学习如何理解复杂指针。