深入理解指针(三)

目录

1.字符指针变量

2. 数组指针变量

2.1 数组指针变量是什么?

2.2 数组指针变量怎么初始化

3. ⼆维数组传参的本质

4. 函数指针变量

4.1 函数指针变量的创建

4.2 函数指针变量的使用

4.3 两段有趣的代码

4.3.1 typedef关键字

5. 函数指针数组

6. 转移表


1.字符指针变量

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

第一种使用方法:

#include <stdio.h>
int main()
{
	char ch = 'a';
	char* p = &ch;
	*p = 'c';
	printf("%c\n", *p);
	return 0;
}

另一种使用方法:

#innclude <stdio.h>
int main()
{
	/*
	char ch[] = "abcdef";
	char* p = ch;
	*/

	char* ch = "abcdef";
	printf("%c\n", *ch);//首字符的地址解引用
	printf("%s\n", ch);//使用%s打印字符串的时候,只需要传首字符的地址

	return 0;
}

 

 前面我们说常量字符串不能被修改,那我们也可以试一下。

ch是常量字符串,对它进行修改是不允许的, 既然内容不能修改,也就是不希望被修改,那我们可以使用const来做限制。

如果我们加上const,如果来修改ch指向的空间中的值的话编译器会报错,但是如果不加,语法上是没有任何问题的,但是运行起来程序会崩溃。

关于字符串的笔试题:

#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");
	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. 数组指针变量

2.1 数组指针变量是什么?

之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。

那么数组指针变量是指针变量?还是数组? 答案是:指针变量。

类比:

字符指针 - char* - 指向字符的指针 - 字符指针变量中存放字符变量的地址。

char ch = 'a';
char* p = &ch; 

整型指针 - int* - 指向整型的指针 - 整型指针变量中存放整型变量的地址。

int a = 2;
int* p = &a;

那么数组指针就是指向数组的地址,数组指针变量存放的是数组的地址,&数组名就得到数组的地址。

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//*说明p是指针,p指向的是[10]数组,指向的数组是10个元素,每个元素int类型
	//p就是数组指针,p中存放的是数组的地址
	int(*p)[10] = &arr;//括号不能去掉,去掉p就和[]结合,说明它是数组,是个元素,每个元素int*类型
	//int(*)[10] = int(*)[10] - 数组指针类型

	//arr - int*	arr+1跳过4个字节
	//&arr[0] - int*	&arr[0]+1跳过4个字节
	//&arr - int(*)[10]	&arr+1跳过40个字节
	//类型决定了+1跳过多少个字节

	return 0;
}
int *p1[10];//指针数组
int (*p2)[10];//数组指针

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

#include <stdio.h>
int main()
{
	char* ch[5];
	char* (*pc)[5] = &ch;
	//(*pc)表示是指针变量,[5]表示pc指向的数组5个元素,
	//char*表示pc指向的每个元素char*类型
    //pc的类型就是char*(*)[5]
	return 0;
}

2.2 数组指针变量怎么初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的&数组名 。

 通过调试我们看到arr和p的类型完全一样

数组指针类型解析:

int (*p) [10] = &arr;
 |    |   |
 |    |   |
 |    |   p指向数组的元素个数
 |    p是数组指针变量名
 p指向的数组的元素类型

 那么这个数组指针到底有什么用呢?我们能不能把放进去的值拿出来呢?

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int (*p)[10] = &arr;
	//使用p这个数组指针来访问arr数组的内容
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);
	}
	return 0;
}

输出结果:

 那么(*p)[i]应该怎么理解呢?

这里用数组指针的形式去访问一维数组太过别扭,这里只是便于理解。

一维数组的话这样访问更容易。

下面就看看数组指针的使用场景。

3. ⼆维数组传参的本质

之前我们把一个一维数组传递给函数,那么也可以把一个二维数组传递给函数。

