基础知识复习二 --- 指针(Jun18)

普通指针
定义:int *p;
这个是定义一个指向int类型数据的指针。但是这样不容易理解,我自己的理解方式如下:
其实,p是一个32位的数字(64位系统为64位,这里以32位),只不过这个数字具体的意思是个内存地址,所以不能进行数学意义上的加减乘除(地址的加减乘除,是以类型大小sizeof(int)为单位的)。
如果我们直接使用p,则是一串数字地址,如果我们想要p地址处的数据,就需要加一个*。
因此,*p诞生了,意思是我要拿出地址p处的数据。
因此,p是一个单独的变量,里面存储的是地址。
因此,我们视觉上,可以这样写:
int* p;
即代表int*类型的变量p。
使用:
既然p是个单独的变量,那么直接使用p,就是在操作地址。为了操作p地址处的值,我们使用*,这时候,*p作为一个整体,含义就与变量一样了。我们直接认为它是个普通的变量即可。修改p地址处的值:
*p = 1;


数组指针与指针数组
数组指针是一个指向数组的指针,指针数组是一个存储指针的数组。这仍然是个抽象的定义,我们来看看例子。
指针数组:
int *p[10];
根据上文提示的,我们可以先转为
int* p[10];
格式,这样我们就很好理解了,p是一个存储int*指针类型的数组。
数组指针:
int (*p)[10];
根据上文理解,由于(*p)带了括号,说明强制*p为一个整体,而*p作为一个整体,可以认为是个普通变量,那我们用普通变量做一下类比,将*p替代为a:
int a[10];
这样就好理解了,*p类似于a,那么p变量保存的就是a的地址。这还有一点,”int (*p)[10];”仅仅是定义了一个指针,p仅占4个字节(32位机器)。
因此,p必须初始化才能用,比如:
int (*p)[10];
int a[10];
p = &a;
这样p初始化后才可以用,否则p是个随机数。

扩展1:
这里有一个问题,数组常量a其实是个地址,那能不能直接:
p = a;
如果这样写,gcc会有警告,类型不匹配。这其实涉及到普通数组的实质。a确实存储了一个地址,但a的类型却不是指针(在这里sizeof(a) = 12而不是4),这就类似于:
long i = 4;
int j = 4;
i和j存储的都为4,但i和j是不同的类型。gcc编译时,就会警告类型不匹配。因此,a与&a的类型是不一样的(在这里sizeof(a) = 12, sizeof(&a) = 4)。但他们存储了相同的内容。
扩展2:
我再附上上面的例子:
int (*p)[10];
int a[10];
p = &a;
然后,”*p[1]“与”(*p)[1]“一样吗?这里涉及到*的优先级,其实*的优先级很低,不如”[]“,因此,*p[1]实际为:
*(p[1])
那么,p[1]是个什么东东呢?写程序验证一下会发现,p[1]代表了另一个跟a一样结构的数组,这个数组紧跟在数组a之后,由于我们没有定义这个数组,因此,在p[1]这个数组中全部是乱码。从内存的角度来说,如果p地址为0,a数组大小为12字节,那么p[1]代表地址12,p[2]代表地址24。这样做有什么意义呢?在二维数组中很有意义,在二维数组中,p[N]代表第N行的首地址。
扩展3:
看以下代码正确吗?

#include <stdio.h>
int main() 
{
    int a=3, b = 5;
 
    printf(&a["Ya!Hello! how is this? %s\n"], &b["junk/super"]);
 
    printf(&a["WHAT%c%c%c  %c%c  %c !\n"], 1["this"],
        2["beauty"],0["tool"],0["is"],3["sensitive"],4["CCCCCC"]);
 
    return 0; 
}

这段代码是正确的,输出如下:

Hello! how is this? super
That is C !

