指针太难?手把手教你理解指针(传参、函数指针)

目录

前言

一、数组和指针的参数

1.一维数组传参

2.二维数组传参

3.一级指针传参

4.二级指针传参

二、函数指针

1.函数的地址

2.函数指针的形式

 3.函数指针的使用

三、加深理解,两段有趣的代码


前言

之前的一篇文章讲到了指针的概念、指针和数组的关系,以及它们的各种组合。今天来补上上一篇文章挖的大坑,来详细介绍一下指针和数组的传参问题,以及函数、指针与数组的关系和组合。

需要看上篇文章的请跳楼👇

指针太难?手把手教你理解指针(指针?数组?)

一、数组和指针的参数

在写代码时,我们会遇到将数组和指针当作参数传给函数的情况。那么,数组或者指针如何传参呢?函数的指针如何设计呢?

1.一维数组传参

  • 与其他变量一样,数组在传参时也可以在函数中创建临时拷贝,将数组的值传给函数(传值调用),如下:
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}

int main()
{
	int arr[10] = { 0 };
	test(arr);
}
  •  与其他变量一样,数组传参也可以让函数接收数组的地址,通过地址对数组进行操作(传址调用),如下:
#include <stdio.h>
void test(int* arr)
{}

int main()
{
	int arr[10] = { 0 };
	test(&arr);
}

但是与其他变量不同的是,数组名可以表示首元素地址,直接传数组就可以用指针接收

  • 对于指针数组,与上面两种方式一样,这里直接列出: 
#include <stdio.h>
void test2(int* arr[20])//传值
{}
void test2(int** arr)//传址
{}
int main()
{
	int* arr2[20] = { 0 };
	test2(arr2);
	test2(arr2);
}

需要注意,这里数组元素是指针,指针的地址要用二级指针来接收。 

2.二维数组传参

二维数组和一维数组一样,也可以通过传值、传址两种方式传参。

首先是传值调用,如下:

void test(int arr[3][5])
{}
void test(int arr[][5])
{}

int main()
{
 int arr[3][5] = {0};
 test(arr);
}

但是,需要注意,
二维数组传参,函数形参的设计只能省略第一个“[ ]”中的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。

其次是传址调用,如下:

void test(int (*arr)[5])
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

这里就已经和前面有所区别了。因为二维数组的数组名虽然也是首元素的地址,但是这个首元素其实是一整个数组。上面的二维数组相当于有三个元素,每个元素都是一个含有五个元素的数组。所以在传址时,要用数组指针来接收

思考一下能不能这样?为什么?

void test(int *arr)//ok?
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

答案是不能。因为二维数组传递的参数是一整个数组的地址,不能用普通指针接收,只能用数组指针接收。

思考一下能不能这样,为什么?

void test(int **arr)//ok?
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

答案是不能。因为二级指针用来存放一级指针的地址,不能存放数组的地址。想要传数组的地址,还是要用数组指针。

3.一级指针传参

这里举一个一级指针传值调用的例子,如下:

#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

这里思考一下,如果一个函数参数部分是一级指针,函数能接收什么样的参数?

举个例子:

void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

分析一下很容易得出,参数部分是一级指针时,函数可以接收不同元素的地址,也可以接收数组首元素地址,还可以接收一级指针的值(注意不是传址,传址就要用二级指针了)。

4.二级指针传参

二级指针作为参数,就可以接收一级指针的地址了。二级指针可以接收二级指针的值、接收一级指针的地址、还可以接收指针数组的首元素地址。例如:

void test(char** p)
{

}
int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc);
	test(ppc);
	test(arr);
	return 0;
}

二、函数指针

1.函数的地址

函数在创建时,会占用栈空间,因此函数也有它自己的地址,可以使用函数指针接收。如果你不敢相信,请看下面的代码:

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

打印结果如下: 

 结果是两个地址。这样我们就很好的证明了,函数也是有地址的,而且函数名就可以表示函数的地址,&函数名也可以表示。

2.函数指针的形式

因此,想要存放函数的地址,我们就需要一个函数指针。还记得数组指针是什么形式吗?它长这样:

void (*parr)[10]

那么,我们也可以很容易推测出,函数指针的形式,它长这样:

void (*pfun)() 

还是和上一篇一样的理解方式,pfun是指针,解引用之后相当于函数名,其余形式和函数一样。

 3.函数指针的使用

从上面函数的地址处的示例可以发现,函数名本身就等同于函数的地址。也就是说,函数指针可以直接当作函数名来使用。如下:

void func()
{}

void (*pfunc) ();
pfunc = &func;
pfunc();//函数指针可以直接调用函数

当然,也可以通过解引用来使用,如下:

(*pfunc)();

三、加深理解,两段有趣的代码

学习了数组指针、指针数组、函数指针,我们就可以尝试理解一下下面的两段代码:

//代码1
(*(void (*)())0)();

这是什么意思呢?别着急,我们从里向外依次解剖。

从最里面有内容的括号开始,即 void (*)  () ,假如说我在“*”后面放一个p,那么p是不是就是一个函数指针呐?但是现在把p去掉,剩下的void(*)() 就是一个函数指针类型。就比如 int n 表示定义一个整形变量n,去掉n,剩下的int就是整形。函数指针也一样。void(*p)()定义一个函数指针p,去掉p,剩下的void(*)()就是函数指针类型。函数指针类型加了括号,后面有一个0,也就是说把0这个数字强制类型转换,把它变成一个地址0x00000000,然后前面一个“*”表示解引用,也就是访问0x00000000这个地址处存放的函数。*(void (*)())0 就相当于函数名,后面再跟括号,就是说这个函数已经要被调用了。整个语句执行的是,调用0x00000000地址处存放的函数。

来看第二段代码:

//代码2
void (*signal(int , void(*)(int)))(int);

仍然先从最里面的有内容的括号开始,最里面的应该是(  int,void(*)(int)   )左边是int类型,右边是函数指针类型,中间用逗号隔开,括号括起来,是不是就是一个函数的参数类型啊?再看前面一个signal,()优先级比*优先级高,signal先跟后面括号结合,所以说signal是个函数。signal(int,void(*)(int))就是整个函数,把他取出来,剩下的void(*)(int)是不是依然是函数指针类型啊?也就是说,里面的函数,它的返回值,是一个函数指针。整个代码就是一个函数的声明,这个函数名为signal,有两个参数,一个是int类型,另一个是函数指针类型。这个函数有一个返回值,返回值是函数指针类型。

这样写代码其实是很复杂的,因为函数指针类型要写成类似“void(*)(int)”这样的形式,括号多还要嵌套,可读性差。我们可以用typedef来把它改一下名字!这样就会好看很多。

例如:

typedef void (*pfunc)(int);

注意类似于函数指针这种类型,在声明、定义、乃至重命名的过程中,变量或者新名字都是放在括号里面的,而不是类型名后面。这个重命名的意思是,把void (*)(int) 这个类型的名字改成 pfunc。这样代码2就可以这样写:

pfunc signal(int ,pfunc);

是不是更容易理解了?

这一部分的内容不是非常好理解,一次性学习太多不好消化。只有前面的完全理解了,才能继续后面的内容。函数,指针,数组有怎样的奇葩组合呢?这些组合又有什么用呢?回调函数是什么?跟函数指针有关系吗?下一篇文章,我将为大家一一解答。

To be continued...

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值