深入理解指针03

1. 字符指针变量

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

⼀般使⽤:

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

还有⼀种使⽤⽅式如下:


int main()
{
	const char* pstr = "hello world";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
	printf("%s\n", pstr);
	return 0;
}

代码 const char* pstr = "hello world."; 特别容易让同学以为是把字符串hello world 到字符指针 pstr ⾥了,但是本质是把字符串 放 hello world.⾸字符的地址放到了pstr中。 

《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:

#include <stdio.h>
 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. 数组指针变量

 2.1 数组指针变量是什么?

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

答案是:指针变量。

我们已经熟悉:

• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。

• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量

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

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

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

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

我们调试也能看到 &arr 和 p 的类型是完全⼀致的

3. ⼆维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。 过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的

#include<stdio.h>

void test(int arr[3][5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr, 3, 5);
	return 0;
}

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗? ⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。 如下图:

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀ 维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 型就是数组指针类型 int [5] ,所以第⼀⾏的地址的类 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址传递的是第⼀ ⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:


#include<stdio.h>

void test(int(*p)[5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ",*(*(p+i)+j));
		}
		printf("\n");
	}

}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr, 3, 5);
	return 0;
}

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

4. 函数指针变量

4.1 函数指针变量的创建

什么是函数指针变量呢?

根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

那么函数是否有地址呢? 我们做个测试 :

#include <stdio.h>
 void test()
 {
 printf("hehe\n");
 }
 int main()
 {
 printf("test:  %p\n", test);
 printf("&test: %p\n", &test);
 return 0;
 }

输出结果如下:

确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 & 函数名 的⽅式获得函数的地址。  如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 ⾮常类似。如下: 

 void test()
 {
 printf("hehe\n");
 }
 void (*pf1)() = &test;
 void (*pf2)()= test;
 int Add(int x, int y)
 {
 return x+y;
 }
 int(*pf3)(int, int) = Add;
 int(*pf3)(int x, int y) = &Add;

函数指针类型解析:

 

4.2 函数指针变量的使⽤

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


#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf3)(int, int) = Add;
	printf("%d\n", (*pf3)(2, 3));
	printf("%d\n", pf3(3, 5));
	return 0;
}

输出结果:

5

8

4.3 两段有趣的代码

 代码1:

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

代码2:
void (*signal(int , void(*)(int)))(int);

两段代码均出⾃:《C陷阱和缺陷》这本书

4.3.1 typedef关键字

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

⽐如,你觉得unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤ 

 如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:

1 typedef int* ptr_t;

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

typedef int(*parr_t)[5]; // 新的类型名必须在 * 的右边

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

typedef void(*pfun_t)(int);// 新的类型名必须在 * 的右边

5. 函数指针数组 

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


int* arr[10];
//数组的每个元素是int*

那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

 答案是:parr1

parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢?

是 i nt (*)() 类型的函数指针 

6. 转移表

函数指针数组的⽤途:转移表

举例:计算器的⼀般实现:


#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int dis(int a, int b)
{
	return a / b;
}

void menu()
{
	printf("*********************\n");
	printf("***1.add   2.sub*****\n");
	printf("***3.mul   4.dis*****\n");
	printf("***0.exit       *****\n");
	printf("*********************\n");

}


int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		menu();
		int input = 0;
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个数字:");
			scanf("%d%d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个数字:");
			scanf("%d%d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个数字:");
			scanf("%d%d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个数字:");
			scanf("%d%d", &x, &y);
			ret = dis(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default: 
			printf("输入错误,请重试\n");
			break;
		}
	} while (input);
	return 0;
}

使⽤函数指针数组的实现:


#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int dis(int a, int b)
{
	return a / b;
}

void menu()
{
	printf("*********************\n");
	printf("***1.add   2.sub*****\n");
	printf("***3.mul   4.dis*****\n");
	printf("***0.exit       *****\n");
	printf("*********************\n");

}


