02指针进阶

Posted 再吃一个橘子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了02指针进阶相关的知识,希望对你有一定的参考价值。

目录

1.本章重点

2.字符指针

 3.指针数组

4.数组指针

4.1数组指针的定义

4.2&数组名VS数组名

 4.3数组指针的使用

 5.数组参数,指针参数

5.1一维数组传参

5.2二维数组传参

 5.3一级指针传参

 5.5二级指针传参

6.函数指针

7.函数指针数组

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

9.回调函数


1.本章重点

1. 字符指针
2. 数组指针
3. 指针数组
4. 数组传参和指针传参
5. 函数指针
6. 函数指针数组
7. 指向函数指针数组的指针
8. 回调函数
9. 指针和数组面试题的解析
 

指针的主题,我们在C语言初阶阶段的《指针》章节已经接触过了,我们知道了指针的概念:

  • 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  • 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  • 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  • 指针的运算。

C语言进阶阶段,我们继续更加深入地讨论指针的高级主题。

2.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*

一般使用:

#include<stdio.h>
int main()
{
	char c = 'w';
	char* pc = &c;//pc指向一个字符变量

	char* p = "hello bit";//这里是把一个字符串放到pstr指针变量里了吗?

	printf("%c\\n", *pc);
	printf("%s\\n", p);
    //上面表达式地作用是:把常量字符串“hello bit”的第一个字符h的地址赋值给p

	return 0;
}

解释:(    注意!!    )

char* pc = &c;   pc是指向一个变量地址的指针变量,解引用以后指向的是变量c的内容,printf("%c\\n", *pc);即可。

char* p = "hello bit";//这里不是是把一个字符串放到p指针变量里,而是指针变量p指向字符串中的第一个字符h的地址,把常量字符串“hello bit”的第一个字符h的地址赋值给pprintf("%s\\n", p);即可,不需要解引用

(     代码 char* p = "hello bit"; 特别容易让同学以为是把字符串 hello bit 放在了字符指针 p 里了,但本质是把字符串 hello bit. 首字符的地址放到了p中    )

但是我们不提倡这样写常量字符串赋值给指针,比如,我们想修改*p = 'Q';没有编译错误,但是运行起来会报错,显示常量字符串不能被修改。

所以我们一般在用指针接收常量字符串的时候往往会加个const 来修饰,让它不能被修改!!

const char* p = "hello bit";

面试题:选自《剑指offer》(       笔刷!! 

//下面程序输出什么结果?
#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	char* str3 = "hello bit.";
	char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\\n");
	else
		printf("str1 and str2 are not same\\n");
	if (str3 == str4)
		printf("str3 and str4 are same\\n");
	else
		printf("str3 and str4 are not same\\n");
	return 0;
}

答案

str1 and str2 are not same

str3 and str4 are same

解释

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

 3.指针数组

在C语言初阶《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。这里我们再复习一下,下面指针数组是什么意思?

	char arr1[5];//字符数组 - 存放字符的数组
	int arr2[5]; //整型数组 - 存放整型的数组

	//指针数组 - 存放指针的数组
	int*
	char*
	short*

举个例子:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;

	int* arr[4] = { &a,&b,&c,&d };//arr是整型指针数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(arr[i]));
	}

	return 0;
}

很简单,不再赘述。

再举个例子,细品~~

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);//p[i] == *(p+i)
			//parr[i][j] == *(parr[i]+j)
		}
		printf("\\n");
	}
	return 0;
}

解释

指针数组parr存放的是arr1[ ],arr2[ ],arr3[ ]数组的首地址(  即:数组名是首元素的地址  ),我们想通过指针数组parr来访问每个数组arr1[ ],arr2[ ],arr3[ ]中的元素,该怎么做呢?

牢记:指针数组本质上也是数组 ,是数组就能通过下标的形式访问数组中的元素 ,只不过访问到的和普通数组访问到的不太一样,指针数组访问到的是地址 。

