【C语言】指针还不会?这一篇就够了

本文详细介绍了C语言中的指针概念,包括内存、指针变量、指针类型、野指针的成因及规避方法、指针运算、数组与指针的关系、二级指针、指针数组、字符指针、数组指针、函数指针和回调函数的使用。文章通过实例和解析深入浅出地阐述了指针在C语言编程中的重要性和各种应用场景。
摘要由CSDN通过智能技术生成

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在回炉重造C语言(2023暑假)
✈️专栏:【C语言航路】
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


一、什么是指针

想要理解什么是指针,必须先了解什么是 内存

1.1 内存

内存是电脑上的存储设备,一般都是4G/8G/16G等,程序运行时会加载到内存中,也会使用内存空间。我们可以看看电脑的任务管理器:

在这里插入图片描述

1.2 内存的管理与使用

我们将内存划分为一个个小格子,每一个格子是一个 内存单元,大小为 一个字节,对每一个内存单元进行 编号,假设未来要找一个内存单元,就可以通过编号(地址)很快的找到,我们把这些 编号叫做地址,而地址在C语言中又叫做指针。

在这里插入图片描述

举一个例子,在下图中,假设定义一个变量int a = 10,一个int类型的变量,需要占4个字节的空间,而每个字节都有地址,&a取出的是4个字节中的哪一个的地址呢?其实取出的是第一个字节的地址(也就是较小的地址),也就是说,&a最终取出来的地址是0x0012ff40。当然,可以把这个地址存到一个变量中,int* pa = &a*表示pa是一个指针,int代表pa所指向的类型是int类型,这个pa也叫做指针变量(它是专门用来存放地址的)。

在这里插入图片描述

总结指针理解的2个要点:

  1. 指针是内存中一个最小的单元编号,也就是地址。
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结:指针就是地址,口语中说的指针通常指的是指针变量。

1.3 指针变量的使用

在这里插入图片描述

通过以上代码就验证了,指针变量p存放的就是a的地址。

接下来,可以使用*解引用操作符来对其使用

在这里插入图片描述

1.4 指针的大小

  • 指针变量,就是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
  • 那么问题来了:一个内存单元到底是多大?刚刚讲过,就是一个字节。那它又是如何编址的呢?
    经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)
    那么32根地址线产生的地址就会是:
    00000000000000000000000000000000
    00000000000000000000000000000001
    .....
    11111111111111111111111111111111
    这里就有232个地址。 一个地址管理一个内存单元,那么232个地址就能管理232个内存单元,也就是232个字节,那2的32次方个字节又是多大空间呢?根据进制转化:
    232 Byte = 232÷1024 KB ÷1024 MB ÷ 1024 GB = 4GB
    同样的方法,64位机器,也可以计算出来。
    264 Byte = 8 GB

这里我们就明白:

  • 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
  • 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:

  • 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

2.1 指针类型的意义

假设在32位或64位机器上,指针大小都是4个字节或8个字节,直接搞一个通用指针不就完事了,那为什么要区分int*char*double*这些呢?那它肯定就有特殊的意义。

先看看下面的代码:

在这里插入图片描述

我们可以通过调试来观察变量a内存 中的变化

在这里插入图片描述

为了方便观察,我们把它调成4列

在这里插入图片描述

我们发现,内存中确实存的是44332211,只不过是倒着放的(为什么是在内存中倒着存放会在后期讲解)

接着按F10再往下走

在这里插入图片描述

我们发现,4个字节全部被改为0,这说明a的值确实被修改成0了

假设我把指针变量pa的类型改为char*结果又会是如何呢?

可以继续通过观察其内存变化

在这里插入图片描述

继续按F10

在这里插入图片描述

它只改变了1个字节!!

总结:指针类型的意义

  1. 指针类型决定了,指针在进行解引用操作的时候,一次性访问几个字节
    如果是char*类型的指针,解引用访问内存中的一个字节
    如果是int*类型的指针,解引用访问内存中的四个字节
    float*double*也同样如此…

指针类型还要其它意义,接着往下看:

在这里插入图片描述

我们发现,pa + 1跳过了4个字节,pc + 1跳过了1个字节

总结:指针类型的意义:
2. 指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过1个字节
整型指针+1,跳过4个字节
其它类型的指针也是如此…

2.2 指针+ 或 - 整数

在这里插入图片描述

注意:指针+一个数,是往高地址走,指针-一个数,是往低地址走

2.3 指针解引用

指针的类型决定了,对指针解引用的时候有多大权限(能操作几个字节)。比如:char*类型的指针解引用就只能访问1个字节,int*解引用就只能访问4个字节

在这里插入图片描述

