首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C语言——指针(2)

C语言——指针(2)

作者头像
用户11352420
发布2024-11-07 21:31:10
发布2024-11-07 21:31:10
27800
代码可运行
举报
文章被收录于专栏:编程学习编程学习
运行总次数:0
代码可运行

前面我们已经了解了指针的基本概念以及简单的使用,那么什么问题一定要使用指针解决呢?

我们来接着往下学习:

传值调用和传址调用

问题:写一个函数,交换两个变量的值。

传值调用

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
void Swap(int x, int y)
{
	int temp = x;
	x = y;
	y = temp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("a=%d b=%d", &a, &b);
	Swap(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

我们发现a和b的值并没有发生交换,这是为什么呢?

我们可以调试来看看

在进入函数内部后,把a的值给x,b的值给y

函数完成交换后,x和y的值发生了交换,但是a和b的值没有发生交换。

我们可以看到在main函数内部,创建了a和b,a的地址是0x009af998,b的地址是0x009af98c,在调用Swap函数时,将a和b传递给了Swap函数,在Swap函数内部创建了形参x和y接收a和b的值,但是x的地址是0x009af8b4,y的地址是0x009af8b8,x和y确实接收到了a和b的值,不过x的地址和a的地址不⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap函数内部交换x和y的值,不会影响a和b,当Swap函数调⽤结束后回到main函数,a和b的没法交换。Swap函数在使⽤的时候,是把变量本⾝直接传递给了函数,这就是传值调⽤。

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。 (单向值传递)

那么为了实现这个函数功能,我们就可以使用传址调用

传址调用

在main函数中将a和b的地址传递给Swap函数,Swap函数⾥边通过地址间接的操作main函数中的a和b,达到交换的效果。 调⽤Swap函数的时候是将变量的地址传 递给了函数,这就是传址调⽤。

代码:

代码语言:javascript
代码运行次数:0
运行
复制
//传址调用
#include<stdio.h>
void Swap(int* x, int* y)
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("a=%d b=%d", &a, &b);
	Swap(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

常见错误

在写这个函数内部的代码的时候可能会出现一些错误。

例1:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
void Swap(int* x, int* y)
{
	int* temp = x;
	x = y;
	y = temp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("a=%d b=%d", &a, &b);
	Swap(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

这一段代码并没有达到我们想要的效果,我们可以看到这一段代码在Swap函数内部创建了一个指针变量temp,经过函数的处理,只是交换了x和y的地址,但是它指向的内容并没有进行交换,这是因为a和b的地址并没有因为x和y的改变, 指针变量也遵循单向值传递 ,实参指针变量&a和&b传递给形参x、y的时候,对形参地址的修改不影响实 参。

例2:

代码语言:javascript
代码运行次数:0
运行
复制
//err2
#include<stdio.h>
void Swap(int* x, int* y)
{
	int* temp;
	*temp = *x;
	*x = *y;
	*y = *temp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("a=%d b=%d", &a, &b);
	Swap(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

这里就是我们之前提到的野指针的问题,temp没有进行初始化,temp的值是随机的,对*temp赋值就是向一个未知的存储单元赋值,而这个未知的存储单元可能存放着有用的数据,这样就有可能影响系统的正常工作情况,编译器会进行报错。

使用指针变量的好处

函数调用使用return语句只可以得到一个返回值,而使用指针变量就可以得到多个变化了的值。传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量。

二级指针

我们在调试中看到&x和&y类型是int**,这是什么的类型呢?事实上,这是二级指针。

指针变量也是变量,是变量就有地址,这就需要二级指针来存放指针变量的地址。

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//指向a的指针变量pa
	int** ppa = &pa;//二级指针ppa存放pa的地址
	printf("%p\n", pa);
	printf("%p\n", *ppa);//*ppa 通过对ppa中的地址进⾏解引用,找到的是pa
	printf("%d\n", **ppa);
	//**ppa==*(*(ppa))==*(pa)==a
	//**ppa先通过*ppa找到 pa,然后对 pa 进⾏解引用操作:* pa,最后找到a
	return 0;
}

这就是二级指针的使用及作用,如果我们看到一个变量是int**类型的,说明它是一个二级指针。

数组名的理解

在前面我们知道一维数组名事实上是首元素地址,那是不是数组名都是代表首元素地址呢?

先说结论:

数组名是数组首元素(第⼀个元素)的地址是对的 但是有两个例外: • sizeof(数组名) ,sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的大小, 单位是字节 • &数组名 ,这⾥的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)

除此之外,任何地方使用数组名,数组名都表示首元素地址。

接下来,我们来慢慢解析:

sizeof(数组名)

使用
代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	int arrt[] = { 1,2,3,4,5,6 };
	printf("sizeof(arrt)=%d\n", sizeof(arrt));
	//                        数组元素为整型,一个元素4个字节,整个数组24个字节
	char arrc[] = "abcdef";
	printf("sizeof(arrc)=%d\n", sizeof(arrc));
	//              数组元素为字符,一个元素1个字节,整个数组7个字节(包括'\0')
	return 0;
}

结果如我们所料,这里需要注意的是sizeof在计算字符数组的时候,包括‘\0’(‘\0’也是字符), sizeof 只关注占⽤内存空间的大小,不在乎内存中存放的是什么数据。

sizeof和strlen的对比

这里可以将sizeof与strlen做一个小小的对比。

sizeof 1. sizeof是 操作符 2. sizeof 计算操作数所占内存的大小,单位是字节 3. 不关注内存中存放什么数据

strlen 1. strlen是 库函数 ,使⽤需要包含头⽂件 string.h 2. srtlen是 求字符串⻓度的,统计的是 '\0'之前字符的个数 3. 关注内存中是否有'\0' , 如果没有'\0',就会持续往后找 ,可能会越界

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
#include<string.h>
int main()
{
	char arrc[] = { 'a','b','c' };
	printf("sizeof(arrc)=%d\n", sizeof(arrc));
	printf("strlen(arrc)=%d\n", strlen(arrc));
	return 0;
}

我们可以看到strlen在这种情况下计算数组长度,因为没有'\0'而发生了越界, 持续往后找,直到找到'\0'为止。

所以我们 如果使用{ }给一个字符数组初始化的话,就需要手动加上一个'\0'.

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
#include<string.h>
int main()
{
	char arrc[] = { 'a','b','c','\0'};
	printf("sizeof(arrc)=%d\n", sizeof(arrc));
	printf("strlen(arrc)=%d\n", strlen(arrc));
	return 0;
}

如果使用" "给一个字符数组初始化的话,就不需要手动加上一个'\0',因为系统会认为这是一个字符串(字符串的结束标志是'\0'),所以系统自动加上'\0'.

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
#include<string.h>//strlen头文件
int main()
{
	char arrc[] = "abc";
	printf("sizeof(arrc)=%d\n", sizeof(arrc));
	printf("strlen(arrc)=%d\n", strlen(arrc));
	return 0;
}
计算数组元素个数

sizeof(数组名)既然代表整个数组的字节数的话,那么我们怎么用sizeof来计算数组元素个数呢?我们可以将整个数组的字节数 / 一个数组元素的字节数就可以计算出数组元素个数了。

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	int sz2 = sizeof(arr) / sizeof(int);
	//数组元素为整型,一个元素4个字节
	printf("sz1=%d\n", sz1);
	printf("sz2=%d\n", sz2);
	return 0;
}

&数组名

&数组名的时候数组名是代表整个数组地址。

我们来看看一段代码:

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int arr[6] = { 1,2,3,4,5,6 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr     = %p\n", arr);
	printf("&arr    = %p\n", &arr);
	return 0;
}

我们可以看到他们输出的结果是一样的,有人会好奇不是说&数组名的时候数组名是代表整个数组地址吗,为什么会输出一样的地址呢?

别着急,我们再来看看一段代码

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int arr[6] = { 1,2,3,4,5,6 };
	printf("&arr[0]   = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0] + 1);
	printf("arr       = %p\n", arr);
	printf("arr+1     = %p\n", arr + 1);
	printf("&arr      = %p\n", &arr);
	printf("&arr+1    = %p\n", &arr + 1);
	return 0;
}

当它们都加1的时候就发生了区别,我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是首元素地址+1就是跳过⼀个元素。 但是&arr 和 &arr+1相差24个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。

所以我们可以得出结论:&数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址。

使用指针访问数组

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int arr[6] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* pa = arr;//一维数组名是数组首元素地址
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", pa+i);
		//scanf("%d", arr+i);
	}
	printf("\n");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(pa + i));
	}
	return 0;
}

