指针进阶【上】详细讲解了字符指针/指针数组/数组指针,分析了如何理解指针,这对我们今后使用它非常重要,本篇文章将接着以类似的思路讲解函数指针/数组参数、指针参数/函数指针数组
//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
表示它指向的每个地址对应的数组有多少个元素,不能省略
写成数组/指针的形式
数组才传参的时候是传递首元素地址(数组名),不是整个数组,数组大小可以省略,(改成其他数字也不会报错,但不推荐),而且函数接收数组,不会再创建一个数组,所以形参可以写成指针(写成数组形式只是语法形式,本质上是一样的)
事实上,编译器在编译时会将数组形式转化为指针形式。
数组形式: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);
}
例子
//形参写成数组的形式
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
类型的元素。
#include
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函数能接收什么参数?
#include
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]
,表示它接收的是整个数组
指向函数的指针
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);
//函数指针 数组
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