三、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1 野指针成因

3.1.1 指针未初始化

在这里插入图片描述

3.1.2 指针越界访问

在这里插入图片描述

3.1.3 指针指向的空间被释放

在这里插入图片描述

首先test函数中的a是一个局部变量,根据局部变量的生命周期,出了它的作用域,生命周期结束。返回的时候a的地址就已经被销毁了,此时的指针变量p就是一个野指针。但是运行的时候结果还是10,这只是侥幸,这是因为之前的内存空间还没有被覆盖。我们加上个输出语句就能破坏掉这个内存空间。

在这里插入图片描述

3.2 如何规避野指针

  • 指针初始化
    在这里插入图片描述
  • 小心指针越界
  • 指针指向空间释放,及时置NULL
  • 避免放回局部变量的地址
  • 指针使用之前要检查有效性

四、指针运算

4.1 指针±整数

#include <stdio.h>
#define N 5 // #define定义的标识符常量
float a[N];
float* p; // 全局变量默认初始化为0,而NULL本质上就是0
int main()
{
	for (p = &a[0]; p < &a[N]; )
	{
		*p++;
	}
	return 0;
}

看下图就很容易理解代码循环了,但这里要主要循环体内的代码,*的优先级是比++操作符要高的,而++是后置的(先使用,后++),所以这代码的意思是把数组内5个元素全部赋值成0

在这里插入图片描述

4.2 指针 - 指针

  • 运算的前提条件:两个指针要指向同一块空间(同个数组)
  • 运算结果是:相减绝对值的结果就是两个指针之间的元素个数

在这里插入图片描述

【解析】

在这里插入图片描述

4.2.1 指针-指针的运用

之前我们求字符串长度是这么写的(不使用库函数strlen的情况下),计数法

#include <stdio.h>
int my_strlen(char* str)
{
	// 计数法
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	char a[] = "abcdef";
	int len = my_strlen(a);
	printf("%d\n", len);
	return 0;
}

当然还可以使用递归

#include <stdio.h>
int my_strlen(char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}

int main()
{
	char a[] = "abcdef";
	int len = my_strlen(a);
	printf("%d\n", len);
	return 0;
}

除了以上两种方法,还可以使用 指针 - 指针

#include <stdio.h>

int my_strlen(char* str)
{
	// 记录起始地址
	char* start = str;
	// 记录'\0'的位置
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}

int main()
{
	char a[] = "abcdef";
	int len = my_strlen(a);
	printf("%d\n", len);
	return 0;
}

【解析】

在这里插入图片描述

注意:++千万不要放到循环表达式中,因为不管是前置还是后置++,它都会有副作用,会导致return的结果有误差。

4.3 指针的关系运算(比较大小)

#include <stdio.h>
#define N 5 // #define定义的标识符常量
float a[N];
float* p; // 全局变量默认初始化为0,而NULL本质上就是0
int main()
{
	for (p = &a[N]; p > &a[0]; )
	{
		*--p;
	}
	return 0;
}

【详解】
在这里插入图片描述

上面的代码的写法有点难以理解,如果写成以下这样,结果还会是一样吗?

#include <stdio.h>
#define N 5 // #define定义的标识符常量
float a[N];
float* p; // 全局变量默认初始化为0,而NULL本质上就是0
int main()
{
	for (p = &a[N - 1]; p >= &a[0];p-- )
	{
		*p = 0;
	}
	return 0;
}

【解析】

在这里插入图片描述

其实,简化过的代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们应该避免这样书写,因为标准规定并不保证它可行

标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

五、指针和数组

  1. 指针和数组是不同的对象
  • 指针是一种变量,存放地址的,大小4/8字节的
  • 数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素类型
  1. 数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针访问数组

如何用指针来访问数组呢?

在这里插入图片描述

或者还能这么写

在这里插入图片描述

【总结】

int arr[10];
int* p = arr;//首元素地址
这里有一层等价关系
arr[i] == *(arr + i) == *(i + arr) == i [arr]
i[arr]是可以正常使用的,因为[]只是一个操作符,i和arr是[ ]的操作数而已
(就像a + b同样也能写成b + a)。在编译的过程中,arr[i]也会被翻译成*(arr+i)

六、二级指针

6.1 什么是二级指针

在这里插入图片描述

变量a的地址存放在指针变量pa中,我们称pa是一级指针。指针变量也是变量,是变量就得有地址,pa的的地址存放在ppa中,所以我们称ppa二级指针

这里再解释一下,pa前面一颗的*是告诉我们pa是指针变量,而pa指向的aint类型,所以pa的类型就是int*;同样的,ppa前一颗的*告诉我们ppa是指针变量,而ppa指向的paint*类型的,所以ppa的类型就是int**