int main()
{
	int x = 0;
	int y = 0;
	int input = 1;
	int ret = 0;
	int (*p[5])(int, int) = { 0,add,sub,mul,dis };
	do
	{
		menu();
		int input = 0;
		printf("请选择:\n");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个数字:\n");
			scanf("%d%d", x, y);
			ret = (*p[input])(x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出游戏\n");
		}
		else
		{
			printf("输入错误\n");
		}
	} while (input);
	return 0;
}

 好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

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

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

相关文章

【Linux实践室】Linux用户管理实战指南:新建与删除用户操作详解

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 &#x1f514;Linux创建用户命令2.1.1 知识点讲解2.1.2 案…

OceanMind海睿思入选中国信通院《2023高质量数字化转型技术解决方案集》

近日&#xff0c;由中国信息通信研究院“铸基计划”编制的《2023高质量数字化转型技术解决方案集&#xff08;第一版&#xff09;》正式发布。 中新赛克海睿思 凭借卓越的产品力以及广泛的行业实践&#xff0c;成功入选该方案集的数据分析行业技术解决方案。 为促进数字化转型…

探索uni-app项目的架构与开发实践:快速开发的项目模板参考

摘要&#xff1a;本文将深入探讨uni-app项目架构的模板设计&#xff0c;以及如何通过使用该模板实现快速开发。我们将重点介绍模板中的组件示例、SDK示例和模板页面&#xff0c;并阐述它们在提高开发效率和优化用户体验方面的作用。 一、引言 随着移动互联网的迅猛发展&#…

每日一题 --- 209. 长度最小的子数组[力扣][Go]

长度最小子数组 题目&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度**。**如果不存在符合条件的子数组&#xff0c…

Learn OpenGL 19 几何着色器

几何着色器 在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader)&#xff0c;几何着色器的输入是一个图元&#xff08;如点或三角形&#xff09;的一组顶点。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而&#xff0c;几何着色器最有趣的地方…

4.KubeSphereV3.4-DevOps配置maven私服

1.修改方法 maven容器模板中用的是中央仓库打包&#xff0c;但是我们打包需要用到私服。那么我们需要将私服配置到容器settings.xml中。 以 admin账号登录 在配置字典里找到 ks-devops-agent 将MavenSetting中mirror标签里配上私服地址&#xff1a; <?xml version"…

印刷企业实施MES管理系统如何做好需求分析

在数字化、信息化的大潮中&#xff0c;印刷企业面临着转型升级的迫切需求。MES管理系统作为连接企业资源计划ERP和现场自动化系统的桥梁&#xff0c;对于提升印刷企业的生产效率、优化资源配置、提高产品质量具有重要意义。因此&#xff0c;做好MES管理系统的需求分析&#xff…

