【C语言】14.指针(4)

🎉个人主页: 缘三水的博客
❄专栏传送门:C语言专栏(新手向)
🎀人生格言:行动是迷茫的最好解药


🚀个人介绍:


往篇回顾

【C语言】指针(1)

【C语言】指针(2)

【C语言】指针 (3)


文章目录


正文开始

(一)字符指针变量

定义

指针变量类型为char*的变量

示例

c 复制代码
char c = 'w';
char* pc = &c;//pc是字符指针变量

对于字符数组

c 复制代码
char arr[] ="abcdef";
char* pc = arr;

也有另一种写法

c 复制代码
char* pc = "abcdef";//"abcdef"是常量字符串,不能被修改
//pc中存放的仍然是首元素的地址

如果我们想打印字符串内容,可以这么写

c 复制代码
printf("%c\n", *pc);//打印字符串的第一个字符
printf("%s\n", pc);//打印整个字符串

"abcdef"是常量字符串不能被修改

如果强制修改,程序运行时就会报错

为了防止错误,我们一般在前头加上const修饰

c 复制代码
const char* pc = "abcdef";

问题
下述代码运行会打印什么?

c 复制代码
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";

	const char* str3 = "hello bit.";
	const 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;
}

结果是

对第一行解释

比较的是首元素地址,而不是数组的内容

数组名是代表首元素的地址

两个数组的首元素地址不同,因此打印这个结果

拓展:字符串比较不能用==,而是用strcmp函数

对第二行解释

当两个指针变量存储的是同一常量字符串

两个指针指向的位置都是这个字符串的首字符

因为常量字符串是不会被修改的,编译器就没必要再创建一个字符串来给第二个指针变量存放

(二)数组指针变量

类比

字符指针变量:存放的是字符的地址

整型指针变量:存放的是整型的地址

定义

数组指针变量:指向数组的指针,存放的是数组的地址

示例

c 复制代码
int arr[10] = {0};
int (*p)[10] = &arr;//数组指针变量
// *P说明p 是指针变量
//[10]说明指向数组的地址
//int说明数组元素类型是int
//类型为int(*)[10]

区分

c 复制代码
int* p[10] ={&arr};//指针数组,存放指针的数组

int (*p)[10] = &arr;//数组指针变量,存放数组地址的指针变量

去掉括号,p就和[10]结合,变成数组,p就变成数组名

数组指针的使用

