C语言深入理解指针(非常详细)(四)
目录
- 字符指针变量
- 数组指针变量
- 数组指针变量是什么
- 数组指针变量怎么初始化
- 二维数组传参的本质
- 函数指针变量
- 函数指针变量的创建
- 函数指针变量的使用
- 代码
- typedef关键字
- 函数指针数组
- 转移表
字符指针变量
字符指针在之前我们有提到过,(字符)(指针)前面的字符代表着存储的元素为字符类型,而指针则是表示这存储的方式。
写法为char*
一般使用的方式如下:
nt main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有一种使用方式如下:
int main()
{
const char* pstr = "hello jack.";
printf("%s\n", pstr);
return 0;
}
值得注意的是:代码 const char pstr = “hello jack.”; 特别容易以为是把字符串 hello jack 放到字符指针 pstr 里了,但是本质是把字符串 hello jack. 首字符的地址放到了pstr中*
《剑指offer》中有一道和字符串有关的题如下:
#include <stdio.h>
int main()
{
char str1[] = "hello jack.";
char str2[] = "hello jack.";
const char *str3 = "hello jack.";
const char *str4 = "hello jack.";
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;
}
这里的char str1[]和charstr2[]在创造时是单独开辟了一个储存空间,因此二者的储存地址是不同的
所以打印出来的是str1 and str2 are not same
char str3和charstr4中因为带有了指针,因此指针指向的字符串hello jack是已经单独创造了一个储存空间,而str3和str4则是存储字符串的储存地址,因此最后是str3 and str4 are same
数组指针变量
数组指针变量是什么
之前的是指针数组,现在的数组指针其实理解方式是相同的(数组)(指针),储存的元素是数组,而储存的方式就是通过指针储存数组的地址
我们举一些其他熟悉的例子:
• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针
下面代码哪个是数组指针变量?
1:int *p1[10];
2:int (*p2)[10];
p1[10]是指针数组
(p2)[10]是数组指针
我们发现这二者的差别就在于括号,指针数组是没有括号的,而数组指针是有括号的,我们可以这样理解
* || p [10]我们把指针数组分开,一个是指针,一个是数组p[10],表示数组p[10]里面存放的是指针
(p)|| [10]我们可以看到p和结合起来了,因此可以理解为将数组p的地址通过指针的形式存放起来
数组指针变量怎么初始化
数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名
int arr[10]={0};
&arr//取的是整个数组的地址
如果要存放个数组的地址,就得存放在数组指针变量中,如下:
int(*p)[10]=&arr;
我们通过调试可以看到,p和&arr的内存地址是一样的
我们对int (*p)[10]=&arr进行解析
int(*p)[10] = &arr
| ___ | ___ | ___ |
| ___ | ___ | ___ |
| ___ | ___ | ___ 整个数组的地址
| ___ | ___ p指向数组的元素个数
| ___ p是数组指针的变量名
p指向的数组的元素类型
二维数组传参的本质
过去我们有一个二维数组的需要传参给一个函数的时候,我们是这样写的
#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);//arr传的是数组首元素地址
return 0;
}
这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?
首先我们再次理解一下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是一个维数组
所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。
根据上面的例子,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。
那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:
#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;
}
可能会有人感到疑惑,为什么不是指针数组呢?我来谈谈我个人的理解:
我们再来回顾一下指针数组的含义:
指针数组是数组里面存放的指针也就是地址,好像和二维数组存放一维数组的地址是一样的.
但是当我们传递参数时我们发现传递的是指针的地址,而不是一维数组的地址,也就是说因为指针是存放的一维数组的地址,而如果我们用了指针数组的话,我们就是取了指针的地址
总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式
函数指针变量
函数指针变量的创建
什么是函数指针变量呢?
根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论:
函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。
那么函数是否有地址呢?
我们做个测试:
#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;//x和y写上或者省略都是可以的,我们只需要函数参数的类型即可
我们对int (*pf3)[int x,int y]进行解析
int(pf3)[int x,int y]
| ___ | ___ ___ |
| ___ | ___ ___ |
| ___ | ___ ___ |
| ___ | ___ ___ pf3指向函数的参数类型和个数的交代
| ___ 函数指针变量名
pf3指向函数的返回类型
int () (int x, int y) //pf3函数指针变量的类型
函数指针变量的使用
通过函数指针调用指针指向的函数
#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;
}
运行结果如下:
代码
代码1:
(*(void (*)())0)();
代码2:
void (*signal(int , void(*)(int)))(int);
代码1:我们分布解析:( * (void ( * )( ) ) 0 )()可以拆成(a)(),a=( void ( * ) ( ) )0
我们先分析a,首先我们看到void()()就应该想到这是一个返回类型为void的函数类型,并且括号将*号括了起来,说明这是函数指针,而那个0可能会把我们头都搞大,但是我们举一个例子
int a=0;
char b='A';
int c=a+(int)b;
这是强制类型转换,也就是说0被强制转换为一个返回值为void,参数为空的函数指针
而(*a)()又是一个函数指针,因此这段代码有两个函数指针
代码2:
void (signal(int , void()(int)))(int)我们依然将代码才分开:
void(a)(int), a=signal(int,b), b=void()(int)
显然这几个都是函数,第一个和第三个都为函数指针,第二个b为void返回类型的函数,然后a的参数包含了int 和void,返回类型为signal
typedef关键字
typedef是用来类型重命名的,可以将复杂的类型,简单化,也可以理解为设置一个代号,方便理解并且减少代码的长度
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:
typedef unsigned int uint;
//这里感觉和define有点类似
define a 8
如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写
typedef int* ptr_t
但是对于数组指针和函数指针稍微有点区别
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
函数指针类型的重命名也是一样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
要简化代码,可以这样写
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
这里面其实有一些规律,我们设置新的类型名时,都会将类型名写在函数的变量中
函数指针数组
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组
比如:
int *arr[10];
//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
parr1 先和 [ ] 结合,说明parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
转移表
函数指针数组的用途:转移表
#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;
}
相关文章:

C语言深入理解指针(非常详细)(四)
目录 字符指针变量数组指针变量数组指针变量是什么数组指针变量怎么初始化 二维数组传参的本质函数指针变量函数指针变量的创建函数指针变量的使用代码typedef关键字 函数指针数组转移表 字符指针变量 字符指针在之前我们有提到过,(字符)&am…...

知识库建设:从0到1搞定知识库建设的方法论分享
如果我们想要搭建一个知识库,前提是我们要明确知道这个知识库是干什么用的,只有了解知识库的应用场景才能知道如何去建设知识库。 知识库建设 以常见的电商客服为例,客户会经常咨询什么时候发货,怎么退货,怎么换货………...

SpringBoot+Vue 的留守儿童系统的研究与实现,2.0 版本,附数据库、教程
博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1.研究背景2. 技术栈3.系统分析4系统设计5系统的详细设计与实现5.1系统功能模块5.2管理员功能模块…...

28.考试
Description 小学期马上就要结束了,为了检验大家的学习成果,老师进行了一次考试。然而小徐前两周半都忙于练习篮球,几乎没有学习,因此考试时很可能做不完所有题目。 但小徐仍然想要拿到尽可能高的分数,因此在做题时需要…...

浏览器窗口间的通信
一、汇总 二、同源策略 三、webSocket (无跨域限制) 优点:无跨域限制 缺点:成本高 四、客户端存储 1、localStorage onStorage 例子: 2、定时器 客户端存储 例子: 缺点: 五、postMessage (无跨域…...
MATLAB 的 plot 绘图
文章目录 SyntaxDescriptionplot(X,Y)plot(X,Y,LineSpec)plot(X1,Y1,…,Xn,Yn)plot(X1,Y1,LineSpec1,...,Xn,Yn,LineSpecn)plot(Y)plot(Y,LineSpec)plot(tbl,xvar,yvar)plot(tbl,yvar)plot(ax,___)plot(___,Name,Value)p plot(___) plot: 2-D line plot Syntax plot(X,Y)plo…...

SpringBoot项目--电脑商城【获取省市区列表】
1.易错点 1.错误做法 新增收货地址页面的三个下拉列表的内容展示没有和数据库进行交互,而是通过前端实现的(将代码逻辑放在了distpicker.data.js文件中),实现方法是在加载新增收货地址页面时加载该js文件,这种做法不可取 2.正确做法 把这些数据保存到数据库中,用户点击下拉…...
使用git把本地项目关联远程代码仓库,并推送到远程仓库
你在本地新建了一个项目,写好了代码,但是没有关联远程仓库,怎么关联并上传呢? 你要先去gitee创建一个代码仓库,然后复制http地址。 首次提交项目代码到一个新建的远程仓库: 1、通过命令 git init 把这个…...

Spring+MyBatis使用collection标签的两种使用方法
目录 项目场景: 实战操作: 1.创建菜单表 2.创建实体 3.创建Mapper 4.创建xml 属性描述: 效率比较: 项目场景: 本文说明了Spring BootMyBatis使用collection标签的两种使用方法 1. 方法一: 关联查询 2. 方法…...

k8s集群中集群方式安装nacos
1、前提条件 一个k8s集群,其中有三个master 节点,这三个节点的标签名称为etcd 三个master节点的ip 分别为:192.165.187.170 、192.165.187.171、192.165.187.172一个mysql 数据库, 数据库的ip 为:192.165.187.180 用户…...
极客时间:数据结构与算法之美【学习笔记+思考实践】
本篇是 《极客时间:数据结构与算法之美》课程的学习笔记和带有自己的一些思考实践。原文学习链接如下:https://time.geekbang.org/column/intro/100017301 开篇词 | 从今天起,跨过“数据结构与算法”这道坎01 | 为什么要学习数据结构和算法&a…...

基于视觉重定位的室内AR导航项目思路(2):改进的建图和定位分离的项目思路
文章目录 一、建图二、定位首先是第一种方法:几何方法其次是第二种方法:图像检索方法最后是第三种方法:深度学习方法 前情提要: 是第一次做项目的小白,文章内的资料介绍如有错误,请多包含! 一、…...

nodejs+vue+elementui精品课程网站设计
前端技术:nodejsvueelementui基于nodejs语言、vue.js框架、B/S架构、Mysql数据库设计并实现了精品课程网站设计。系统主要包括首页、个人中心、用户管理、课程信息管理、课程分类管理、学习论坛、在线试题管理、试题管理、系统管理、考试管理等功能模块。 本文首先介…...
40个Linux常用命令组合
1.删除0字节文件 find -type f -size 0 -exec rm -rf {} \; 2.查看进程 按内存从大到小排列 ps -e -o "%C : %p : %z : %a"|sort -k5 -nr 3.按cpu利用率从大到小排列 ps -e -o "%C : %p : %z : %a"|sort -nr 4.打印说cache里的URL grep -r -a jpg …...

NIFI关于Variables的使用
说明 NIFI中的变量(variables)和参数(parameters)在引用的时候是有区别的。 参数的引用是使用#{参数名}的形式。 变量是使用${变量名}的形式来引用。 nifi版本:1.23.2(docker镜像) Variable…...
2、VRP基础
本节我们介绍华为设备的VRP系统平台的一些基本知识,为以后学习路由交换的配置命令做好基本的准备。在这里,为软考网络工程师做准备,只需要了解其中比较基础的即可,包括VRP是什么、提供了哪些用户界面、VRP的用户级别、基本的配置视…...

docker容器运行成功但无法访问,原因分析及对应解决方案(最新,以Tomcat为例,亲测有效)
原因分析: 是否能访问当运行docker容器虚拟机(主机)地址 虚拟机对应的端口号是否开启或者防墙是否关闭 端口映射是否正确(这个是我遇到的) tomcat下载的是最新版,docker运行后里面是没有东西的&am…...
第15章 秒杀商品隔离解决方案
mini商城第15章 秒杀商品隔离解决方案 一、课题 商品秒杀-热门数据实时收集 二、回顾 1、掌握热门分析收集方案 2、Lua高级语法 3、Kafka使用 4、Lua垂直日志收集 5、Apache Druid大数据实时处理系统 三、目标 1、MyBatis查询Apache Druid 常规查询 复杂查询 2、热门…...
2023-08-31力扣每日一题-姜汁水题
链接: 1761. 一个图中连通三元组的最小度数 题意: 选择两两相连的三个点,要求度最小 解: 什么暴力hard 实际代码: #include<bits/stdc.h> using namespace std; int minTrioDegree(int n, vector<vect…...

五大优化技巧,让你的视频直播app源码更加流畅
优化技巧一:性能调优 视频直播app源码在确保流畅体验方面是至关重要的。为了提升性能,以下是几项关键的优化技巧: 使用轻量级编码器和解码器:选择高效的编码器和解码器,以减少资源占用,并确保视频流畅播放…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...

Spring AOP代理对象生成原理
代理对象生成的关键类是【AnnotationAwareAspectJAutoProxyCreator】,这个类继承了【BeanPostProcessor】是一个后置处理器 在bean对象生命周期中初始化时执行【org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization】方法时…...