为什么呢?本例主要展示了一种另类的用法。下面的两种用法是相同的:
“hello”[2]
2["hello"]
如果你知道:a[i] 其实就是 *(a+i)也就是 *(i+a),所以如果写成 i[a] 应该也不难理解了。
函数指针
函数指针在感官上比较复杂,但实际上仍然是有规律的。首先我们要明白,函数名的本质是个指针常量,是的,你没看错,它和数组名是一样的道理。比如sizeof(function) = 1,如果用printf打印function,则是函数的入口地址。如果我们理解了数组指针,那我们同理可以推出函数指针。只不过函数指针在书写格式上有些特别,比如以下:
void (*fun)();
我们仍然需要将*fun用括号括起来,因为默认*是与void结合的,如果不用括号,则变成如下:
void* fun();
代表一个返回值为void*的fun函数。
带参数的函数指针
先看一个函数指针的例子,如下:
int (*fun)(int, int);
这句话定义了一个叫做fun的函数指针,它可以指向形参为(int, int),返回值为int的函数。形参不需要名称,只需要指定类型即可。再来一个复杂点的:
int (*fun)(int, int (*)(int));
int (*)(int)是函数的一个参数,表示带一个int参数并且返回值为int的函数指针。中间括号里只有一个*可能让大家不习惯,其实我们上面说过了,我们只需要指定类型,不需要名字,本来*后面是放一个名字的,这里去掉了,就变成了*。当然,我们也可以画蛇添足,把名字加上:
int (*fun)(int a, int (*b)(int));
返回值为函数指针的函数指针
那这里有个问题,如果我们需要定义一个函数指针,该函数的返回值是另一个函数指针,该怎么定义呢?是这样吗:
(void (*)(int)) *fun(int, int)
放到gcc里,发现编译错误。
实际上,C采用了一种很难理解的方式来处理这种返回函数指针的情况,不过格式是固定的。在阐述”返回值为函数指针的函数指针”前,我们先讨论一下,如何定义一个“返回值为函数指针的函数”
根据上面的经验,以下函数定义明显不对了:

(void (*)(int)) fun(int a, int b) {

}

那什么格式是正确的呢?我们需要将整个函数体”fun(int a, int b)”搬迁到返回值类型”(void (*)(int))”内部。如下:
(void (*fun(int a, int b))(int))
做这样的修改后,我们不需要大括号了,去掉大括号:
void (*fun(int a, int b))(int)

因此,最终代码为:

void (*fun(int a, int b))(int){

}

很绕吧。但是C就是这么定义的,呵呵。我们以一个返回普通指针的例子做类比:

int* fun2(int c, int d) {}
这是一个返回int型指针的函数,分拆对比表:

int* fun2(int c, int d) {} void (*fun(int a, int b))(int) {}
int* void (*…)(int)
fun2(int c, int d) fun(int a, int b)

可见其实语法都是统一的,只不过返回值如果是函数指针的话,看着比较别扭而已。
知道了”返回值为函数指针的函数”,那我们再来探讨”返回值为函数指针的函数指针”。我再将上文错误的写法写一遍:
(void (*)(int)) *fun(int, int)
对于”返回值为函数指针的函数指针”,我们也需要将右边”*fun(int, int)”整个放到左边”(void (*)(int))”中,如下:
(void (*(*fun)(int, int))(int))
做这样的修改后,我们不需要大括号了,去掉大括号:
void (*(*fun)(int, int))(int)

注意1:右边”*fun(int, int)”中的”*fun”我们加了括号,这主要是优先级问题,*优先级比较低,如果不加括号,默认含义为”*(fun(int, int))”,引起错误。
注意2:上面的写法其实还是可以与普通的指针做类比的,例如:
int * p;
“void (*(*…)(int, int))(int)”是一个整体函数指针类型的定义,虽然写了这么多,实际上大部分是对函数参数、返回值的定义,其整体就是一个函数指针类型,对应于int*。如下表:

int * p; void (*(*fun)(int, int))(int);
int* void (*(*…)(int, int))(int)
p fun

更复杂的函数指针
直接上个例子:
void (*(*fun2_p2)(int, void(*)(int)))(int);
此类定义已经快逆天了(对的,是”快”,还没到,坚持住啊),不过我们按照前面的方法,还是比较容易理解的。首先看到这个结构,这肯定是个被”函数指针返回值”包裹的指针,因此,我们尝试按上文学到的规则进行剥离:

  void (*(*fun2_p2)(int, void(*)(int)))(int);
函数返回值类型 void(*…)(int)
左边括号里的*代表了这是定义一个函数指针类型,而不是定义一个函数,与int*中的*号作用一样
右边括号定义了此函数类型的参数
(*…)(int, void(*)(int))
函数指针名称 fun2_p2

我们再以普通指针做对比:

int * p; void (*(*fun2_p2)(int, void(*)(int)))(int);
int* void (*(*…)(int, void(*)(int)))(int)
p fun2_p2

可以看出,”void (*(*…)(int, void(*)(int)))(int)”这一大坨其实就是定义了一个函数指针类型,只不过我们需要描述此函数的参数、返回值结构,包上了一层又一层的括号。
因此,这是一个名字叫fun2_p2的函数指针,指向一个函数,这个函数返回一个函数指针,并且参数里也有一个函数指针。那么这个指针可以指向的函数长什么样呢?如下:

void (*fun2(int a, void(*b)(int)))(int) {

}

我们把这个函数定义拆一下:

int* fun(int a, int b) void (*fun2(int a, void(*b)(int)))(int)
int* void (*…)(int)
fun(int a, int b) fun2(int a, void(*b)(int))