c 复制代码
//利用数组指针打印数组内容
int main()
{
	int arr[] = { 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;
}

但是这种使用十分不利于阅读,因此不推荐这么使用

数组指针的使用场景一般在二维数组的传参中体现

(三)二维数组传参的本质

示例
打印一个二维数组的内容

c 复制代码
void test(int arr[][5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", arr[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;
}

之前我们学过,数组名代表首元素地址

而二维数组的元素其实是一维数组

因此,二维数组的数组名代表的是第一行一维数组的地址

所以,test函数的形参还可以这么写

c 复制代码
void test(int (*arr)[5], int r, int c)
//*arr说明是指针变量
//[5]说明指向的数组有5个元素
//int说明指向的数组的元素类型是int整型类型

我们这里是用数组指针接收二维数组的首元素(第一行 一维数组的地址)

而打印的部分也能用指针来写

c 复制代码
printf("%d ", arr[i][j]);// *(*(arr+i)+j) == *(arr[i]+j) == arr[i][j]

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

(四)函数指针变量

4.1 定义

函数指针变量:存放函数的地址,指向的是函数

示例

c 复制代码
取出函数的地址
&+函数名

不同于数组,数组名代表首元素地址
函数名  等价于  &函数名

4.2 函数指针的创建

c 复制代码
(*pf)说明pf是指针变量
后面的(int,int)说明指针指向的是函数,形参类型分别是int类型,int类型
前面的int说明指向的函数返回值类型为int

函数指针类型

c 复制代码
指针变量名去掉就是函数指针变量的类型
如:int (*)(int,int) 是函数指针变量pf的类型  

4.3 函数指针的使用

示例

因为&Add 等价于 Add

所以pf 等价于Add

因此代码也可以这么写

示例2

4.4 两段有趣的代码

一段代码

c 复制代码
(*(void (*)())0)();

该怎么解释这段代码呢?

c 复制代码
这段代码在调用0地址处的一个函数
1. void(*)() 是一个函数指针类型,指向的函数没有参数,返回值类型是void
2. 外面加一个括号,( void (*)() )是将0强制类型转换成这种函数指针类型,意味着0地址处有这么一个函数
3. 最后,(*(void (*)())0)() 是对0地址处进行解引用,调用0地址处的函数

一段代码

c 复制代码
void ( *signal(int , void(*)(int)) )(int);

解释

c 复制代码
这段代码可以这么看
void ()(int) *signal(int , void(*)(int))但这么写是错的

1.这是一个signal函数声明
2.signal函数的参数类型是int,void(*)(int)
3.signal函数的返回类型是void ()(int)

4.5 typedef关键字

作用: 重命名数据类型

1.使用示例

2.指针类型的重命名
好处:可以连续创建指针变量

使用示例

3.数组指针类型的重命名
示例

4.函数指针类型的重命名
示例

所以,我们可以利用这个typedef对上面的第二段代码(函数声明)进行简化
原代码

c 复制代码
void ( *signal(int , void(*)(int)) )(int);

优化代码

(五)函数指针数组

定义
存放同类型函数指针的数组

存放函数指针示例

使用函数指针数组示例

这样我们就利用函数指针数组一次完成了加减乘除四个函数的调用

(六)转移表

要求
写一个程序,能实现加减乘除的计算器

c 复制代码
//实现一个能进行加减乘除的计算器

void menu()
{
	printf("*********************\n");
	printf("***1.add     2.sub***\n");
	printf("***3.mul     4.div***\n");
	printf("***0.exit         ***\n");
	printf("*********************\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 input = 0;
	int x = 0;
	int y = 0;
	int r = 0;
	do
	{
		
		menu();
		printf("请输入:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			r = Add(x, y);
			printf("%d\n", r);
			break;
		case 2:
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			r = Sub(x, y);
			printf("%d\n", r);
			break;
		case 3:
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			r = Mul(x, y);
			printf("%d\n", r);
			break;
		case 4:
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			r = Div(x, y);
			printf("%d\n", r);
			break;
		case 0:
			printf("退出计算器");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

我们发现这样的代码有许多重复的

也不利于功能的拓展

此时,我们就能使用函数指针数组进行优化

优化代码

c 复制代码
void menu()
{
	printf("*********************\n");
	printf("***1.add     2.sub***\n");
	printf("***3.mul     4.div***\n");
	printf("***0.exit         ***\n");
	printf("*********************\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 input = 0;
	int x = 0;
	int y = 0;
	
	do
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if(input > 0 && input <= 4)
		{
			int (*pArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			int r = pArr[input](x, y);
			printf("%d\n", r);
		}
		else
		{
			printf("选择错误,请重新选择\n");
		}
	} while (input);
	return 0;
}

这样以后想要拓展功能时,只要在菜单,函数功能说明,函数指针数组里和条件判断改变就可以了


总结

这篇文章我们详细介绍了指针部分内容,希望能对你有帮助

有错误的地方希望可以得到大佬的指正
最后不要吝啬你的点赞,收藏和评论!!!
感谢你的观看

相关推荐
疯狂的挖掘机1 天前
记一次基于QT的图片操作处理优化思路(包括在图上放大缩小,截图,画线,取值等)
开发语言·数据库·qt
cnxy1881 天前
围棋对弈Python程序开发完整指南:步骤4 - 提子逻辑和劫争规则实现
开发语言·python·机器学习
意趣新1 天前
C 语言源文件从编写完成到最终生成可执行文件的完整、详细过程
c语言·开发语言
李艺为1 天前
根据apk包名动态修改Android品牌与型号
android·开发语言
黄河滴滴1 天前
java系统变卡变慢的原因是什么?从oom的角度分析
java·开发语言
老华带你飞1 天前
农产品销售管理|基于java + vue农产品销售管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
superman超哥1 天前
Rust Workspace 多项目管理:单体仓库的优雅组织
开发语言·rust·多项目管理·rust workspace·单体仓库
kylezhao20191 天前
C#通过HSLCommunication库操作PLC用法
开发语言·c#
lengjingzju1 天前
一网打尽Linux IPC(三):System V IPC
linux·服务器·c语言
JIngJaneIL1 天前
基于springboot + vue房屋租赁管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端