首先:先获取指针数组parr中存放的的每一个数组的首地址(parr[ i ]

其次:通过parr[ i ]访问到的数组地址,来遍历访问数组中的每个元素*( parr[ i ] + j ),也可以写成parr[ i ][ j ]。(  仔细体会~ )

 栗子3:

int main()
{
	const char* arr[5] = { "abcdef","bcdefg","hhhh","KKKK","OOPOO" };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\\n", arr[i]);
	}
	return 0;
}

4.数组指针

4.1数组指针的定义

	int* p;//整型指针 - 指向整型的指针
	char* p;//字符指针 - 指向字符的指针
	//数组指针 - 指向数组的指针

说明:

	int a = 10;
	int* pa = &a;//整型的地址存放在整型指针中

	char ch = 'Q';
	char* pc = &ch;//字符的地址存放在字符指针中

	int arr[10] = { 0 };
	int* p = arr;//数组首元素的地址

	int* parr[10];//这样写是指针数组
	&arr;//取出的是数组的地址,应该存放在数组指针中
	//数组指针怎么写??


	int(*parr)[10] = &arr;//取出的是数组的地址,应该存放在数组指针中

思考对比:下面哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

解释

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

总结

整型指针:int* pa = &a;      类型是  int*

字符指针:char* pc = &ch;     类型是  char*

那么数组指针:int (*p)[10];    类型是   int  (* )[10]

4.2&数组名VS数组名

arr &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?我们来通过代码对比一下:

    int arr[10] = { 0 };
	arr;//数组名是首元素的地址
	&arr[0];//首元素的地址
	&arr;//数组的地址

	printf("%p\\n", arr);
	printf("%p\\n", &arr[0]);
	printf("%p\\n", &arr);

	printf("---------------------\\n");

	//地址+1跨越的长度取决于地址的变量类型
	printf("%p\\n", arr + 1);//int*
	printf("%p\\n", &arr[0] + 1);//int*
	printf("%p\\n", &arr + 1);//int (*)[10]

解释

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。

 数组名就是首元素的地址的两个例外

  1. sizeof(数组名)  这里的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
  2. &数组名,这里的数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址
int main()
{
	int arr[10] = { 0 };

	//数组名是首元素的地址
	//但是有2个例外
	//1.sizeof(数组名)  这里的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
	//2.&数组名,这里的数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址

	printf("%d\\n", sizeof(arr));//40
	return 0;
}

 4.3数组指针的使用

那数组指针是怎么使用的呢?既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址,那么先看如下代码,再用数组指针进行编写:

我们想要访问数组中的每一个元素,可以用整型指针来访问,如代码1:(  不再赘述   )

void Print1(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print1(arr, sz);

	return 0;
}

代码2:(数组指针的错误示范

//数组指针
void Print2(int(*parr)[10], int sz)//错误示范
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", parr[i]);//parr[i] == *(parr+i)
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print2(&arr, sz);//数组的地址

	return 0;
}

我们想通过数组指针直接来访问到每一个元素,而我们存储的是数组的地址,所以parr+i中,比如parr+1会直接跳过整个数组,移动到数组末尾。很明显这样的数组指针用法是错误的。

 代码3:(       多想想     )【    难点!!  

//数组指针
void Print3(int(*parr)[10], int sz)
{
	//*(parr+0) == parr[0]
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", parr[0][i]);
		//printf("%d ", (*(parr + 0))[i]);
		printf("%d ", (*parr)[i]);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print3(&arr, sz);//数组的地址

	return 0;
}

我们也可以利用这个思想,来对数组指针应用:

数组指针parr是指里面存储的是数组的地址,对parr解引用,就是parr的“1行”,即:*parr,也可以写成*(parr+0),即:parr[0]

然后,我们通过这“1行”,来找到该行中的元素,那么parr[0][i]。也等价于:(*(parr + 0))[i],即:(*parr)[i]。

