指针(3)

二级指针

 二级指针就是存一级指针的地址的变量,其中类型如int **,char **。

除了二级指针,还有三级指针,四级指针,(不过三级指针,四级指针用的很少,到后面更是);

对于二级指针的计算以及*二级指针的访问权限。经过验证,我们得知指针计算时其整数乘多少倍取决于其指针指向的空间大小。代码如下

 这个道理对于任何指针都适用。其指向的空间为哪个类型:只要指针的类型除去*就是它的类型

其中空间的类型只要带*就直接默认其内存大小为8个字节或4个字节。因为其是存地址的空间。

所以二级指针,三级指针其指针计算时整数是乘4或8(看是x86还是x64环境) 权限同理只能往下访问4或8。(个人推理出来的,如有误,请大佬纠正)

指针数组 

 如  int *arr[5] 中数组为int *[5]类型,其中包含五个int *类型的指针变量。这就是指针数组。

 指针数组模拟二维数组

 该代码是模拟二维数组,并不是真正的二维数组。三个数组内存并没连续存放。它只是通过指针数组实现了二维数组的功能:arr[][]。

根据之前学的当[]为下标访问操作符时其本质,可以知道arr[1][2]本质为  ((*arr+1)+2)

其中arr为指针数组首元素arr1的地址,所以为二级指针(指针变量的地址),可以用两次*,代码没有问题。


字符指针变量 

 对于字符串来说,由于其每个字符内存是连续存放的。跟数组类似,所以举个例子,有字符串“abcdef”,其整体就是首个元素的地址。跟数组一样,其除了用sizeof代表整个数组和&代表整个数组,其他地方都是代表首个元素的地址。(也存在其他个例,等我们慢慢自己去发现)

 但是有些地方又跟数组不一样,这里其字符串是常量,不可以修改里面的值。所以其常量字符串首元素地址类型为const char*。如果要用指针变量接受该地址,该指针变量必须要类型相同,否则会报错。所以在接受常量字符串地址时,指针变量要多个const(必需的)。

对于常量字符串(“adashhd”,如这不可修改里面的值),其里面每个字符依然都是char类型,但是其指针类型都要加个const。

对于变量字符串(可以修改里面的值)  ,其每个字符都是char类型,其指针类型也是不带const。

这是编程规定的说法。

对于char可以隐式转换为const char,但是const char  不能隐式转换为char,其他类型也依然符合该规律,无const可以隐式转换为有const相同类型,反之有了const不能隐式转换为无const

到这时候,我们来看一个题目

 

 首先说一下,常量也是存在地址的,但其地址内容只可读不可写,也就是可以用它(比如打印),但是不能修改它其中内容。 而且如果当一个常量出现较多次,它常量的内存从内存空间节省方面看,只会出现一个其常量内存空间。这是一个结论。

所以根据该结论来看 ,两个数组地址不一样,所以指针变量值不一样。而后面常量字符串相同,所以是同一块内存空间,指针变量值相同,

数组指针变量

 对于其最重要一点就是其指针数组和数组指针变量创建的区别

根据操作符优先级和结合性的关系p1优先和[]结合,所以*和int结合 ,此时就是一个整形数组,每个元素都是int *类型。

所以我们需要一个数组指针时,我们就需要*优先和p1结合,这时就用到括号。所以创建了一个数组指针

记住一点,变量对于*和[],优先和[]结合 。

 二维数组传参本质

 之前我们在指针(2)中学会了一维数组传参的本质,现在我们来学习二维数组传参的本质,其实两者是差不多的。 

二维数组传参本质上是传其首元素地址,二维数组的元素是一维数组,所以传的是其一维数组的地址,从这得知二维数组名可以代表其首个一维数组的地址。

此时我们就用数组指针去接收它。但像一维数组传参一样,为了更加直观的理解,编译器让其可以用二维数组的形式去接收,使其更直观表现。但其本质并不是数组,本质是数组指针,它只是一个固定的格式。跟一维数组传参一样。

而对于传参后的p[i][j]该怎么解释。因为a[b]本质为*(a+b),所以这运用到了两个解引用操作符,而我们为一级指针。所以可能有人觉得这段代码是错误的。但其是正确的。首先p[i][j]本质上是

*(*(p+i)+j) ,*(p+i)本质上是第i-1个一维数组,在对其加j再*时其意义瞬间由一整个一维数组转变为这个一维数组首元素地址,然后得出这个一维数组的第几个数,从而实现效果。

所以之所以能实现是因为对于数组名有两个意义,一个是数组首元素地址,一个是整个数组,且这两个意义可以瞬间变换。当面对不同环境时可以瞬间变意义。(这个结论在后面也很有用,对于这个结论是我自己推出来的,自己这样认为的,我学识浅薄,如有大佬,可以修正下我的结论)。

(2024年3月11日订正,上面说的都是错的,之所以能出现以上代码,是因为二维数组本质上是个特殊的一维数组,其具体内容请看java中的数组的定义与使用,我在那用二维数组的内存分布图解释了为什么能出现如上现象。c语言和java中的二维数组的创建逻辑是极其相像的,只是c语言是内存全分布在栈区,而java内存分布在栈区和堆区中)

 函数指针变量

 函数指针变量的创建

 不是个人理解的地方

