前言
相信大多数的同学都是第一门能接触到语言是C/C++,其中的指针也是比较让人头疼的部分了,因为光是指针都能专门出一本叫《C和指针》的书籍,足见指针的强大。但如果不慎误用指针,这些指针很大可能就会像恶魔一样把你的程序给直接搞崩溃。
3个月前,我编写了一份这些指针都是恶魔吗?.c
的文件,里面从大多数常用的指针类型,一路延伸到纯粹只是在窥探编译器所能产生的恐怖造物,为了增加趣味性,我还把这些指针都划分了段位,只有辨识出该段位绝大多数的指针才能升段。目前看到的同学基本上都分布在青铜到黄金的水平。现在我要将这些恶魔般的指针公诸于世,欢迎大家前来接受挑战自虐。
前置声明:
- 题目会包括数组、指针、函数,以及它们的各种谜之复合体;
- 本文后面提及的一些指针不考虑什么实用性,就当做是玩个游戏,但适当情况下会对这些指针做必要讲解;
- 如果你对指针开始产生不适、恐惧感,建议你提前离开,以免伤到你对C语言的热情;
- 你想从这些指针里面挑一道作为自己的题目?随你喜欢。
这些指针都是恶魔吗?
青铜(答对所有题升至该段位,正确率100%)
请用文字描述下列指针、数组的具体类型:
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题升至该段位,正确率80%)
请用文字描述下列指针、数组、函数的具体类型:
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题升至该段位,正确率70%)
请用文字描述下列函数的具体类型。而对于指针,请描述其可读写的情况(可以代码描述):
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