逆天前的准备:存储((返回值为函数指针的函数)的指针)的数组
还能坚持住不?先解释一下标题吧……
这是一个数组;
这数组存储指针;
这些指针指向函数;
这些函数的返回值是函数指针。
上例子:
void (*(*fun_p[4])(int, void(*)(int)))(int);
我们以返回int型指针的函数的指针数组做类比:

int* (*p[4])() void (*(*fun_p[4])(int, void(*)(int)))(int)
int* void (*…)(int)
*p[4] *fun_p[4]
() (int, void(*)(int))

逆天!指向(存储((返回值为函数指针的函数)的指针)的数组)的指针
好吧,你到这了,我就不卖关子了,关门放狗:
void (*(*(*fun_arr_p)[10])(int, void(*)(int)))(int);
我们不得不再解释一下标题先:
这是一个名为fun_arr_p的指针;
它指向一个数组;
这数组存储指针;
这些指针指向函数;
这些函数的返回值是函数指针。
是的,这个指针可以指向上面那个准备逆天的数组!我们先从最外面剥皮:
1、
void (*(*(*fun_arr_p)[10])(int, void(*)(int)))(int);
=>
void(*…)(int);
好熟悉的格式,显然是个函数指针类型的返回值。说明里面定义了一个函数指针。
2、
(*(*fun_arr_p)[10])(int, void(*)(int))
=>
(*…[10])(int, void(*)(int))
这显然是在定义一个函数指针数组。可以跟简单点的函数指针数组做类比:
int (*fun[10])();
其中(*…[10])();就是上面的部分。而int对应于我们上面剥离的函数指针类型的函数返回值。
3、
(*fun_arr_p)
有*号,这显然是个指针定义,说明不是在定义函数指针数组,而是定义指向函数指针数组的指针。
乱了吧?实际上我在写上面内容的时候也纠结死了。我们再做一个类比吧,以下是一个普通的”指向函数指针数组的指针”:
int* (*(*p)[10])(); //写法:先写一个指针数组int* p[10],然后我们将p改为指针int* (*p)[10],这样就变成指向指针数组的指针
他只有四个字节,类比表:

int* (*(*p)[10])() void (*(*(*fun_arr_p)[10])(int, void(*)(int)))(int)
int* void (*…)(int)
(*…[10])() (*…[10])(int, void(*)(int))
(*p) (*fun_arr_p)

我花了两天才写到这里……不过我现在又晕了……
我们梳理一下:
普通的函数指针是这样的:void (*p)(int);
普通的函数指针数组是这样的:void (*p[10])(int);
普通的指向函数指针数组的指针是这样的:void (*(*p)[10])(int);
如果函数的返回值是函数指针类型int(*)(),那上面的指针是这样的:
int(*(*(*p)[10])(int))();
好吧,我只能写到这了。
既然返回值是函数指针,那么如果这个函数指针所对应的函数类型如果返回值仍然是个函数指针该怎么办呢?你有没有发现子子孙孙无穷尽呢?
C中对于逆天函数及函数指针的折中解决方法
如果C中全部是这种逆天的定义,那代码就直接没办法看了。后来C标准中出现了typedef,用来解决这种超复杂定义的问题。
typedef的作用是给类型定义别名,因此,我们可以把复杂的类型用简单的方式定义。例如:
对于函数:
void (*fun2(int a, void(*b)(int)))(int);
我们将其返回值类型定义一个别名:
typedef void (*HANDLER)(int);

这样,函数就可以定义为:
HANDLER fun2(int a, HANDLER b);
看,这样就简洁多了,而且看起来比较符合普通函数的定义。
对于函数指针:
void (*(*fun2_p2)(int, void(*)(int)))(int);
我们可以写为:
HANDLER (*fun2_p2)(int, HANDLER);
是不是简单了许多呢?注意哦,*fun2_p2的括号仍然不能去,优先级问题,不加括号会被理解为”*(fun2_p2(int, HANDLER))”,前面已经解释过了。
下面分析一下逆天的指针:
void (*(*(*fun_arr_p)[10])(int, void(*)(int)))(int);
结果:
HANDLER (*(*fun_arr_p)[10])(int, HANDLER);
强制转换为函数指针
(void (*)(int))fun”,这是强制类型转换的效果。
几个例子
1、
(*(void(*)())0)();
首先,我们先判断最外面,显然是两个括号”()()”,这说明这句话应该是在调用某个无参函数。
然后我们分析”*(void(*)())0″,由于*的优先级比较低,所以代码可以理解为:
*((void(*)())0)
也就是”*(…)”的形式,这说明,整句代码是在用某个函数指针调用函数。
第三步,就是”(void(*)())0″了,这个很好理解了,将地址0强制转换为”(void(*)())”型指针。
最后,整句代码的意思就是调用地址0处的无参函数。
2、
int *a;                    //指针
int **a;                //指向指针的指针
int a[10];                //数组
int *a[10];            //存储指针的数组
int (*a)[10];            //指向数组的指针
int (*a)(int);            //指向函数的指针
int (*a[10])(int);        //存储指向函数的指针的数组
int (*(*a)[10])(int);    //指向上面数组的指针
一段验证代码
上面说到的大部分内容的测试代码,可以很方便的验证结论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include<stdio.h>
 
