c语言——指针漫谈

来源:互联网 发布:网络暴力的案例 编辑:程序博客网 时间:2024/06/09 09:13

在浅显的用过链表后,这几天我又开始仔细的重新看数据结构这本书。但突然发现一个细节:书上对有头指针的单链表进行初始化(分配内存)时,是将结构体指针的地址(指针的指针,也就是二级指针)作为参数进行操作。在想过很多,又查过很多博客后,现在感觉对指针的理解又更深刻了。

指针,其实就是一个特殊的变量,用这个变量来存储另外一个普通变量的地址,那么我们就从普通的变量开始说起。

我们在没有接触指针前,不会去想声明的变量会被存在哪里,也就是说,我们只会关注变量的值。但其实在接触指针后,我们会知道,指针就是一个值的地址,一般用法如下:

//声明一个空指针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代表一个用来存储指针(地址值)的指针。

那么总结并归纳一下之前的想法(下面图中的箭头由相对数据端指向相对地址端):声明变量时已经给分了一定的空间来存储数值,那么我们直接赋值变量值;

普通变量

声明一个普通指针(与指针的指针,也就是二级指针作对比)时,已经分配一段内存来存储指针的地址值,我们需要先把一个地址值存入这段内存,然后再赋值到存入的那个地址值;

一级指针

然后我们再说指针的指针(二级指针),那么就是说,声明一个二级指针时,已经自动分配了一段内存来存储指针的指针(存放一个内存地址值的内存地址值),然后再顺着之前的思路推,我们需要先给指针的指针分配一段内存来存储指针,然后再给指针分配一段内存来存储值,最后再赋值即可。

二级指针

一般在使用时,一级指针和二级指针用的比较多,多级指针用起来过于复杂,一般不用。说实话,我在写二级指针时都差点又混淆了……

最后,我想把二级指针放入开始说的环境下应用:链表初始化,如果对链表不是很熟悉,也没关系,此处基本上不涉及链表的使用,先看代码

//定义一个结构体,别名为NODEtypedef 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;}

我相信,只要看过c语言书的人,都不会掉入这个坑,都知道a的值还是1,不会变成2。那么类似的,我们如果将声明的一级指针作为输入参数,这个指针以及和它有关的一切都不会有任何变化,那么怎么办呢?就像我们需要改变已经声明的普通变量时将其地址(也就是指针)作为输入参数输入,我们现在为了改变已经声明的一级时要将存储其地址的地址(也就是指针的指针)作为输入参数输入,即可达到效果。

这个用法基本上算是类推,但之前没有仔细思考过原理,今天有一点想法,感觉比较靠谱,和大家分享。

因为按之前说的,我们声明一个一级指针时已经自动分配了一段内存来储存一级指针的内存地址,那么将二级指针作为参数输入时,相当于将这段已经分配了的地址值赋给ListInit()函数中的head的地址的地址值(此处指未注释的版本,如果是注释的版本,则直接将值赋给head),这么一来,函数中的操作都会在这个既有的地址上操作,而不是在临时内存上操作,在函数结束时也不会被清除。

把这些搞清楚之后,我对指针的理解也更深了,而且对于一直很陌生的malloc函数我也做了一回实验,如果不连续的调用malloc函数,其分配的内存地址一般不会是连起来的,但是如果连续调用,除非内存地址不够,一般会连起来,效果如下图(倒数第二块代码的运行结果)。

malloc

第一次讨论这么复杂的问题(至少之前对我一直很复杂),希望大家看后觉得有什么建议麻烦留言!

原创粉丝点击