指针详解——高级指针的解析及应用

news/2024/11/27 23:41:38/

目录

    🐑指针的初步了解

    🐂指针的深入认识

    🦛1.指针数组

    🐀指针数组的介绍

    🐀指针数组的用法介绍

    🐫2.数组指针

    🦌数组指针的介绍以及使用

    🦮3.函数指针

    🐈函数指针的介绍

    🐈函数指针的使用介绍

    🦘能力提升复杂指针的解析:

    🐫1.函数指针数组

    🦕2.数组指针数组

    🐧3.函数指针数组的指针

    🦏相信大家对于指针都是既熟悉又陌生,想深入了解有对指针感到畏惧。指针其实并不像大家想象的那样难于理解。在本次博客当中我们就来看一看指针的大家族都有哪些成员吧!

    🐑指针的初步了解

    🐘在了解高阶指针之前我们先来简单的介绍一下最简单的指针——一级指针。一级指针是我们最常见的指针,当我们想通过指针找到一个数据并将其改变的时候就可以通过指针进行改写。我们只需要创建一个相同类型的指针变量并把数据的地址交给指针变量保管即可。

#include<stdio.h>
int main()
{int num = 10;int* pa = &num;//通过指针变量的解引用找到数据的值printf("num=%d\n", *pa);//通过指针变量的解引用修改数据的值*pa = 20;printf("num=%d\n", num);return 0;
}

     🐘就像我们上面的示例,我们想要修改一个值,就可以利用指针很方便的进行操作,但是指针的使用并不是简简单单的修改一个变量的值而已,毕竟直接对变量进行修改也可以下到我们想要的效果。指针的价值在较长程序的书写当中往往会起到意想不到的作用。

    🐘接下来我们在来认识一个新的比较简单的指针概念——二级指针。对于二级指针相信有的小伙伴可能不像是一级指针那么熟悉,但是不要担心道理都是一样的。我们先来认识一下二级指针。

    🐘我们在使用指针的时候都是将数据的地址赋给指针变量,让指针变量进行代替保管。那么我们就很容易的可以想到:一个变量拥有地址,指针变量也是一个变量他是不是也有地址呢?指针变量的地址应该用什么来保存呢?那么这就涉及到我们的二级指针了。二级指针的作用就是保存我们一级指针变量的地址,并且进一步进行操作。使用的方法和上面的一级指针的使用方法是相同的。

#include<stdio.h>
int main()
{int num = 10;int* pa = &num;int** ppa = &pa;//通过二级指针变量的解引用找到数据的值printf("num=%d\n", **ppa);//通过二级指针变量的解引用修改数据的值**ppa = 20;printf("num=%d\n", num);return 0;
}

     🐘那么二级指针变量又该如何理解呢?我们在上图中样例中可以发现二级指针由两个 * 之后是一个变量,表示我们这个变量是一个二级指针变量。我们可以将我们的书写形式更改一下,更改为:int* (*ppa) =&pa; 这里我们可以将这样书写的代码理解成:因为括号具有最高的优先级所以我们要先解读括号里的内容(* ppa)代表主题是一个指针,那么是谁的指针呢?就需要向前找,发现类型为int * 那么我们就可以知道这是一个指针的指针,也就是二级指针。同样的道理我们还可以导出三级指针,四级指针,但是这些指针很难见到,所以我们先不做过多的介绍。

    🐘看到这里的时候相信你已经了解了一级指针和二级指针,但是这有什么用呢?别着急,我们接下来在高阶指针就带你体会到指针的妙用。

    🐂指针的深入认识

    🦛1.指针数组

    🐀指针数组的介绍

    🦔指针数组,是不是听着有点迷茫?这到底是一个什么东西呢?我们可以通过主次关系的思路进行理解什么是指针数组。相信大家在小学的时候都学过动宾关系,蓝蓝的天空,好看的花,漂亮的小姐姐......这些词语都是有主次关系的,我们的高级指针也是一样的。就像是修饰词修饰的主语,指针数组——主体是数组,那么数组内的元素是什么呢?——指针。这时候我们指针数组就分析完毕了。没错,就是这么简单。指针数组就是一个元素为指针的数组。

    🦔但是我们需要特殊记忆的就是指针数组到底应该怎么书写。我们先通过一个例子来预先感受一下指针数组的书写形式:

