【C语言】指针(三)
目录
一、字符指针
1.1 ❥ 使用场景
1.2 ❥ 有关字符串笔试题
二、数组指针
2.1 ❥ 数组指针变量
2.2 ❥ 数组指针类型
2.3 ❥ 数组指针的初始化
三、数组指针的使用
3.1 ❥ 二维数组和数组名的理解
3.2 ❥ 二维数组传参
四、函数指针
4.1 ❥ 函数的地址
4.2 ❥ 函数指针变量
4.3 ❥ 函数指针变量的使用
五、函数指针数组
六、转移表
一、字符指针
字符指针:指向字符的指针
1.1 ❥ 使用场景
【使用场景一】
#include <stdio.h>int main()
{char c = 'w';char* pc = &c;*pc = 'x';printf("%c\n", *pc);return 0;
}
【使用场景二】
#include <stdio.h>int main()
{char* p = "abcdef";//这里把首字符a的地址赋给了变量pprintf("%s\n", p);return 0;
}
注意
这里printf中%s的格式逻辑是:从给定的地址处开始,逐个向后输出字符,直到遇见结束标记\0为止。解引用的操作符在printf函数内部造成。
如果由用户解引用,那printf函数将只能拿到单个字符,反而无法实现功能。
易错点1:
场景二中如果修改*p的值,代码就会报错。报错类型如下:
原因:
"abcdef"是个常量字符串。
常量字符串的意思是:这个字符串本身是不能被更改的。
而这个*p没有被限制,它其实是可以去改变后面的字符串的,所以char* p="abcdef"; 报警告是正常的。
防止方法:在char*p 前加const
易错点2:
不能解引用p,解引用打印的是一个字符,一个字符不能用%s打印。
char* p = "abcdef";
printf("%s", *p);//error
以下是将字符串放在数组里面:
char arr[] = "abcdef";
而数组的内容是可变的。
1.2 ❥ 有关字符串笔试题
#include <stdio.h>int main()
{char arr1[] = "hello";char arr2[] = "hello";const char* p1 = "hello";const char* p2 = "hello";if (arr1 == arr2)printf("arr1=arr2\n");elseprintf("arr1!=arr2\n");if (p1 == p2)printf("p1=p2\n");elseprintf("p1!=p2\n");return 0;
}
运行结果如下:
原因如下:
arr1!=arr2
- 我们知道:arr1和arr2是数组首元素的地址,这两个数组是两块独立的内存空间,它们只是存储的内容相同,都是hello字符串。
p1=p2
- 这里的p1和p2指向的是同一个常量字符串。c/c++会把常量字符串存储到一个单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存(代码段中)。
- arr1,arr2,p1,p2都是放在栈区,指向的hello(常量字符串)是放在代码段。
二、数组指针
整型指针:存放整型变量的地址,能够指向整型数据的指针
浮点型指针:存放浮点型变量的地址,能够指向浮点型数据的指针
那数组指针:存放数组的地址,指向数组的指针
2.1 ❥ 数组指针变量
看下面两行代码,p1,p2分别是什么?
int* p1[10];
int(*p2)[10];
- p1是指针数组
p1是数组名,该数组里存放了10个元素,每个元素是int*类型
- p2是数组指针
因为p2先和*结合,说明p是一个指针变量,然后指向一个大小为10个整型的数组,所以p是一个指针,指向一个数组,所以叫数组指针。
注意:
- [ ]的优先级要高于*号,所以必须加上()来保证p先和*结合。
- int(*p2)[10];里面的*不是解引用的意思,这颗星就代表是指针,只有前面不加类型的时候才是解引用。
2.2 ❥ 数组指针类型
去掉指针变量名就是指针(变量)的类型。
看如下代码:
它们跳过的字节不同就是因为他们的类型不同导致。
注意:
- 一个指针是否是野指针取决于你是否用它。这个指针虽然指向这里,但是没有产生坏的结果。只要不使用它就没关系。
- 虽然它指向的空间不属于“我”,但是它并不危险。
2.3 ❥ 数组指针的初始化
数组指针存放的是数组的地址。
所以初始化的时候要给整个数组的地址。代码如下:
注意:
[ ]里的元素个数不能省略,不然编译器会自动认为是数组元素个数为0
三、数组指针的使用
3.1 ❥ 二维数组和数组名的理解
首先我们来理解一下二维数组及其数组名:
在c语言中,只有一维数组(N维数组的元素是数组),数组名作为指针时永远指向第一个元素。
如:
- 数组a[3]; *a=a[0]
- 数组a[3][4]; *a=a[0] 只不过这时候a[0]又是一个数组。
这时候的a[0]又是指向它自己元素的第一个元素,又有 *a[0]=a[0][0]
- 这种方式可以推广到N维数组,所有数组直接对数组名取地址(如:&a),得到的指针指向该数组,而不是指向第一个元素。注意这点区别。
举个例子: int board[3][4];
board:一维数组的地址。
二维数组的数组名,数组名就是首元素地址。我们知道,可以把一维数组看作二维数组的元素。所以,board就是一维数组的地址。
&board:取出的是整个二维数组的地址。
board[0]:第一行第一个元素的地址。
解引用,相当于拿到第一行数组的数组名,也就是首元素地址,即第一行第一个元素的地址。
board[0]=*board=&board[0][0]
&board[0]:第一行的地址。
board=&board[0]
3.2 ❥ 二维数组传参
清楚了上面的概念之后,我们来看下面一段代码:
之前二维数组传参时,形参部分用的是数组接收。
#include <stdio.h>void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", a[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;
}
发现:实参arr是数组名,通过刚才的分析,直到数组名是首元素地址,首元素地址就是第一行的地址,也就是一维数组的地址,那么它的类型就是数组指针类型。
那么实参就可以写成数组指针的形式,代码如下:
#include <stdio.h>void test(int(*p)[5], int r, int c)//数组指针
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (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;
}
问题:为什么*(p+i) 跳过的是一行数组?
回答:
数组的类型决定了它+1跳过几个字节。
p的类型是:int(*)[5];
p是指向一个整型数组的,数组5个元素 int [5]
p+1 :跳过一个5个int元素的数组
四、函数指针
函数指针:存放函数的地址,指向函数的指针
4.1 ❥ 函数的地址
函数是否有地址呢?我们来测试一下:
由测试结果可知:函数存在地址,取&函数名和函数名拿到的都是函数的地址。
4.2 ❥ 函数指针变量
我们通过函数指针来存储函数的地址。
int (*pf) (int x, int y) = &Add;
int (*pf) (int , int ) = Add;//xy可以省略,只写类型
- 地址要存起来,放到(指针)变量里去。
- pf是变量名,*pf说明是指针,指向的是函数,所以加上括号()。
- 函数的参数是int (参数名写不写无所谓,只要类型交代清楚即可),函数的返回值类型也是int。
int (*pf) (int x, int y)
| | ------------
| | |
| | pf指向函数的参数类型和个数的交代
| 函数指针变量名
pf指向函数的返回类型int (*) (int x, int y) //pf函数指针变量的类型
4.3 ❥ 函数指针变量的使用
通过函数指针调用指针指向的函数。
代码如下:
#include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf)(int, int) = Add;printf("%d\n", (*pf)(2, 3));//输出结果为5printf("%d\n", pf(2, 3));//输出结果为5return 0;
}
把函数的地址存到pf里,通过解引用pf找到函数,找到这个函数要调用这个函数,调用函数需要传参,所以();(传参),传2,3。这样的话它会把2和3相加,得到5。
问题一:为什么*pf必须带上括号()呢?
回答:因为假如不带上括号,调用返回5,就会对5进行解引用。
问题二:为什么能写成pf(2, 3)这种形式?
回答:在C语言里,pf前的*其实是个摆设,可以不写,也可以写多个。
这是个技术细节问题,不涉及到语法原则,从不同的思考角度出发,观点会略有不同,但不影响C语言实践,初学者也不必过多纠结。
应用:通过函数指针的方式进行调用
#include <stdio.h>int Add(x, y)
{return x + y;}
void cale(int(*pf)(int,int))
{int a = 3;int b = 5;int ret = pf(a, b);printf("%d\n", ret);
}
int main()
{cale(Add);return 0;
}
这里的cale没有直接调用Add函数,而是通过函数指针的方式进行调用。
五、函数指针数组
函数指针数组:把一个函数的地址存放到一个数组中。
是个函数指针类型的数组。
去掉 函数名+[ ] 就是该数组的类型。
int (*parr[3])( );
解释:parr先和[ ]结合,说明parr是数组,数组的内容是int(*)()类型的函数指针。
当对函数指针数组进行初始化的时候,后面初始化的可以省略掉数组的大小,它会根据后面初始化的内容来确定数组的大小。
例如:
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
int(*p[])(int x, int y) = { 0, add, sub, mul, div };
六、转移表
使用了函数指针数组,避免大篇幅地修改内容;也可实现跳转的功能。
所以函数指针数组也叫:转移表。
计算机的一般实现
#define _CRT_SECURE_NO_WARNINGS 1#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 div(int a, int b)
{return a / b;
}
int main() {int x, y;int input = 1;int ret = 0;do{printf("————----------------------\n");printf("1.add 2.sub \n");printf("3.mul 4.div \n");printf("0.exit \n");printf("————----------------------\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("输入操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输入操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输入操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输入操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %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 div(int a, int b)
{return a / b;
}int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表do{printf("————----------------------\n");printf("1.add 2.sub \n");printf("3.mul 4.div \n");printf("0.exit \n");printf("————----------------------\n");printf("请选择:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("输入操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输入有误\n");}} while (input);return 0;
}
相关文章:

【C语言】指针(三)
目录 一、字符指针 1.1 ❥ 使用场景 1.2 ❥ 有关字符串笔试题 二、数组指针 2.1 ❥ 数组指针变量 2.2 ❥ 数组指针类型 2.3 ❥ 数组指针的初始化 三、数组指针的使用 3.1 ❥ 二维数组和数组名的理解 3.2 ❥ 二维数组传参 四、函数指针 4.1 ❥ 函数的地址 4.2 ❥ 函数…...

【Linux】从零开始认识进程间通信 —— 管道
送给大家一句话: 人要成长,必有原因,背后的努力与积累一定数倍于普通人。所以,关键还在于自己。 – 杨绛 从零开始认识进程间通信 1 为什么要进程间通信2 进程如何通信3 进程通信的常见方式4 管道4.1 什么是管道4.2 管道通信的系…...

Top3专业课150满分,怎么考的?
这个系列会邀请上岸学长学姐进行经验分享~ 今天经验分享的同学是小马哥上海交大819的全程班学员,专业课150分满分,这位同学也是819期末考试的第一名,非常厉害!大家吸吸欧气! 初试成绩单 前言 先介绍下自己࿰…...
Windows Presentation Foundation(WPF)要点总结
Windows Presentation Foundation(WPF)是微软推出的一种用于构建Windows桌面应用程序的框架。自从WPF在.NET Framework 3.0中引入以来,它以其强大的功能和灵活性,逐渐成为开发人员构建现代、富用户界面应用程序的首选。本文将概述…...

【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法
文章目录 前言 Timer中断调度 Event中断调度 StateFlow调度 分析和应用 总结 参考资料 前言 近期在一些嵌入式系统开发项目中,在使用嵌入式处理器时,遇到了挺多费时费力的事情。所以利用晚上和周末时间,在这些方面深入研究了一下&…...

揭秘Python的魔法:装饰器的超能力大揭秘 ♂️✨
文章目录 Python进阶之装饰器详解1. 引言装饰器的概念与意义装饰器在Python编程中的作用 2. 背景介绍2.1 函数作为对象2.2 高阶函数 3. 装饰器基础3.1 理解装饰器3.2 装饰器的工作原理 4. 带参数的装饰器4.1 为什么需要带参数4.2 实现带参数的装饰器使用函数包裹装饰器使用类实…...

怎么一键消除路人?教你三个消除方法
怎么一键消除路人?在数字时代,摄影已成为我们记录生活、表达情感的重要方式。然而,完美的照片背后往往隐藏着一些不那么完美的元素——比如那些不经意间闯入镜头的路人。他们或许只是匆匆过客,但却足以破坏你精心构图的美好瞬间。…...
Android Settings系统属性读写
Settings系统属性存储均为xml,分三种: 1.global:所有的偏好设置对系统的所有用户公开,第三方APP有读没有写的权限; 源码地址:frameworks/base/core/java/android/provider/Settings.java 对应xml路径&…...
2024年,企业的人才管理怎么做?这5点是关键!
当今时代,各行各业都面临着激烈的竞争。这些竞争归根结底都是人才的竞争。企业若想在竞争中掌握主动权,实现基业长青,就必须努力留住人才,并充分发挥他们的积极性、主动性和创造性。因此,做好人才管理是企业实现长期可…...
数据库DDL语句
数据库DDL语句: 查询所有数据库: show databases;查询当前数据库的名称 select database();创建数据库 create database [if not exists] 数据库名 [default charset 字符集] [collate 排序规则]注意:排序规则指定后,它会影响…...

《艺术大观》知网艺术刊:可加急, 出刊上网快
《艺术大观》 《艺术大观》征文通知 《艺术大观》期刊诚邀学者、艺术家和文化工作者积极投稿,共同探索艺术领域的前沿问题,促进学术交流和艺术创作的发展。我们欢迎各类艺术形式的研究与评论,包括但不限于绘画、雕塑、音乐、舞蹈、戏剧、电…...

如何在go语言中调用c语言代码
1.安装c语言编译器 要使用cgo,需要安装c语言编译器 gcc 2.检查CGO_ENABLED时候开启 使用以下命令查看: go env CGO_ENABLED 如果go env CGO_ENABLED被禁用(为0),需要将其设置为开启(为1) 3.编写c语言程序,并用go语言调用c语言程序 1ÿ…...

Monodle centerNet3D 瑞芯微RKNN、地平线Horizon芯片部署、TensorRT部署
一直想做一点3D目标检测,先来一篇单目3D目标检测Monodle(基于centernet的),训练代码参考官方【代码】,这里只讲讲如何部署。 模型和完整仿真测试代码,放在github上参考链接【模型和完整代码】。 1 模型训练…...
Android Studio 使用MQTT协议开发应用时怎样关闭MQTT连接
Android Studio 使用MQTT协议开发应用时怎样关闭MQTT连接 Android Studio 使用MQTT协议开发应用时关闭MQTT连接 在使用mqtt开发的时候,有时候需要通过 返回 按钮关闭界面或者Activity时,关闭当前页面使用的mqtt连接,这里有两种方式彻底销毁…...

Sping源码(八)—registerBeanPostProcessors
序言 之前我们用大量的篇幅介绍过invokeBeanFactoryPostProcessors()方法的执行流程。 而invokeBeanFactoryPostProcessors的主要逻辑就是遍历执行实现了BeanDefinitionRegistryPostProcesso类(主要是针对BeanDefinition的操作)和BeanFactoryPostProcessor(主要针对BeanFacrot…...

MaxEnt模型文章中存在的问题和处理方法(050B更新)2024.5.24
目前多数MaxEnt文章中存在的问题和处理方案。 **问题一:**变量数据使用问题,很多文章把所有变量数据直接使用,但是温度和土壤、植被类型等属于不同数据类型,在数据使用时参数配置是不一样的,产生的结果文件也是不一样的…...

Modular RPG Hero PBR
-掩码着色着色器提供了无限的颜色变化。(适用于标准/HDRP/URP 11.0.0) -为剑与盾/双剑/双剑姿态提供了简单的角色控制器。(不包括弓和魔杖控制器)(它是用旧的输入系统建造的) -HDRP/URP(11.0.0)SRP 100%支持常规着色器和遮罩着色着色器(基于着色器图形) -具有许多模块…...

机器学习之常用算法与数据处理
一、机器学习概念: 机器学习是一门多领域交叉学科,涉及概率论、统计学、计算机科学等多门学科。它的核心概念是通过算法让计算机从数据中学习,改善自身性能。机器学习专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识…...
Git管理
git作用:代码回溯 版本切换 多人协作 远程备份 git仓库:本地仓库:开发人员自己电脑上的Git仓库 原程仓库:远程服务器上的Git仓库 commit:提交,将本地文件和版本信息保存到本地仓库 push:推送࿰…...

osgearth 3.5 vs 2019编译
下载源码 git clone --recurse-submodules https://github.com/gwaldron/osgearth.git 修改配置文件 主要是修改bootstrap_vcpkg.bat,一处是vs的版本,第二处是-DCMAKE_BUILD_TYPERELEASE 构建 执行bootstrap_vcpkg.bat vs中生成安装 vs2019打开bu…...
详细介绍uni-app中Composition API和Options API的使用方法
uni-app 中 Composition API 和 Options API 的使用方法详解 一、Options API(Vue 2.x 传统方式) 1. 基本结构 Options API 通过配置对象的不同选项(如 data、methods、computed 等)组织代码: <template><…...

基于安卓的文件管理器程序开发研究源码数据库文档
摘 要 伴随着现代科技的发展潮流,移动互联网技术快速发展,各种基于通信技术的移动终端设备做的也越来越好了,现代智能手机大量的进入到了我们的生活中。电子产品的各种软硬技术技术的发展,操作系统的不断更新换代,谷歌…...

西门子 S7-1200 PLC 海外远程运维技术方案
西门子 S7-1200 PLC 海外远程运维技术方案 一、面向海外场景的核心优势 针对跨国企业、海外项目及远程技术支持需求,本方案基于巨控GRM552Y-CHE模块提供无缝的全球化远程PLC运维能力,突破地域及时差限制,显著提升国际项目响应效率。 二、海…...
MCP通信方式之Streamable HTTP
目录 一、前言二、三种传输方式对比1、Stdio和 HTTP SSE工作原理2、Streamable HTTP3、Streamable HTTP解决什么问题三、Streamable HTTP MCP设计原理四、Streamable HTTP MCP demo演示1、MCP server示例2、MCP Client示例一、前言 2025年5月9日,MCP(Model Context Protocol)…...
浏览器后台服务 vs 在线教育:QPS、并发模型与架构剖析
本文深入分析浏览器后台服务与在线教育平台在高并发场景下的架构设计差异,涵盖 QPS(每秒请求数)承压能力、服务模型、数据一致性、容灾机制等多个维度,力图为系统架构师和后端工程师提供实战参考。 一、什么是高并发场景ÿ…...
Hive终极性能优化指南:从原理到实战
摘要:本文系统总结Hive在生产环境的核心调优手段,涵盖执行引擎选择、存储优化、SQL技巧、资源调配及数据倾斜解决方案,附可复用的参数配置与实战案例。 一、执行引擎优化:突破MapReduce瓶颈 启用Tez/Spark引擎 优势&am…...
使用 Unstructured 开源库快速入门指南
引言 本文将介绍如何使用 Unstructured 开源库(GitHub,PyPI)和 Python,在本地开发环境中将 PDF 文件拆分为标准的 Unstructured 文档元素和元数据。这些元素和元数据可用于 RAG(检索增强生成)应用、AI 代理…...

springMVC-11 中文乱码处理
前言 本文介绍了springMVC中文乱码的解决方案,同时也贴出了本人遇到过的其他乱码情况,可以根据自身情况选择合适的解决方案。 其他-jdbc、前端、后端、jsp乱码的解决 Tomcat导致的乱码解决 自定义中文乱码过滤器 老方法,通过javaW…...

RPA+AI:自动化办公机器人开发指南
RPAAI:自动化办公机器人开发指南 系统化学习人工智能网站(收藏):https://www.captainbed.cn/flu 文章目录 RPAAI:自动化办公机器人开发指南摘要引言技术融合路径1. 传感器层:多模态数据接入2. 决策层&…...
docker镜像下载到本地,并导入服务器
应用场景 : 本地环境可以连接外网,但服务器连接不了外网,直接用docker pull 命令执行拉起镜像报异常。 1.本地拉取xuxueli/xxl-job-admin:2.2.0及查看所有下载的镜像 docker pull xuxueli/xxl-job-admin:2.2.0 docker images 2.保存镜像到…...