apappa关系如下:

在这里插入图片描述

形象点就是如下所示:

在这里插入图片描述

6.2 二级指针的使用

在这里插入图片描述

七、指针数组

7.1 什么是指针数组

指针数组是指针还是数组? 
--- 是数组(存放指针的数组)

可以类比整型数组和字符数组
整型数组是存放整型的数组
字符数组是存放字符的数组
那么指针数组就是存放指针(地址)的数组

举一个简单的例子:

在这里插入图片描述

7.2 用一维数组模拟二维数组

假设要模拟三行四列的数组

思路:开辟一个数组用来存放3个数组首元素的地址,因为数组在内存中是连续存放的,所以知道首元素的地址,后面也自然而然也就跟着知道了。

在这里插入图片描述

【代码实现】

#include <stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	int b[] = { 5,6,7,8 };
	int c[] = { 8,10,11,12 };
	int* arr[] = { a,b,c };
	for (int i = 0; i < 3; i++)
	{
		// 偏移量
		for (int j = 0; j < 4; j++)
		{
			printf("%d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

八、字符指针

以往我们都是用一个字符指针指向一个字符变量:

int main()
{
	char ch = 'x';
 
    //ptr前的*是在告诉我们ptr是指针
    //字符变量的地址用字符指针ptr接收
	char* ptr = &ch;
	return 0;
}

现在还有另外一种使用方式:

#include <stdio.h>
int main()
{
    //常量字符串不能被修改,用const修饰
	const char* ptr = "hello world!";
	printf("%s\n", ptr);
	return 0;
}

以上代码很容易认为是把hello world!放到字符指针ptr里了。其实它 本质是把字符串hello world!的首字母h的地址放到了ptr中。
在这里插入图片描述

8.1 笔试题

#include <stdio.h>
int main()
{
	char a[] = "hello";
	char b[] = "hello";
 
	const char* c = "hello";
	const char* d = "hello";
 
	if (a == b)
		printf("a和b相同\n");
	else
		printf("a和b不相同\n");
 
	if (c == d)
		printf("c和d相同\n");
	else
		printf("c和d不同\n");
 
	return 0;
}

【答案】

在这里插入图片描述

【解析】

  1. 不同的数组在创建时,会在内存中开辟不同的空间。由于地址不同,则ab一定不相同。
    在这里插入图片描述
  2. 因为cd是常量字符串,这说明它们不会被随意修改。并且它们字符的首字母都是h,说明字符指针变量cd都存储的是首字符h
    在这里插入图片描述

九、数组指针

9.1 数组指针的概念

  • 字符指针char* - 存放字符地址的指针 - 也是指向字符的指针
  • 整型指针int* - 存放整型地址的指针 - 也是指向整型的指针
  • 浮点型指针float double - 存放浮点数地址的指针 - 也是指向浮点数的指针
  • 数组指针 - 存放数组地址的指针 - 指向数组的指针(数组指针本质上是指针)

9.2 数组指针的定义

同样可以类比字符指针、整型指针…

int main()
{
	// 以整型指针为例
	int n = 0;
	int* pn = &n;
	
	//数组指针仿照上面也就是
	int arr[10] = {0};
	int (*p)[10] = &arr;
	// *的优先级比[]低,首先要保证p是指针
	
	return 0;
}

注意要与指针数组区分开来

指针数组本质上是一个数组,是存放指针(地址)的数组

int arr[] = {1,2,3,4};
// 存放数组首元素地址
int *p[10] = {arr};
 
解释:
优先级 * < [],所以p指向的是数组,并且每一个元素的类型都是int*,
所以它是一个指针数组
 
int arr[10];
int (*p)[10] = &arr;
 
解释:
由于加了括号,p先和*结合,这说明了p是一个指针,然后指向一个大小
为10的数组,并且每一个元素都是int类型,所以它是一个数组指针

9.3 &数组名和数组名的区别

往期博客:点击跳转

我们都知道arr是数组名,数组名表示数组首元素的地址

&arr是什么意思,我们分别打印出它们的地址来观察:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr:%p\n", arr);
	printf("&arr:%p\n", &arr);
 
	return 0;
}

【程序结果】

在这里插入图片描述

可见arr&arr地址都是一样的,难道它们真的是一样的吗?我们再来看一段代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr:%p\n", arr);
	printf("arr:%p\n", arr + 1);
 
	printf("&arr:%p\n", &arr);
	printf("&arr:%p\n", &arr + 1);
 
	return 0;
}

【程序结果】

在这里插入图片描述

根据上面的代码我们发现,其实&arrarr,虽然值是一样的,但是意义却不一样

实际上:&arr是的是数组的地址(整个数组)。&arr + 1,跳过的是整个数组的大小 因此,&arr的类型是int (*)[N],是一种数组指针类型
在这里插入图片描述

9.4 数组指针的使用

一般用于二维数组

例:用一个数组指针打印一个二维数组

#include <stdio.h>
void Print(int(*p)[3], int row, int col)
                                         
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", *(p+i)[j]);
			// 本质p[i][j]
		}
		printf("\n");
	}
}
  
