在浅显的用过链表后,这几天我又开始仔细的重新看数据结构这本书。但突然发现一个细节:书上对有头指针的单链表进行初始化(分配内存)时,是将结构体指针的地址(指针的指针,也就是二级指针)作为参数进行操作。在想过很多,又查过很多博客后,现在感觉对指针的理解又更深刻了。
指针,其实就是一个特殊的变量,用这个变量来存储另外一个普通变量的地址,那么我们就从普通的变量开始说起。
我们在没有接触指针前,不会去想声明的变量会被存在哪里,也就是说,我们只会关注变量的值。但其实在接触指针后,我们会知道,指针就是一个值的地址,一般用法如下:
//声明一个空指针
int *p;
//给这个空指针分配空间(考虑一下这个地址分配给谁了)
p = (int *)malloc(sizeof(int));
//给分配的空间一个值
*p=1;
//输出指针指的地址所对应的值
printf("指针p所指的地址存储的值为%d\r\n",*p);
//输出指针指的地址
printf("指针p所指的地址为%x\r\n",p);
结果如下:
但其实反过来用的话也是一样,比如说不用(声明)指针也是可以的,只是操作起来不方便而已,代码如下:
//声明一个变量a并赋值
int a=1;
//输出a的值
printf("a的值为%d\r\n",a);
//输出存储a的值的地址
printf("a的地址为%x\r\n",&a);
也许你觉得之前说的都是废话,那我们在前面留了一个问题的尾巴:在声明一个指针后,必须要给其分配一定的空间,那么,这个“其”是谁呢?
那么我们还是从普通变量说起,我们在声明一个变量后,只会考虑给他赋值,那么我们声明一个指针后,在赋值前又要分配空间,在我看来,这个分配空间其实也是一种赋值,那其实这个赋值就是给变量的指针(也就是地址)进行赋值,换句话说,也就是分配一段地址以存储变量的值,并用p这个变量来储存“储存变量的值的地址”(为了断句清楚并强调,我用引号引起来)。
那将这些想清楚的话,我们现在再回到刚开始的那个问题——指针的指针。当我们声明一个指针的指针时,也就是int **p;
p代表一个用来存储指针(地址值)的指针。
那么总结并归纳一下之前的想法(下面图中的箭头由相对数据端指向相对地址端):声明变量时已经给分了一定的空间来存储数值,那么我们直接赋值变量值;
声明一个普通指针(与指针的指针,也就是二级指针作对比)时,已经分配一段内存来存储指针的地址值,我们需要先把一个地址值存入这段内存,然后再赋值到存入的那个地址值;
然后我们再说指针的指针(二级指针),那么就是说,声明一个二级指针时,已经自动分配了一段内存来存储指针的指针(存放一个内存地址值的内存地址值),然后再顺着之前的思路推,我们需要先给指针的指针分配一段内存来存储指针,然后再给指针分配一段内存来存储值,最后再赋值即可。
一般在使用时,一级指针和二级指针用的比较多,多级指针用起来过于复杂,一般不用。说实话,我在写二级指针时都差点又混淆了……
最后,我想把二级指针放入开始说的环境下应用:链表初始化,如果对链表不是很熟悉,也没关系,此处基本上不涉及链表的使用,先看代码
//定义一个结构体,别名为NODE
typedef struct Node {
//int类型的data成员
int data;
//指向下一个节点的结构体指针,不熟悉可以忽略
struct Node *next;
}NODE;
//初始化链表
//输入参数为一个NODE类型的结构体指针
void ListInit(NODE *head)
{
//为结构体分配一段内存,并把这段内存的内存值存入其指针中
head = (NODE *)malloc(sizeof(NODE));
//声明头指针后紧跟着的那个结构体指针(我表述不太清楚,但不熟悉可以跳过)
(head)->next = NULL;
}
//下面的初始化函数是书上的写法,它很明显使用了二级指针,但我认为完全没有必要去声明一个二级指针,因为我们只需要获取地址,不需要储存地址
/*
void ListInit(NODE **head)
{
//为结构体分配一段内存,并把这段内存的内存值存入其指针中
(*head) = (NODE *)malloc(sizeof(NODE));
//声明头指针后紧跟着的那个结构体指针(我表述不太清楚,但不熟悉可以跳过)
(*head)->next = NULL;
}
*/
int main(void)
{
NODE *p;
ListInit(&p);
printf("%d\r\n%X\r\n%X\r\n",*p,p,&p);
return 0;
}
很明显,在main函数中,我们声明了一个一级指针,链表的初始化函数也只需要一个一级指针做输入参数,但为什么要取一级指针的地址,也就是一个二级指针作为参数输入呢?
结合前面所分析的,我在这儿还需要再提醒一点:c语言的输入参数是传值输入,不管输入参数是什么类型,都是传值。
那么试想,在函数中声明的那个一级指针被储存在一段临时内存中,如果我们传入一个一级指针,那么我们只是将这个一级指针,也就是这个地址值存在了一个临时内存中,不管在函数里做了什么工作,到最后函数执行完,那个临时内存会被释放,一切照旧。这个和下面的情况非常类似:
void Add(int a)
{
a += 1;
}
int main(void)
{
int a=1;
Add(a);
printf("%d",a);
return;
}