本节主要内容
- typedef函数声明
- 函数指针变量声明类型
- 函数指针与函数指针数组
- sizeof
- 数组与指针的关系
- 函数形参接收数组与接收指针的关系
基本函数声明类型与使用
使用typedef进行函数声明类型的设置,请观察下方代码
- FUNC1使我们自定义的函数声明类型名字,它声明的是一个接受两个int形参并返回int值的函数
- FUNC1 a; 表示对函数a执行函数声明!
- 最后一行代码表示FUNC1 a;所表示的等价代码格式
// 使用typedef的最简单的函数声明类型
typedef int FUNC1(int,int);
FUNC1 a;
// 以上代码的等价形式
int a(int,int);
由以上分析我们知道,FUNC1仅仅是对函数声明,他不具备构造一个函数的能力,所以我们根据下方一个简单的例子来介绍如何具体使用
- 首先,先给出函数声明类型FUNC1
- 然后我们需要编写一个返回值类型、形参完全和FUNC1对应的函数,譬如这里的add函数,它的功能就是求两数和
- 现在,把目光放到addTotal的第一个形参上,FUNC1 c,这是什么意思?
他首先确定c为一个函数,且规定了他的函数格式(即FUNC1对应的函数声明格式)
那么我们要做的,就是在调用addTotal函数时,为形参c传入一个和FUNC1类型完全适配的函数(如我们准备好的add函数) - c(a,b) 这完全是按照FUNC1声明的函数类型而来的,FUNC1规定的函数接收俩形参,这里就是ab,他返回int值,所以对应addTotal函数所返回的int值
- addTotal(add,10,20) 到这里就清晰了!传入实参为一个函数add,此处按照运行逻辑,执行10+20,所以最终输出30
// 声明一返回int且接受俩int形参的函数声明类型
typedef int FUNC1(int,int);
// 编写适配FUNC1的函数
int add(int a,int b){
return a+b;
}
// 调用一个函数
int addTotal(FUNC1 c,int a,int b){
return c(a,b);
}
// 最终执行
int main() {
printf("%d\n",addTotal(add,10,20));
return 0;
}
函数指针了解
使用typedef声明一函数指针类型,他指向一对应格式的函数(函数指针类型只能用作声明,我们不可以直接定义一个指针函数!),代码解析
- 定义一函数指针类型func,它返回int,且接受俩int形参
- 定义函数指针f,然后就是必不可少的内存分配(因为这是一个返回int的指针,所以sizeof使用int*即可)
- 设置符合对应规则的函数fun
- f=fun 表示将函数指针f直接指向函数fun,可以当做为fun取了个别名f,我们对f的所有操作都和fun一致!
- f(1,2) 因为f已经指向fun,所以传参后实际上就是为fun传参,由fun执行并最后return结果
- 最终得到结果1+2=3
typedef int (*func)(int, int);
func f = (func) malloc(sizeof(int *));
int fun(int a, int b) {
return a + b;
}
int main() {
f=fun;
cout<<f(1,2)<<endl;
return 0;
}
进阶——函数指针数组
创建函数指针数组有俩种方法,如以下代码所示
- func f1[100] = {NULL} 直接使用我们定义好的函数指针类型func声明了100个函数指针,然后赋值NULL来为他们分配空间
- int (*f2[100])(int,int)={NULL} 这是传统的声明办法,按照从内到外分析法则
f2[100] - 声明具有100个元素的数组,他的名字叫f2
(*f2[100]) - f2数字中的所有元素都是指针
(*f2[100])(int,int) - f2还是一个函数,他接受两个int类型的实参
int (*f2[100])(int,int) - f2的返回值是int类型的
typedef int (*func)(int, int);
func f1[100] = {NULL};
int (*f2[100])(int,int)={NULL};
了解了基本构成后,就是如何使用了,既然是函数指针数组,那么我们就可以按照数组的样子使用就好了!
add使我们前面写好的函数,后续操作不做赘述,和使用函数指针是一致的!
f1[0] = add;
f2[0] = add;
返回值为指针的函数指针声明类型
听起来挺绕的是吧?实际上这种函数声明类型说人话就是:指向一个函数,该函数的返回值是指针类型的
大致意思请看代码,结合以上所描述的代码来理解一下就好啦!
typedef int* (*func)(int, int);
int* add(int a, int b) {
return (int*)(a + b);
}
func f=add;
秒懂sizeof用法
- 只要涉及到内存分批malloc等,都会经常用到它
- sizeof直接对数组使用,取出的是该数组所有元素占用的总字节数
- sizeof直接对数据类型使用,取出的是该数据类型(在本计算机配置下)的字节数
// 我们可以利用特性使用sizeof直接计算出一个数组的长度
int p1[10];
size_t size = sizeof(p1)/sizeof(int);
关于数组初始化内存长度的问题
c1字符串数组很容易知道他的长度为6(hello五个字符加上结尾\0一个字符)
c2虽然初始化只传入了三个字符,外加最后的\0也就一共占用4个字符,但因为我们指定了长度,那么系统就直接分配了6个char类型内存!
所以我们最终使用sizeof比较得到的结果是一致的,都是6
char c1[]="hello";
char c2[6]="bey";
cout<<sizeof(c1)<<sizeof(c2)<<endl;
数组和指针的化学反应
总结:注意以下几个等价关系
- p2 = &p1[0] <-当我们用指针指向数组时,实际上是指向数组的第一个元素的地址
- p2+1 = &p1[1] <-直接对指针做加法,地址后移一位,自然就指向了数组的第二个元素的地址,并且此操作是永久的!
- *(p2+1) = p1[1] <-解引用自然就得到了数组中原来的数值
- p2[0] = p1[0] <-很神奇吧!对一个数组指针直接使用下标运算符就可以省去解引用过程而直接取出数值
int p1[] = {4, 3, 2, 1};
int *p2 = p1;
printf("%p\n", p2); // 取指针指向数组的第一位地址
printf("%p\n", &p2[0]); // 和上一条含义一样
printf("%p\n", &p2); // 取整个数组指针
printf("%d\n", *(p2 + 1)); // 对指向数组的第二位元素解引用
printf("%d\n", *(p1 + 1)); // 直接对数组开刀也是可以的,和上一条结果一样
当指针地址不断变化会出现怎样的化学反应呢?这一特性经常用在循环里面,请看如下例子的解析:
- 创建数组p1,使用指针p2指向它,num代表我们需要乘的数值
- for很明显表示循环4次
- *p2++ 他表示先对p2解引用后,在执行对其的自增,而因为p2是一个指针,他的自增就是地址的自增,最终结果即数组下标后移一位
- 注意顺序:p2解引用 -> 执行它和num的相乘并赋值p2 -> p2地址自增1
- 地址自增具有记忆性,所以每次都会后移一位直到遍历全部的数组!
// 给p1中的每个元素都乘上4的小程序
int p1[] = {4, 6, 2, 1};
int *p2 = p1;
int num = 4;
for (int k = 0; k < 4; k++) {
*p2++ *= num;
}
函数形参接收数组后两种状况
一般的,我们会在设计某函数时设置他的形参是一个数组类型的,因为我们想让他对该数组执行处理;
但我们还需要记住以下两个法则:
- 传给函数的实参实际上是一个指针,这表示函数中使用的是一个指针而不是单纯的数组(浅拷贝)
- 函数内部使用的是指针,所以通过该指针可以直接修改函数外的数组内容,达成联动!
// 形参写成一个数组
void P12_1(int arr[],int size){
for(int i=0;i<size;i++){
printf("%d ",*(arr+i));
}
}
// 形参写成一个指针
void P12_2(int *arr,int size){
for(int i=0;i<size;i++){
printf("%d ",*(arr+i));
}
}