前言
相信大多数的同学都是第一门能接触到语言是C/C++,其中的指针也是比较让人头疼的部分了,因为光是指针都能专门出一本叫《C和指针》的书籍,足见指针的强大。但如果不慎误用指针,这些指针很大可能就会像恶魔一样把你的程序给直接搞崩溃。
3个月前,我编写了一份这些指针都是恶魔吗?.c
的文件,里面从大多数常用的指针类型,一路延伸到纯粹只是在窥探编译器所能产生的恐怖造物。为了增加趣味性,我还把这些指针都划分了段位,只有辨识出该段位绝大多数的指针才能升段。现在我要将这些恶魔般的指针公诸于世,欢迎大家前来接受挑战自虐。
前置声明:
- 题目会包括数组、指针、函数,以及它们的各种谜之复合体;
- 本文后面提及的一些指针不考虑什么实用性,就当做是玩个游戏,但适当情况下会对这些指针做必要讲解,请理智对待这些指针,认真你就输了;
- 如果你对指针开始产生不适、恐惧感,建议你提前离开,以免伤到你对C语言的热情;
这些指针都是恶魔吗?
下面的所有题目,你可以把自己的思路写在评论中。
青铜(答对所有题升至该段位)
请用文字描述下列指针、数组的具体类型:
int * p0;
int arr0[10];
int ** p1;
int arr1[10][10];
int *** p2;
int arr2[10][10][10];
下面适当留白以供思考,想好后就可以往下翻看答案。
青铜题解
对于初学C指针的同学基本上应该都能答出来:
int * p0; // p0是 int指针
int arr0[10]; // arr0是 int数组(10元素)
int ** p1; // p1是 int二级指针
int arr1[10][10]; // arr1是 int二维数组(10*10元素)
int *** p2; // p2是 int三级指针
int arr2[10][10][10]; // arr2是 int三维数组(10*10*10元素)
白银(答对4题升至该段位)
请用文字描述下列指针、数组、函数的具体类型:
int (*p3)[10];
int *p4[10];
int *func0(int);
int func1(int * p);
int func2(int arr[]);
这些指针还是比较常见、实用的,想好后就可以往下翻看答案。
白银题解
int (*p3)[10];
中的p3
与*
先结合,说明p3
是一个指针,然后把(*p3)
拿开,剩下的就是p3
这个指针所指之物(即int[10]
)。答案:p3
是一个指向[int数组(10元素)]的指针
,符号化描述即p3
是int(*)[10]
类型。
int *p4[10];
中的p4
考虑到优先级,会先与[]
先结合,而不是*
,说明p4
是一个含10元素的数组,然后把p4[10]
拿开,则元素类型为int*
。答案:p4
是一个int指针的数组(10元素)
,符号化描述即p4
是int* [10]
类型。
int *func0(int);
中的func0
先与括号结合,并且括号内仅是形参类型,说明func0
是一个函数,返回值类型为int*
。答案:f0是函数(形参为int, 返回值为int指针)
int func1(int * p);
答案:func1是 函数(形参为int指针, 返回值为int)
int func2(int arr[]);
中,留意int arr[]
的写法,仅在函数中才可以这样写,是因为编译器将arr
判定为指针类型,即和int * arr
的写法是等价的。 答案:func2是 函数(形参为int指针, 返回值为int)
黄金(答对7题升至该段位)
请用文字描述下列函数的具体类型。而对于指针,请描述其可读写的情况(可以代码描述):
int func3(int arr[10]);
int func4(int *arr[10]);
int func5(int(*arr)[10]);
int func6(int arr[10][10]);
int func7(int arr[][10]);
int func8(int **arr);
const int * p5;
int const * p6;
int * const p7;
const int * const p8;
警告: 到这一步如果你对这些指针已经有所不适的话,建议提前离开,以免你产生了放弃C/C++语言的想法。如果你硬要坚持的话。。。想好后就可以往下翻看答案。
黄金题解
int func3(int arr[10]);
你以为这里int arr[10]
就觉得这个函数的形参是一个int[10]
那么简单么?那就错了。事实上这里的arr
仍然是int *
类型!你要想,如果将一个数组按值传递的话就以为着需要拷贝一份数组给该函数用,10个就算了,那int arr[1000000000]
呢,一次copy就可以享受爆栈的快乐了。因此这里编译器会将其视作int *
类型,并无视掉后面的10,实际上就是将指针按值传递,这样做可以节省大量内存,但多了一层间接性与越界风险(收益远大于风险)。这里的10实际上也仅仅是要提醒作为开发者的你,传入的数组(or指针)必须保证其地址后面sizeof(int) * 10
字节都要能够访问。你可以传入元素个数大于等于10的数组,至于小于10的话...后果自负。答案:func3是 函数(形参为int指针, 返回值为int)
int func4(int *arr[10]);
这道题也好说了,即arr
实际上是int **
类型,而作为开发者的你,需要保证传入一个元素个数大于等于10的int指针数组。答案:func4是 函数(形参为int二级指针, 返回值为int)
准则1:函数形参中所谓的数组实际上都是指针类型
int func5(int(*arr)[10]);
注意arr
本身又不是一个数组,而是指针!一个指向数组的指针! 答案:func5是 函数(形参为指向[int数组(10元素)]的指针, 返回值为int)
int func6(int arr[10][10]);
你以为arr
是int**
吗?那就又错了。如果退化成int**
类型的话,那么对于传入的指针做类似arr[3][5]
的操作是十分危险的。通常int**
用于指向两个维度都是动态分配的二维数组(一个动态的指针数组,每个指针是一个动态数组),即把第一行的元素都当做int*
而不是int
来看待。把一个二维数组强制变成变成int**
,再解除一次引用就会引起野指针的危险操作。因此实际上编译器只会对第一维度的[10]
当做*来处理,即等价于int func6(int (*arr)[10]);
。 答案:func6是 函数(形参为指向[int数组(10元素)]的指针, 返回值为int)
准则2:对于函数形参中的多维数组,只会将第一维度作为指针处理
int func7(int arr[][10]);
和上一题等价。答案:func7是 函数(形参为指向[int数组(10元素)]的指针, 返回值为int)
int func8(int **);
这里只接受两个维度都是动态分配的二维数组(即int指针数组)。 答案:func8是 函数(形参为int二级指针, 返回值为int)
const int * p5;
《C++ Primer》称其为底层const,即指向常量的指针,其所指数据不可修改,但指针本身可以替换,例:
p5 = NULL; // 正确!
*p5 = 5; // 错误!
而像const int num = 5
这种是顶层const,数据本身不可以被修改
int const * p6;
和p5
等价。
int * const p7;
《C++ Primer》称其为顶层const,即指针本身为常量,其所指数据可以修改,但指针本身不可以替换,例:
p5 = NULL; // 错误!
*p5 = 5; // 正确!
const int * const p8;
包含了顶层与底层const,这样所指和数据与指针本身都不可以修改。
钻石(答对6题升至该段位)
请用文字描述下列指针、函数、函数指针的具体类型:
int (*pfunc1)(int);
int (*pfunc2[10])(int);
int (*(*pfunc3)[10])(int);
int func9(int (*pf)(int, int), int);
const int ** p9;
int * const * p10;
int ** const p11;
int * const * const p12;
实用性正在逐步降低中...
钻石题解
int (*pfunc1)(int);
答案:pfunc1是 函数(形参为int, 返回值为int)的指针
,符号化描述即int(*)(int)
int (*pfunc2[10])(int);
f2先与[10]结合,说明f2是一个数组,把f2[10]
拿开,则元素类型为int(*)(int)
。**答案:pfunc2是 函数(形参为int, 返回值为int)的指针数组(10元素)
**
int (*(*pfunc3)[10])(int);
函数没法作为数组的元素,但函数指针可以。经过前面的磨难,应该可以看出来这是一个指向数组的指针,数组的元素是函数指针。 答案:pfunc3是 指向[函数(形参为int, 返回值为int)的指针数组(10元素)]的指针
int func9(int (*pf)(int, int), int);
一个函数里面需要接受一个函数指针作为形参,通常将以这种方式传递的函数叫做回调函数。 答案:func9是 函数(形参为{函数(形参为{int, int}, 返回值为int)的指针, int}, 返回值为int)
const int ** p9;
具体可以参考下面的示范:
p9 = NULL; // 正确!
*p9 = NULL; // 正确!
**p9 = 5; // 错误!
int * const * p10;
具体可以参考下面的示范:
p10 = NULL; // 正确!
*p10 = NULL; // 错误!
**p10 = 5; // 正确!
int ** const p11;
具体可以参考下面的示范:
p11 = NULL; // 错误!
*p11 = NULL; // 正确!
**p11 = 5; // 正确!
int * const * const p12;
具体可以参考下面的示范:
p12 = NULL; // 错误!
*p12 = NULL; // 错误!
**p12 = 5; // 正确!
大师(答对5题升至该段位)
如果你有幸能够坚持到这一步,或者已经放弃治疗想看看后续内容,那么接下来你将要面对的可能是各种匪夷所思的、恶魔般指针,这些奇奇怪怪的写法甚至能够通过编译,简直就是恶魔。
考虑到后面的题目用文字描述就太恶心了,现在允许你使用一种伪lambda的描述方式,来对函数或函数指针进行拆解。示例如下:
int (*pfunc1)(int); // (*pfunc1)(int)->int
int f1(int); // f1(int)->int
int func9(int (*pf)(int, int), int); // func9((*pf)(int, int)->int, int)->int
箭头所指的为返回值类型。
那么。。。祝你好运,请用伪lambda描述方式拆解下面函数和函数指针:
int (*pfunc4)(int*());
int (*func10(int[]))[10];
int (*func11(int[], int))(int, int);
int (*(*pfunc5)(int))(int[10], int);
int (*(*pfunc6)(int[10]))[10];
int (*(*pfunc7[10])(int[10]))[10];
int (*func12(int(*(int(*())))));
int (*(*(*pfunc8)[10])(int[], int))(int, int);
大师题解
int (*pfunc4)(int*());
基本上都倒在了形参的int*()
这种什么鬼写法是吧,当初我也不知道怎么这样也能通过编译,不过不这样怎么能叫恶魔指针呢,哈哈哈... 反正在这篇文章里,就让可读性就统统见鬼去吧!如果你有Visual Studio的话,把这份声明粘贴到VS,然后光标放在上面,你会发现实际上形参的int*()
会被解读为int*(*)()
。 答案:(*pfunc4)((*pf)()->int*)->int
int (*func10(int[]))[10];
这个在《C++ Primer》上似曾相识,如果你之前在里面做过类似的题目话,就会知道这个函数,返回的是一个指向数组的指针。你可以将该函数类似于函数调用的部分func10(int*)
拿掉,剩下的就是返回值类型int(*)[10]
了。 答案:func10(int*)->int(*)[10]
int (*func11(int[], int))(int, int);
函数返回了一个函数指针。 答案:func11(int*, int)->int(*pf)(int, int)
int (*(*pfunc5)(int))(int[10], int);
函数指针,所指函数返回了一个函数指针。 答案:(*pfunc5)(int)->((*pf)(int*, int)->int)
int (*(*pfunc6)(int[10]))[10];
答案:(*pfunc6)(int*)->int(*)[10]
int (*(*pfunc7[10])(int[10]))[10];
答案:(*pfunc7[10])(int*)->int(*)[10]
int (*func12(int(*(int(*())))));
这又是什么鬼玩意???我们先根据现有的经验来进行层层解耦。首先像这种int(*())
的外层括号是可以去掉的,只是一个误导,然后就变成了int*()
的鬼形式,然后编译器会认为它是int*(*)()
。那答案也就呼之欲出了。 答案:(*func12)((*pf1)((*pf2)()->int*)->int*)->int*
int (*(*(*pfunc8)[10])(int[], int))(int, int);
答案:((*pfunc8)[10])(int*, int)->((*pf)(int, int)->int)
结语
如果你能完成上面的所有题目,那么你将获得隐藏称号:人形编译器。
这里的指针几乎就是你这辈子能见到的所有指针了。至于其余变种指针,基本上都围绕这上面提到的方法构成。毕竟我们还没加上C++的引用呢...
坑挖的太大也难免会有一些错漏,欢迎指正。
现在,我们都是恶魔了
一封来自恶魔的挑战邀请函,那些你见过或者没见过的C语言指针都在这里了的更多相关文章
-
守卫者的挑战(据说在bzoj有但我没找到)
芒果君:一看就是概率dp(可是我不会啊,就算再裸也不会啊).然后先从最后想,能够满足题意的状态是 挑战次数>=L,获得价值>=0,那一定有f[总挑战数i][挑战成功数j][价值k].转移很 ...
-
我希望我知道的七个JavaScript技巧
如果你是一个JavaScript新手或仅仅最近才在你的开发工作中接触它,你可能感到沮丧.所有的语言都有自己的怪癖(quirks)——但从基于强类型的服务器端语言转移过来的开发人员可能会感到困惑.我就曾 ...
-
听说alphago又要挑战sc2了?——我眼中的人工智能
乱谈: 之前alphago进行的围棋比赛相当火爆. 一时间我的朋友圈都爆了,因为同学以及相关专业的同学都在发这个,毕竟逼格一下就起来了,我也大肆转发.各种角度,不同层次的不同深度的文章也都扫了几眼. ...
-
成功的背后!(给所有IT人)----转载:来自CSDN第一名博主
转载:来自CSDN第一名博主:http://blog.csdn.net/phphot/article/details/2187505 放在这里激励你我! 正文: 成功的背后,有着许多不为人知的故事,而 ...
-
百度领跑BAT all in O2O机遇大于挑战
近期一年O2O可谓是互联网领域最"炙手可热"的话题,似乎是个创业项目都要和O2O沾点关系.所以我们看到各种细分O2O模式层出不穷,并且,似乎外卖.洗车.租房等传统服务已经通 ...
-
Redis集群方案(来自网络)
参考: https://www.zhihu.com/question/21419897 http://www.cnblogs.com/haoxinyue/p/redis.html 为什么集群? 通常, ...
-
汪莹:以RELX悦刻为例,复盘中国品牌出海的跨文化挑战
海外销售额每月2倍增速,3个月拿下东南亚市场第一,出口43个国家,拥有250万用户--你可能不知道,这是一家成立仅一年半.出海仅7个月的中国企业交出的答卷. 这家企业就是中国第一大电子烟品牌RELX悦 ...
-
pytest封神之路第五步 参数化进阶
用过unittest的朋友,肯定知道可以借助DDT实现参数化.用过JMeter的朋友,肯定知道JMeter自带了4种参数化方式(见参考资料).pytest同样支持参数化,而且很简单很实用. 语法 在& ...
-
寒假挑战PythonTip(一人一python)总结——算法是程序的灵魂,程序员的心法
2014年2月中旬,我上升到挑战python英雄榜第3名.这是我寒假修炼算法的成果之一.来一下总结吧! Linux的创始人Linus Torvalds在一次演讲中有一段涉及“什么才是优秀程序员 ...
随机推荐
-
three.js学习笔记
一.名词解释 scene - 场景 camera - 摄影机 renderer - 渲染器:描绘器 Vector - 三维向量的对象 orthographic - 正射 field of view - ...
-
Nine-Patch图片
Nine-Patch图片以.9.png结尾,用作背景图片时,可使背景随着内容拉伸(缩小)而拉伸(缩小). 如何将普通图片制作为Nine-Patch图片: 在Android sdk目录下有一个tools ...
-
谈JavaScript代码封装
前言 也算老生常谈的问题了,再深入搞一搞怎么玩儿封装,如果看到这篇文章的你,正好你也是追求完美的代码洁癖狂者,那么这篇文章相信非常适合你. 举一个例子,编写一个Person类,具有name和birth ...
-
Perl5中19个最重要的文件系统工具
在写脚本处理文件系统时,经常需要加载很多模块.其中好多有用函数分散在各种不同的模块中.它们有些是Perl的内置函数,有些是在同Perl一起发行的标准模块中,另外一些是通过CPAN安装的. 下面来看15 ...
-
plsql通过instantclient连接oracle数据库报连接超时
配置:数据库oracle10.2;服务器操作系统centos5.5:客户机操作系统win7 32位:plsql版本10.0.5 配置前提必须关闭客户机与服务器操作系统的防火墙,否则会出现“连接超时”的 ...
-
【网络流#1】hdu 3549 - 最大流模板题
因为坑了无数次队友 要开始学习网络流了,先从基础的开始,嗯~ 这道题是最大流的模板题,用来测试模板好啦~ Edmonds_Karp模板 with 前向星 时间复杂度o(V*E^2) #include& ...
-
UVA 10954 Add All
题意: 给出n个数,要将n个数相加,每次相加所得的值为当次的计算量,完成所有的求和运算后,要求总的计算量最小. 分析: 直接一个优先队列,由小到大排序,每次前两个相加就好. 代码: #include ...
-
接口返回数据Json格式处理
有这样一个页面 , 用来显示用户的账户记录数据,并且需要显示每个月的 收入 支出合计 ,在分页的时候涉及到一些问题,需要对返回的Json格式做处理,处理起来比较麻烦,后端返回的Json数据格式形式如下 ...
-
perl学习笔记--搭建开发环境
windows下perl开发环境搭建 perl下载地址:http://www.activestate.com/developer-tools 各个插件的安装方法:(通过代理上网的方法) 方法一:pad ...
-
Python练习十一
1.写一个程序,提示输入整数X,然后计算从1到X连续整数的和. num = int(input('please the input number:')) sum_num = 0 for i in ra ...