【C语言】(指针系列3)数组指针+函数指针+typedef+函数数组指针+转移表

前言:前言:开始之前先感谢一位大佬,清风~徐~来-CSDN博客,由于是时间久远,博主指针的系列忘的差不多了,所以有顺序部分借鉴了该播主的,同时也加入了博主自己的理解,有些地方如果解释的不到位,请翻看这位大佬的,感谢大家!!!!!!

目录

一,指针数组

二、指针数组和数组指针的区别

数组指针和指针数组的写法

数组指针的初始化

三、二维数组传参的本质

重点

二、函数指针

什么是函数指针变量呢?

函数指针变量的使用

两端有趣的代码:

1.( *( void ( * )( ) )0 ) ( );

2.void( *signle(int, void( * )(int) ) ) (int)

typedef重命名函数

 四、函数指针数组

函数指针数组的实际应用:转移表

结尾祝福语:


 

一,指针数组

思考:我们上一系列已经知道什么是数组指针了,那么指针数组又是什么哪

答案是:指针数组是一个数组是数组,储存的是地址(首元素),数组指针是数组还是指针哪?答案是--------指针变量

我们已经熟悉:

  • 整形指针变量: int * pi; 存放的是整形变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
  • 组指针:存放的是数组的地址,能够指向数组的指针变量

二、指针数组和数组指针的区别

数组指针和指针数组的写法

int*parr[10]//指针数组 

解释数组名先和[]结合,说明这是一个数组,数组中有10个元素,元素的类型是(int*),所以p2是一个数组,数组元素指针,叫做指针数组。

int(*p2)[10]//数组指针

解释p2先和(*)结合说明p2是一个指针变量,然后指针指向的是一个大小为10个整形的数组,,所以p2是一个指针,叫做数组指针。

[]的优先级要高于*号,所以必须要叫()来提高优先级,确保p2与*结合


数组指针的初始化

数组指针是用来存放数组地址的,那怎么才能获得数组地址?这里就要用到&的符号,如果要存放数组地址,就得存放数组指针变量中

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
	return 0;
}

 我们调试可以看到

 

我们调试也能看到 &arr 和 p 的类型是完全⼀致的。

三、二维数组传参的本质

我们将之前,先来回忆一下一维数组传参,一维数组传参为了避免开辟额外的空间,所以只需要传数组的首地址即可

我们再来看我们曾经写过的一段二维数组传参的一段代码:

	void test(int a[3][5], int r, int c)
	{
		int i = 0;
		int j = 0;
		for (i = 0; i < r; i++)
		{
			for (j = 0; j < c; j++)
			{
				printf("%d ", a[i][j]);
			}
			printf("\n");
		}
	}
	int main()
	{
		int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
		test(arr, 3, 5);
		return 0;
	}

这里实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗? 

 

重点

  • 二维数组在内存中是连续存储
     
  • 二维数组可以理解为一维数组的数组,二维数组的每一行都可以看成一个一维数组
     
  • 二维数组名也是元素的首地址,这里的首地址是指第一行首元素,传过去的是第一行这个一维数组的地址,也就是arr[0]的地址。
     
  • 第一行的一维数组的类型就是int[5],所以第一行的地址的类型就是int(*) [5]
     

二维数组传参,形参的部分可以写成数组形式,也可以写成指针形式,如下:

#include <stdio.h>
void test(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));//等价于p[i][j]
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	return 0;
}
  • p:数组首元素的地址,也就是一维数组arr[0]的地址。
     
  • p+i:跳过 i 个 int[5] 这样的数组(p的类型是数组指针),指向arr[i],p+i 就是一维数组 arr[i] 的地址
     
  • *(p+i):访问一维数组arr[i],等价于一维数组arr[i],而 arr[i] 是数组名,又是数组首元素的地址,也就是 arr[i][0] 的地址
     
  • *(p + i) + j:由于 *(p+i)是 arr[i][0] 的地址,所以 +j 跳过 j 个整形(指向整形),也就是 arr[i][j] 的地址
     
  • *( *(p + i) + j):由于 *(p + i) + jarr[i][j] 的地址,进行解引用操作,就是找到 arr[i][j]
     
  • 最终:*( *(p + i) + j) 等价于 arr[i][j]。

二、函数指针

什么是函数指针变量呢?


根据前面学习整型指针,数组指针的时候,类比一下,我们不难得出结论

函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。那么函数是否有地址呢?我们做个测试:

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

 