#include <stdio.h>
void print(int arr[3][5], int x, int y)
{
	for (int m = 0; m < x; m++)
	{
		for (int n = 0; n < y; n++)
		{
			printf("%d ", arr[m][n]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print(arr, 3, 5);
	return 0;
}

输出结果:

经过上面的理解,也可以不写成数组的形式,可以写成指针的形式。

#include <stdio.h>
void print(int (*arr)[5], int x, int y)
{
	for (int m = 0; m < x; m++)
	{
		for (int n = 0; n < y; n++)
		{
			printf("%d ", *(*(arr + m)+n));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print(arr, 3, 5);
	return 0;
}

输出:

 二维数组传参的话形参要写成数组指针,那么打印部分的*(*(arr + m)+n)应该怎么理解呢?

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

4. 函数指针变量

4.1 函数指针变量的创建

变量有地址,数组有地址。

#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;//一级指针

	int arr[5] = { '\0' };
	int (*parr)[5] = &arr;//数组指针

	return 0;
}

那么函数是否有地址呢?

&变量名就可以取出变量的地址,&数组名就可以取出整个数组的地址,那么&函数名是不是就取出函数的地址了。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//数组名表示首元素的地址,&数组名取出的是整个数组的地址,这两个是有区别的
	//但是&函数名和函数名都是函数的地址,没有区别
	printf("&Add = %p\n", &Add);
	printf("Add  = %p\n", Add);
	return 0;
}

输出结果:

 函数是有地址的,&函数名和函数名的都是函数的地址,这两个是没有任何区别的。

以前取出数组的地址想要存起来就得创建数组指针变量,整型取出地址想要存起来就得创建整型指针变量,那我想把函数的地址存起来呢?那我该怎么写类型呢?

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int,int) = Add;//pf 函数指针变量,和数组指针的写法类似
	int (*pf)(int x,int y) = Add;//x y写上或者省略都是可以的
	//int (*)(int,int) 函数指针类型
	return 0;
}

函数指针类型解析:

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

int (*) (int x, int y) //pf函数指针变量的类型

4.2 函数指针变量的使用

通过函数指针调用指针指向的函数。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;
	int index = (*pf)(30, 40);
	printf("%d\n", index);
	return 0;
}

输出结果:

调试:

 我们通过函数指针来调用函数,算出的结果也是合适的。

就算把取地址符号加上,结果也是没有问题的。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{

    //没有讲函数指针之前是这样调用函数的
	int index1 = Add(30, 40);//Add是函数的地址,直接用函数的地址可以调用
	printf("%d\n", index1);

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

	int index = pf(30, 40);//pf里面就存放的函数的地址,所以*不写也是可以的
	printf("%d\n", index);
	/*
	(*pf)(30, 40) - *写上也行,不写也行,写多个也行,其实没有真的去解引用,
	但是这样写更加容易理解,pf是指针变量,对应指针的理解,要解引用然后去调用它,
	所以如果也写*就必须加上括号,如果不加就去调用函数,然后返回70,*70对70解引用
	就会出问题
	*/

	return 0;
}

4.3 两段有趣的代码

代码1:

 (*(void (*)())0)();

这段代码什么意思?

 

代码2:

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

这段代码什么意思?

这种代码看起来比较费劲,类型比较多,其实我们可以使用typedef关键字简化一下代码。

4.3.1 typedef关键字

typedef是用来类型重命名的,可以将复杂的类型简单化。

 ⽐如,unsigned int写起来不方便,如果能写成uint就方便多了。

typedef unsigned int uint;//将unsigned int 重命名为uint
int main()
{
	unsigned int a;//写起来太麻烦
	uint b;//写起来简单
    //上面两句代码一个意思
	return 0;
}

如果是指针类型,其实也是可以的,比如,将 int* 重命名为 pint_t 。

typedef int* pint_t;
int main()
{
	int* p;
	pint_t p2;
	return 0;
}

但是对于数组指针和函数指针稍微有点区别:比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t,那可以这样写:

//数组指针
typedef int(*parr_t)[10];//重命名

int mainn()
{
	int arr[10] = { '\0' };
	int (*p)[10] = &arr;//p是数组指针变量
	parr_t p2;
	return 0;
}

函数指针类型的重命名也是⼀样的,比如,将int(*)(int,int)类型重命名为pf_t ,就可以这样写:

typedef int(*pf_t) (int, int);
int Add(int x, int y)
{
	return x + y;
}
int mainn()
{
	int (*p)(int,int) = Add;
	pf_t p1 = Add;
	return 0;
}

那么我就可以使用typedef来对我们的代码2做出相应的简化。

typedef void(*pf_t)(int);
int main()
{
	//简化前
	void (*signal(int, void(*)(int)))(int);
	//简化后
	pf_t signal(int, pf_t);
	//这样写就容易理解多了,函数名是signal,参数是int类型和函数指针类型,返回值是函数指针类型
	return 0;
}

5. 函数指针数组

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组。

int* arr[10];//整型指针数组
char* arr2[10];//字符指针数组

函数指针我们也学习了,函数指针里面存放的是函数的地址。

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 (*p1)(int ,int) = Add;
	int (*p2)(int ,int) = Sub;
	int (*p3)(int ,int) = Mul;
	int (*p4)(int ,int) = Div;
	return 0;
}

 我们会发现,这么写太麻烦了,而且每个类型都是一样的,int (*)(int ,int),我们知道,数组是存放相同元素类型的集合,那么现在就要创建一个函数指针数组,里面放函数的地址。

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(*parr[4])(int, int) = { Add,Sub,Mul,Div };
	//是数组就有下标             0   1   2   3
	return 0;
}

