skaiuijing
我们在学习指针时,经常有人会告诉我们指针是能直接修改内存的地址变量。
但是,真正理解指针作为变量的本质,才能真正理解指针。
int a,代表在内存中开辟一片空间,空间存放a的值。int *a,代表在内存中开辟一片空间,空间存放a的值,只不过此时a的值是一个地址,可以通过这个值找到另一块内存空间。
当我们进行 int *a = malloc(sizeof(xxx))的操作时,实际上就是改变这片空间存储的值。此时a就是一个有意义的值,它代表一个存在且可以被存放的内存空间的地址。
而二级指针,代表指向指针的指针。
以下是示例:(开发环境的编译器为32位,所以指针为4个字节)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include<stdio.h> #include<malloc.h>
int main() { int a =1; int *pa = &a; int **ppa = &(pa); int ***pppa = &(ppa);
//printf("pppa-value:%d",(int)(&(&(&a)))); //请思考这样做是否可行?
printf("pa-value:%d\n",(int)&a); printf("ppa-value:%d\n",(int)(&(pa))); printf("pppa-value:%d\n",(int)(&(ppa)));
}
|
为了直观理解,多级指针的内存布局如下(模型更接近汇编):
变量 |
存储的值 |
地址 |
int ***pppa |
6422016 |
*** |
int **ppa |
6422024 |
6422016 |
int *pa |
6422036 |
6422024 |
int a |
1 |
6422036 |
*其实int a和int a没有本质区别,它们只不过都是变量而已
这么一看,(int)(&(&(&a)))这个操作肯定是不行的,认为这样是可行的朋友,一定是把多级指针当成了这样:
1 2
| graph LR pppa(地址)-->ppa(地址)-->pa(地址)-->a(a的值)
|
参考上文的模型,这是不合理的。
真实的情况是这样的:
1 2 3 4 5
| graph LR pa(a的地址)--存放-->a(a的值) pb(pa的地址)--存放-->b(a的地址) pc(ppa的地址)--存放-->c(pa的地址) pd(pppa的地址)--存放-->d(ppa的地址)
|
所以说,(int)(&(&(&a)))中,只有&a是成立的,&(&a)完全是错误且荒谬的。
现在我们该面对多级指针了。
经常有人看见多级指针就害怕,其实多级指针并不神奇。
如果我们寻求ai或者搜索引擎,渴望得到多级指针的精髓,以二级指针为例,我们得到的答案往往是这样的:
- 动态内存分配:二级指针可以用于动态分配二维数组或更高维数组的内存。例如,分配一个
m x n
的二维数组,可以使用二级指针来管理内存。
- 传递指针并修改其值:通过使用二级指针,可以在函数中修改指针的值。例如,函数可以分配内存并将其地址返回给调用者。
- 实现复杂数据结构:二级指针在实现链表、树和图等复杂数据结构时非常有用。例如,链表中的每个节点可以包含一个指向下一个节点的指针,而这些指针本身可以通过二级指针进行操作。
- 多级间接引用:在某些情况下,程序需要多级间接引用数据。二级指针允许通过多级指针访问和操作数据。
- 节省内存:虽然二级指针本身并不会直接节省内存,但它们可以帮助更有效地管理和分配内存。例如,通过动态分配内存,可以避免分配不必要的内存块,从而提高内存使用效率。
二级指针最常见的用法就是修改一级指针。
示例:
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
| #include<stdio.h>
void sparrowfly() { printf("sparrowfly!\n"); }
void eaglefly() { printf("eaglefly!\n"); }
typedef void (*impletation)();
struct bird *table[] = { eaglefly, sparrowfly };
void change(struct function **x,int id) { *x = table[id]; }
int main() { impletation impll; struct function **twopinter = &impll;
change(twopinter, 1);
impll();
return 0; }
|
使用二级指针,这样就是传址调用,而不是传值调用了。
笔者也可以自己实验一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void change(struct function *x,int id) { x = table[id]; }
int main() { impletation impll; struct function **twopinter = &impll;
impll = eaglefly; change(impll, 1);
impll();
return 0; }
|
传址调用对于多级指针也是成立的,毕竟笔者前面已经说过,指针的本质是变量。
多级指针还有一个好处:增加程序可扩展性和解耦合。
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
| #include<stdio.h> #include<malloc.h>
typedef void (*fly)(struct bird **self);
struct bird{ fly common; };
struct sparrow{ struct bird *interface; char name; };
struct eagle{ struct bird *interface; char name; };
void sparrowfly() { printf("sparrowfly!\n"); }
void eaglefly() { printf("eaglefly!\n"); }
struct bird **fly_table[] = { (struct bird **)&(struct sparrow) { .interface = &(struct bird) {.common = sparrowfly}, .name = 's' }, (struct bird **)&(struct eagle){ .interface = &(struct bird){.common = eaglefly}, .name = 'e' } };
void polymorphism(int opt) { struct bird **p = fly_table[opt]; (*p)->common(p); }
int main() {
polymorphism(0);
return 0; }
|
这一段程序虽然self指针也可以被一级指针代替,但是当我们在main函数中修改一些局部指针变量时,二级指针的扩展性就体现出来了:
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
| #include<stdio.h> #include<malloc.h>
typedef void (*fly)(struct bird **self);
struct bird{ fly common; };
struct sparrow{ struct bird *interface; char name; };
struct eagle{ struct bird *interface; char name; };
void sparrowfly() { printf("sparrowfly!\n"); }
void eaglefly() { printf("eaglefly!\n"); }
struct bird **fly_table[] = { (struct bird **)&(struct sparrow) { .interface = &(struct bird) {.common = sparrowfly}, .name = 's' }, (struct bird **)&(struct eagle){ .interface = &(struct bird){.common = eaglefly}, .name = 'e' } };
void implement(struct bird **p) { (*p)->common(p); }
void polymorphism(int opt) { struct bird **p = fly_table[opt]; (*p)->common(p); }
struct bird *table[] = { eaglefly, sparrowfly };
void change(struct bird **self,int id) { (*self)->common = table[id] ; }
int main() { struct sparrow sparrow1; struct bird bird1; sparrow1.interface = &bird1;
struct bird **Ainterface ; Ainterface = malloc(sizeof(int)); *Ainterface = sparrow1.interface;
change(Ainterface,0); implement(Ainterface);
polymorphism(0);
return 0; }
|
当然,指针也并不是越多级就越好,我们也看到了,指针的本质仍然是变量,只是对存储的值有一定要求而已。过多级数的指针并不会带来更好的程序优化,相反,可能使程序性能、可读性、可维护性变差。