关于指针在函数方面应用的疑惑!
今天,我在某本书上看到链表的完整操作过程,小弟可能刚入门,有个非常大的疑惑,我先贴源代码:
/*单链表的各种操作*/
# define null 0
typedef char ElemType; /* 字符型数据*/
typedef struct LNode
{
ElemType data;
struct LNode *next;
};
setnull(struct LNode **p);
int length (struct LNode **p);
ElemType get(struct LNode **p,int i);
void insert(struct LNode **p,ElemType x,int i);
int delete(struct LNode **p,int i);
void display(struct LNode **p);
main()
{
struct LNode *head,*q; /*定义静态变量*/
int select,x1,x2,x3,x4;
int i,n;
int m,g;
char e,y;
head=setnull(&head); /*建议链表并设置为空表*/
printf( "请输入数据长度: ");
scanf( "%d ",&n);
for(i=1;i <n;i++);
{
printf( "将数据插入到单链表中: ");
scanf( "%d ",&y);
insert(&head,y,i);} /*插入数据到链表*/
display(&head); /*显示链表所有数据*/
printf( "select 1 求长度 length()\n ");
printf( "select 2 取结点 get()\n ");
printf( "select 3 求值查找 locate()\n ");
printf( "select 4 删除结点 delete()\n ");
printf( "input your select: ");
scanf( "%d ",&select);
switch(select)
{
case 1:
{
x1=length(&head);
printf( "输出单链表的长度%d ",x1);
display(&head);
}break;
case 2:
{
printf( "请输入要取得结点: ");
scanf( "%d ",&m);
x2=get(&head,m);
printf(x2);
display(&head);
}break;
case 3:
{
printf( "请输入要查找的数据: ");
scanf( "%d ",&e);
x3=locate(&head,e);
printf(x3);
display(&head);
}break;
case 4:
{
printf( "请输入要删除的结点: ");
scanf( "%d ",&g);
x4=delete(&head,g);
printf(x4);
display(&head);
}break;
}
}
}
setnull(struct LNode **p)
{
*p=null;
}
int length (struct LNode **p)
{
int n=0;
struct LNode *q=*p;
while (q!=null)
{
n++;
q=q-> next;
}
return(n);
}
ElemType get(struct LNode **p,int i)
{
int j=1;
struct LNode *q=*p;
while (j <i&&q!=null)
{
q=q-> next;
j++;
}
if(q!=null)
return(q-> data);
else
printf( "位置参数不正确!\n ");
}
int locate(struct LNode **p,ElemType x)
{
int n=0;
struct LNode *q=*p;
while (q!=null&&q-> data!=x)
{
q=q-> next;
n++;
}
if(q==null)
return(-1);
else
return(n+1);
}
void insert(struct LNode **p,ElemType x,int i)
{
int j=1;
struct LNode *s,*q;
s=(struct LNode *)malloc(sizeof(struct LNode));
s-> data=x;
q=*p;
if(i==1)
{
s-> next=q;
p=s;
}
else
{
while(j <i-1&&q-> next!=null)
{
q=q-> next;
j++;
}
if(j==i-1)
{
s-> next=q-> next;
q-> next=s;
}
else
printf( "位置参数不正确!\n ");
}
}
int delete(struct LNode **p,int i)
{
int j=1;
struct LNode *q=*p,*t;
if(i==1)
{
t=q;
*p=q-> next;
}
else
{
while(j <i-1&&q-> next!=null)
{
q=q-> next;
j++;
}
if(q-> next!=null&&j==i-1)
{
t=q-> next;
q-> next=t-> next;
}
else
printf( "位置参数不正确!\n ");
}
if(t=null)
free(t);
}
void display(struct LNode **p)
{
struct LNode *q;
q=*p;
printf( "单链表显示: ");
if(q==null)
printf( "链表为空! ");
else if (q-> next==null)
printf( "%c\n ",q-> data);
else
{
while(q-> next!=null)
{
printf( "%c-> ",q-> data);
q=q-> next;
}
printf( "%c ",q-> data);
}
printf( "\n ");
}
在这段代码中,每个函数中的参数都有struct LNode **p,比哪void display(struct LNode **p)这个函数,如果我将它改为void display(struct LNode *p),然后用q=p;进行赋值,结果还不是一样的吧,为什么要传指针的指针,我一直想不通-郁闷中,有没有谁帮我解答这个问题?
[解决办法]
这主要是指针和函数的实现机制问题,是c/c++的东西,在函数的参数中,如果想返回操作结果,大概有三种方法,一,用返回值返回(return),二,用2级指针,即指针的指针,并且涉及到值要用引用,才能返回操作结果,三,用引用(&),只有c++中可以,c中没有这种方法.这是c/c++很基本很重要也很难的东西.建议你有时间多看下书.
[解决办法]
传指针的指针的一个好处就是可以在函数内部分配空间.
比如,下面的insert函数
void insert(struct LNode **p,ElemType x,int i)
{
int j=1;
struct LNode *s,*q;
s=(struct LNode *)malloc(sizeof(struct LNode));
s-> data=x;
q=*p;
if(i==1)
{
s-> next=q;
p=s;//这儿应该是*p=s;吧,这样才能返回,如果你传入的仅仅是个*p,怎么作呢?
} //如果你让insert返回一个指针也可以,直接传入*p,然后在返回*p .
[解决办法]
比如,int *s=NULL;
我要用函数对s分配空间。
fun1(&s);//ok
fun2(s);//false
s=fun3(3);//ok
void fun1(int **p)
{
*p=(int *)malloc(sizeof(int)*10);
}
void fun2(int *p)
{
p=(int *)malloc(sizeof(int)*10);//只是分配给了形参临时变量
}
int* fun3(int *p)
{
p=(int *)malloc(sizeof(int)*10);
return p;
}
[解决办法]
使用二级指针主要是为了解决在函数内部改变链表头结点指针head的问题(要返回对它的修改)
还有其它解决方案,如将head定义成全局变量,或者规定空链表也有一个固定结点。
关于C函数的参数传递方式,你要记住只有传值一种!(传值意味着修改实参不影响形参的值)
说什么地址传递,只会越描越黑,(仅管它是官方说法,它们喜欢上升到理论层次上去解说)
fun(char *p){p= "hi ";} main(){char* str= "welcome "; fun(str); puts(str);}
查看运行结果可以发现指针str的值没有被fun()函数改变。
fun(char **pp){*pp= "hi "} main(){char* str= "welcome "; fun(&str); puts(str);}
函数fun()不能改变实参(&str)的值,但它可以修改这个二级指针所指向的指针str的值。
[解决办法]
void CreateList(LinkList *&L,elemtype a[],int n) //引用指针是由程序上下文所决定的!
一般建链表时首先想到定义 struct LNode *head; 有没有想过不把head定义成指针呢?
其实对于创建链表这个函数最好定义成:LinkList* CreateList(elemtype a[], int n)
不修改链表结构的话当然就简单些了,比如:void display(struct LNode *p)
仅仅修改链表结点数据域的值,不是在头结点处进行向前插入,都没有使用二级指针的必要
------解决方案--------------------
致:fenxue (Newton.cld)
程序代码我没看,说说我的看法吧
如果调用在链表中进行删除和插入等改变链表的函数时,应该把要链表的头指针的地址作为参数
相反如果不用&head,只用head的话,例如:在head后面插入一个节点,那么在程序返回后,实际
上,在原函数中,head的next根本没有变化。你可以自己简单调试一下,记得以前自己也出现过
类似问题
还有为什么要使用LinkList *&L呢?以为首先你要保证传进来的参数是个指针呀,如果不是指针的话,后面的L=(LinkList *)malloc(***);就会出错
[解决办法]
牛顿先生:
如果你真的很想定义 void CreateList(LNode &L, elemtype a[], int n)
我得提醒你,要重新认识一下“空链表”这个概念,在以往它是指 LNode* phead=NULL;
现在我们定义 LNode Head={ '0 ',NULL}; 就是一个空链表,仅管它的确有一个结点,但这个结点是不被使用的,给这个链表增加一个结点:Head.next=new LNode; (Head.next)-> next=NULL;
这样定义链表有一个好处,就是在增删结点时,头结点是永恒不变的,并且头结点中的数据域还可以用来存放链表长度这一不能在常数时间内获取的信息(当然你程序中data是char型,用于记录链表长度表示范围太小了)。
如果接受上面的概念,你的程序上上下下要改很多地方,比如输出链表时不要显示头结点的值。
[解决办法]
致:fenxue (Newton.cld)
对可以这么说,不过在链表中一般传进来的都是指针,所以LinkList &L几乎没看见过,不过有的在定义时直接把LinkList定义成指针的形式,例如
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LinkList*; //很多时候都是这样写的
再看LinkList &L就很舒服了
[解决办法]
上面那个写错了一点
应该是下面这个
typedef struct LNode
{
ElemType data;
struct LNode *next;
}*LinkList; //很多时候都是这样写的
[解决办法]
re:yl02520
typedef struct LNode
{
ElemType data;
struct LNode *next;
}*LinkList;这样写确实不错,在其他的函数中传参也方便多了,但是指针的引用用的是在不多见,最好还是指针的指针。
另外除了uwinb所说的关于头节点的好处外,再补充一些,其实头节点本身就是(如果专门定义)为了增删操作方便而引入的一个辅助节点,他不应当存储任何真实节点信息,当然可以如uwinbs所说的等有用信息。这样的话,在增删时,就可以一是同仁的对待所有节点,而不必花费过多精力在第一个节点的插入和删除上。