⼀维数组传参的本质

数组传参的时候,传递的是数组名,本质上数组传参传递的是数组⾸元素的地址。

那我们可不可以在数组外部求数组元素个数呢?

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
void test(int arr[])
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2=%d\n", sz2);
}
int main()
{
	int arr[6] = { 0 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1=%d\n", sz1);
	test(arr);
	return 0;
}

通过代码的结果,我们可以知道答案是 不可以在数组外部求数组元素个数的。这是因为 在test函数内部我们写 sizeof(arr) 计算的是⼀个地址的⼤⼩ (单位字节)【4(VSx86环境下)或者8(VSx64环境下)】⽽不是数组的⼤⼩(单位字节)。

因为函数的参数部分是本质是指针,所以在test函数内部是没办法求数组元素个数的。

所以一般我们会把数组元素个数一起传给函数。

既然传递的是指针,所以我们在使用数组形式写函数参数的时候,就不用写大小。

代码语言:javascript
代码运行次数:0
运行
复制
//计算指针变量的大小
#include <stdio.h>
//使用数组传参的两种形式
void test1(int arr[])
{
	printf("test1:%zd\n", sizeof(arr));
}
void test2(int* arr)
{
	printf("test2:%zd\n", sizeof(arr));
}
int main()
{
	int arr[6] = { 0 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	test1(arr);
	test2(arr);
	return 0;
}

如果对指针在VS不同环境是不同大小不清楚的,可以看看上一篇博客哦!

总结: ⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

字符指针

使用方式

这里单独讲解一下字符指针。

从名字上来看,字符指针就是指向字符的指针,存放的是字符变量的地址,类型为char*

例:

代码语言:javascript
代码运行次数:0
运行
复制
//字符指针
#include<stdio.h>
int main()
{
	char c = 'd';
	char* pc = &c;//字符指针
	printf("%c\n", *pc);//字符指针解引用
	return 0;
}

字符指针只有一种使用方式吗?

当然不是

我们来看看另外一种字符指针使用的方式:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	char* pc = "abcdef";
	return 0;
}

字符指针变量被一个常量字符串赋值,那么这里的字符指针存放的是一整个字符串吗?

我们来简单的验证一下:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	char* pc = "abcdef";
	printf("%c\n", *pc);
	printf("%s\n", *pc);
	return 0;
}