那么我们怎么通过下标来找到对应的数组呢?

#include <stdio.h>
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(*parr[4])(int, int) = { Add,Sub,Mul,Div };
	//是数组就有下标             0   1   2   3

	for (int i = 0; i < 4; i++)
	{
		int index = parr[i](6 , 3);
		printf("%d\n", index);
	}

	return 0;
}

正是因为这个函数的参数个数,参数类型,返回值是一样的,所以才能写成函数指针数组的形式,如果这个各不相同,是不能这样写的。

要学会先写函数指针,在函数指针的基础上改造函数指针数组是最容易的。

6. 转移表

函数指针数组的用途 - 转移表

计算器的实现:

#include <stdio.h>
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 index = 0;
	do
	{
		menu();//菜单
		printf("请输入:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			index = Add(x, y);
			printf("%d\n", index);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			index = Sub(x, y);
			printf("%d\n", index);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			index = Mul(x, y);
			printf("%d\n", index);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			index = Div(x, y);
			printf("%d\n", index);
			break;
		case 0:
			printf("退出计算器!!!\n");
			break;
		default:
			printf("输入错误,请重新输入!!!\n");
			break;
		}
	} while (input);
	return 0;
}

运行代码:

这种代码只能完成加减乘除,如果将来还要算%,&&,||,&,|,<<,>>,这类的运算,那么我们的菜单就得扩充,函数也得添加,这些都是必须的,但是switch中的case语句也得添加,这会使我们的代码越来越长,其实我们可以简化代码,把这些函数放到一个指针数组里面。

#include <stdio.h>
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 x = 0;
	int y = 0;
	int input = 0;
	int index = 0;
	int (*pf[4])(int, int) = {0, Add,Sub,Mul,Div };
    //下标                    0   1   2   3   4   加0是为了对齐
	do
	{
		menu();//菜单
		printf("请输入:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			index = pf[input](x, y);
			printf("%d\n", index);
		}
		else if (input == 0)
		{
			printf("退出计算器!!!\n");
		}
		else
		{
			printf("选择错误,请重新选择!!!\n");
		}
	} while (input);
	return 0;
}

输出结果:

这样的写法如果今后我要在代码中加其他的运算,只需要修改菜单,添加函数体,在函数指针数组中添加函数,修改判断范围即可,do while循环中不会因为添加函数而导致代码变长。

函数指针数组 - 转移表 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/711349.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot与flowable(7):流程变量

一、启动时添加流程变量 拿第一个流程图举例&#xff0c;创建一个新的流程定义。 Testvoid contextLoads() {DeploymentBuilder deployment repositoryService.createDeployment();deployment.addClasspathResource("process01/FirstFlow.bpmn20.xml");deployment.…

【枚举】564. 寻找最近的回文数

本文涉及知识点 枚举 LeetCode564. 寻找最近的回文数 给定一个表示整数的字符串 n &#xff0c;返回与它最近的回文整数&#xff08;不包括自身&#xff09;。如果不止一个&#xff0c;返回较小的那个。 “最近的”定义为两个整数差的绝对值最小。 示例 1: 输入: n “123”…

性能测试包括哪些方面?

性能测试、通过自动化测试工具模拟多种正常&#xff0c;峰值&#xff0c;以及异常的负载情况下对系统各项性能指标进行的测试。 负载测试、压力测试、容量测试都属于性能测试。 性能测试指标是衡量系统性能的评价标准 主要关注一些响应时间、并发用户/并发、点击率、吞吐量、…

【CDN】逆天 CDN !BootCDN 向 JS 文件中植入恶意代码

今天在调试代码&#xff0c;突然控制台出现了非常多报错。 这非常可疑&#xff0c;报错指向的域名也证实了这一点。 因为我的 HTML 中只有一个外部开源库&#xff08;qrcode.min.js&#xff09;&#xff0c;因此只有可能是它出现了问题。 我翻看了请求记录&#xff0c;发现这…

房地产房型展示信息小程序的内容是什么

地产业规模之大且品牌众多&#xff0c;还有房屋租赁、中介等&#xff0c;无论开发商公司还是衍生行业商家都需要多渠道宣传品牌和客户触达沟通转化&#xff0c;除了线下各种传单&#xff0c;线上也是主要场景&#xff0c;通过各种连接来达到相应目标。 也因此需符合平台生态开…

【菜狗学前端】uniapp(vue3|微信小程序)实现外卖点餐的左右联动功能

记录&#xff0c;避免之后忘记...... 一、目的&#xff1a;实现左右联动 右->左 滚动&#xff08;上拉/下拉&#xff09;右侧&#xff0c;左侧对应品类选中左->右 点击左侧品类&#xff0c;右侧显示对应品类 二、实现右->左 滚动&#xff08;上拉/下拉&#xff09;右…

