前言

相信大多数的同学都是第一门能接触到语言是C/C++,其中的指针也是比较让人头疼的部分了,因为光是指针都能专门出一本叫《C和指针》的书籍,足见指针的强大。但如果不慎误用指针,这些指针很大可能就会像恶魔一样把你的程序给直接搞崩溃。

3个月前,我编写了一份这些指针都是恶魔吗?.c的文件,里面从大多数常用的指针类型,一路延伸到纯粹只是在窥探编译器所能产生的恐怖造物。为了增加趣味性,我还把这些指针都划分了段位,只有辨识出该段位绝大多数的指针才能升段。现在我要将这些恶魔般的指针公诸于世,欢迎大家前来接受挑战自虐。

前置声明:

  1. 题目会包括数组、指针、函数,以及它们的各种谜之复合体;
  2. 本文后面提及的一些指针不考虑什么实用性,就当做是玩个游戏,但适当情况下会对这些指针做必要讲解,请理智对待这些指针,认真你就输了
  3. 如果你对指针开始产生不适、恐惧感,建议你提前离开,以免伤到你对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元素)]的指针,符号化描述即p3int(*)[10]类型。

int *p4[10];中的p4考虑到优先级,会先与[]先结合,而不是*,说明p4是一个含10元素的数组,然后把p4[10]拿开,则元素类型为int*答案:p4是一个int指针的数组(10元素),符号化描述即p4int* [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]); 你以为arrint**吗?那就又错了。如果退化成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语言指针都在这里了的更多相关文章

  1. 守卫者的挑战(据说在bzoj有但我没找到)

    芒果君:一看就是概率dp(可是我不会啊,就算再裸也不会啊).然后先从最后想,能够满足题意的状态是 挑战次数>=L,获得价值>=0,那一定有f[总挑战数i][挑战成功数j][价值k].转移很 ...

  2. 我希望我知道的七个JavaScript技巧

    如果你是一个JavaScript新手或仅仅最近才在你的开发工作中接触它,你可能感到沮丧.所有的语言都有自己的怪癖(quirks)——但从基于强类型的服务器端语言转移过来的开发人员可能会感到困惑.我就曾 ...

  3. 听说alphago又要挑战sc2了?——我眼中的人工智能

    乱谈: 之前alphago进行的围棋比赛相当火爆. 一时间我的朋友圈都爆了,因为同学以及相关专业的同学都在发这个,毕竟逼格一下就起来了,我也大肆转发.各种角度,不同层次的不同深度的文章也都扫了几眼. ...

  4. 成功的背后!(给所有IT人)----转载:来自CSDN第一名博主

    转载:来自CSDN第一名博主:http://blog.csdn.net/phphot/article/details/2187505 放在这里激励你我! 正文: 成功的背后,有着许多不为人知的故事,而 ...

  5. 百度领跑BAT all in O2O机遇大于挑战

        近期一年O2O可谓是互联网领域最"炙手可热"的话题,似乎是个创业项目都要和O2O沾点关系.所以我们看到各种细分O2O模式层出不穷,并且,似乎外卖.洗车.租房等传统服务已经通 ...

  6. Redis集群方案(来自网络)

    参考: https://www.zhihu.com/question/21419897 http://www.cnblogs.com/haoxinyue/p/redis.html 为什么集群? 通常, ...

  7. 汪莹:以RELX悦刻为例,复盘中国品牌出海的跨文化挑战

    海外销售额每月2倍增速,3个月拿下东南亚市场第一,出口43个国家,拥有250万用户--你可能不知道,这是一家成立仅一年半.出海仅7个月的中国企业交出的答卷. 这家企业就是中国第一大电子烟品牌RELX悦 ...

  8. pytest封神之路第五步 参数化进阶

    用过unittest的朋友,肯定知道可以借助DDT实现参数化.用过JMeter的朋友,肯定知道JMeter自带了4种参数化方式(见参考资料).pytest同样支持参数化,而且很简单很实用. 语法 在& ...

  9. 寒假挑战PythonTip(一人一python)总结——算法是程序的灵魂,程序员的心法

        2014年2月中旬,我上升到挑战python英雄榜第3名.这是我寒假修炼算法的成果之一.来一下总结吧! Linux的创始人Linus Torvalds在一次演讲中有一段涉及“什么才是优秀程序员 ...

随机推荐

  1. DVWA 黑客攻防演练(十)反射型 XSS 攻击 Reflected Cross Site Scripting

    XSS (Cross-site scripting) 攻击,为和 CSS 有所区分,所以叫 XSS.又是一种防不胜防的攻击,应该算是一种 "HTML注入攻击",原本开发者想的是显示 ...

  2. 如何从GitHub下载csv文件

    当打开存.csv文件的页面时,不用直接点击页面的Download,这样会使csv文件直接用浏览器打开. 要点击Raw按钮,鼠标右键,文件另存为,可以直接把csv文件下载到本地.

  3. easyui实现分页

    主要参考官方的文档,欢迎评论 1.集成easyui,下面是我的引入方式,我引入到了head.html 每次只要引入该页面就可以了. <!-- easyui样式支持 --><link ...

  4. AI-2048 注释

    针对2048游戏,有人实现了一个AI程序,可以以较大概率(高于90%)赢得游戏,并且作者在 stackoverflow上简要介绍了AI的算法框架和实现思路. 有博客介绍了其中涉及的算法,讲的很好 其中 ...

  5. c语言static关键字的理解

    static 一.概述 在c语言中static恰当的使用能让程序更加完美,细节上的严谨,代码会更好,也更利于程序的维护与扩展. 而static使用灵活,且又有两种完全无关的用法,所以整理总结一下. 二 ...

  6. javaEmail发邮件是问号乱码,已解决

    寒假学习了ssm,就把之前看过的一个商城项目用ssm重构了. 然后在本地一切都正常,放到个人服务器上就凉了. 因为这个项目注册需要邮箱激活,然后就在发邮件的时候出了问题. 一.发送端口 因为源程序是用 ...

  7. php类注释生成接口文档

    参考地址:https://github.com/itxq/api-doc-php

  8. RAC Wait Event: gcs log flush sync 等待事件 转

    RAC Wait Event: gcs log flush sync https://www.hhutzler.de/blog/rac-wait-event_gcs_log_flush_sync/#o ...

  9. c++ primer plus 第二章 \n与endl在输出上的区别

        在书上看到如下一段话:     一个差别是,endl确保程序继续运行前刷新输出(将其立即显示在屏幕上):而使用"\n"不能提供这样的保证,这意味着在有些系统中,有时可能在您 ...

  10. windows系统中给qt工程添加第三方库

    · TEMPLATE = app CONFIG += console c++11 CONFIG -= app_bundle CONFIG -= qt SOURCES += main.cpp LIBS ...