其实我们发现

不提倡代码3,显得有点复杂,其实一维数组用数组指针多多少少显得有点麻烦。主要是对二维数组应用比较方便:

void Print1(int a[3][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 ", 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} };
	Print1(arr, 3, 5);
	return 0;
}

改写成数组指针:

代码

void Print2(int(*p)[3], 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");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };

	Print2(arr, 3, 5);

	return 0;
}

练习:

对比以下区别:

int arr[5];//整型数组

int* parr1[10];//parr1是一个数组,有10个元素,每个元素是int*类型

int(*parr2)[10];//parr2是一个数组指针,该指针指向的数组有10个元素,每个元素是int型

int(*parr3[10])[5];
//parr3是一个数组,该数组含有10个元素
//每个元素是一个数组指针,该指针指向的数组有5个元素
//每个元素都是int型

 5.数组参数,指针参数

在写代码的时候难免要把【数组】或者【指针】作为实参传给函数,那函数的形参该如何设计呢?

5.1一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int arr[100])//ok?
{}
void test(int arr[1])//ok?
{}
void test(int* p)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

注释讲解

#include <stdio.h>
//数组传参,用数组接收
void test(int arr[])//OK ——> 数组传参,用数组接收,当然是对的;数组大小可以省略,因为形参是不会真实创建数组的,所以数组大小没有用
{}
void test(int arr[10])//OK ——> 当然,加了数组大小也同样没问题
{}
void test(int arr[100])//OK ——> 当然,加了数组大小100也行,反正形参不会真实创建数组,数组大小是多少没关系
{}
void test(int arr[1])//OK  ——>  道理同上,但是以上两种写法虽然语法上可以,但是这样写毫无意义,数组大小随便起的毫无意义之处
{}
void test(int* p)//OK ——>  传过来的是数组名字,即:首元素地址,所以也可以写成void test(int* arr)
{}

void test2(int* arr[20])//OK ——>  和传过来的形式一样,肯定是可以的
{}
void test2(int* arr[])//OK ——>  形参是不会真实创建数组的,所以数组大小没有用
{}
void test2(int** arr)//OK ——> arr2是数组名,也是首元素的地址,而arr2是int* 类型的,所以需要二级指针int**来接收
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

5.2二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* p)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);

	return 0;
}

注释讲解

void test(int arr[3][5])//OK ——> 和传过来的二维数组形式一样,这是肯定没问题的
{}
void test(int arr[][])//err! ——> 二维数组,数组行,列都不写,那么无法确定这是几行几列的,这种写法是错误的
{}
void test(int arr[][5])//OK ——> 二维数组,确定列即可,行可以省略
{}
//总结:
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。

void test(int* p)//err ——> arr是二维数组,首元素表示的是第一行,所以用一个整型的指针变量来接收一行是不行的
{}
void test(int* arr[5])//err ——> 整型指针数组来接收?   完全不搭边,大错特错
{}
void test(int(*arr)[5])//OK ——> 看图解
{}
void test(int** arr)//err! ——> 二维数组,不是二级指针!
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);

	return 0;
}

 5.3一级指针传参

一级指针传参,参数写成一级指针当然没有问题。

void Print(int* p, int sz)//一级指针传参,参数写成一级指针当然没有问题
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);

	Print(p, sz);
	return 0;
}

一级指针传参,参数写成一维数组当然也没有问题,本质上还是指针。

void Print(int p[], int sz)//一级指针传参,参数写成一维数组当然也没有问题,本质上还是指针。
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);

	Print(p, sz);
	return 0;
}

倒着试想一下,我们能把什么作为实参传过去用一级指针接收呢?

void test(int* p)
{}
int main()
{
	int a = 10;
	int arr[10];
	int* p = arr;

	test(&a);
	test(arr);
	test(p);

	return 0;
}

 5.5二级指针传参

二级指针传参,参数写成二级指针当然没有问题。

