C语言指针错误使用分析

  在数据结构中,最常用的就是指针,C++相对于C语言,可以使用引用,相对比较方便,但是使用纯C的话,还是有点不适应,什么都要指针,下面就是我自己犯的一个错误,将一个指针传递给函数,在函数中尝试修改一个指针的相关信息,以期望达到修改函数外部该指针对应的相关信息:

#include<stdio.h>
#include<stdlib.h>
struct Node{
    int data;
    struct Node* next;
};
typedef struct Node* LinkList;
void CreateList(LinkList L){
    LinkList t;
    L = (LinkList)malloc(sizeof(struct Node));
    t = (LinkList)malloc(sizeof(struct Node));
    t->data =2;
    t->next = NULL;
    L->data = 1;
    L->next = t;
}
main(){
    LinkList L;
    CreateList(L);
    printf("%d\n",L->data);
}

  上面的代码一旦运行就会崩溃,因为,在main函数中,首先声明了一个LinkList的变量,其实是struct Node的指针变量,该变量此时没有赋值,则为空NULL。然后调用函数CreateList,将指针变量作为参数传递给函数之后,在函数中为该指针变量赋值一个新申请地址块的首地址,然后为新申请的地址块的data域和指针域赋值,注意是为新的地址块,而不是指针变量,但可以说是指针变量保存的地址对应的地址单元,但这个地址单元并不是实参所指向的那个空间。

  这里涉及最熟悉的概念,形参与实参,函数定义时的使用的是形参,调用函数,传递的是实参,在函数中,对传递进来的参数进行操作,并不会体现到实参上面,比如下面的例子:

#include<stdio.h>
void add(int i){
    i = i+5;
}
main(){
    int x = 10;
    printf("before : %d\n",x);
    add(x);
    printf("after : %d\n",x);
}
//before : 10
//after : 10

  要实现在函数内部的操作体现到实参上,可以使用指针的方式,比如上面的例子:

#include<stdio.h>
void add(int *i){//传递的i是一个int型的指针(地址)
    (*i) = (*i)+5;
}
main(){
    int x = 10;
    printf("before : %d\n",x);
    add(&x);//传递地址
    printf("after : %d\n",x);
}
//before : 10
//after : 15

  上面这段代码也很好理解,函数定义时使用的是一个指针类型的形参,传递的是一个指针类型的实参,函数虽然接收的是一个形参,但是这个形参保存的却是实参内容的地址。虽然在函数中,只要不使用*符号来操作传入的指针形参,那么,对传入的形参的任意操作都不会作用到实参上,比如下面这个例子:

#include<stdio.h>
void add(int *i){//传递的i是一个int型的指针(地址)
    int y = 5;
    i = &y;//尝试让指针i指向另外一个地方,会成功,但是不会影响到实参。
}
main(){
    int x = 10;
    printf("before : %d\n",x);
    add(&x);//传递地址
    printf("after : %d\n",x);
}
//before : 10
//after : 10

  所以在回到最上面的代码,你就知道为什么不可以了。但是如果非要实现对形参L的操作作用到实参L,可以用下面几个方法:

Case 1:

#include<stdio.h>
#include<stdlib.h>
struct Node{
    int data;
    struct Node* next;
};
typedef struct Node* LinkList;
void CreateList(LinkList L){
    LinkList t;
    t = (LinkList)malloc(sizeof(struct Node));
    t->data =2;
    t->next = NULL;
    L->data = 1;
    L->next = t;
}
main(){
    struct Node L;//注意这里是一个已经分配了内存空间的L,这一点很重要
    CreateList(&L);
    printf("%d\n",L.data);
}
//输出1

  看了上面的代码,你可能会疑惑,为什么传入的一个指针,没有使用解引用*,照样修改了实参L的内容呢?其实这样想也很正常,因为,传入的就是L的地址呗,CreateList函数内部也只是使用的L地址的副本,但是,通过该指针,可以找到实参的内容的内存地址,对吧?有了这个地址,是不是可以访问这个地址的内容了呢?这是肯定的,然后,我们不使用解引用来修改L的指向,比如*L = NULL; 这样就真的作用到实参了,我们并不需要这样做。我们现在只需要修改形参(此时实参和形参都指向一个地方)所指向的空间的内容,比如L->data = 1;这是可以成功作用到实参的,其实也不算作用到实参,因为实参仍指向原来的地方。

  在主函数中,也不一定必须创建一个结构体L,可以创建一个该结构体的指针,再用malloc为该指针分配内存,原理是一样的。

 

Case 2:

#include<stdio.h>
#include<stdlib.h>
struct Node{
    int data;
    struct Node* next;
};
typedef struct Node* LinkList;
void CreateList(LinkList *L){//注意这里是指针的指针
    //解引用一次,就是L指向的内存,给该内存赋值
    (*L) = (LinkList)malloc(sizeof(struct Node));
    LinkList t;
    t = (LinkList)malloc(sizeof(struct Node));
    t->data =2;
    t->next = NULL;
    (*L)->data = 1;
    (*L)->next = t;
}
main(){
    LinkList L;
    CreateList(&L);
    printf("%d\n",L->data);
}
//输出1

  其实上面的代码也挺好理解的,和上面的原理是一样的,在main函数中声明一个指针L,初始值为NULL,虽然L的值是NULL,但是仍旧有一段空间保存这个NULL,这就是L的地址,然后将该指针的地址(也是指针)传递给函数,在CreateList函数中解引用一次,获得的就是保存该指针的内容,此时申请了一段空间,将起始地址赋值给该指针(L)的内容,也就是说L现在指向了新申请的内存块,这个是实打实的作用到了实参上。

 

  

posted @ 2018-04-12 23:34  寻觅beyond  阅读(936)  评论(0)    收藏  举报
返回顶部