指针破解(上)指针和数组

关卡预览

  • C语言中的声明
  • 数组指针
  • 指针数组
  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的数组指针
  • 破解经典复杂缠绕,平步青云

初期接触指针时,我们大都是对指针进行简单的解引用赋值等操作,指针的操作对象大都是int或者char等基本类型,所以简单理解后便可以轻松应对。
但是,就如同刚学会骑自行车后就要飞向赛博坦星球(汽车人老家),基本的int *,char *还没有掌握完全,取而代之的是各种形态各种变化的“机械”怪物
(指针等复杂的声明),尚未反应过来就已经被绕的头晕目眩。
对于复杂的声明,万变不离其宗。就如同交通工具一般,需要引擎,轮子,方向盘。无论二手奥拓还是布加迪威龙,都离不开这核心的部分。所以我们从C语言中的声明入手,破解指针的复杂缠绕

C语言中的声明

  • int * p
    从该声明中我们获取到的信息是
    • 该表达式声明了一个变量p
    • p的类型是int * ,从而得出p 的类型是指针变量
    • p 指向的类型是整型
  • int * arr[10]
    • 首先看到的是 arr 这个变量名,紧接着他的后缀操作符是[],优先级最高,表示arr是个数组,它有十个元素
    • 现在观察前缀操作符*,那么表示该数组的元素每个元素类型是指针,指向的类型是int
    • 那么arr 便是一个指针数组
  • void * test(char *dest,int sz)
    • 首先看到的是test变量名,紧接着是()函数调用操作符,优先级最高,表示test是一个函数,它的参数类型分别是char *int
    • 观察前缀操作符*,表示这个函数的返回的是指针,类型是void

以上分别是对指针、指针数组、函数的声明,通过简单分析可得,一个声明中一般都需要 变量名、后缀操作符(来判断是函数还是数组)、前缀操作符(判断元素或者函数的返回值类型),将这三个要素拿捏在手,那么其他问题都会迎刃而解


数组指针

指向数组的指针,称之为数组指针

C语言中每种类型都有对应的操作方法,比如将一个整型赋值给一个指向整型的指针就需要强制类型转换,即在右值前键入(int *)来进行转换。如果没有进行强制类型转换,将不同的类型强制赋值,编译器就会发出警告,并且后续的操作有可能发生截断,导致运算出现匪夷所思的差错。
例如将char *类型的指针p赋值int类型操作数的地址,那么p+1将仅仅跳过一个字节的内存来进行读取,然而int类型的内存字节数为4,那么解引用就会发生截断,导致结果出错。

  • 指向一维数组的指针该如何声明?

一般当我将数组名当作参数传递给函数时,此时传递的仅仅时数组首元素的首地址,对于整型数组,那么它传递的就是第一个元素,也就是第一个整型的地址来进行操作,它指向的是单个整型值。
对于二维数组而言,int arr[2][3]它的数组名是也是第首元素的首地址,但此时它的首元素是arr[0],它又包含了一个含有三个整型的一维数组,那么此时,我们传入的就是一个指向一维数组的指针,声明如下

 int(*p)[3]

同第一部分声明一样,对其进行分析

  • 首先它的变量名为p,括号的优先级最高,所以它同*集合,表示它为指针
  • 他的后缀操作符[3],代表他是指针数组吗?错误!p的类型在第一个操作符跟它结合后就固定了,剩下的所有信息都表示指向的内容相关。此时代表其指向的是一个一维数组,有三个元素,此时前缀为int那么每个元素的类型为便为int。

其实指针声明的核心是没有改变的,先看名字,再看类型,再看内容的相关信息
对于声明int *p而言,就好比这是自行车,p为骑车的人,*p结合后我们知道骑的是交通工具而不是骑在别人脖子上,哪种交通工具?28自行车(int *)。
对于一维数组的指针,显然要比int高级一点,他要指向一段连续的空间,自行车显然是解决不了问题的,所以座驾升级!电动自行车!
如何让编译器知道我们骑的是电动自行车呢?他的声明显然跟28自行车不同。(*p)后的[3]表示这个电动车的电池容量,int表示它还是两个轮子的,这些信息都很关键,所以编译器知道了这些信息,才能给我们把路铺好然后尽情驰骋。


