深入解剖指针篇(3)

个人主页(找往期文章) :我要学编程(ಥ_ಥ)-CSDN博客

目录

二级指针

指针数组

指针数组模拟二维数组

字符指针变量 

数组指针

数组指针初始化 

二维数组传参的本质 

函数指针

函数指针的使用

typedef关键字

函数指针数组 


二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?答案是存放在二级指针里头。 指针变量的地址存放在哪里?这句话的主语是地址,问的是地址存放在哪里?我们从前面的学习知识可以知道地址是存放在指针里。只不过这里用了指针变量这个定语来修饰罢了。

上面这个图就是二级指针创建的流程图。

1. *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa。

2. **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作, *pa ,那找到的是 a 。

至于这个**ppa的两颗*的理解:

这个是通过分解成一级指针变量来理解的。

至于ppa+-整数,能访问几个字节,是取决于 int* 的 。这个是指针是4/8个字节。因此ppa+-整数,能访问4/8个字节。

指针数组

指针数组是指针还是数组? 我们类比⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?是存放指针的数组。

指针数组的每个元素都是用来存放地址(指针)。如下图:

指针数组的每个元素是地址,又可以指向一块区域。 

指针数组模拟二维数组

 当我们想要打印二维数组的所有元素时,我们是使用下标引用操作符([ ])来实现的。现在学习了指针,那么可以用指针来实现吗?答案是可以的。我们先用一个数组来存放另外几个数组的地址,再通过地址来找到对应的数组,最后再通过打印一维数组的方法来实现。

#include <stdio.h>
void Print(int** p, int sz, int sz1)//arr的类型是int*
{int i = 0;for (i = 0; i < sz; i++){int j = 0;for (j = 0; j < sz1; j++){//printf("%d ", p[i][j]);//下标引用的方法printf("%d ", *(*(p + i) + j));//指针引用的方法}printf("\n");}
}
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int sz1 = sizeof(arr1) / sizeof(arr1[0]);int* arr[] = { arr1,arr2,arr3 };int sz = sizeof(arr) / sizeof(arr[0]);Print(arr, sz, sz1);return 0;
}

上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。

字符指针变量 

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

一般使用方式:

要注意这个ch的内容不可更改,因为这个ch中存放的是一个常量字符串。常量字符串的内容不可更改。但是存放在数组里的字符串可以更改。即数组内容可以更改。

还有一种使用方式如下:

这个 str 中存放的是hello world 的首字符的h,而不是存放hello world 。因为说到底这个str是一个指针变量,存放的是一个地址:这个字符串的地址,然而这个字符串的地址起始就是h的地址,因此就存放的是h的地址。至于在打印这个字符串时,为什么不用解引用?其实是因为str指向的内容就是这个字符串。如果我们还去解引用的话,就会把这个首字符给打印出来。

例如:

《剑指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");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

答案如下:

我们思考一下是为什么? 首先,str1与str2都是一个地址,在内存中,不可能有两个一模一样的地址。就比如:我们在生活中点外卖,如果在地图上有两个一模一样的地址,那么外卖小哥怎么会知道送去哪一个地方呢?这就产生了错误。因此就打印 str1 and str2 are not same 。接下来就看str3与str4,它们都是一个字符指针,指向的也都是同一个字符串。而我们刚刚知道了这个字符指针存放的是字符串首元素的地址。这个字符串是同一个,那么它们的首元素地址也是一样的。即str3与str4都是指向这个地址,所以str3等于str4,打印 str3 and str4 are same 。(C/C++会把常量字符串存储到单独的一个内存区域,因此str3与str4都是指向这个内存区域)。

数组指针

数组指针是指向数组的指针。我们已经知道了整形指针: int * p;,存放的是整形变量的地址,能够指向整形数据的指针。那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

想要知道答案,首先就得明白 * 和 [ ] 这两个操作符的优先级。

C 运算符优先级 - cppreference.com   这个是运算符优先级和结合性的网址。

由此可知:上面一个是数组,存放整形指针变量的,因此称为指针数组。下面一个是指针(()使p与*先结合——>指针),指向的是一个数组,因此称为数组指针。既然知道是指针了,那么怎么解读这个指针呢?如图所示:

数组指针初始化 

数组指针是指向数组的指针,存放的是数组的地址,那怎么获得数组的地址呢?就是我们之前学习的 &数组名 。这个就是得到的整个数组的地址。

这里可能会有小伙伴有疑惑:这个数组指针的元素个数能不能省略? 答案是不能。因为我们在数组里学过可以省略,但是这个不是数组,而是指针。举例:

二维数组传参的本质 

我们还没有学习指针之前,二维数组传参是这样的:

#include <stdio.h>
void Print(int arr[3][5], int row, int col)//用数组传参的方式接收
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf("%d ", arr[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} };//打印数组Print(arr, 3, 5);return 0;
}

