C语言指针的深入讲解(二)

目录

1.函数指针

2.函数指针数组

3.指向函数指针数组的指针

4.回调函数

5.指针和数组的相关题目

6.指针笔试题

7.答案


OK,兄弟们,这篇文章我们继续来谈指针。

1.函数指针

函数指针,顾名思义就是指向函数的指针,也就是说这个指针变量内部存储的是函数的地址。

下面我们来看一个代码。

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

这段代码很明显是要比较函数名跟&函数名的区别,话不多说我们直接来看结果。

e1703304ed1248969ee43aaa7ad2fec4.png

 在打印完之后呢我们发现,这两个竟然是相同的,知道了这一点之后,我们来继续往下看一下如何存储这个地址。

首先我们可以想到函数的声明,在函数声明的时候,我们首先要声明函数的返回值类型,然后是函数名,最后是函数的参数,其实函数指针的创建也是如此,所以我们还是以上述代码为栗,来看一下下面的这两行代码。

    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/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

关于题目的讲解我会在之后的博客中更新。

那么今天就到这里了,感谢您的观看,我们下次再见。

 

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值