指针数组

是的。十分明了,指针数组,就是指针的数组,point在数组。

  • 声明指向一维指针数组的数组指针
    有了上部分的要素,书写就会容易很多。
int *(*p)[3]

同理,p先与*结合,表示它是指针,后缀表示它指向的是一个有三个元素的数组,前缀一看,int *表示每个元素是指向整型的指针。
所以,p是一个指向包含三个元素的每个元素为int *的数组的指针

那么如果表示存储了三个上述指针的指针数组?

int *(*arr[3])[3]

同样分析,arr先同[3]结合,表示它是一个包含三个元素的数组,然后同前缀*结合,表示它的元素类型是指针,现跳出括号看后缀[3],表示每个元素指向一个包三个元素的数组,现在又跳向前缀int *表示该数组每个元素的类型是int *

所以,数组arr是一个有三个元素,每个元素是指针,指向有三个元素,元素类型为int*的指针数组的指针数组
是的,这太绕了,我们这么做值是为了理解它。


函数指针

第一部分的声明已经对函数部分的声明有了初步的介绍
void * test(char *dest,int sz)
其中test为函数名,表示该函数的地址

  • 指向上方函数指针的声明
   void *(*p)(char *,int);

函数指针依然遵循上述原则,首先p与*结合表示这是一个交通工具(指针)。现在看后缀操作符,发现是()函数调用操作符,它的参数类型分别是char *和int,(它的引擎不同,是全新发动机),前缀是void *,噢,原来他是摩托车(函数的返回类型是void*)
所以,指针p是指向参数为char* ,int 的返回值类型为void*的函数


函数指针数组

有了上述的经验,那么就会很容易写出函数指针的数组的声明

void *(*arr[3])(char *,int );

首先,arr与后缀操作符[3]结合,表示它为一个数组,现与*结合,表示的元素类型为指针,现跳出括号,发现是函数调用操作符,参数的类型是char* ,int类型。再次跳转到最前方,发现函数的返回值类型为void*。

所以,数组arr是一个有三个元素,每个元素是指向参数类型分别为char*,int的返回值类型为void *的函数指针的数组


指向函数指针数组的数组指针

不成威胁,都是纸老虎。

 void *(*(*p)[3])(char*,int)

首先,p与*集合,表示它是一个指针,紧接着,与后缀操作符[3]集合,表示这个指针指向一个包含三个元素的数组,紧接着再同括号内最前面操作符*结合,表示每个元素的类型为指针。现在跳出括号后方同样为函数调用操作符,参数的类型是char* ,int类型。再次跳转到最前方,发现函数的返回值类型为void*。
所以,p是一个指向有三个元素,每个元素又是指向参数类型为char ,int。返回值类型为void 的函数指针的数组的数组指针


通关在即

void (*signal(int,void(*)(int)))(int)

请解释上述声明的意思

老规矩.先看变量名,发现后缀操作符是函数调用操作符,优先级高于*,率先结合,表示signal是一个参数类型为int,void(*)(int)(该为函数指针的类型符),的函数,然后前缀操作符*表示signal的返回值为指针,现在跳出大括号发现后缀为函数调用操作符,那么返回的指针指向的函数类型为int,现在又跳转到最前面,发现为void。
所以,signal函数是参数类型分别为 int, 指向参数类型为int,无返回值的函数指针类型,返回值为指向int,无返回值的函数指针


阅读至此,恭喜你冲破了指针初阶的种种复杂缠绕,这些看上去令人生畏的代码并不是为了酷炫而选择层层相绕,而是为了让我们更好的理解指针,理解C语言的声明格式,只要心中有自行车和摩托等交通工具的构架,这些都难不住我们

当然,我们为了避免这样令人生畏的复杂缠绕代码应使用typedef简化它:
typedef是在计算机编程语言中用来为复杂的声明定义简单的别名,使用它可以将“有多个组成的交通工具(复杂的类型符)”打包,从而使代码便于阅读,简化代码。

void (*signal(int,void(*)(int))(int);

CODE:
typedef void (*HANDLER)(int);//注意,此处定义的名字应符合类型格式
HANDLER signal(int,HANDLER);//末尾处的HANDLER即void(*)(int)类型符
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值