| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 还搞不懂指针?点进来!手把手带你进阶指针(数组指针、指针数组、指针传参、指向函数指针数组的指针等等一切关于复杂指针的问题,统统搞定!) -> 正文阅读 |
|
[C++知识库]还搞不懂指针?点进来!手把手带你进阶指针(数组指针、指针数组、指针传参、指向函数指针数组的指针等等一切关于复杂指针的问题,统统搞定!) |
【手把手带你进阶】指针(数组指针、指针数组、指针传参、指向函数指针数组的指针等等一切关于复杂指针的问题,统统搞定!)前面我们已经对指针进行了一个初步的讲解,相信大家对于指针都已经有了一个基本的认识,那么今天,我们就把指针拿出来,更加深入全面地扒开指针的皮,看看指针的内涵。 首先我们先回顾一下初阶指针中的内容。(具体可以看这篇看完这篇文章,别再说指针难了!手把手带你入门指针的基本使用
正文开始之前,别忘了先给文章点个赞噢! 字符指针在指针的类型中我们知道有一种指针类型为字符指针char* 。 我们一般这么使用它: 但是实际上,它还有以下这种使用方法:
在这里,我们可以对p解引用,看看p的地址到底指向谁。 那么如果我们想打印整个字符串,我们只需要给出第一个字符的地址(即p)就可以了。(不需要解引用)
虽然字符指针可以指向常量字符串,但是有时候在一些编译器中也可能会报错或警告。
明白了以上,我们来看下面这段代码(出自《剑指offer》),它的输出结果是什么呢?
答案如下:
那么为什么str3和str4一样,str1和str2却不一样呢? 因为str1和str2是我们创建的两个字符数组,它们是两块独立的内存空间,互相之间是不影响的。而数组名是首元素地址,str1和str2分别指向的是各自数组首元素的地址,所以它们不一样。 而str3和str4指向的是一个常量字符串,常量字符串是开辟在内存的静态区的,它是不能修改的,所以我们在内存中只需要存一份,没必要再开辟一块空间存放它,所以str3和str4指向的是同样内容、同一块空间的常量字符串,so,str3 and str4 are same。 指针数组我们知道数组有整型数组、字符数组等等,数组中存放的是什么类型的元素,这个数组就叫什么数组。 对应每种指针的类型,则又分为相应的指针类型的数组。 指针数组的意义那么指针数组有什么意义呢? 当我们有多个相同类型变量时,如果我们要分别取出它们的地址存起来,就需要每个地址都创建一个指针变量存起来,比较麻烦。 并且可以直接通过数组下标访问每一块对应的空间。 *p和p[ ]我们再看下面的代码:代码中有三个整型数组,把它们的数组名都存放在一个指针数组parr。 而parr中的元素是一个地址,这个地址就是对应的数组的首元素的地址,如果我们拿到这个地址,并对它进行解引用,就可以拿到整个数组。 打印结果如下:
所以上面代码中在对parr中的元素解引用时,我们直接写成了p[i][j],这看起来貌似和二维数组有点像,但是大家要注意,这可不是二维数组哦,它们之间还是有区别的! 但是我们可以把通过对[ ]对数组访问和通过*对指针进行解引用进行一个联系,因为数组名在一般的情况下表示的就是首元素的地址。 字符串在指针数组中我们已经知道,字符串放到数组中时,数组名的首元素就是字符串中第一个字符的地址。 那么,我们来看看下面这段代码。 其实和之前用字符串初始化数组同理,我们看似把整个字符串放到数组中去了,但是实际上存的是字符串第一个字符的地址,所以上面的arr中存的分别是‘a’,‘b’,‘h’的地址。 可以看到,数组arr的类型是char*,说明这是一个存放字符指针的数组。 我们可以通过对数组进行解引用,访问每一个字符串。 数组指针定义前面我们了解了什么是指针数组,那么下面我们再来看看数组指针。 首先我们要明确,数组指针指的到底是数组还是指针? 上面已经说过了,指针数组本质是一个数组,数组中的每个元素的类型为指针。那么轮到数组指针了,这一次它的本质应该是一个指针了。 我们知道,指针分为整型指针、字符指针等类型:
那么同理,如果一个指针能够指向数组,那么我们就称它为数组指针。 那么指针数组应该长什么样呢?
去掉指针的名字,剩下的就是指针的类型,即int(*)[10],所以这是一个指向数组的指针,数组包含10各元素,每个元素的类型是int。
所以这里我们也可以获得一个判断指针和数组类型的方法:
数组名和&数组名我们之前说过,数组名在通常情况下是数组首元素地址,但是&数组名时,取出的是整个数组的地址。
通过对指针的学习,我们现在可以从本质上来理解为什么它们不一样了。 因为arr是数组首元素的地址,这个数组的整型数组,所以arr是一个指向整型的指针,所以当我们给它+1时,跳过的是一个整型的大小(4)。 而&arr是整个数组的地址,所以指针的类型应该是数组指针,指向的是一个包含10个元素的整型数组,所以如果我们给它+1,跳过的就是整个数组的大小(40)。 那么如果让p=&arr,我们试一下写出p的类型。 数组指针的使用那么数组指针到底应该怎么使用呢? 之前我们都用数组名和下标引用操作符对数组进行访问,如下: 但是当我们知道arr的本质之后,我们就可以这样写:
我们知道,&arr取出的是整个数组的元素,所以如果我直接对它+1再解引用,就直接跳过了整个数组,因此是这样做的不能访问到数组中的元素的。 但是,这并不代表用&arr我们就无法访问数组的元素了。 这里博主讲一个比较别扭的方法,虽然它可行,但是重在理解这种方法的思考方式,在平常我们使用访问数组元素时,最常用的还是第一种,因为比较好理解,且不容易出错。 这里我们把数组arr看成一个只有一行的二维数组,则我们把这里的&arr就看成是第一行的地址。 根据二维数组的使用,我们先访问第一行,再访问第一行的每一个元素。 如果p=&arr。则*(p+0)相当于拿到了这个二维数组的第一行。 然后我们再访问第一行中的元素,即*(p+0)[i]。 也可以写成parr[0][i]。
讲完了一维数组的访问,接下来我们再看看二维数组的访问。 一般我们用以下这种方式访问二维数组的元素: 由上我们其实可以直接知道,在一般情况下,数组名表示数组首元素的地址,所以二维数组的数组名是二维数组第一行的地址,而第一行相当于一个一维数组,所以此时二维数组的数组名本质上就是一个数组指针。 数组传参和指针传参我们在使用函数进行传参时,有时候也需要把数组或者指针传给函数,那么这时函数的参数应该怎么设计呢? 接下来我们就来看看。 一维数组传参如果我们把一个数组传过去,我们可以用一个数组来接收,因为数组名就是数组首元素的地址,所以我们也可以用一个地址来接收。 二维数组传参同理,二维数组传参时,我们既可以选择以数组的形式传参,也可以选择以指针的形式传参。 但是我们要注意两点:
一级指针传参如果我们传给函数的是一个一级指针,那么我们就用一个一级指针来接收。 那么反过来思考一下,一个一级指针的参数可以接收什么类型的值呢?
二级指针传参二级指针的传参其实和一级指针一样,用二级指针传参,就对应地用二级指针接收即可。 同样地,我们也可以反过来思考,一个类型为二级指针的参数可以接收什么样的值呢? 函数指针定义我们前面已经认识了字符指针、整型指针、数组指针等等,那么函数有地址吗?存放函数的地址是不是就是函数指针呢? 答案是肯定的。用来存放函数地址的变量就是函数指针变量。 而函数名就是函数的地址。 我们可以看到函数名和&函数名打印出来的地址都是一样的。 这里要声明一点,与数组不同,数组名是数组首元素的地址,而函数的数组名就是函数的地址,函数名和&函数名在本质上是一样的。 那么函数的地址应该如何保存呢? 我们再用下面这段代码来进一步分析: 我们要注意区别下面两个变量:
意义那么函数指针有什么意义呢? 要知道,我们可能并不总是能直接拿到一个函数的,有时候可能我们需要通过函数的地址来对函数进行调用,这个在后面会讲到回调函数,这种函数就是利用函数指针在进行调用的。
更深的理解下面我们看《C陷阱和缺陷》中的两段代码:
我们看代码1:
所以代码1实际上是一次函数调用。 代码2:
所以代码2实际上是一个函数声明,声明有一个名为signal的函数,函数有两参数,参数类型分别为int和void()(int),返回类型是void ()(int)。 上面的代码那么复杂,我们可不可以按照我们平常的思路这样写呢?
答案是不行的,这样的写法是错误的!! 包括前面我们写定义一个函数指针时,不能把变量名和指针类型完全独立开来写。
虽然这样看起来更容易理解,但是它是错误的噢! 那么上面你的代码2可不可以有简单一点的写法呢? 我们可以利用typdef对类型进行重定义(即对它重新起个名字)。
函数指针数组我们刚刚学了指针数组,有学了函数指针,那么我们能不能把函数指针都放到一个数组中去呢? 答案当然是可以的。 函数指针数组,顾名思义,这是一个存放函数指针的数组。
那么像上面这样一段代码,我们可以用一个它来实现一个简单能实现加减乘除功能的计算器。
我们可以发现,这样写我们就能很容易地实现一个计算器啦! 在《C和指针》中,把这样的函数指针数组称为转移表,我们可以利用数组的下标找到相应的函数,然后转移到该函数中去。 除此之外,我们还可以使用下面这种方法: 回调函数这里我们再介绍一个概念:回调函数。 回调函数就是一个通过函数指针调用的函数。
注意!回调函数不是通过函数指针调用其他函数的函数,而是指被函数指针调用的那个函数噢~大家可千万不要搞错了! 在上面计算器的实现中,我们就利用了函数指针调用了Add、Sub、Mul、Div这几个函数,所以Add、Sub、Mul、Div就是回调函数。 指向函数指针数组的指针说完了函数指针之后,我们再来看看指向函数指针数组的指针。 是不是看起来听起来读起来都特别拗口呢?虽然它看着很绕,但是只要我们把它“肢解”了,就能很好地弄懂它咯。 首先看中间的部分“函数指针数组”,说明的一个数组,数组的元素是指针,每个指针指向的是一个函数。 然后再看外面部分“指向XXX的指针”,说明这本质上是一个指针,指向的是我们刚才分析的中间部分。 所以总的来说,这是一个指针,指向的是一个数组,数组中的每个元素都是函数指针。 那么我们应该如何定义一个指向函数指针数组的指针呢? 在前面,我们已经讲了函数指针数组,那么函数指针数组的类型是怎么写的呢? 还是以上面的例子来说~
pfArr是变量名,与[4]先结合,所以这是一个数组。 说明这是一个存放函数指针的数组。 那么如果我们要写出指向这个数组的指针(即指向函数指针数组的指针),应该如何做呢? 首先我们写出变量名:ppfArr。 那么最后剩下的就是数组元素的类型,前面已经知道,数组元素的类型为int(*)(int, int)。 所以,我们只要把(*ppfArr)[4]和数组元素类型放在一起就好啦!
当然我们还可以无限往下套:函数指针数组指针的数组、指向函数指针数组指针的数组的指针等等等等…… 但是这就太复杂啦!!!我们一般探讨到指向函数指针数组的指针就不再套了,不过如果你实在有兴趣,可以自己继续深入“研究”一下噢~ 关于指针的进阶,就讲到这里啦! 如果你觉得文章对你有帮助,记得点赞收藏评论关注一波噢! 关注我,一起精进C语言! |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 23:05:24- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |