究竟用 LinkList L 还是 LinkList *L?

C语言的学习总是遇到各种问题,而且很多问题几乎离不开指针这鬼玩意。在学习数据结构与算法之蜜汁C语言版的时候,我也是被LinkList这个东西弄得心累啊!

毕竟后面发现严蔚敏版的教材的代码应该是C++风格(我还没学过),里面充斥着LinkList &L这种写法。然后,每次将代码copy到编辑器后,一编译就泪流满面。事实上,LinkList &L是C++的一种引用语法。

其实在参数中究竟用哪一种不重要,关键是知道自己在做什么(换句话说就是为什么要用它)

接下来有必要先说一下链表的结构,

1
2
3
4
5
6
typedef int ElemType;

typedef struct Node {
ElemType data;
struct Node *next;
} Node, *LinkList;

如上,需要注意的地方是LinkList的身份是什么。在这里,*LinkList等同于struct Node,换句话说,LinkList等同于struct Node *,它是一个指向结构体Node的指针!接着,我们要清楚,LinkList *L就是指向结构体Node的指针的指针了!

对于LinkList L: L是指向定义的Node结构体的指针,可以用->运算符来访问结构体成员,即L->data,而*L就是个Node型的结构体了,可以用点运算符访问该结构体成员,即(*L).data

对于LinkList *L: L是指向定义的Node结构体指针的指针,所以*L是指向Node结构体的指针,可以用->运算符来访问结构体成员,即(*L)->data,当然,**L就是Node型结构体了,所以可以用点运算符来访问结构体成员,即(**L).data

在链表操作中,我们常常要用链表变量作物函数的参数。这时,用LinkList L还是LinkList *L就很值得考虑深究了,一个用不好,函数就会出现逻辑错误。

其准则是:如果函数会改变指针L的值,而你希望函数结束调用后保存L的值,那你就要用LinkList *L。这样,向函数传递的就是指针的地址,结束调用后,自然就可以去改变指针的值;而如果函数只会修改指针所指向的内容,而不会更改指针的值,那么用LinkList L就行了。

这里大家可能有点懵,我觉得关键在于理解C语言函数参数传递的原理!

注意理解以下例子!

1
2
3
4
int a = 6;
void getReturn(int a) {
a = 10;
}

函数外部是得不到a变化为10的结果的,因为a在传进函数的时候被复制了一次,虽然两个a的内容相同,但是它们已经是两个不同的变量了!为了得到这个结果你需要使用:

1
2
3
4
int a = 6;
void getReturn(int *a) {
*a = 10;
}

才能得到变化值的int变量。

如果你是想改变指针a的值呢?

1
2
3
4
int b = 10;
void getReturn(int *a) {
a = &b;
}

酱紫肯定是无法得到变化了指针地址的a指针的。因为a指针在传进函数的时候被复制了一次,也就是说即使我们在函数内部改变了a指针的值(即地址),外面的a指针的值也不受影响!

此时你需要修改为:

1
2
3
4
int b = 10;
void getReturn(int **a) {
*a = &b;
}

才能得到一个变化了指针地址的a指针。注意这里a在传进函数时也会被复制,但无伤大雅,因为我们需要改变的是*a而不是a,前者没有被复制。

最后结合具体实例来进一步理解下如上所有内容:

  1. 初始化链表 InitList

    1
    2
    3
    4
    void InitList(LinkList *L) {
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    }

    函数调用完毕后,L会指向一个空的链表,即会改变指针的值,所以要用LinkList *L

  2. 清空链表 ClearList

    1
    2
    3
    4
    5
    void ClearList(LinkList L) {
    LinkList p;
    while(p = L->next)
    free(p);
    }

    函数调用完后不会改变指针L的值,只会改变指针L所指向的内容(即L->next的值),所以要用LinkList L

  3. 销毁链表 DestroyList

    1
    2
    3
    4
    5
    6
    7
    void DestroyList(LinkList *L) {
    LinkList p;
    while(p = (*L)->next )
    free(p);
    free(*L);
    *L = NULL;
    }

    释放链表L申请的内存,使L的值重新变为NULL,所以会改变L的值,得用LinkList *L

最后,大家可能会遇到这个问题,what aboutListInsert?这个是一个值得深思的问题呵呵!目前,我觉得在带有头结点上使用LinkList L也是可以的。在没有头结点的时候,如果还是使用LinkList L就可能有问题,因为我们可能会在第一个结点前插入新结点呢!所以,我也是一脸懵逼啊,可能大伙们都在遵循潜在规则吗?

潜在规则:访问用LinkList L,修改用LinkList *L

欢迎大家指出错误的地方,我很水的!

0%