void test(int** ppa)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int** ppa = &p;

	test(ppa);

	return 0;
}

倒着试想一下,我们能把什么作为实参传过去用二级指针接收呢?

void test(int** ppa)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int** ppa = &p;
	int* arr[5];//指针数组

	test(ppa);
	test(&p);
	test(arr);

	return 0;
}

6.函数指针

整型指针

字符指针

数组指针

函数指针? ——  存放函数的地址

//对比

//数组
//数组名  ——  数组首元素的地址
//&数组名 ——  数组的地址 

//函数
//函数名
//&函数名

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\\n", &Add);//&函数名   
	printf("%p\\n", Add);//函数名

	//结果都是005E13B6

	return 0;
}

总结

想拿到函数的地址,可以有两种方式

  1. 函数名
  2. &函数名

那么可以求得函数的地址了,那如何将函数地址存进一个变量里呢??

	int (*pf)(int, int) = &Add;//pf是用来存放函数地址的,pf就是函数指针变量

解释

首先,存函数的地址,需要一个指针变量,我们姑且叫做函数指针变量pf,所以pf得是指针类型(*pf);之后在(*pf)后写出接收的参数的数据类型(int ,int ),

然后,函数的返回类型也是int,所以就有int (*pf)(int, int) = &Add;

类比记忆一下数组指针变量的写法

	int arr[10] = { 0 };
	int(*parr)[10] = &arr;//parr是数组指针变量

解释

首先,存数组的地址,需要一个指针变量,我们姑且就叫数组的指针变量parr,所以parr得失指针类型(*parr);之后再(*parr)后写出数组的元素个数[10],然后,这10个元素都是int类型,所以就有int(*parr)[10] = &arr;

那么既然是变量,就会有类型,那么函数指针的类型是什么?

    //类型:去掉名字剩下的就是类型
	
	//int型
	int a = 10;
	int arr[10] = { 0 };

	//int(*)[10]型
	int(*parr)[10] = &arr;//parr是数组指针变量

	//int (*)(int, int)型
	int (*pf)(int, int) = &Add;//pf是用来存放函数地址的,pf就是函数指针变量

那么会有同学说了:>老师老师,你看你看,int a = 10;类型是int型,那是不是就意味着我都可以用类型,后面跟上变量名嘞?比如这个样子:

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

int (*)(int, int)  pf= &Add;

这是肯定不行的!!我们不能想当然的自以为是这样子

因为

每一个类型对应的名字写在哪,都有自己的规则的。不能一概而论。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;//pf是用来存放函数地址的,pf就是函数指针变量

	int ret1 = Add(2, 3);
	printf("%d\\n", ret1);

	//用函数指针变量

	int ret2 = (*pf)(2, 3);
	printf("%d\\n", ret2);
	
	//结果都是5
	return 0;
}

🧨🧨:我们这的    int ret2 = (*pf)(2, 3);也可以写成 int ret2 = pf(2, 3);

原因:

我们的int (*pf)(int, int) = &Add;是等价于int (*pf)(int, int) = Add;,也就是说,Add这个函数的名字,和变量名pf一样,也就是Add(2,3)也就等于pf(2,3)。

⚡来看几个有意思的代码吧:    ——————   出自C语言陷阱和缺陷

//代码1
(*(void (*)())0)();

//代码2
void (*signal(int , void(*)(int)))(int);

代码1解释

(  *     (    void (*)()   ) 0  )();堆叠在一起不好看出猫腻,所以分开一些距离。

总体来说是一次函数调用

首先,在最内层void (*)()是一个参数是空,无返回值的函数指针类型;然后,用函数指针类型强制转换数字0,即:将0强制转换成类型为void (*)() 的一个函数的地址;最后,再解引用0的地址,完成函数调用,即:去调用0地址处的这个函数(  被调用的函数是无参数,返回值类型是void  )

代码2解释

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