我们分别以单个字符和字符串的形式来打印字符指针pc解引用的内容。

我们可以看到以字符串形式打印的时候并没有得到我们想要的结果,虽然编译器没有报错,但是它给出了警告。

事实上,这里本质是把字符串 "abcdef\0" , 首字符的地址放到了pc中,如果打印字符串,需要首元素(字符)地址来进行打印。

正确形式:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	char* pc = "abcdef";
	printf("%c\n", *pc);
	printf("%c\n", *(pc + 1));
	printf("%s\n", pc);
	return 0;
}

所以这种形式是把⼀个常量字符串的 首字符的地址 存放到 字符指针变量 中,而 常量字符串不可以修改 ,我们可以使用 const进行修饰 来提醒我们。

趣味代码

我们来看看一个有趣的代码:

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";

	const char* str3 = "hello world.";
	const char* str4 = "hello world.";

	if (str1 == str2)
		printf("str1 and str2 are same\n");//1
	else
		printf("str1 and str2 are not same\n");//2

	if (str3 == str4)
		printf("str3 and str4 are same\n");//3
	else
		printf("str3 and str4 are not same\n");//4

	return 0;
}

你知道这一段代码答案是什么吗?

答案:2,3(不知道你做对了吗?)

我们一起来看看:

C/C++会把常量字符串存储到单独的⼀个内存区域,我们知道常量字符串是不可以被修改的,既然不可以被修改,那么内容相同的常量字符串只需要保存一次就好了,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存,所以str3和str4得到的是相同的首元素(字符)地址;而数组是可以被修改的,如果⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存,所以str1和str2得到的是不同的首元素(字符)地址。