对于其函数指针变量创建来说,其跟数组指针和指针数组一样,创建时因一个()而导致不同。如int * pf3(int x,int y) ,pf3相比于*,优先与()结合,所以这是指针函数(返回值为指针)。而当为int  (*pf3)(int x,int y)时就为函数指针变量,指针指向 int  (int ,int)类型的函数,本身类型为int (*)(int,int) 。

对于函数来说其函数名就是地址,如存在一函数为int (*pf3)(int x,int y)则其函数名pf3为地址且类型为int (*)(x,y),对于&pf3其为地址且类型跟pf3类型一样,两者等价相同。所以这是跟我们上面讲的数组名和字符串不一样的地方。

这里额外说一点,sizeof()操作数不能是函数。

个人理解的地方

所以我推测函数名只能代表整个函数以及整个函数的地址。(对于字符串和数组来说同理,如& 字符串时,字符串代表其整体,当只有字符串时,代表其首元素地址。其他两个:函数,数组也同理)下面讲下我的个人理解,作者水平有限,尽可能把我的见解说一下。

 对于函数来说,只有调用的时候才会创造出空间,才会有函数的整个地址。而我们单是拿出一个函数名就能得到地址,其中经过我的调试得知仅仅是调用函数名并不能创造出空间去执行函数里面的内容。那么空间并没创建出来,地址是从哪得来的?

这里我就有个猜想 虽然空间没有创建出来。但函数的地址在我们写完代码后已经确认了,如果我们修改代码,函数的地址也会变化。不管其函数空间创建不创建,函数执不执行,其地址都存在。所以我们不执行函数也能得出函数地址。

假设有个函数为void arr(int x,int y)那执行函数只要 arr(4,5)就能执行。之前我们已经了解了函数名本质就是函数地址,所以也就意味着我们只要知道了,函数的地址加上(参数)就能执行函数了。这也就意味着我们能有很多形式去使用函数。这里留个悬念,下面会讲到。  所以这就是我的个人理解地方,可能会有错误,如果有错误请大佬指点。

函数指针变量的使用 

 我们大概有四种表达方式

以下一一介绍,加上我的个人理解:

pf3为整个函数地址将其解引用为整个函数,其之后瞬间变换意义为整个函数的地址。从而使用。

 pf3值为整个函数地址,直接使用。

Add此时直接表示整个函数地址

&Add时Add表示整个函数,此时得出的也是整个函数地址。

并且需注意add在面对()和&优先与()结合所以得出的是返回值的地址,会报错。

此时我们碰到了三种这种情况    *与()优先与()结合,*与[]优先与[]结合,&与()优先与()结合

 这就是我们的不同表达形式,了解了实质则更好记。

 两段有趣的代码

 

 代码一表示

void(*)()表示函数指针类型 其中将其放到强制类型转换操作符里从而将0转换为这个类型的地址。而后将其解引用得到其函数,类型为void ()。而后瞬间转变意义得到函数的地址,再加上后面的()就执行函数,创造出空间去执行函数内部。最后该表达式语句结果为空语句。

执行图如上。

代码二表示 

 

代码二表示函数声明。函数声明参数名可以不要。

所以此时表示声明了 返回值类型为void (*)(int) 函数名为signal ,参数有两个,类型分别为int ,void(*)int  这样的函数

其中我们如果要创建一个这样的函数的话,表达形式就是这样(把参数名加上),没有其他的表达形式。不能 void (*)(int)   (signal (int ,void(*)(int))),这样的话依据优先级和结合律,格式都表示错了 根本表示不出来为什么类型。

所以上方表达式才是正确的表达式,且只能为这种。

typedef关键字

面对特别复杂的类型创建,像上方两段有趣的代码。虽然我们了解其本质,但是为了写的方便点,可以重命名类型。

如上是各种类型的命名。此时加上我的个人理解来说一下

对于重命名时,我们可以把重命名的名字看作是创建的变量。其位置就为创建的变量所在位置(这样子想更好理解)。所以就创建成功。

而后真正用重命名创建变量时只需要将变量放在重命名的数据类型之后就行。其本质上变量依旧是在真正的类型中原来所在的位置上。只是看起来位置为这样而已,本质并不是。

重命名例子如下:对于重命名的数组类型at也是在中间,并不是指针类型特有的。所以我上面说的结论没问题

函数指针数组

 

函数指针数组的格式是  int  (*parr1[3])(),其中parr1与[3]先结合,说明数组有三个,而后再用函数指针类型对其parr1[3]整体进行修饰,所以将其与*放在一起,说明是其存放的都是指针,且都指向 int  ()类型的函数。 

数组都是以这种格式去创建。其中我们需要用到类比的思想。

转移表

转移表其实就是一个函数指针数组,使用它能代替一些东西从而提高效率。 

 我们只需要知道这个定义就行,用的并不多。

下面有篇文章专门讲述转移表,讲的很仔细

https://blog.csdn.net/2303_79064370/article/details/132512336

总结 

到这里,我们的指针(3)就讲完了。其中这里面的内容是指针的重点核心内容。

学完了指针(1)(2)(3)后,指针的知识讲完了大部分就只差一点小内容了。 

一起加油吧 !

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值