#include<stdio.h>
int main()
{int num1 = 10;int num2 = 20;int num3 = 30;int* pa1 = &num1;int* pa2 = &num2;int* pa3 = &num3;//将多个指针变量作为元素构建一个数组,如下是一个指针数组的书写形式int* arr[3] = { pa1,pa2,pa3 };int i = 0;for (i = 0; i < 3; i++){//arr[i]通过下标找到相应的指针变量进行解引用得到不同的值printf("%d ", *arr[i]);}return 0;
}

    🦔我们肯定注意到了指针数组的书写形式似乎有些奇奇怪怪,没关系我们先来一点一点进行拆解。(更奇怪的指针还在后面呢!)还记得我们在介绍二级指针的时候所用的方法吗?(有括号就从括号里面的内容进行解读,没有括号的话就从数组名开始按照优先级进行解读)int* arr[3] 我们可以看到的是在指针数组的书写当中没有括号,所以我们需要做的就是从变量名开始解读:由于中括号 [ ] 的优先级高于 * 所以我们就需要先将变量名和 [ ] 结合,所以我们的变量表示的就是一个数组,数组当中有三个元素,那么数组中的元素类型是什么样的呢?这就需要我们向前进行寻找:找到的就是 int * 所以我们的数组中每个元素的类型就是 int * 的指针。

    🦔在解析完指针数组之后相信大家对于指针数组的用法会很好奇,那我也就直接直白的通过几个例子向大家介绍一下指针数组的使用方法好了。

    🐀指针数组的用法介绍

    🦔指针数组单独使用的意义其实并不大,但是要是结合其他指针进行使用的话就会出现很神奇的效果,在这里我们将指针数组和函数指针相结合,书写一个计算器程序。让我们想象一下,假如要是书写一个多功能可选择的计算器应该怎么办?利用 case 语句?然后输入一个值就进行相应的函数调用。这样书写也不是不行,只不过是会出现大量的代码冗余。那么有没有一种效果可以代替switch 语句直接进行函数的选择调用呢?这就利用到了我们的指针数组了。程序效果如下:

#include<stdio.h>
#include<assert.h>int add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x-y;
}
int mul(int x, int y)
{return x*y;
}
int div(int x, int y)
{assert(y != 0);return x/y;
}
void meun()
{printf("*************************************\n");printf("***  1.add          2,sub         ***\n");printf("***  3.mul          4div          ***\n");printf("***  0.退出                       ***\n");printf("*************************************\n");
}
int main()
{int (*pa[4])(int, int) = { add,sub,mul,div };int input = 0;do{meun();scanf("%d", &input);printf("%d\n", pa[input - 1](3, 5));} while (input);return 0;
}

     🦔就像是我们上面的程序那样——我们将一个函数指针放在一个数组中(函数指针的书写以及函数指针数组的书写参考下面的博客内容)之后我们就可以通过定义一个变量,向变量中传值的方式进行函数的选择。是不是感觉一下子就变得很高级了呢?别着急更高级的还在后面呢!

    🐫2.数组指针

    🦌数组指针的介绍以及使用

    🐆相信看到这里不少小伙伴们都会头疼不已,为什么刚送走一个指针数组又出来了一个数组指针,像绕口令,脑筋急转弯似的。我们按照上面的办法再来试着将这个难题解决。显而易见的是数组指针,主体是指针,那么我们指针指向的类型是什么呢?没错就是一个数组。通常我们在使用数组指针的时候都会配合二级指针进行使用。我们接下来通过一个例子进行i进一步的了解:

#include<stdio.h>//利用指针数组传参进行一个二位数组的打印
void print(int(*pa)[5],int row,int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf("%d ",*(*(pa+i)+j));}printf("\n");}
}
int main()
{//先创建一个二维数组int arr[2][5] = { 1,2,3,4,5,6,7,8,9,10 };print(arr,2,5);return 0;
}

     🐆由于我们在数组传参传输的是数组中第一个元素的地址,我们可以将我们的二维数组看成是一个有两个元素的数组,数组中的元素类型又是一个数组,也就是将数组的形式写成:                int arr[ 2 ] [ 5 ] = { {1,2,3,4,5} , {6,7,8,9,10} } ; 那么我们第一个元素的内容就可以看成是第一个数组:即 {1,2,3,4,5} 。那么我们要想用一个指针进行接收这个实参,那么我们形参的类型就需要定义为一个数组指针类型。我们可以将数组指针中的内容第一次进行解引用得到 {1,2,3,4,5},之后再进行进一步调整之后再次进行解引用得到具体的元素内容进行打印。

    🐆数组指针的书写形式就像是上图一样,为了便于理解我们来一步步分析一下数组指针的书写形式:首先数组指针主体是一个指针,那么我们的变量名就需要先跟 * 结合,即(*pa)表示变量的主体内容是一个指针,接着往外查找指针所指向的元素的类型——根据优先级先和 [ ] 结合,表示这个指针指向的变量是一个数组,数组的每个元素的类型再往前查找就只剩下剩下的 int 了。那么我们的分析也就结束了。int(数组的元素类型)(*pa)(表示主体是指针)[ 4 ] (表示指针指向的数据类型是一个数组,数组有四个元素)。

    🐆那么我们的数组指针的介绍和使用方法就一并介绍完毕了。那么单独的指针类型就还只剩最后一个——函数指针了。那么让我们赶紧来认识一下什么是函数指针以及怎样使用函数指针吧!

    🦮3.函数指针

    🐈函数指针的介绍

    🐩函数指针顾名思义就是一个函数的指针,我们同样可以利用上面的主次关系分析方法进行分析:首先主体部分就是指针,指针所指向的类型就是一个函数。我们可以利用函数指针进行调用特定的函数,(回调函数)或者在函数中调用其他函数。废话少说,再说就变成绕口令了,那么接下来我们就来通过示例进行认识函数指针吧!

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

     🐩一般指针相关的分析方式都是相通的。所以我们可以按照我们之前的步骤进行再次分析:函数指针,主体是指针,所以需要先用括号将 * 和变量名结合。表示主体是一个指针。之后继续分析,向外查找我们会发现:又出现了一个括号,(表示函数)括号里面的内容是两个 int ,int 那么就代表我们这个指针指向的内容是一个函数,函数的形参为两个 int 类型的数据。那么函数的返回类型是什么呢?我们继续查找,发现只剩下最前面的一个 int ,那么我们的函数的返回值类型就为int 。在想要通过函数指针调用函数的时候就可以通过直接在指针后面的括号里传参的方式进行函数调用就可以了。那么到此我们的函数指针也就分析完毕了,接下来我们来体会一下函数指针正式的用法。

    🐈函数指针的使用介绍

    🐩就像是上面我们在指针数组的程序写到的那样,我们既可以将我们的函数指针放到数组中进行函数的间接调用也可以通过向函数中传参的形式将函数作为参数传递给另一个函数达到我们预期中的效果。那么我们就通过程序来了解函数指针的使用吧。

#include<stdio.h>
#include<assert.h>int add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x-y;
}
int mul(int x, int y)
{return x*y;
}
int div(int x, int y)
{assert(y != 0);return x/y;
}
void meun()
{printf("*************************************\n");printf("***  1.add          2,sub         ***\n");printf("***  3.mul          4div          ***\n");printf("***  0.退出                       ***\n");printf("*************************************\n");
}
int calc(int(*pa)(int, int),int x,int y)
{return pa(x, y);
}
int main()
{int input = 0;do{meun();printf("请输入你想选择的计算方式:");scanf("%d", &input);printf("请输入你要进行计算的数据:");int x = 0, y = 0;scanf("%d %d", &x, &y);switch (input){case 1:printf("%d\n", calc(add, x, y));break;case 2:printf("%d\n", calc(sub, x, y));break;case 3:printf("%d\n", calc(mul, x, y));break;case 4:printf("%d\n", calc(div, x, y));break;default:printf("输入错误请重新输入。");break;}} while (input);return 0;
}

     🐩通过上面的程序我们可以发现想要向函数中将函数作为参数传递给另一个函数,只要学好指针,指针可以做你想做的任何事情。接下来我们来认识一些比较复杂的指针搭配吧!

    🦘能力提升复杂指针的解析:

    🐫1.函数指针数组

    🦖就像是我们上面所写到的那样:函数指针数组就是将函数指针放到数组中进行使用,以达到快速调用函数并且缩短代码长度的作用。那么如何写出一个函数指针数组呢?我们就来一步一步进行深入的分析。还记得我们上面说到的方法吗?就是从变量名开始由主体依次深入的模式。那个方法在长指针分析也同样适用,就比如:int (*pa[4])(int, int)  我们这段代码的分析:先从我们的变量名开始:由于 pa 没有括号结合,所以根据优先级规律,pa 会先和我们的 [ ] 结合。表示我们的 pa 变量表示的是一个数组。之后数组中的元素类型就是剩下的部分:(将我们已经分析过的部分拿走)int (*)(int, int) 这样的格式很容易就发现这是一个函数指针类型。所以我们就只找到了数组中元素的类型是一个函数指针。那么这样我们的函数指针数组就分析完毕了。

    🦕2.数组指针数组

    🦆和上面的函数指针同样的道理,我们的数组指针数组顾名思义就是将我们的数组指针放到一个新的数组当中存储形成的新的指针。我们根据上面的思路进行重新分析:int (*pa[3])[5];我们的变量名同样的没有被括号所约束,所以我们的变量会先和 [ ] 结合表示一个数组,之后拿走我们已经分析过的部分就只剩下了 int(*)[5]表示我们数组中元素的类型是一个数组指针,数组指针指向的数组有五个元素。我们数组指针数组中装的有三个数组指针。

    🐧3.函数指针数组的指针

    🦥在第一个函数指针数组的基础上我们可以深入一下——了解分析一下函数指针数组的指针。是不是已经开始套娃了?哈哈哈,别着急这是我们可能用到的最后一层。同样的道理我们先来看一下函数指针数组的指针的书写形式:int(*(*pa)[3])(int,int); 我们根据我们的逻辑依次进行分析:第一步我们的变量由于括号和我们的*结合表示我们的变量是一个指针然后拿走分析过的部分剩下的部分就是指针所指向的变量的类型为 int(*   [3])(int,int); 这就是我们上面所书写到的函数指针数组。(空白处先和 [ ] 结合表示一个数组,之后数组的元素类型表示一个函数指针)所以将我们分析的结果结合到一起就得到了我们最后的答案——函数指针数组的指针。

    🦡那么上面我们指针的分析也就结束了。希望可以帮到大家。那么祝大家天天开心,学业有成。

    

    