windows下的eclipse按Ctrl+Shift+F格式化代码不起作用的处理

1、先上张图&#xff1a; 上面Format&#xff1a;CtrlShiftF&#xff0c;按了以后不起作用。 2、这个快捷键不起作用的原因&#xff1a;可能是快捷键冲突了。 机器上装了Sougou输入法&#xff0c;将输入法切换为英文模式是起作用的。 那么应该就是这个原因了。 3、解决方法…

Golang——gRPC认证和拦截器

一. OpenSSL 1.1 介绍 OpenSSL是一个开放源代码的软件库包&#xff0c;用于支持网络通讯过程中的加密。这个库提供的功能包含了SSL和TLS协议的实现&#xff0c;并可用于生成密钥、证书、进行密码运算等。 其组成主要包括一下三个组件&#xff1a; openssl&#xff1a;多用途的命…

VMware软件的安装与安装Win10系统

上一篇写了&#xff08;虚拟机&#xff09;VMware软件的安装及Ubuntu系统安装&#xff0c;这次续上部分&#xff0c;安装完Ubuntu系统后&#xff0c;又安装了win10&#xff0c;也记录一下。 事前准备好win10镜像文件&#xff0c;可在微软官网下载 入口地址&#xff1a;软件下…

flask部署mtcnn

目录 打印人脸检测信息 输出结果 保存检测结果 浏览器查看nginx&#xff08;nginx配置这里就不多介绍了&#xff09; url图片检测人脸 输出结果 Flask hello-world Flaskmtcnn python调flaskmtcnn 打印人脸检测信息 import cv2 from mtcnn.mtcnn import MTCNNimg cv2.c…

比特币全节点搭建

比特币全节点搭建 参考: https://www.cnblogs.com/elvi/p/10203927.html

基于单片机的太阳能无线 LED 灯设计

摘 要 &#xff1a; 文章设计一款太阳能 LED 灯 &#xff0c; 经过太阳能给锂电池充电 &#xff0c; 利用 51 单片机通过检测电路对整个系统施行管理和监控&#xff0c; 可以使用手机和 WIFI 作为通信工具 &#xff0c; 利用光敏电阻检测光照 &#xff0c; 进而控制灯的亮…

全面解析OpenStack架构:掌握云计算核心组件!

Web Frontends Horizon 技术原理&#xff1a;Horizon是OpenStack的基于Web的用户界面&#xff0c;利用Django框架开发&#xff0c;提供用户友好的界面来管理和使用OpenStack资源。应用场景&#xff1a;用于管理虚拟机、存储、网络等资源。举例&#xff1a;管理员通过Horizon界面…

【微信小程序开发实战项目】——如何去申请腾讯地图账号和在微信公众平台,配置request路径和添加地图插件

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

墨香戏韵,重塑经典

创意名称 墨香戏韵&#xff0c;重塑经典|基于AIGC对戏剧创新 创意概述 京剧作为中国传统戏曲之一&#xff0c;源远流长&#xff0c;承载了丰富的文化内涵和艺术特色。水墨画则是中国传统绘画的瑰宝&#xff0c;以其独特的墨色表达和极简的形式赢得了广泛的赞誉。我们的项目将…

Cheat Engine 学习

文章目录 Exact Value scanning任务实现步骤Unknown initial value任务实现步骤原理说明Floating points任务实现步骤原理说明Code finder任务实现步骤原理说明Pointers任务实现步骤原理说明Change Pointer 操作:Active(活跃状态)和数值修改:Code Injection任务概述实现步骤…

vue3:实现图片放大浏览功能组件

两种实现方式&#xff1a; 1.将原本的盒子与img标签放大至全屏浏览。 2.新建一个div和img标签进行全屏浏览。这样不会改变布局。 第一种&#xff1a; 效果&#xff1a; 组件代码&#xff1a; <template><div :class"isScreen ? fullImg : norImg">…

[Python学习篇] Python字符串

字符串是 Python 中最常用的数据类型&#xff0c;一般使用单引号或引号来创建字符串 语法&#xff1a; 字符串变量名A 字符串变量值A 字符串变量名B "字符串变量值B" 示例&#xff1a; a Hello A print(a) b "Hello B" print(b) 字符串特征 一对引号字…

centos7系统使用docker-compose安装部署jenkins

CentOS7系统使用docker-compose安装部署jenkins&#xff0c;并实现前后端自动构建 记录一次在给公司部署jenkins的真实经历&#xff0c;总结了相关经验 1.准备环境 1.java 由于最新的jenkins需要jdk11以上才能支持&#xff0c;而系统里的jdk是1.8的&#xff0c;因此等jenkins…