一、数组指针和指针数组的回顾

在这里插入图片描述
验证: * 号 和&符号是存在抵消关系的

  • [ i ]--------i 数组元素的下标,通过方括号可以进行下标访问,访问下标为i的数组元素
  • arr[i][j]------是访问行i,j列的元素
#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	printf("%d\n", *p);//实际上就是p里面存放的就是&a,*p=*(&a)=a
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*parr)[10] = &arr;//数组指针(存放数组的地址)
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*parr)[i]);//实际上是*(&arr)[i]=arr[i]=*(arr+i)
	}
	return 0;
}

结果是:
在这里插入图片描述

二、维数组传参的本质

二维数组在内存中存储的图例:
在这里插入图片描述

  • 二维数组在内存中是连续存储的,行下标代表了他在哪一组(行),列下标代表的是他在这一组的第几个元素
    在这里插入图片描述
  • 二维数组的数组名代表的也是首元素地址,但是它的首元素是第一行的一维数组(下标为0),所以二维数组数组名是第一行一维数组的地址

在这里插入图片描述

将二维数组传参打印

//将二维数组传参打印
void print1(int arr[3][5],int r,int c)//写成数组接收也可以
//void printf1(int (*arr)[5],int r,int c)本质上传过来的是一维数组的指针,二维数组的首元素就是第一行一维数组的地址。
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
		//以下三种写法均可以
			//printf("%d ",(*(p+i))[j]);
            //printf("%d ", p[i][j]);
              printf("%d ", *(*(p + i) + j));
		}
		      printf("\n");
	}
}
void print2(int(*p)[5],int r,int c)//利用数组指针接收,p是指针名,*p代表是指针
//指针p指向5个元素的数组,每个元素类型是int
{

	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ",(*(p+i))[j]);//p[i]
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5 },{2,3,4,5,6},{ 3,4,5,6,7 } };
	print1(arr, 3, 5);//将数组名和行,列传进去
	print2(arr,3,5);//直接传进二维数组的数组名,也就是首个数组的地址也就是第一行的一维数组{1,2,3,4,5}的地址
	return 0;
}

三、函数指针变量

1、变量有地址,函数也有地址,可以存储在函数指针变量中
2、&函数名=函数名,都是函数的地址
3、当没有获得函数名时,通过函数地址也可以去调用函数
4、看一个变量的类型,就将变量名去掉,剩余部分就是它的类型

int Arr(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int* pa = &a;//整形指针变量,存放整型变量地址
	int arr[5] = { 0 };
	int(*parr)[5] = &arr;//数组指针变量,存放数组的地址
	//arr &arr不一样
	int x = 0;
	int y = 0;
	Arr(x, y);
	printf("Arr= %p\n", Arr);
	printf("&Arr= %p\n", &Arr);
	//Arr 和 &Arr一样
	int (*pf)(int, int) = Arr;//函数指针变量,用来存放函数的地址
	//int (*pf)(int, int) = &Arr;
	//当没有获得函数名时,通过函数地址也可以去调用函数
	int ret1 = (*pf)(4, 5);//pf里面存着函数地址&Arr,解引用得到函数名再传参可以调用
	int ret2 = pf(4, 5);//pf里面存的就是函数名Arr(函数地址)
	int ret3 = Arr(4, 5);//直接通过函数地址(函数名),函数传参调用函数
	printf("ret1 = %d\n", ret1);
	printf("ret2 = %d\n", ret2);
	printf("ret3 = %d\n", ret3);
	return 0;
}

在这里插入图片描述

四、两段有趣的代码(提高代码的阅读能力)

一、一段有趣的函数调用

在这里插入图片描述
(类型)常量----------------强制类型转换

将0强制转化为函数指针类型,所以0就是一个函数指针变量,所以0就是函数的地址,*代表解引用,也就是找到0地址处的那个函数了,加上小括号就是传参的意思,也就是没有参数,无返回值void,所以是一次函数调用,调用的是0地址的那个函数

在这里插入图片描述

模拟开机时会调用0地址函数,其实0地址处是不可以调用的

二、一段有趣的函数声明

在这里插入图片描述

函数声明,定义,调用有什么区别

函数定义:将函数所有都交代了的过程,返回类型,函数名,函数参数,参数名,函数体
函数声明:返回值类型,函数名,参数类型(强调类型)
函数调用:函数名,参数

注·:上述图片有举例子

阅读这种代码,一般需要从里到外慢慢剖析,找里面认识的东西,然后一步一步慢慢的向外探索

五、typedef关键字

像上述代码一样,其中类型特别乱,代码的可读性也不是很高,那么我们是否可以简化类型呢,使用typedef关键字方可解决(type(类型)+ define(定义)将类型重新定义为新名字)
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

