目录
OK,兄弟们,这篇文章我们继续来谈指针。
1.函数指针
函数指针,顾名思义就是指向函数的指针,也就是说这个指针变量内部存储的是函数的地址。
下面我们来看一个代码。
#include <stdio.h>
void test()
{
printf("azaz_plus\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
这段代码很明显是要比较函数名跟&函数名的区别,话不多说我们直接来看结果。
在打印完之后呢我们发现,这两个竟然是相同的,知道了这一点之后,我们来继续往下看一下如何存储这个地址。
首先我们可以想到函数的声明,在函数声明的时候,我们首先要声明函数的返回值类型,然后是函数名,最后是函数的参数,其实函数指针的创建也是如此,所以我们还是以上述代码为栗,来看一下下面的这两行代码。
void (*p1)() = &test;
void* p2() = &test;
还记得我们在之前的文章中提到过()的优先级是要高于*的,所以如果是第二种写法的话,p2会先于括号结合,然后void与*结合,所以p2写法实际上是一个返回值为void*类型的函数的写法,固然这种写法是错误的。然后是第一种写法,因为加了括号的原因,所以*会先与p1结合,表示指针,然后再与()跟void结合,表示这是一个返回值为void类型的函数指针。
所以说第一个代码的意思就是,一个名为p1的指针是一个返回值为void类型,且无参数的函数的指针。
然后我们再来举几个栗子,大家自己感受一下
#include <stdio.h>
void test()
{
printf("azaz_plus\n");
}
int add(int a, int b)
{
return a + b;
}
char ch(char a)
{
return a;
}
int* Init(int* a, int b)
{
return 0;
}
int main()
{
void (*p1)() = &test;
int (*p2)(int, int) = &add;
char (*p3)(char) = ch;
int* (*p4)(int*, int) = Init;
return 0;
}
好的,下面的话,我们还是继续来讨论这个函数指针的问题,首先大伙思考一下,下面几个变量的名字和类型都是什么
char a;
int arr[10];
int(*p1)[10];
int* p2[10];
void (*p3)(int, int);
首先第一个自然是很简单,名字是a类型是char。对于第二个,可以说是他的名字是arr,类型是int[10],第三个是一个数组指针,他的名字是p1类型是int(*)[10]。第四个是一个指针数组,他的名字 是p2, 类型是 int*[10]。 现在我们来类比一下, 最后一个就很明显了, 他的名字是p3, 类型是void(*) (int,int)。
那么在了解了这些东西之后,我们来看一下《C陷阱与缺陷》中的两段代码,先来看第一段
(*(void (*)())0)();
ヽ( ̄▽ ̄)و,大体一看这什么玩意,但是没有关系啊,因为我们是糕手,不着急我们慢慢来分析,首先我们可以发现void(*)()这个东西好像是一个函数指针,但是因为没有名字,所以是我们上面研究的函数指针的类型,再进行分析我们发现,这个函数指针类型被括号括起来了,而且后面紧跟了一个零,这不就是强制类型转换吗,然后括号外面又跟了一个*,因为前面给0强制类型转化为指针类型了,所以说这个*的作用是对这个指针进行解引用,然后紧跟了一个括号给他括起来,之后呢又跟了一个括号,很明显,最后的这一个括号是调用这个东西的,因为函数指针解引用就是一个函数,所以是可以被调用的。
所以,这段代码表示,把0强制类型转化为void(*)()类型之后再解引用然后调用,关于这段代码的意义或者说作用我们不妨日后再谈。
现在来看第二段
void (*signal(int, void(*)(int)))(int);
这段代码的复杂程度对于第一个来说只能说是有过之而无不及,但是没有关系啊,因为我们是糕手。我们还是慢慢的来分析。先大体的一看结构,好像跟个函数指针差不多,我们先从最外面的括号开始,可以先不管内部括号的内容,因此可以简化为void(*signal())(int),这个形式就跟函数指针非常接近了,所以我们可以先去研究他跟函数指针不同的地方,也就是signal()是什么东西,先把这段代码提取出来 signal(int,void(*)(int)),此时,我们不难发现,这是一个函数,他的参数是int类型和函数指针类型,然后如果除去这一段代码的话我们发现剩下的 void(*)(int)不正是一个函数指针类型吗,所以现在我们似乎明白了,这是一段函数的声明。
函数名为 signal参数是int和void(*)(int)返回值为void(*)(int)。
ok,兄弟们,对于函数指针学到现在我们发现,这玩意的写法也太复杂了,万一以后我们在写代码的时候有一个不注意的话不就出错了?这要怎么搞呢?
不知道大伙还记不记的typedef这个操作符,所以我们可以用这个来简化一下函数指针的写法。
但是在简化的时候对于写法有一定的要求,比如下面的栗子
typedef void(*pfun_t)(int);
pfun_t add;
pfun_t signal(int, pfun_t);
第一行代码的意思是将 void(*)(int)类型重名为pfun_t,因为语法的要求,所以pfun_t要写在*后面,这一点大家注意就好了,然后大家类比一下 char a 的创建,类型在前,变量名在后所以对于一个函数指针的创建就可以变成第二行的写法,函数指针的名字是add,类型是pfun_t。
关于第三行代码呢实际上是一个函数声明,函数名字是signal返回类型是pfun_t,参数类型是int 和pfun_t。具体原因这就不再解释了,如果不懂可以来私信我。
Ok,关于函数指针就搞到这里,下面我们来看另一块内容。
2.函数指针数组
我们在前面学过指针数组这个东西,也就是存放指针的数组,既然这样,被存放的指针也就可以使函数指针了,所以自然就出现了函数指针数组。
我们不妨先来看指针数组
int *arr[5];
很明显,这段代码的数据类型是int(*)[5]的,所以我们只需要吧这个数据类型换一下,换成函数指针是不是就可以了呢
我们以下面代码为栗子
int add(int a, int b)
{
return a + b;
}
对于这个函数而言,他的类型是函数指针类型是int(*)(int,int),假设我们要创建一个有5个元素的函数指针数组,并且数组中存的这五个函数的返回值都是int,所以我们只需要吧上面提到的int(*)[5]是有五个整形指针,所以我们不妨先把整形指针换一下,也就成了int(*)(int,int)[5] ,我们不妨假设数组名是p,所以我们此时创建的函数数组指针也就是 int(*p)(int,int)[5],但是如果这样写的话,岂不是除去两个括号之后就剩下了int[5]?这样函数的返回值类型不就成数组了吗,先不提函数是不允许返回数组的,单纯就是这样写,也没达到我们的目的啊.
所以我们需要让p先跟[5]结合,表明这是一个数组,然后再声明数组内容的类型,所以正确的写法就是这样的 int(*p[5])(int,int)
在上面的写法中,p先和[5]结合,表明这是一个数组,然后数组的元素类型是int(*)(int,int)
ok,写到这里之后,我们先暂时不向下继续推进,而是停下来看一下函数指针数组的一个用法
比方说我们先写一个简单的计算机小程序
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
然后啊,我们不难发现,在这个小程序中有很多重复出现的地方,比如说输入两个数这边就有很多重复出现的代码,所以我们不妨应用一下刚学到的函数指针数组,稍加修改,代码就变成了这样
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
这个写法如果你对前面的知识已经了如指掌的话,要看懂其实是非常简单的,所以在这里我们就不再做过多的解释了。
3.指向函数指针数组的指针
就跟数组指针类似,只不过数组指针指向数组,而函数指针数组指针指向的是函数指针数组。(好像有点绕)。
首先我们要清楚的是指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素是函数指针类型的。
既然知道了这些,结合我们前面讲的与数据类型相关的知识,定义这么一个变量就变的简单起来了
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
在上述的定义中ppfunArr是指针的名字,而指针指向的类型是void(*(*)[5])(const char*)类型,表示的是有5个类型为void(*)(const char*)类型的函数指针。
关于这个,我们暂时就了解这些,更多的知识我们在以后的实践中进行讲解。
4.回调函数
先来看一下回调函数的定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。
关于回调函数的详细介绍我会在之后几天更新一篇关于C语言qsort函数的应用以及模拟实现,在那一篇文章中,我会以栗子的形式来展示回调函数。 所以在这里就不过多的介绍了,大家目前只需要知道回调函数是通过函数指针来间接访问函数,因为是间接访问,所以说即使是被static修饰的函数也是可以访问的。
5.指针和数组的相关题目
下面的话,我们来一些题目,稍作练习,题目的答案我会写在博客后面,题目的解析我会尽力在本周内完成更新(推荐一次性做完之后再去看答案,因为我没给答案搞题号,直接看会比较麻烦,并且也会搞乱你的思路)
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p + 1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p + 1));
printf("%d\n", strlen(&p[0] + 1));
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
6.指针笔试题
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
7.答案
一维数组 16 4/8(这是4或者8的意思) 4 4/8 4 4/8 16 4/8 4/8 4/8
字符数组 6 4/8 1 1 4/8 4/8 4/8 随机 随机 访问冲突 访问冲突 随机 随机 随机 7 4/8 1 1 4/8 4/8 4/8 6 6 访问冲突 访问冲突 6 随机 5 4/8 4/8 1 1 4/8 4/8 4/8 6 5 访问冲突 访问冲突 随机 随机 5
二维数组 48 4 16 4/8 4 4/8 16 4/8 16 16 16
指针笔试题 2,5 0x100014 0x100001 0x100004 4,2000000 1 FFFFFFFC,-4 10,5 at POINT ER ST EW
关于题目的讲解我会在之后的博客中更新。
那么今天就到这里了,感谢您的观看,我们下次再见。