前言
指针进阶【上】详细讲解了字符指针/指针数组/数组指针,分析了如何理解指针,这对我们今后使用它非常重要,本篇文章将接着以类似的思路讲解函数指针/数组参数、指针参数/函数指针数组
复习回顾
//int arr[5]; //arr是一个整形数组,每个元素是int类型的,有5个元素 //int* parr1[10]; //parr1是一个数组,数组10个元素,每个元素的类型是int* //int(*parr2)[10]; //parr2是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是int //int(* parr3[10])[5]; //parr3 是一个数组,数组有10个元素,每个元素的类型是:int(*)[5] //parr3是存放数组指针的数组
对于int(* parr3[10])[5]
parr3
先与括号结合,是一个数组,数组名是parr3
,去掉数组名和[10]
,int(*)[5]
是元素类型这个
5
表示它指向的每个地址对应的数组有多少个元素,不能省略
4. 数组参数、指针参数
4.1 一维数组传参
写成数组/指针的形式
数组才传参的时候是传递首元素地址(数组名),不是整个数组,数组大小可以省略,(改成其他数字也不会报错,但不推荐),而且函数接收数组,不会再创建一个数组,所以形参可以写成指针(写成数组形式只是语法形式,本质上是一样的)
事实上,编译器在编译时会将数组形式转化为指针形式。
数组形式:1. 初学者更容易学习使用 2. 更容易看得清楚,用数组传,用数组接收
//形参写成数组的形式 void test(int arr[])//ok {} void test(int arr[10])//ok {} void test(int arr[100])//ok//不会报错,但这样写会误导人的 {} void test(int *arr)//ok {} //形参写成指针形式 void test2(int *arr[20])//ok {} void test2(int **arr)//ok//二级指针 {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); }
4.2 二维数组
例子
//形参写成数组的形式 void test(int arr[3][5])//ok {} void test(int arr[][])//no {} void test(int arr[][5])//ok {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //因为数组是一块连续存放的内存,而不是形象的“XoY表格”↑,知道列数可以算出行数 //形参写成指针形式 void test(int* arr)//no//一级指针//接收普通变量的地址 {} void test(int* arr[5])//no//这是指针数组 {} void test(int (*arr)[5])//ok {} void test(int* * arr)//no//二级指针//接收一级指针的地址 {} int main() { int arr[3][5] = {0}; test(arr); }
对(int (*arr)[5])
的理解
数组首元素地址(数组名)对二维数组来说是第一行首元素的地址,它代表着二维数组的第一行。
当函数形参要接收二维数组首元素地址时,需要包含两个信息:数组的元素类型和元素个数。
(*arr)
表示接收的是一个地址,int
和[5]
表示接收的地址指向5个int
类型的元素。
4.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,10}; int* p = arr; int sz = sizeof(arr)/sizeof(arr[0]); //一级指针p,传给函数 print(p, sz);//打印数组的函数 return 0; }
理解
将数组首元素地址给
p
,将p
传给函数,函数中接收的即为数组首元素地址。要使用某一位的数组元素,首地址+对应数字再解引用即可。
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int *p) {} //test1函数能接收什么参数? void test2(char* p) {} //test2函数能接收什么参数?
4.4 二级指针传参
#include <stdio.h> void test(int** ptr)//传二级指针,用二级指针接收 { printf("num = %d\n", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; char* arr[4];//指针数组 test(pp);//直接传二级指针 test(&p);//传一级指针的地址 test(arr);//每个元素是char*型指针,首元素地址即char*型指针的地址,即char* *型 return 0; //可不可以传二维数组的首元素地址呢?形参如何表示? //char (*arr)[5]; //char* arr[3][5]可以吗? }
传递二维数组的数组名和首元素地址
void test1(int (*p)[5]) {} void test2(int(*p)[3][5]) { *p; } int main() { int arr[3][5]; test1(arr);//传递的第一行的地址 test2(&arr);//传递的是整个二维数组的地址//仅举例,实际上几乎不这么用 return 0; }
对arr
和&arr
的理解
对于二维数组,
arr
是第一行首元素的地址,它代表着第一行,所以函数形参要定义为int (*p)[5]
,表明它接收的是以行为单位的地址
&arr
是数组的地址,代表着整个数组,所以函数形参要定义为int(*p)[3][5]
,表示它接收的是整个数组
5. 函数指针
指向函数的指针
例子
int test(char* str) { } int Add(int x, int y) { return x + y; } int main() { int arr[10]; //arr //&arr int (*p)[10] = &arr;//p是一个数组指针变量 printf("%p\n", &Add); printf("%p\n", Add); //**对于函数,写法不同,意义相同** int (* pf)(int, int) = Add;//pf是函数指针变量 int ret = (*pf)(2,3);//解引用找到函数,然后传参 //int ret = Add(2, 3); //int ret = pf(2, 3); printf("%d\n", ret); //int (*pt)(char*) = test; return 0; }
对于函数,&Add和Add的写法不同,意义相同
对int (* pf)(int, int) = Add
的理解
它与数组指针的分析思路非常相似。
- 先从指针出发,因为是指针所以有
(*pf)
- 因为函数的参数都是
int
型,而且有两个,所以有(int, int)
- 因为函数返回类型是
int
,所以有开头加上int
- 关于函数指针类型:去掉变量名即为函数指针类型,如:
int (*)(int, int)
以下两种调用函数的写法等价
int ret = (*pf)(2,3);//解引用找到函数,然后传参 int ret = pf(2, 3);//直接将pf当作函数名
pf
存放的是函数的地址(即Add
/&Add
),对它解引用符合语法,符合常理,容易让人理解,不过注意要加括号。- 之前我们调用函数是直接用函数名的,但前面将函数地址赋值给
pf
,说明pf
和Add
是同一个东西,所以可以直接用pf
代替函数名调用函数- 由以上两点表明,解引用的这个
*
可有可无(且不管有多少个*
),只不过这样做容易让人理解,是一种符合语法的补充
解读两段代码
//代码1 (*(void (*)())0)();
对 (*(void (*)())0)()
的理解
- 括号从外层匹配
- 指针去掉函数名即指针类型,所以
void (*)()
表示指针类型- (指针类型) +对象⇒强制类型转换。所以
( void (*)() )
表示强制类型转换( void (*)() )0
表示对0进行强制类型的转换*( void (*)() )0
表示找到0
这个地址的函数,其实这里最前面的*
可以省略,请看以上对两种调用函数的写法等价的解读。( *( void (*)() )0 )()
,表示函数无参- 总的来说:首先是把
0
强制类型转换为一个函数指针类型(指针是干嘛的?),这就意味着0
地址处放一个返回类型是void
、无参的一个函数,然后调用0地址处的这个函数- 注意:这里只是用0举例,实际上0地址在一般情况下无法被使用,这种强转给人的感觉就好像一个数字被当成地址使用一样,而且地址本身也是用数字表示的
//代码2 void (*signal(int , void(*)(int)))(int);//这是一个函数声明
对函数声明void (*signal(int , void(*)(int)))(int)
的理解
signal
先与括号结合,signal()
,说明它是一个函数的声明- 括号内表示参数类型 ,
int型
和void(*)(int)
函数指针型 ,所以signal(int , void(*)(int))
表示一个函数- 举例:我们这样声明一个函数:返回值类型+函数名+(形参类型),只要将函数名和形参类型去掉就是返回值类型,所以对于
void (*signal(int , void(*)(int)))(int)
,去掉signal(int , void(*)(int))
,剩下void (*)(int)
即为signal
函数的返回值类型,它是一个函数指针
函数的一个参数和函数的返回值类型都是相同的,如何简化以上代码?
将void (*)(int)
看成一个整体
typedef void(* pf_t)(int) ; //给函数指针类型void(*)(int)重新起名叫:pf_t pf_t signal(int, pf_t);
6. 函数指针数组
例子
//函数指针 数组 int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } int main() { //指针 数组 //字符指针 数组 char* arr[5]; //整型指针 数组 int* arr2[4]; int (*pf1)(int, int) = Add; int (*pf2)(int, int) = Sub; int (*pf3)(int, int) = Mul; int (*pf4)(int, int) = Div; //函数指针 数组 int (* pf[4])(int, int) = { Add, Sub, Mul, Div };
对int (* pf[4])(int, int)
的理解
pf1
-pf4
是指向函数的指针,用数组存放这些指针,即为函数指针数组- 首先它得是个数组,所以数组名
pf
要紧跟[ ]
,其他与函数指针的形式一致- 函数指针数组在形式上:在函数指针的基础上+
[ ]
,初始化时括号内数字可省略
通过函数指针数组调用函数
for (int i = 0; i < 4; i++) { int ret = pf[i](8, 2);//函数传参 printf("%d\n", ret); } return 0; }
体会:函数指针数组就好像一个中介,它起着链接函数的作用,对于以上代码的更多解读,请戳通过模拟实现计算器掌握函数指针数组的用法
练习
//定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针, //返回的指针指向一个有一个int形参且返回int的函数 //int* ((*p)(int, int))(int) //一个参数为int *,返回值为int的函数指针 //int (*p)(int*) //声明一个指向含有10个元素的数组的指针, //其中每个元素是一个函数指针,该函数的返回值是int,参数是int* //int(*(*p)[10])(int*) //设有以下函数void fun(int n,char* s){……}, //则如何对函数指针的定义和赋值? //void (*p)(int n, char* s); //p = &fun 或p = fun