函数的地址确实是有的,同时也验证了函数的地址也是可以用&调取出来的。同时我们观察发现,函数名和取出来的地址是一样的

函数指针变量的使用

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。如下:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//int a = 10;
	//int* pa = &a;//整型指针变量

	//int arr[5] = {0};
	//int (*parr)[5] = &arr;//parr 是数组指针变量
	//arr:数组首元素的地址   &arr:数组的地址
	
	//&函数名和函数名都是函数的地址,没有区别
	//printf("%p\n", &Add);
	//printf("%p\n", Add);

    //int(*pf3)(int, int) = Add;
	//int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的 
	
	//int (*pf)(int,int) = &Add;//pf 函数指针变量,()不能省略
	int (*pf)(int, int) = Add;//pf 函数指针变量
	int ret1 = (*pf)(4, 5);
	int ret2 = pf(4, 5);//pf等价于Add
	printf("%d\n", ret1);
	printf("%d\n", ret2);

	int ret = Add(4, 5);
	printf("%d\n", ret);

	//int (*pf)(int x, int y) = &Add;//pf 函数指针变量
	//int (*)(int,int) 函数指针类型
	return 0;
}
  1. int (*pf)(int, int) = Add,*pf外的 () 不能省略。
  2. pf == (*pf) == Add == &Add。

 

两端有趣的代码:

1.( *( void ( * )( ) )0 ) ( );

在这里插入图片描述

在这里插入图片描述

2.void( *signle(int, void( * )(int) ) ) (int)

在这里插入图片描述

 


typedef重命名函数

typedef是用来重命名的,可以将复杂的名字简单化,规范化 

比如我们命名了一个结构体叫做jinfsjajngijiasogjoiasjda(随便打的),我们每次调用都要写很长一段复杂的东西,但是有了typedef这个东西,我们可以将它重命名为js对!就这两个字符就可以表达这个结构体

typedef unsigned int uint;

//将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t; 

但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; 

函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写: 

typedef void(*pfun_t)(int);//新的类型名必须在*的右边

那么要简化代码2,可以这样写:

typedef void(*pfun_t)(int);

pfun_t signal(int, pfun_t); 

重点

给出typedef命名一个数组的例子:

#include<stdio.h>
int main()
{
typedef int IntArray[5]//定义一个包含5个int类型元素的数组类型IntArray
IntArray arr={1,2,3,4,5};// 使用 IntArray 声明一个数组

for(int i=0;i<5;i++)
{
printf("%d",arr[i]);

}
return 0;
}

 

 四、函数指针数组

定义:

int(*parr[3])();

解释:parr先和[]结合表明这是一个数组,数组的内容是什么,是int(*)()类型的函数指针


函数指针数组的实际应用:转移表

 计算器的一般实现路径代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
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;
}
void menu()
{
	printf("*************************\n");
	printf("**1:add***********2:sub**\n");
	printf("**3:mul***********4:div**\n");
	printf("*********0:exit**********\n");
	printf("*************************\n");

}
int main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请输入:");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d+%d=%d\n", x, y, ret);
			break;
		case 2:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d-%d=%d\n", x, y, ret);
			break;
		case 3:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d*%d=%d\n", x, y, ret);
			break;
		case 4:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d/%d=%d\n", x, y, ret);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

用函数指针实现的:

#include<stdio.h>
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;
}
void menu()
{
	printf("*************************\n");
	printf("**1:add***********2:sub**\n");
	printf("**3:mul***********4:div**\n");
	printf("*********0:exit**********\n");
	printf("*************************\n");

}
int main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请输入:");
		scanf("%d", &input);
		int(*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };//转移表
		if (input == 0)
		{
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = arr[input](x, y);
			printf("ret=%d\n", ret);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);
	return 0;
}

 不仅仅可以实现加减乘除,还能实现按位与,或,异或,左移,右移等操作,只需在数组中追加函数地址即可,当然前提是将函数敲出来,这种就叫作转移表。

结尾祝福语:

指针系列三到这里就结束了,我们对指针的熟悉又近了一步,指针说是新手的噩梦,其实当你完全了解之后,指针也只不过是取经之路的一难而已,当我们取完经之后,我们会发现,关关难过关关过,斗罢艰险再出发!!!!!!!

最后:风会带来故事的种子,时间是指发芽,但也请不要忘记,旅途中你所看到的风景,人最不能忘的就是初心!!!!!

我们指针系列四再会!!!!!!!!!!!!!!!!!!!!!!!!

 


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wheel down ~~

爱意随风起,而你的打赏会变成风

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值