大模型 - 相关工程总结(待更新

文章目录 下图转自 公众号 AI工程化 推理执行引擎 server vLLM vLLM 部署 Qwen https://ezcode.blog.csdn.net/article/details/135947607 HF PipelineTGIDeepSpeed-MIITensorRT-LLM pc/edge ggmlollama https://ezcode.blog.csdn.net/article/details/136482825mlc-llm Web …

不启动BMIDE,Teamcenter如何查看property的real property name

问题描述&#xff1a; Teamcenter客户端&#xff0c;查看Item 属性&#xff0c;属性名称默认显示的是Display Name。 在各类开发过程中&#xff0c;对属性的操作&#xff0c;需要使用real property name才能进行。开发可能不在server端&#xff0c;没有安装BMIDE&#xff0c;如…

新手小白学剪辑视频的知识点,什么是视频分辨率和位深度?

新手小白需要了解的视频剪辑知识点&#xff0c;什么是视频分辨率尺寸(文件大小)和位深度&#xff1f; 分辨率尺寸/文件大小 常见的视频分辨率是高清和 4K。高清素材的屏幕像素&#xff08;宽度 x 高度&#xff09;测量值通常为 1920 x 1080&#xff0c;而 4K 素材是其四倍&am…

二叉树的层次遍历经典问题-算法通关村

二叉树的层次遍历经典问题-算法通关村 1 层次遍历简介 广度优先在面试里出现的频率非常高&#xff0c;整体属于简单题。广度优先又叫层次遍历&#xff0c;基本过程如下&#xff1a; 层次遍历就是从根节点开始&#xff0c;先访问根节点下面一层全部元素&#xff0c;再访问之后…

51单片机入门:定时器

定时器的介绍 定时器&#xff1a;51单片机的定时器属于单片机的内部资源&#xff0c;其电路的设计连接和运转均在单片机内部完成。根据单片机内部的时钟或者外部的脉冲信号对寄存器中的数据加1&#xff0c;定时器实质就是加1计数器。因为又可以定时又可以计数&#xff0c;又称…

禁欲28天!一宅男居然肝出如此详细Web安全学习笔记,学妹看完直接抽搐了!

1.1. Web技术演化 1.1.1. 简单网站 1.1.1.1. 静态页面 Web技术在最初阶段&#xff0c;网站的主要内容是静态的&#xff0c;大多站点托管在ISP上&#xff0c;由文字和图片组成&#xff0c;制作和表现形式也是以表格为主。当时的用户行为也非常简单&#xff0c;基本只是浏览网…

51单片机—直流电机

1.元件介绍 2.驱动电路 3.电机调速 一般会保证一个周期的时间是一样的 应用&#xff1a; 1.LED呼吸灯 #include <REGX52.H>sbit LEDP2^0;void Delay(unsigned int t) {while(t--); } void main() {unsigned char Time,i;while(1){for(Time0;Time<100;Time){for(i0;…

Linux相关命令(1)

1、找出文件夹下包含 “aaa” 同时不包含 “bbb”的文件&#xff0c;然后把他们重新生成一下。要求只能用一行命令。 find ./ -type f -name "*aaa*" ! -name "*bbb*" -exec touch {} \;文件系统操作命令 df&#xff1a;列出文件系统的整体磁盘使用情况 …

RocketMQ基础知识和常见问题

RocketMQ 是一个 队列模型 的消息中间件&#xff0c;具有高性能、高可靠、高实时、分布式 的特点。它是一个采用 Java 语言开发的分布式的消息系统&#xff0c;由阿里巴巴团队开发&#xff0c;在 2016 年底贡献给 Apache&#xff0c;成为了 Apache 的一个顶级项目。 在阿里内…

Linux 创建交换空间

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

明牌空投:Cosmos生态项目Joltify零撸教程

简介&#xff1a;Joltify Finance 是基于Cosmos SDK的Layer1公链&#xff0c;做RWA赛道的&#xff0c;它可以将加密世界中的大量流动性与现实世界的金融资产合并&#xff0c;将有形资产转换为代币或NFT的过程&#xff0c;使它们能够在链上进行交易&#xff0c;从而在DeFi和传统…

内存卡损坏怎么修复数据,内存卡损坏修复数据方法

内存卡损坏是许多用户都可能面临的问题。当我们的内存卡损坏时,其中存储的重要数据可能会受到威胁,承载着我们无尽回忆的数据,一旦失去,将成为大家心中永远的遗憾。因此我们迫切需要找到一种方法来修复这些数据。本文将介绍一些内存卡损坏修复数据方法,帮助大家解决因为内…

外卖店优先级c++

题目 输入样例&#xff1a; 2 6 6 1 1 5 2 3 1 6 2 2 1 6 2输出样例&#xff1a; 1样例解释 6时刻时&#xff0c;1 号店优先级降到 3&#xff0c;被移除出优先缓存&#xff1b;2 号店优先级升到 6&#xff0c;加入优先缓存。 所以是有 1 家店 (2 号) 在优先缓存中。 思路 …
最新文章