指针数组

使用

指针数组?是指针?还是数组呢?我们来类比一下

整型数组——存放整型的数组,数组元素类型是整型。

字符数组——存放字符的数组,数组元素类型是字符。

所以:

指针数组——存放指针的数组,数组元素类型是指针。

指针数组事实上是数组,那么它应该如何使用呢?

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	return 0;
}

我们来看看这一段代码,创建了3个指针变量来存放3个整型变量的地址,这样看起来是不是显得十分臃肿多余,如果需要存放更多的指针呢?我们以前知道存放更多的数据可以使用数组,那么在这里我们就可以使用指针数组。

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	/*int* pa = &a;
	int* pb = &b;
	int* pc = &c;*/
	int* p[3] = { &a,&b,&c };
	//下标         0  1  2
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d\n", *p[i]);
	}
	return 0;
}

解释 int* p[3] p先与[3]结合 ([ ]的优先级要高于*号的),说明p是数组 3 ——p数组里面有三个元素 int* ——每个数组元素是int* 类型的,也就是整型指针

结论:指针数组的每个元素都是用来存放地址(指针)的。

模拟二维数组

指针数组的每个元素是地址,就又可以指向⼀块区域, 那么我们可以用指针数组来简单模拟一下二维数组。

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,3,4,5 };
	int arr3[4] = { 3,4,5,6 };
	int* p[3] = { arr1,arr2,arr3 };
	//数组名是首元素地址
	//p先与[3]结合,说明p是数组,有三个元素
	//int* 说明每个数组元素是int* 类型的,也就是整型指针
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			//printf("%-3d", p[i][j]);
			printf("%-3d", *(*(p + i) + j));
			//*(p+i)== p[i]
			//先对p+i解引用,得到一个地址(指针)
		}
		printf("\n");
	}
	return 0;
}

上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏的地址并⾮是连续的

数组指针

使用

类比:

整型指针——指向整型的指针,存放的是整型变量的地址。

字符指针——指向字符的指针,存放的是字符变量的地址。

所以:

数组指针——指向数组的指针,存放的是数组的地址。

想要得到数组的地址,通过前面我们知道应该是 &(数组名)

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
int main()
{
	int arr[6] = { 1,2,3,4,5,6 };
	int(*p)[6] = &arr;//数组指针
	return 0;
}

解释 int (*p)[6] (*p)—— p先和*结合,说明p是⼀个指针变量 【[ ]的优先级要⾼于*号的,加上()来保证p先和*结合】 6 ——p指向数组的元素个数 int —— p 指向的数组的元素类型

所以p是⼀个指针,指向⼀个数组,这就是数组指针。

类型

数组指针是什么类型呢?

相信大家都有经验了吧!

去掉名字就是类型,那么上面代码中数组指针的类型是 int(*)[6] 。

二维数组传参的本质

前面我们了解到一维数组传参的本质 ,那我们接下来一起来了解下二维数组传参的本质 。

前面我们把二维数组做函数参数,会写出下面的代码:

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
void print_arr(int arr[][4])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
			printf("%d ", arr[i][j]);
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	print_arr(arr);
	return 0;
}

在前面,我们写二维数组做函数参数的时候,形参也写成⼆维数组的形式(可以省略行,但是不可以省略列),那么有没有其他的形式呢?

深度理解二维数组