总体来说这是一次函数定声明         (      函数总共有三种功能:函数声明函数定义函数调用    )

声明的函数名是signal,signal函数有2个参数,第一个参数是int类型,第二个参数是 void(*)(int) 的函数指针类型

signal函数的返回值依然是: void(*)(int)的函数指针类型

是不是觉得有点  “变态”

其实有一种帮我们快速理清思路的方法:     ——————     简化代码

//例如:
//typedef int int32;      意思是把int这个类型重新命名成int32类型,  同理:
//typedef void(*)(int) $$$;   想要把void(*)(int)  重新命名成$$$类型,但是这样写并不正确,此void(*)(int)不能直接在后面跟变量,而是:
typedef void(* pfun_t)(int);

void (* signal(int, void(*)(int)))(int);
//等价于
pfun_t signal(int, pfun_t);

7.函数指针数组

就像整型指针数组,里面存放的是整型类型的数据的指针(即:地址);同样的,函数指针数组,也是有存在必要的,可以将相同数据类型的函数地址,放在数组中。

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
int main()
{
	//整型指针数组 - 存放整型指针的数组
	int* arr[10];

	//函数指针数组 - 存放函数指针的数组
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;

	return 0;
}

等价于:

	//pfArr就是一个函数指针数组
	int (*pfArr[4])(int, int) = { Add,Sub,Mul,Div };

pfArr 先和 [ ] 结合,说明pfArr是数组,数组的内容是什么呢? 是 int (* )( ) 类型的函数指针。

应用:计算器

不应用函数指针的代码:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	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, y;
	int input = 1;
	int ret = 0;
	do
	{
        menu();
		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;
}

我们发现,每一次其实都是在进行重复的工作,我们的确可以再将他们封装成一个函数,来每次调用,可是这样的话函数里仍然需要每一出来一个新功能再一个一个添加,也是很麻烦,(见下面的代码1)而且,每次再去调用函数本身也是一个重复性的工作。那么我们可以将他们用函数指针来解决:

代码1:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	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");
}
void Calc(int (*pf)(int, int))
{
	int x, y;
	int ret = 0;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\\n", ret);
}
int main()
{
	int input = 1;
	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);
			break;
		case 0:
			printf("退出程序\\n");
			break;
		default:
			printf("选择错误\\n");
			break;
		}
	} while (input);

	return 0;
}

应用函数指针的代码:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	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 = 1;
	do
	{
		int x, y;
		int ret = 0;
		int(*pfArr[5])(int x, int y) = { 0, Add, Sub, Mul, Div }; //转移表 —— 《C和指针》
		                               //0   1    2    3    4
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\\n");
		}
		else if (input <= 4 && input >= 1)
		{
			printf("输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\\n", ret);
		}
		else
		{
			printf("输入有误\\n");
		}
			
	}while (input);

	return 0;
}

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

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
//指向函数指针数组的指针
 

int arr[10];
int (*p)[10] = &arr;
//p是一个指向整型数组的指针 


int* arr[10];//整型指针的数组
int* (*p)[10] = &arr;//整型指针数组的地址
//p是一个指向整型指针数组的指针


int (*pf)(int, int) = Add;//pf是一个函数指针
int (*pfArr[5])(int, int);//pfArr是一个函数指针的数组
int (*(*ppfArr)[5])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针

9.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

如上述代码1就是回调函数:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	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");
}
void Calc(int (*pf)(int, int))
{
	int x, y;
	int ret = 0;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\\n", ret);
}
int main()
{
	int input = 1;
	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);
			break;
		case 0:
			printf("退出程序\\n");
			break;
		default:
			printf("选择错误\\n");
			break;
		}
	} while (input);

	return 0;
}

再更新中🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴🌴

以上是关于02指针进阶的主要内容,如果未能解决你的问题,请参考以下文章

C语言进阶指针的进阶

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

活动到片段通信:当我尝试从活动更新片段中的文本视图时,出现空指针异常