我们这个是用形参的方式来接收的。如果用指针呢?我们知道数组名是首元素的地址,在二位数组中,我们学过把二位数组看成几个一维数组组成。那么就可以在一维数组的层面把二维数组中的一维数组看成一个一个的元素。那么就可以推出来,在一维数组的层面,二维数组的数组名是首元素的地址,也就是二维数组中第一个一维数组的地址。

#include <stdio.h>
void Print(int(*p)[5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf("%d ", *(*(p + i) + j));//(p+i)中的p是整个(第一个)一维数组的地址,+1,跳过的是整个一维数组。//因为int(*)[5]是数组指针的类型,这个是+1,就是跳过的。//*p访问的是第一个数组,而*(*p+j)访问的就是第一个数组里的元素。}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;
}

有的小伙伴可能会疑惑为什么 int(*)[5]是数组指针的类型?这个我在数组知识点这篇文章写过,大家可以去看看。意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址

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

函数指针

什么是函数指针呢? 根据前面学习整型指针,数组指针的时候,我们可以类比关系,我们不难得出结论: 函数指针应该是用来存放函数地址的,未来通过地址能够调用函数的。我们可以先看看函数的地址是啥样?是不是和数组是一样的?

void test()
{printf("hehe\n");
}
int main()
{printf("%p\n", &test);return 0;
}

看来&函数名是能够把函数的地址地址取出来,那么就看看这个函数名是否能代表函数的地址? 

如此看来,这个函数名也是代表函数的地址。

总结:函数的地址用两种方法可以取出:1.  &函数名   2.   函数名

既然能把函数的地址取出,就肯定要放到函数指针里头。怎么存放呢?

还可以写成这样:

函数指针的使用

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

#include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int a = 0;int b = 0;scanf("%d%d", &a, &b);int (*p)(int, int) = Add;int ret = (*p)(3,4);//相当于Add(3, 4);printf("%d\n", ret);
}

然而我们会发现这个代码也是可以的:

这也就意味着这个 * 其实有没有都无所谓。这个函数指针的变量名就相当于这个函数名。

typedef关键字

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

比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:

typedef unsigned int uint//将无符号整型重新命名为uint,这个就代表无符号整型

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

typedef int* ptr_t

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

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

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

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

函数指针数组 

函数指针数组是一个数组,用来存放函数指针的。那么函数指针的数组如何定义呢?

p先和 [ ] 结合,说明 p 是数组,数组的内容是什么呢? 是 int (*)(int) 类型的函数指针。 

好啦,这就是C语言深入解剖指针第三篇的全部内容了!下期见!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

缓存击穿,商详页进不去了!!!

故事 对于小猫来讲&#xff0c;最近的一段日子是不好过的&#xff0c;纵使听着再有节拍的音乐&#xff0c;也换不起他对生活的热情。由于上一次“幂等事件”躺枪&#xff0c;他已经有几天没有休息好了。他感觉人生到了低谷。 当接手这个商城项目之后&#xff0c;他感觉他一直没…

视频怎么加水印?分享两个简单的加水印的方法

在数字媒体时代&#xff0c;视频已经成为信息传播的重要方式。许多人在创作视频是会加上自己独特的水印&#xff0c;防止视频被盗用。水印作为数字版权保护技术的一种&#xff0c;可以有效地防止视频被非法复制、传播或篡改&#xff0c;从而保护创作者的权益和利益。下面我分享…

关闭idea之后,项目还在运行,端口被占用

今天在写项目的时候&#xff0c;中途安装了一个插件&#xff0c;而且插件显示需要重启idea&#xff0c;重启的时候项目正在运行&#xff0c;重启之后发现idea没有显示有项目正在运行&#xff0c;当我要开启项目的时候&#xff0c;发现无法开启&#xff0c;显示端口被占用了&…

【leetcode题解C++】654.最大二叉树 and 617.合并二叉树

654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums …

C++ QT入门2——记事本功能实现与优化(事件处理+基本控件)

C QT入门2——记事本功能优化&#xff08;事件处理基本控件&#xff09; 一、记事本功能优化编码乱码问题QComboBox下拉控件QString、string、char * 间的数据转化编码问题解决整合 光标行列值显示记事本打开窗口标题关闭按钮优化—弹窗提示快捷键设计 二、☆ QT事件处理事件处…

flutter GridView控件实践

gridView顶部自带padding问题 如图所示&#xff1a; 顶部有一个比较大的padding。 如何处理&#xff1a;给gridView设置&#xff1a;padding: EdgeInsets.zero,

关于torch_xla.core.xla_model无法导入的问题

直接使用pip install或github中的代码发现仍然无法成功导入torch_xla.core.xla_model&#xff0c; 在conda中conda list发现存在torch_xla为1.0版本&#xff0c;尝试更新发现只有该版本。发现conda_xla库内除了__init__.py以外&#xff0c;没有任何文件。在__init__.py中提示包…

Java 正则匹配sql