int main()
{
	int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
	
	//封装一个数组来打印
	Print(arr, 3, 3);
 
	return 0;
}

二维数组传参可以是传数组:arr[][3]arr[3][3],当然也可以传指针,二维数组的数组名是第一行数组的地址,也就是一个一维数组的地址,因此可以用数组指针接收。

数组指针还可用于一维数组,不过比较少

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);
	}
	return 0;
}

p进行解引用,访问的是整个数组,再用下标访问操作符[],访问对应下标元素。

9.5 剖析数组指针和指针数组的区别

int arr[5];  
//整型数组,数组有5个元素
 
int* p[10];  
//p是指针数组,数组有10个元素,每个元素的类型是int*
 
int(*p)[10]; 
//p是数组指针,指向10个元素的数组,每个元素是int类型
 
int(*p[10])[5]; 
// 优先级:* < [],因此p是一个数组,数组有10个元素
// 数组的每个元素的类型是:int(*)[5]的数组指针类型

十、数组传参和指针传参

10.1 一维数组传参

int main()
{
	int arr[10] = { 0 };
	//函数传参传参
	test(arr);
	return 0;
}
//1. 一维数组传参的形参可以这么写
void test(int arr[])
void test(int arr[10])

//2. arr是数组首元素的地址,整型的地址,用整型指针接收
void test(int* arr) 

int main()
{
	int* arr[10] = { 0 };
 
	test(arr);
 
	return 0;
}
//1. 数组传参,形参可以用数组接收
// 大小也可以不写
void test(int *arr[10])
void test(int *arr[])
 
//2. arr是首元素地址,数组每一个元素类型都是int*
// 一级指针传参可以拿二级指针接收
void test(int **arr) 

10.2 二维数组传参

int main()
{
	int arr[3][3] = { 0 };
 
	test(arr);
 
	return 0;
}
// 1.二维数组传参,二维数组接收
//注意:行可省,列不可省
void test(int arr[3][3])
void test(int arr[][4])

//2. 二维数组arr是第一行的地址
//第一行的地址就是整个一维数组的地址,所以用数组指针接收
void test(int (*p)[3]) 

10.3 一级指针传参

#include <stdio.h>

int main()
{
	char* p = "hello world!";
	test(p);
	return 0;
}
// 一级指针传参,一级指针接收
void test(char* p)

10.4 二级指针传参

思考:函数的参数为二级指针,实参可以是什么??

void test(int** p)
{
	;
}

int main()
{
	//1. 二级指针传参,二级指针接收
	int** p;
	test(p);
 
 	//2. 一级指针地址传参,二级指针接收
	int* p;
	test(&p);
 
 	//3. 当arr作为首元素地址,每个元素都是int*类型,二级指针接收
	int* arr[10];
	test(arr);
 
	return 0;
}

十一、函数指针

11.1 函数指针的定义

函数指针的本质上是指针,它是指向函数的指针

这可以类比数组指针(指向数组的指针)

int arr[10];
int (*p)[10] = &arr;

我们也能不能 &函数名,拿到函数的地址呢?然后再把函数的地址存到一个函数指针变量里呢?

来看看下面的一段代码:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p", &Add);
	return 0;
}

【程序结果】

在这里插入图片描述

通过以上代码我们发现,函数名也是有地址的,所以我们能不能创建一个变量并把这个地址存起来呢?

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*p)(int, int) = &Add;
	return 0;
}

【解析】

在这里插入图片描述

接下来还能类比,&arr不加&arr则是数组首元素的地址,如果不&数组名,那么单独的数组名是否是函数首元素的地址呢?

在这里插入图片描述

总结:&数组名数组名都是函数的地址

11.2 函数指针的使用

其实就是对指针进行解引用操作:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*p)(int, int) = &Add;
	
	//对指针p解引用找到Add函数
	int res = (*p)(100, 200);
	printf("%d\n", res);
	return 0;
}

【程序结果】

在这里插入图片描述

通过上述代码我们发现:*p 等价于Add,又因为&数组名数组名都是函数的地址,因此Add又等价于&Add,而指针变量p又存储Add的地址。所以可以得出关系:*p == Add == &Add == p

