春暖花开

再谈 C 语言中的指针

据说,指针是 C 语言中最灵活的地方,但可能也是 C 语言中最容易出错的地方。

指向指针的指针是指针的指针:

前两天,写一个关于十字链表存储稀疏矩阵的数据结构题,结果在初始化的时候就出现语法错误,而且折腾好久都没发现,先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef int Status;
typedef int ElemType;
#define OK 1
#define OVERFLOW 0
typedef struct OLNode{
int i, j; //该非零元的行和列下标
ElemType e;
struct OLNode *down, *right;
} OLNode, *OLink;
typedef struct{
OLink *rhead, *chead;//行和列链表头指针向量
int mu, nu, tu;
}CrossList;

上面的代码是十字链表存储的结构定义。

在初始化时:

1
2
3
4
5
6
7
CrossList L;
CrossList *M = &L;
M->mu = m;
M->nu = n;
M->tu = t;//m,n.t is constants
M->rhead = (OLink) malloc ((m + 1) * sizeof(OLink));
M->chead = (OLink) malloc ((n + 1) * sizeof(OLink));

其实仔细点就应该发现,rheadchead 都是指向 OLink 的指针,所以它们是指向指针的指针,而我把它们转换成了指向 OLNode 的指针,自然就出错了,正确的应该是:

1
2
3
4
5
6
7
CrossList L;
CrossList *M = &L;
M->mu = m;
M->nu = n;
M->tu = t;//m,n.t is constants
M->rhead = (OLink *) malloc ((m + 1) * sizeof(OLink));
M->chead = (OLink *) malloc ((n + 1) * sizeof(OLink));

这一点在函数传参过程中也很关键,下面再说。

C 语言中的传参是值传参

关于C语言中的值传参,最简单的一个典型例子就是交换两个变量的值:

1
2
3
4
5
void wrongchange(int a, int b){
int t = a;
a = b;
b = a;
}

我们都知道,上面的这个函数是无法交换 a,b 的值的,因为传入函数的只是a,b的值,所以函数里面发生什么并不会改变a,b的值,正确的做法是传入a,b的地址:

1
2
3
4
5
coid correctchange(int *a, int *b){
int t = *a;
*a = *b;
*b = t
}

也就是说,当把一个变量传入一个函数以后,不管你在这个函数里面对该变量做什么,在函数外面,这个变量都不会有任何变化,因为传参时仅仅是把该变量的值传入函数中去了。所以当我们要对变量进行改变时,需要传入变量的地址。

对于一般的变量,我们容易注意到这一点,可对于指针变量,当要改变指针变量的值的时候,就容易忽略了这一点,因为想着已经是地址了。

这次错误是在一个关于广义表的题:题目要求删除广义表中所有值等于 x 的原子节点。

第一个错误发生在创建一个广义表的时候,我把一个指向广义表的指针传入函数,这是没问题的。但是,后面就出现问题了:

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
typedef int AtomType;
typedef enum{ATOM,LIST}ElemTag;
typedef struct GLNode {
ElemTag tag;// = ATOM or = LIST
union {// 原子结点和表结点的联合部分
AtomType atom;
struct {struct GLNode *hp, *tp;} ptr;//ptr.hp,ptr.tp分别指向表头和表尾
};
}GLNode, *GList;
void CreatGList(GList L){
//part1
int i;
GList p = L;
p->tag = LIST;
p->ptr.hp = (GList) malloc (sizeof (GLNode));
if(!p->ptr.hp){
printf( "Error!" );
exit(0);
}
p->ptr.hp->tag = ATOM;
p->ptr.hp->atom = rand()%10;
p->ptr.tp = (GList)malloc(sizeof(GLNode));
//part2
p = p->ptr.tp;
for( int i = 0; i < 40 ; i ++ ){
p->tag = LIST;
p->ptr.hp = (GList) malloc(sizeof(GLNode));
p->ptr.hp->tag = ATOM;
p->ptr.hp->atom = rand()%10;
p->ptr.tp = (GList) malloc (sizeof(GLNode));
p = q->ptr.tp;
}
p->tag = ATOM;
p->atom = rand()%10;
}

如果懂得一些 C 语言知识的话,很容易看出我的算法是什么样的,但是这样的做法是错的。首先,part1 是没有问题的,但part2有问题了:

L是一个指向GLNode 的指针,因次,把它传入函数以后,可以改变L->ptr.hp,L->ptr.tp的值,这是有效的,但是,再继续给L->ptr.tp指向的节点进行操作,就有问题了。在这个函数里面是没有问题的,但是,离开这个函数以后后,那些节点都是没有意义的。

要解决这个问题,其实很简单:不要调用新的函数,直接在 main 函数中进行就可以了。

下一个又是传参的问题。

删除节点时,实际上要改变的是地址的值,因此,我们需要传入的参数不能是地址了,而应该是地址的地址,传入的变量也就是指针的指针变量。这话说起来好别扭……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int DeleX(GList *L, AtomType x){
if(!*L)
return 1;//空表直接返回
else if((*L)->tag == ATOM){
if((*L)->atom != x)
return 1;//原子值不等于x
else{ //删除值等于x的原子项
free(*L);
*L = NULL;
return 1;
}
}
else{
DeleX(&((*L)->ptr.hp), x);
DeleX(&((*L)->ptr.tp), x);
return 1;
}
}

上面的代码是正确的,错误的就是传入的参数为 GList L,因此,传入的就是L的值,所以,无法改变L,也就不能成功删除节点了。

0%