//返回类型我们一般是什么_t,size_t
//⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
typedef unsigned int uint;//整形
//如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;//整形指针
//但是对于数组指针和函数指针稍微有点区别:
//⽐如我们有数组指针类型 int(*)[6] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[6];////新的类型名必须在*的右边
//函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef int (*pf_t)(int, int);//函数指针类型,重定义要把名字放在*后面
//
int Add(int x, int y)
{
	return x + y;
}
int main()
{
   //unit = unsigned int
	unsigned int n1 = 10;
	uint n2 = 10;
	//int* = pint_t
	int* p = &n1;
	ptr_t p1= &n1;
	//int(*)[6] = parr_t
	int arr[6] = { 0 };
	int(*parr1)[6] = &arr;
	parr_t parr2 = &arr;
	//int (*)(int, int) = pf_t
	Add(3, 4);
	int (*PAdd)(int, int) = &Add;
	pf_t padd1 = &Add;
	return 0;
}

有了以上的基础:我们可以将这段代码给简化了

void (* signal ( int , void (*) (int) ) ) (int);

typedef void(*pf_t) (int)
pf_t signal(int,pf_t);

六、 函数指针数组

数组是⼀个存放相同类型数据的存储空间,可以将函数指针类型的数据集合到一个数组内,形成了函数指针数组

运算符优先级 * <[ ]

int * parr[5]:parr先与[5]结合变为数组,意思是parr是数组,它有5个元素,每个元素的类型是int*
int (*parr)[5] : parr先与 * 结合变为指针,指向一个数组,数组有5个元素,每个元素是int类型

了解了这个,请看下面代码和注释理解函数指针数组:

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()
{
	int* parr2[5] = { 0 };//整形指针数组存放整形地址
	char* parr1[6] = { 0 };//字符指针数组存放字符地址
	//
	//以下是用函数指针存放函数的地址
	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 };
	                        //   0   1   2   3
	//代表pf是一个数组,他有4个元素,每个元素的类型是int(*)(int,int)函数指针类型
	//遍历函数调用
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", pf[i](4, 6));
		//printf("%d ",(*pf)[i](4, 6));这块不可以这样写,因为*只能解引用指针,不能解引用数组
	}
}

这里给大家介绍*只能解引用指针:


	int a, b, c, d, e;
	int* p1 = &a;
	int* p2 = &b;
	int* p3 = &c;
	int* p4 = &d;
	int* p5 = &e;
	int* parr[5] = { p1,p2,p3,p4,p5 };//整形指针数组存放整形地址
	int* (*pint)[5] = &parr;//意思是pint是指针,指向数组为5个元素,每个元素类型是int*
	for (int i = 0; i < 5; i++)
	{
		printf("%p ", (*pint)[i]);//这里是解引用数组指针了,将每一个地址给打印
	}
	return 0;

七、计算器

1、首先我们先写一个普通的就算器

//计算器
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 1:
			printf("请输入两个整数: ");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("结果是:%d", ret);
			break;
		case 2:
			printf("请输入两个整数: ");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("结果是:%d", ret);
			break;
		case 3:
			printf("请输入两个整数: ");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("结果是:%d", ret);
			break;
		case 4:
			printf("请输入两个整数: ");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("结果是:%d", ret);
			break;
		case 0:
			printf("正在退出计算器....");
			break;
		default:
			printf("请重新选择: ");
			break;
		}
		printf("\n");
	} while (input);
	return 0;
}

二、计算器转移表实现(函数指针数组实现函数调用)

由于考虑计算器可能会添加其他的计算,到时候switch语句会更长,增添了许多不必要的代码,然而实现计算器运算的部分,基本逻辑是一样的,只是函数调用不一样,所以可以直接通过函数指针数组来通过下标调用函数
转移表------------------------------------其实就是通过函数指针转移到函数调用在把结果返回的过程

//计算器
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 And(int x, int y)
{
	return x & y;
}
int Or(int x, int y)
{
	return x | y;
}
int Nor(int x, int y)
{
	return x ^ y;
}
int contray(int x)
{
	return ~x;
}
void menu()
{
	printf("*********************************\n");
	printf("**********1.Add  2.Sub***********\n");
	printf("**********3.Mul  4.Div***********\n");
	printf("**********5.And  6.Or ***********\n");
	printf("**********7.Nor  8.contray*******\n");
	printf("**********0.exit      ***********\n");
	printf("*********************************\n");
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int (*parr[8])(int, int) = { 0,Add,Sub,Mul,Div,And,Or,Nor };
	do {
		menu();
		printf("请选择: ");
		scanf("%d", &input);
		if (input >= 1 && input <= 7)
		{
			printf("请输入两个数: ");
			scanf("%d %d", &x, &y);
			int ret = parr[input](x, y);
			printf("结果是:%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器");
		}
		else if (input == 8)
		{
			printf("请输入一个数:");
			scanf("%d", &x);
			int ret = contray(x);
			printf("结果是:%d\n", ret);
		}
		else
		{
			printf("请重新输入: \n");
		}
	} while (input);
	return 0;
}

预知回调函数是什么请看下一节笔记;

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