当前位置: 首页 > news >正文

C语言超详细指针知识(三)

在经过前面两篇指针知识博客学习之后,我相信你已经对指针有了一定的理解,今天将更新C语言指针最后一篇,一起来学习吧。


1.字符指针变量

在指针类型的学习中,我们知道有一种指针类型为字符指针char*,之前我们是这样使用的:

int main()
{
	char ch = 'o';
	char* pc = &ch;
	*pc = 's';
	return 0;
}

 其实他还有一种使用方法:

int main()
{
	char* pstr = "hello world";
	printf("%s\n", pstr);
	return 0;
}

观察结果,我们是不是认为将字符串"hello world"存入了字符指针pstr里了,其实并不是,第三行本质上是将"hello world"首字符的地址放到了pstr中。

上⾯代码的意思是把⼀个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。 

我们来看一道笔试题:

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

结果为: 

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

 2.数组指针变量

上篇博客中我们学习了指针数组,指针数组是一种数组,数组中存放的是地址。

那么数组指针又是指针还是数组呢?

在之前的学习中,我们学习了整型指针变量和字符型指针变量,它们都是指针,由此类推,数组指针应该是:存放数组的地址,能够指向数组的指针变量。

我们先来看一个简单的数组指针变量:

int (*p)[10];

 p先与*组合,说明p是一个指针变量,指向的是一个大小为10个整型的数组。

注意:[ ]的优先级要高于*,所以必须加上( )来保证p与*结合,如果没有( ),就变成了int*   p[10],这是一个指针数组,该数组里可以存放10个整形指针变量。

 了解了数组指针,那么该怎么初始化呢?

经过之前的学习,我们知道&arr表示的是整个数组的地址,所以我们可以这样初始化:

 可以看到,&arr和p的类型是完全一致的。

数组指针类型分析:

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

|       |      |
|       |      |
|       |   p 指向数组的元素个数
|     p 是数组指针变量名
p 指向的数组的元素类型


3.二维数组传参的本质

根据数组名就是数组首元素地址这个规则,二维数组的数组名表示的应该就是第一行的地址,是一维数组的地址,如图,第一行的一维数组类型是 int[5],所以第一行的地址类型是 int (*p)[5]。

看下面代码:

void test(int (*p)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; 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} };
	test(arr, 3, 5);
	return 0;
}

 ⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。


4.函数指针变量

4.1函数指针变量的创建与使用

什么是函数指针变量?

通过前面整型变量,数组指针变量的类比,我们不难看出函数指针变量也是一种指针变量。函数指针变量是用来存放函数地址的,通过地址可以调用函数,有些人可能这时候就要问了,函数还有地址吗?我们来做个测试验证一下:
 

可以看到,我们成功打印出了地址,说明函数就是有地址的,函数名就是函数的地址,当然也可以通过&函数名的方法获取函数地址。

函数指针变量就是将函数的地址存放起来,他与数组指针变量类似,如下:

void test()
{
	int i = 0;
}

void (*pf1)() = test;
//void (*pf1)() = &test;

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

int (*pf2)(int, int) = Add;
//int (*pf2)(int, int) = &Add;

函数指针变量类型分析:

int                (*pf3)               ( int x, int y)
|                      |                     ------------
|                      |                            |
|                      |         pf3 指向函数的参数类型和个数的交代
|           函数指针变量名
pf3 指向函数的返回类型
int   ( * )  ( int x, int y) //pf3 函数指针变量的类型

学会了就要马上应用起来,我们来看一个代码:

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

int main()
{
	int (*pf)(int, int) = Add;
	printf("%d\n", (*pf)(3, 5));
	printf("%d\n", (*pf)(3, 10));
	return 0;
}

这是一段很简单的示例,大家自己要多多尝试。

 4.2两段有趣的代码

1   ( * ( void (*) ( ) ) ) ( );

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

这两段代码一开始看可能会出现看不明白的情况,我们要学会将它们拆分。

首先,我们来看第一段代码:
1.void (*) ( ):这是一个函数指针类型,表明该指针指向的函数没有参数,并且返回值为void

2.void (*) ( ) ) ):把整数0强制转换为void (*) ( )类型的函数指针,意味着让这个指针指向内存地址0

3.* ( void (*) ( ) ) 0:对这个函数指针进行解引用操作,从而得到函数本身。

4.( * ( void (*) ( ) ) ) ( ):调用解引用后得到的函数,也就是调用位于内存地址0处的函数。

第二段代码:

 1.void  (*) (int):这是一个函数指针类型,表明该指针指向的函数中有一个参数,类型为int,并且返回值为void

2.signal ( int , void  (*) (int) ):这是一个函数,该函数有两个参数,一个参数类型为int,另一个参数类型是一个函数指针

3.void (*signal ( int , void  (*) (int) ) ) (int):signal 函数返回一个函数指针,这个返回的函数指针类型也是 void (*)(int)

