【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…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
