距离我学习C语言也有四年之久了,我不禁想到,自己是否真的了解和会完C语言指针,以下是我对C语言指针的理解(如有纰漏,请指出
我做了codewars
上的 C Puzzle: Pointer Monster
,其中运用到了一些关于指针相关的内容,我感觉这是一道学习指针和数组和函数之间的较好的一个例子
特别是不用编译器去算指针,数组,偏移后所得出来了的最后的答案,我觉得对理解数组和指针帮助还是有一定的帮助的
题目链接
什么是指针呢?
学过计算机组成原理就知道,哦,这个是个间接寻址~
比如声明一个指针 int* x
,在其中 int*
是 x 的类型,x的值是一个 int*
我们可以使用 *x
去访问 这块地址的值!
有间接寻找那味儿了
什么是数组呢?
我们知道,内存被分为了一系列连续的小块,对于每一个小块,我们给它们取了个编号
这样,我们就有了我们的地址
这个概念
那么,我们的指针是指向一个地址的,它可以支持加减操作,每次操作对应着移动到下一个块去
那么,我们是不是可以使用 *(x + 1)
来访问 下一个地址的值呢?
显然是可以的
那么数组是个什么东西呢?
首先,数组给的内存肯定是先分配好的,不然会段错误。
其次,我眼中的数组其实就是一个 int * const
(这个指针是不能够被改变的
当然,无论是指针还是数组,他都可以写成一个乖乖的形式:
int* t;
int arr[2];
1[t] = 1;
1[arr] = 2;
上述操作是可以的哦
大概是因为1[t] 在编译时会被翻译为 1+t 吧~
那么,二重指针呢?
套娃嘛。
二重指针指向的是一个int*
那么我们的指针就应该长成 int* *
那么,二维数组呢?
同理,我们的二维数组呢?
应该长成 int[][]
二重数组的地址分布是什么样呢?
那么,指向数组的指针呢?
注意,这是指向数组的指针,是一个指针,元素是数组
int (*)[k]
(k代表数组长度,得指定,数组和指针还是有区别的)
那么,指向指针的数组呢?
注意,这是指向指针的数组,是一个数组,元素是指针
int *[k]
(k 代表数组长度)
看到这里,可能开始模糊起来了,可能就读不清楚是个什么类型了
简单过一下螺旋法则吧~
[x]
或 []
=> 容量为 x
的数组或数组(type1,type2...)
=> 接收 type1
、type2
… 的函数,返回值为(待定)*
=> 指向(类型待定)的指针 +-------+
| +-+ |
| ^ | |
int *arr[7];
^ ^ | |
| +---+ |
+-----------+
arr
这个需要被判定的对象出发。[
左方括号 ->arr
是一个尺寸为 7 的数组。*
-> 所以 arr
是一个尺寸为 7 的数组,元素为指针。;
-> 结束。int
-> 所以 arr
是一个尺寸为 7 的数组,数组元素: int
类型的指针。下面自己分析一个复杂的吧~
+-----------------------------+
| +---+ |
| +---+ |+-+| |
| ^ | |^ || |
void (*signal(int, void (*fp)(int)))(int);
^ ^ | ^ ^ || |
| +------+ | +--+| |
| +--------+ |
+----------------------------------+
接下来,可能要变得有趣起来了
因为指针和数组,一重时,最多两种情况,而二维时,我们则有4中情况了
我们可以来枚举一下:
设:用指针代表 A ,数组代表 B 一重 : A 、 B 二重 : A A 、 A B 、 B A 、 B B 三重: A A A 、 A B A 、 B A A 、 B B A 、 A A B 、 A B B 、 B A B 、 B B B 设:用指针代表 A, 数组代表B \\ 一重: A、B \\ 二重: AA、AB、BA、BB \\ 三重:AAA、ABA、BAA、BBA、AAB、ABB、BAB、BBB 设:用指针代表A,数组代表B一重:A、B二重:AA、AB、BA、BB三重:AAA、ABA、BAA、BBA、AAB、ABB、BAB、BBB
三重理论上排列出来的组合还是蛮多的,可能有这么多种,但实际不一定,这里就介绍四种
第一种是 指向 数组的指针 的指针
int(**)[k]
由于 int(*(*))[k]
中的(*)
没必要,可以省略
第二种是 声明 数组的指针 的数组
int(*)[k][k]
加一维即可
第三种是 指向 指针的数组 的指针
int *(*)[k]
嵌套一个进去
第四种是 声明 指针的数组 的数组
int (*[k])[k]
加一个维度
int main() {
#define k 7
using A = int *;
using B = int[k];
using AA = int **;
using BB = int[k][k];
using BA = int(*)[k];
using AB = int *[k];
using BAA = int(**)[k];
using BAB = int(*)[k][k];
using ABA = int *(*)[k];
using ABB = int (*[k])[k];
A a;
B b;
AA aa;
BB bb;
BA ba = &b;
AB ab = {a};
BAA baa = &ba;
BAB bab = {&bb};
ABA aba = &ab;
ABB abb = {ba};
return 0;
}
没有毛病可以通过。
只要按照这样套娃,100层我都套的出来,不过那样的代码味道一定蛮大的~
可以看出C语言中的这种类型属实不好认。。。
所以,后续我们有了 typedef 和 using(C++)来补救
让我们的心智负担降低了不少
那么,上续说的很简单,可是我们遗漏掉了一个东西,函数
形如void (*signal(int, void (*fp)(int)))(int);
这种比较 容易 读得懂的东西来说。
还有更多烦人的东西呢
来试试?
我们的函数声明长这个样儿
int add(int x, int y) {}
我们的函数指针类型长什么样子呢?
简单,我们把指针塞到变量的位置嘛!
int (*)(int, int)
, 好,现在它长这个样子了
还是比较好理解的,用螺旋法则。
现在,让我们来升级一下我们的函数指针
现在我们要定义如下东西:
声明 函数指针的 数组
int (*[k])(int, int)
嗯,现在开始犯迷糊了
记住,记住,螺旋法则看待,我们先是一个数组
用的时候,大概长这个样子
int (*w[k])(int, int) = {add};
指向 函数指针的 指针
int (**)(int, int)
这个简单!加一个指针就是
接下来,还要继续么。
回想我们刚才,采用枚举的方式,构造了一系列复杂的指针和数组
通过枚举的方式,对于函数指针也是一样的可行道理
懒得写了,太多了,枚举不过来
现在,我们来写一个比较复杂的函数,就用我们刚才的那几个组成
using BAA = int(**)[k];
using BAB = int(*)[k][k];
using ABA = int *(*)[k];
using ABB = int(*[k])[k];
using F = BAA(ABB, BAB, ABA);
现在函数样式已经定下来了
大抵上是
int(**)[k] fun(int(*[k])[k], int(*)[k][k], int *(*)[k]) {
return any;
}
当然,上述声明放到编译器中去编译是不会通过的
tips : 貌似C语言中并不能返回一个数组,可以返回值
那么,现在我们考虑如何把函数写出来
我们先看返回值:
int(**)[k]
我们若是要定义一个变量如何定义?
即为
int (**a)[k]
那么,现在换做我的函数呢?
我们可以等价的替换掉,即:
int(**fun(int(*[k])[k], int(*)[k][k], int *(*)[k]))[k]
把他看作一个变量名一样放进去
现在我们再给参数加上变量名,就可以得到我们的函数
int (**fun(int (*a[k])[k], int (*b)[k][k], int *(*c)[k]))[k] {
return a;
}
那么,我们的函数指针呢?
不要着急,仔细看:
替换的规律,我们把fun替换为一个 (*)
就可以办到这一件事情
using T = int(**(*)(int(**)[k], int(*)[k][k], int *(*)[k]))[k];
好,现在写出来了,如何,还是比较复杂吧
当然,还要更复杂的,我可以返回一个函数数组指针,参数也是函数数组指针,还要指针数组和数组指针
数组和指针是非常灵活的东西,我们的C/C++程序员,尽可能用好指针
最好使用typedef和using来简化我们的操作
现代语言,比如golang、rust这种现在都已经采用后置类型的方式了,就我而已,我任务后置类型是比c/c++这种形式要来的好的
且可以保持统一的风格:
var xxx int
var yyy double
fun func(a int, b double) int {}
当然,C++也可以做到一点保持统一风格
auto fun(int a, int b) -> decltype(a + b) {
}
auto func(int x) -> double {
}
但不可忘记这些东西,举个例子:
你不理解函数指针,你就搞不懂 c++ 中的 function
是如何实现的,有没有其他实现方式
C++的类型萃取更是离不开类型的分辨
甚至你可以使用 模板来给类型添加指针这样的操作
模板再编译期是可以执行的,所以基本上没有损耗一般
比如:
using std::add_pointer;
add_pointer<add_pointer<int>::type[7]>::type t;
就很容易的达到了 指向 数组指针的 指针
最后,附上我开始说的那道题的代码,感受一下吧~
#include
char a[4][7] = {"Common", "Point", "Boost", "Better"};
char (*b[4])[7] = {a+3, a+1, a, a+2};
char (*(*C(void))[4])[7]
{
return &b;
}
char (**d(void))[7]
{
return C()[1] - 3;
}
char buf[256];
char *pointer_monster(char (**f(void))[7])
{
int len;
len = sprintf(buf, "%s", *f()[0]);
len += sprintf(buf + len, "%s ", *((**f)()-1)[0]+4);
len += sprintf(buf + len, "%s", (*f())[0]-4);
len += sprintf(buf + len, "%s", f()[1][2]+3);
len += sprintf(buf + len, "%s", *((**f)()-1)[0]+4);
return buf;
}
[author: amjieker]