文章目录 正则匹配sql表名称insert intoupdate 正则表达式什么时候要加^$ 在线正则校验 正则匹配sql表名称 insert into insert into PING_TABLE (CODE, NAME) VALUES(0, 待提交),(1, 审核中),(2, 审核通过),(3, 已驳回); regex -> insert\sinto\s(\w)\s*\(?update upda…

架构整洁之道-组件构建原则

5 组件构建原则 大型软件系统的架构过程与建筑物修建很类似&#xff0c;都是由一个个小组件组成的。所以&#xff0c;如果说SOLID原则是用于指导我们如何将砖块砌成墙与房间的&#xff0c;那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。 5.1 组件 组件是软件的…

想好新年去哪了吗?合合信息扫描全能王用AI“留住”年味

还有不到十天&#xff0c;除夕就要到了。近几年春节假期中&#xff0c;有人第一次带着孩子直击海面冰风&#xff0c;坐船回老家&#xff1b;也有人选择“漫游”国内外&#xff0c;在旅行中迎接新春的朝气。合合信息旗下扫描全能王APP通过AI扫描技术&#xff0c;提供了一种全新的…

Acwing---798.差分矩阵

差分矩阵 1.题目2.基本思想3.代码实现 1.题目 输入一个 n n n 行 m m m列的整数矩阵&#xff0c;再输入 q q q 个操作&#xff0c;每个操作包含五个整数 x 1 , y 1 , x 2 , y 2 , c x1,y1,x2,y2,c x1,y1,x2,y2,c&#xff0c;其中 ( x 1 , y 1 ) (x1,y1) (x1,y1) 和 ( x …

创新大赛专访丨移步到岗荣膺2023年度人力资源服务质量卓越品牌:“人财税法”综合解决方案专家

日前&#xff0c;2023第三届全国人力资源创新大赛颁奖典礼暨成果展圆满举行。自2023年10月份启动以来&#xff0c;大赛共吸引了457个案例报名参赛&#xff0c;经组委会专家团队评审严格审核&#xff0c;企业赛道共有103个案例获奖、72家企业、13位个人、7个产业园斩获荣誉。 广…

RocketMQ消息队列(一)—— 基本概念和消息类型

RocketMQ是一个来自阿里巴巴的分布式消息中间件&#xff0c;于2012年开源&#xff0c;并在2017年正式成为Apache顶级项目。据了解&#xff0c;包括阿里云上的消息产品以及收购的子公司在内&#xff0c;阿里集团的消息产品全线都运行在RocketMQ上&#xff0c;并且最近几年的双十…

mybatis的一级缓存和二级缓存

一、介绍 1、mybatis缓存&#xff1a; mybatis包含一个非常强大的查询缓存特性&#xff0c;可以非常方便的定制和配置缓存&#xff0c;通过缓存减少Java Application与数据库的交互次数&#xff0c;从而提升程序的运行效率。 2、mybatis一二级缓存 mybatis的缓存分为一级缓存…

Docker中配置MySql环境

目录 一、简单安装 1. 首先从Docker Hub中拉取镜像 2. 启动尝试创建MySQL容器&#xff0c;并设置挂载卷。 3. 查看mysql8这个容器是否启动成功 4. 如果已经成功启动&#xff0c;进入容器中简单测试 4.1 进入容器 4.2 登录mysql中 4.3 进行简单添加查找测试 二、主从复…

MySQL-----初识

一 SQL的基本概述 基本概述 ▶SQL全称: Structured Query Language&#xff0c;是结构化查询语言&#xff0c;用于访问和处理数据库的标准的计算机语言。SQL语言1974年由Boyce和Chamberlin提出&#xff0c;并首先在IBM公司研制的关系数据库系统SystemR上实现。 ▶美国国家标…

MySQL亿级数据的查询优化-历史表该如何建

前端时间在知乎上看到一个问题&#xff0c;今天有空整理并测试了一下&#xff1a; 这个问题很具体&#xff0c;所以还是可以去尝试优化一下&#xff0c;我们基于InnoDB并使用自增主键来讲。 比较简单的做法是将历史数据存放到另一个表中&#xff0c;与最近的数据分开。那是不是…

如何使用Linux Archcraft结合内网穿透实现SSH远程连接

&#x1f4d1;前言 本文主要是使用Linux Archcraft结合内网穿透实现SSH远程连接的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#…

go gin 响应数据

go gin 响应数据 package mainimport ("fmt""github.com/gin-gonic/gin" )type UserInfo struct {UserName string json:"user_name"Age int json:"age"Password string json:"-" }func JsonTest(ctx *gin.Context…

黑马Java——常见API

一、游戏打包exe 游戏打包exe要考虑的因素&#xff1a; 游戏打包exe核心步骤&#xff1a; 详见《打包exe文档》 二、Math &#xff08;一&#xff09; Math类的常用方法 1、代码实现 2、小结