⼆维数组可以看做是每个元素是⼀维数组的数组,也就可以理解为 ⼆维数组的每个元素是⼀个⼀维数组 ,⼆维数组的⾸元素就是第⼀行,是一个⼀维数组。因为数组名是首元素地址,所以 二维数组的数组名表⽰的就是第⼀⾏的地址 ,是 ⼀维数组的地址 。 第⼀⾏的⼀维数组的类型就是 int [4] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[4] 。

我们可以写一个代码,看看二维数组名是不是代表首行的地址

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	printf("arr     == %p\n", arr);
	printf("arr + 1 == %p\n", arr + 1);
	printf("arr + 2 == %p\n", arr + 2);
	return 0;
}

有人可能会说打印的地址不是相差20吗,怎么是相差的一个一维数组的字节数16呢?

注意:这里是以十六进制打印的,逢16进1,这里一个一维数组的字节数正好是16,就直接进1,就得到了上面的结果。

⼆维数组传参本质上也是传递了地址,传递的是第⼀行 ⼀维数组的地址,那么形参也是可以写成指针形式的。

形参写成指针形式

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
void print_arr(int (*p)[4])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
			//printf("%d ", p[i][j]);
			printf("%d ", *(*(p + i) + j));
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	print_arr(arr);
	return 0;
}

有人可能会好奇为什么打印可以写成 *(*(p+i)+ j)的形式呢?

我们知道⼆维数组的每个元素是⼀个⼀维数组,那么这个一维数组的数组名是什么呢?

事实上,在上面的代码中,一维数组的数组名就是arr[i] , 数组名arr[i]又是一维数组的首元素地址,再进行加 j 解引用就可以得到相应的元素了。

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	for (int i = 0; i < 3; i++)
	{
		printf("%p\n", arr[i]);
	}
	return 0;
}

arr[i]从形式上看,是arr数组中序号为 i 的元素。

如果arr是一维数组名,那么arr[i]是arr数组中序号为 i 的元素的存储单元。

如果arr是二维数组名,那么arr[i]是一维数组数组名,是一个地址,不代表一个存储单元,也不代表存储单元里面的值。

函数指针

相信大家已经有经验了

函数指针——指向一个函数的指针,存放的是函数的地址。

那么函数是不是也有地址呢?我们来简单测试一下

函数的地址

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
void test(int n)
{
	printf("n  ==  %d\n", n);
}
int main()
{
	int a = 10;
	test(a);
	printf("test  == %p\n", test);
	printf("&test == %p\n", &test);
	return 0;
}

我们可以发现函数也是有地址的,并且无论是以函数名还是以&函数名用取地址的形式打印,得到的是一样的地址。

结论:函数是有地址的,函数名就是函数的地址,函数名和&函数名都是代表函数地址,没有区别

函数指针变量

那么如果存放函数地址就需要创建一个函数指针变量,那么应该如何创建一个函数指针变量呢?我们可以类比于数组指针的写法

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int Max(int x, int y)
{
	return x > y ? x : y;
}
int main()
{
	//数组指针
	int arr[6] = { 1,2,3,4,5,6 };
	int(*p1)[6] = &arr;
	//函数指针
	int a = 10;
	int b = 20;
	int(*p2)(int, int) = &Max;
	int(*p3)(int x, int y) = &Max;

	return 0;
}

p2,p3先与*结合说明是一个指针变量

(int ,int) ——指向函数的参数类型和个数的交代 【也可以写成(int x,int y)】 p2,p3 ——函数指针变量名 int ——指向函数的返回类型

上面代码中函数指针变量类型就是int(*)(int,int)

使用函数指针变量

那么应该如何使用函数指针变量呢?

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
int Max(int x, int y)
{
	return x > y ? x : y;
}
int main()
{
	//函数指针
	int a = 10;
	int b = 20;
	int(*p)(int, int) = &Max;
	int ret1 = Max(a, b);//函数调用
	//函数名其实就是函数的地址
	int ret2 = (*p)(a, b);//使用函数指针变量
	//对p解引用找到这个函数
	int ret3 = p(a, b); 
	//p里面存放的是函数的地址
	//与int ret1 = Max(a, b);效果相同
	printf("ret1 == %d\n", ret1);
	printf("ret2 == %d\n", ret2);
	printf("ret3 == %d\n", ret3);
	return 0;
}