所以上述的代码中,解引用操作符就可以去掉了

在这里插入图片描述

11.3 有趣的代码

int main()
{
	(*(void(*)())0)()
	// 拆分
	// void(*)()是函数指针类型
	// 对int类型的0进行强制类型转化函数指针类型
	// 意思是0被当做一个函数的地址
	// *(void(*)())0 再对地址解引用,就是调用函数
	// 最右边的括号表示在调用函数时没有传参
	// 因此以上代码就是一次函数调用

	void (*p(int, void(*)(int)))(int);
	//该代码是函数的声明,声明的名字是p
	//p函数有两个参数,一个是int类型
	//另一个是函数指针类型void(*)(int),并且该函数指针能够指向的那个函数的参数是int,返回类型是void
	//p的返回类型是函数指针,类型是:void (*)(int)

	return 0;
}

十二、函数指针数组

12.1 函数指针数组的定义

函数指针数组本质上是一个数组,是用来存放函数地址的数组。

类比一下:

int Add(int x, int y)
{
	return x + y;
}

int main()
{
   	int (*p)(int,int) = &Add //函数指针
      
    //函数指针数组就是基于函数指针改造的
    //函数指针数组本质上是个数组
 
    int (*p[3])(int,int) = {&Add};
 
    //[]的优先级高于*,所以p先和[]结合,所以p是个数组
    //类型是  int (*)(int,int) -->函数指针类型
}

12.2 函数指针数组的用途

在讲函数指针数组用途之前,首先用代码为大家实现一个简单的计算器,能实现加、减、乘、除的功能。

#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 input = 0;
	int x = 0;
	int y = 0;
	int res = 0;
	do
	{
		//打印菜单
		menu(); 
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			res = Add(x, y);
			printf("相加的结果为:%d\n", res);
			break;
		
		case 2:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			res = Sub(x, y);
			printf("相减的结果为:%d\n", res);
			break;
		
		case 3:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			res = Mul(x, y);
			printf("相乘的结果为:%d\n", res);
			break;
		
		case 4:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			res = Div(x, y);
			printf("相除的结果为:%d\n", res);
			break;
		
		case 0:
			printf("退出计算器");
			break;
		
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}

【程序结果】

在这里插入图片描述

若此时计算器中还要加入按位与&、按位或|等操作符运算,虽然只要在代码内部多加几行函数,但这也会导致代码越来越来长。所以,可以使用函数指针数组来分别存放AddSub的地址

#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 (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
//这里的0,是为了和菜单对应,当然也可以写NULL
 
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int res = 0;
	do
	{
		menu(); //打印菜单
		printf("请选择:");
		scanf("%d", &input);
 
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
 
			//当input为0则退出,
			//当input为1则调用Add函数
			//以此类推...
 
			int res = p[input](x, y);
			printf("结果为:%d\n", res);
		}
		else
		{
			printf("选择错误,请重新选择\n");
		}
		
	} while (input);
}

十三、指向函数指针数组的指针

指向函数指针数组的指针本质上是个指针,指针指向一个数组,数组每个元素的类型都是函数指针

13.1 定义

int Add(int x, int y)
{
	return x + y;
}
 
int main()
{
	//整型数组
	//arr是数组,&arr
	int arr[10] = { 0 };
	int(*p)[10] = &arr; //数组指针
 
	//函数指针数组
	//同样的,p也是个数组,也&p
	int (*p[3])(int, int);
	int (*(*pp)[3])(int, int) = &p; //这就是指向函数指针数组的指针
}

用概念再来分析一下:

int (*(*pp)[3])(int, int) = &p

pp先和*结合,说明pp是一个指针

②指向一个数组有3个元素

③每个元素的类型都是int (*)(int, int) --> 函数指针类型

Q:这么复杂类型怎么看?

int (*(*pp)[3] )(int, int),把(*pp)[3]去掉,剩下就是类型

十四、回调函数

回调函数就是一个通过函数指针调用的函数。

14.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 calc(int (*p)(int ,int)) //函数传参,函数指针接收
{
	int x = 0;
	int y = 0;
	printf("请输入两个整数:");
	scanf("%d %d", &x, &y);
	int res = p(x, y);
	printf("计算结果为:%d\n",res);
}
 
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 input = 0;
	do
	{
		menu(); //打印菜单
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
 
		case 2:
			calc(Sub);
			break;
 
		case 3:
			calc(Mul);
			break;
 
		case 4:
			calc(Div);
 
		case 0:
			printf("退出计算器");
			break;
 
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}

【图解】

在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值