【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;
}

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


总结

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

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

相关推荐
咕咕嘎嘎10241 小时前
C/C++内存对齐
java·c语言·c++
认真敲代码的小火龙1 小时前
【JAVA项目】基于JAVA的图书管理系统
java·开发语言·课程设计
爱敲代码的小冰1 小时前
js 时间的转换
开发语言·前端·javascript
电子_咸鱼1 小时前
【QT SDK 下载安装步骤详解 + QT Creator 导航栏使用教程】
服务器·开发语言·网络·windows·vscode·qt·visual studio code
AAA阿giao1 小时前
深入理解 JavaScript 中的面向对象编程(OOP):从构造函数到原型继承
开发语言·前端·javascript·原型·继承·原型模式·原型链
2301_797312261 小时前
学习Java22天
java·开发语言
jllllyuz1 小时前
MATLAB雷达系统设计与仿真
开发语言·matlab
IMPYLH1 小时前
Lua 的 type 函数
开发语言·笔记·后端·junit·lua
老华带你飞1 小时前
英语学习|基于Java英语学习系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端·学习