typedef void (*HANDLER)(int);
 
void fun(int a) {
    printf("Hi, I'm fun! a = %d\n", a);
    return;
}
 
int* fun0(int* a) {
    return a;
}
 
//(void (*)(int)) (fun)(int a, int b) {
void (*fun1(int a, int b))(int) {
    printf("fun1 invoked. a = %d, b = %d\n", a, b);
    return fun;
}
 
void (*fun2(int a, void(*b)(int)))(int) {
    printf("fun2 invoked. a = %d, I will invoke your function and return the function\n", a);  
    (*b)(a);
    return b;
}
 
int main() {
 
    int a[3] = {1, 2, 3};
    int (*b);
    int (*p)[3];
    printf("p = %p, sizeof(p) = %d, sizeof(a) = %d\n", p, sizeof(p), sizeof(a));
    p = a;
    printf("p = a\n");
    printf("&p = %p, p = %p, a = %p\n", &p, p, a);
    printf("p[0] = %p, *p[0] = %p, p[1] = %p, *p[1] = %d, sizeof(p[1]) = %d, (*p)[1] = %d\n", p[0], *p[0], p[1], *p[1], sizeof(p[1]), (*p)[1]);
    printf("**p = %d, *p = %p\n", **p, *p);
 
    p = &a;
    printf("p = &a\n");
    printf("&p = %p, p = %p, a = %p\n", &p, p, a);
    printf("p[0] = %p, *p[0] = %p, p[1] = %p, *p[1] = %d, sizeof(p[1]) = %d, (*p)[1] = %d\n", p[0], *p[0], p[1], *p[1], sizeof(p[1]), (*p)[1]);
    printf("**p = %d, *p = %p, *a = %d\n", **p, *p, *a);
 
    int i = a;
    printf("i = a, i = %d, sizeof(a) = %d\n", i, sizeof(a));
    i = &a;
    printf("i = &a, i = %d, sizeof(&a) = %d\n", i, sizeof(&a));
 
    printf("sizeof(fun) = %d, fun addr = %p\n", sizeof(fun), fun);
 
    void (*fun_p)(int);
    fun_p = &fun;
    (*fun_p)(1);
 
    int* (*fun0_p)(int*);
    fun0_p = &fun0;
    i = 0;
    printf("fun0: I'm return int point. a = %d\n", *(*fun0_p)(&i));
 
    void (*(*fun1_p1)(int, int))(int);
    fun1_p1 = &fun1;
    fun_p = (*fun1_p1)(2, 3);
    (*fun_p)(4);   
 
    void (*(*fun2_p2)(int, void(*)(int)))(int);
    fun2_p2 = &fun2;
    (*fun2_p2)(5, fun)(6);
 
    HANDLER (*fun1_p3)(int, int);
    fun1_p3 = &fun1;
    fun_p = (*fun1_p3)(7, 8);
 
    HANDLER (*fun2_p4)(int, HANDLER);
    fun2_p4 = &fun2;
    (*fun2_p4)(9, fun)(10);
 
    void (*(*fun2_arr[4])(int, void(*)(int)))(int);
    for (i = 0; i < sizeof(fun2_arr) / sizeof(fun2_arr[0]); i++) {
        fun2_arr[i] = &fun2;
        fun2_arr[i](i, fun)(i);
    }
 
    void (*(*(*fun2_arr_p)[4])(int, void(*)(int)))(int);
    fun2_arr_p = &fun2_arr;
    for (i = 0; i < sizeof(*(fun2_arr_p)) / sizeof((*fun2_arr_p)[0]); i++) {
        (*fun2_arr_p)[i] = &fun2;
        (*fun2_arr_p)[i](i, fun)(i);
    }
 
    void (*fun_arr[10])(int);
    void (*(*fun_arr_p)[10])(int);
    fun_arr_p = &fun_arr;
 
    int(*(*(*fun_arr_p1)[10])(int))();
    //int (*fun_p2)(int a, int (*b)(int));
    //void (*fun(int, int))(int);
 
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值