4.3typedef关键字 

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得 unsigned int写起来不方便,如果能写成 uint 就方便多了,那么我们可以使⽤:
typedef unsigned int unit;
//将unsigned int 重命名为unit
如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5];//新类型名必须在*的右边
函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pun_t)(int);

我们在重新来看这个代码:

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

是不是要简单很多了,函数名为signal,有两个参数,一个是int,一个是prse,返回值也是prse。

5.函数指针数组

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组, 比如:
int* arr[10];
那要把函数的地址存到⼀个数组中,叫函数指针数组,他又该如何定义呢?举个例子:
int (*parr[3])();

这就是一个简单的函数指针数组,parr先与[ ]结合,说明parr是数组,存放的是int(*)( )类型的函数指针。

6.转移表

下面我们来做一个函数指针数组的应用:转移表

举例:计算机的一般实现

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("******  0.exit   ******\n");
	printf("******  1.Add    ******\n");
	printf("******  2.Sub    ******\n");
	printf("******  3.Mul    ******\n");
	printf("******  3.Div    ******\n");
	printf("***********************\n");
}

int main()
{
	int input;
	int x, y;
	do
	{
		menu();
		printf("选择算法->");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			printf("输入被运算数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", Add(x, y));
			break;
		case 2:
			printf("输入被运算数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", Sub(x, y));
			break;
		case 3:
			printf("输入被运算数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", Mul(x, y));
			break;
		case 4:
			printf("输入被运算数->");
			scanf("%d %d", &x, &y);
			printf("%d\n", Div(x, y));
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input != 0);
	return 0;
}
使⽤函数指针数组的实现:
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("******  0.exit   ******\n");
	printf("******  1.Add    ******\n");
	printf("******  2.Sub    ******\n");
	printf("******  3.Mul    ******\n");
	printf("******  3.Div    ******\n");
	printf("***********************\n");
}

int main()
{
	int input;
	int x, y;
	int (*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("选择算法->");
		scanf("%d", &input);
		printf("输入被运算数->");
		scanf("%d %d", &x, &y);
		printf("%d\n", (*pf[input])(x, y));
	} while (input);
	return 0;
}

使用函数指针数组之后代码篇幅大大减少。

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

针对转移表的改动,我们可以利用回调函数,如下:

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("******  0.exit   ******\n");
	printf("******  1.Add    ******\n");
	printf("******  2.Sub    ******\n");
	printf("******  3.Mul    ******\n");
	printf("******  4.Div    ******\n");
	printf("***********************\n");
}

void cacl(int (*pf)(int, int))//回调函数
{
	int x, y;
	printf("输入被运算数->");
	scanf("%d %d", &x, &y);
	printf("%d\n", (*pf)(x, y));
}

int main()
{
	int input;
	do
	{
		menu();
		printf("选择算法->");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			cacl(Add);
			break;
		case 2:
			cacl(Sub);
			break;
		case 3:
			cacl(Mul);
			break;
		case 4:
			cacl(Div);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input != 0);
	return 0;
}

相关文章:

  • Java设计模式实战:装饰模式在星巴克咖啡系统中的应用
  • 【JavaScript】二十、电梯导航栏的实现
  • C++之二叉搜索树
  • arcgis几何与游标(1)
  • 【NLP】 自然语言处理笔记
  • Spring-AI-alibaba 结构化输出
  • 技术视界 | 人形机器人运动控制框架详解:解锁智能机器人的“灵动”密码
  • 如何使用maxscale实现mysql读写分离
  • Java 反序列化之 XStream 反序列化
  • [c语言日记]轮转数组算法(力扣189)
  • JavaScript中的运算符与语句:深入理解编程的基础构建块
  • CentOS下,Xftp中文文件名乱码的处理方式
  • 【第42节】windows双机调试环境搭建和SEH原理剖析
  • RadioMaster POCKET遥控器进入ExpressLRS界面一直显示Loading的问题解决方法
  • 【科普】轨道交通信号系统相关名词解释
  • 基础贪心算法集合2(10题)
  • flutter-Text等组件出现双层黄色下划线的问题
  • android-根据java文件一键生成dex文件脚本
  • js | 网页上的 json 数据怎么保存到本地表格中?
  • NAS-相关软件推荐——非相册和备份的
  • “家门口的图书馆”有多好?上海静安区居民给出答案
  • 山西省援疆前方指挥部总指挥刘鹓已任忻州市委副书记
  • 韩国检方起诉前总统文在寅
  • 外卖江湖战火重燃,骑手、商家、消费者在“摇摆”什么?
  • 佩索阿稳定常销,陀翁不断加印,青少年喜欢黑塞
  • 国际货币基金组织报告:将今年全球经济增长预期由此前的3.3%下调至2.8%