http://www.ppmy.cn/news/10953.html

相关文章

【数据结构与算法】——第六章:图

文章目录1、图的定义1.1 图的其他定义1.2 图的顶点与边之间的关系1.3 连通图相关术语2、图的存储结构2.1 邻接矩阵2.2 邻接表3、图的遍历3.1 深度优先遍历3.2 广度优先遍历4、最小生成树4.1 普利姆算法(Prim)4.2 克鲁斯卡尔(kruskal)5、最短路径5.1 迪杰斯特拉(Dijkstra)算法5.…

【图的存储】

更好的阅读体验\color{red}{更好的阅读体验}更好的阅读体验 文章目录1. 邻接矩阵2. 边集数组3. 邻接表4. 链式邻接表5. 链式前向星总结1. 邻接矩阵 思想&#xff1a; 利用二维数组 g[N][N] 存储所有的点到点的权值。其中 N 为点的数量&#xff0c;g[i][j] 表示点 i 到点 j 的权…

外业调查工具助手,照片采集、精准定位、导航、地图查看

你是不是在外业调查时要背着一堆图纸 是不是一不小心图纸污损或丢失&#xff0c;工作又得重做 是不是经常会出现图纸标注的空间不足 是不是外业采集中要携带一大堆繁琐的仪器 是不是每次收集的数据、照片等在整理的过程中发现工作量巨大 是不是经常会出现采集回来的内容跟…

Spring Boot 程序优化的 14 个小妙招!

1.定义配置文件信息2.用RequiredArgsConstructor代替Autowired3.代码模块化4.抛异常而不是返回5.减少不必要的db6.不要返回null7.if else8.减少controller业务代码9.利用好Idea10.阅读源码11.设计模式12.拥抱新知识13.基础问题14.判断元素是否存在1.定义配置文件信息有时候我们…

QT Echarts 联动共享数据表图 使用详解

Echarts是百度的一款可视化界面开发的平台&#xff0c;里面的地图以及数据可视化内容十分丰富&#xff0c;适合用于一些大屏数据显示项目或者一些ui界面开发。每一个ECharts图表使用一个无边框的QWebView来展示&#xff0c;这样多个不同类型的ECharts图表就是多个封装不同类型E…

ubuntu docker elasticsearch kibana安装部署

ubuntu docker elasticsearch 安装部署 所有操作尽量在root下操作. 安装docker 1. 由于是基于宝塔面板安装的所以简答的点击操作即可完成安装. 我这里已经是正常的安装好了. 2.dcoker 镜像加速 https://cr.console.aliyun.com/cn-hangzhou/instances访问这个网址进去进行了…

[数据库迁移]-LVM逻辑卷管理

[数据库迁移]-LVM逻辑卷管理 森格 | 2023年1月 1、本文旨在记录数据库迁移过程&#xff08;下云至机房&#xff09;中&#xff0c;对新磁盘做逻辑卷管理的过程&#xff0c;并对Linux的文件系统和分区做了相关介绍&#xff0c;如有不对之处&#xff0c;敬请指正。 2、对Linux文…

SpringCloud(12)— 分布式事务(Seata)

SpringCloud&#xff08;12&#xff09;— 分布式事务&#xff08;Seata&#xff09; 一 事务基础 1.事务的ACID原则 2.分布式事务问题 在分布式系统下&#xff0c;一个业务跨越多个服务或数据源&#xff0c;每一个服务都是一个事务。 要保证所有分支事务的最终状态一致&am…