这里呢,*也可以不写,因为p得到的就是函数Max的地址,与int ret1 = Max(a, b); 效果相同。

当然写上更加方便我们理解,首先对p解引用找到这个函数,再进行运算,记得带上( )让p先与*结合。

函数指针数组

定义

把多个函数的地址存到⼀个数组中,那这个数组就叫函数指针数组。

那么它应该怎么样去定义呢?

正确形式: int (*parr[ 3 ]) ();

parr1 先和 [ ] 结合,说明 parr是数组 数组的内容 是 int (*)() 类型的函数指针

计算器的实现

如果我们想要写一个代码来实现加减乘除的计算,我们很容易写出下面的代码:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
void menu()
{
	printf("*****请选择计算方式*****\n");
	printf("*****1.Add    2.Sub*****\n");
	printf("*****3.Mul    4.Div*****\n");
	printf("*****    0.exit    *****\n");
}
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 a = 0;
	int b = 0;
	int input = 0;
	do 
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入操作数:\n");
			scanf("%d %d", &a, &b);
			printf("结果为:%d\n", Add(a, b));
			break;
		case 2:
			printf("请输入操作数:\n");
			scanf("%d %d", &a, &b);
			printf("结果为:%d\n", Sub(a, b));
			break;
		case 3:
			printf("请输入操作数:\n");
			scanf("%d %d", &a, &b);
			printf("结果为:%d\n", Mul(a, b));
			break;
		case 4:
			printf("请输入操作数:\n");
			scanf("%d %d", &a, &b);
			printf("结果为:%d\n", Div(a, b));
			break;
		case 0:
			printf("退出计算器!\n");
			break;
		default:
			printf("选择有误!请重新选择!\n");
			break;
		}
		printf("\n");
	} while (input);
	return 0;
}

我们可以看到这个代码中有许多重复的部分,显得有点冗长,那么有没有什么办法可以进行优化呢?

我们来看看下面的代码:

代码语言:javascript
代码运行次数:0
运行
复制
#include<stdio.h>
void menu()
{
	printf("*****请选择计算方式*****\n");
	printf("*****1.Add    2.Sub*****\n");
	printf("*****3.Mul    4.Div*****\n");
	printf("*****    0.exit    *****\n");
}
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 a = 0;
	int b = 0;
	int input = 1;
	int(*p[5])(int x, int y) = { 0, Add, Sub, Mul, Div };//函数指针数组
	//函数名就是函数的地址
	do
	{
		menu();
		scanf("%d", &input);
		if (input > 0 && input <= 4)//满足条件才输入操作数
		{
			printf("请输入操作数:\n");
			scanf("%d %d", &a, &b);
			int ret = (*p[input])(a, b);//使用
			printf("结果为:%d\n", ret);
		}
		else if (input == 0)
			printf("退出计算器!\n");
		else
			printf("选择有误!请重新选择!\n");
		printf("\n");
	} while (input);
	return 0;
}

我们可以看到这一段代码进行了进一步的优化,使用了一个函数指针数组,来存放不同函数的地址,在下面通过下标来访问使用函数,这种间接的使用函数的方式我们叫转移表。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 传值调用和传址调用
    • 传值调用
    • 传址调用
    • 常见错误
    • 使用指针变量的好处
  • 二级指针
  • 数组名的理解
    • sizeof(数组名)
      • 使用
      • sizeof和strlen的对比
      • 计算数组元素个数
    • &数组名
  • 使用指针访问数组
  • ⼀维数组传参的本质
  • 字符指针
    • 使用方式
    • 趣味代码
  • 指针数组
    • 使用
    • 模拟二维数组
  • 数组指针
    • 使用
    • 类型
  • 二维数组传参的本质
    • 深度理解二维数组
    • 形参写成指针形式
  • 函数指针
    • 函数的地址
    • 函数指针变量
    • 使用函数指针变量
  • 函数指针数组
    • 定义
    • 计算器的实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档