一级指针与二级指针在动态链表中的应用

btypedef struct Node
{
    int elem;
    struct node *next;
}node,*LinkList;

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

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

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

如果函数会改变指针L的值,而你希望函数结束调用后保存L的值,那你就要用LinkList *L,这样,向函数传递的就是指针的地址,结束调用后,自然就可以去改变指针的值;

而如果函数只会修改指针所指向的内容,而不会更改指针的值,那么用LinkList L就行了;

下面说个具体实例吧

#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;
typedef struct Node
{
    ElemType elem;
    struct Node *next;
}Node, *LinkList;

//初始化链表,函数调用完毕后,L会指向一个空的链表,即会改变指针的值,所以要用*L
void InitList(LinkList *L)
{
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
}

//清空链表L,使L重新变为空链表,函数调用完后不会改变指针L的值,只会改变指针L所指向的内容(即L->next的值)
void ClearList(LinkList L)
{
    LinkList p;
    while(p = L->next)
        free(p);
}

//销毁链表L,释放链表L申请的内存,使L的值重新变为NULL,所以会改变L的值,得用*L
void DestroyList(LinkList *L)
{
    LinkList p;
    while(p = (*L)->next )
        free(p);
    free(*L);
    *L = NULL;
}

int main()
{
    LinkList L=NULL;
    InitList(&L);
    ClearList(L);
    DestroyList(&L);
    return 0;
}
LinkList L  定义了一个LinkList的对象,叫L
LinkList *L 定义了一个可以指向LinkList对象的指针,叫L
(*L).elem   指针L指向的对象的成员变量elem,与L->next等价
L.elem      对象L的成员变量elem
L->next     指针L指向的对象的成员变量next
(*L)->next       指针L指向的对象指向的对象的成员变量next

 

                  关于一级指针和二级指针作为函数的参数

一:一级指针动态去申请内存

void  GetMemory(char  *p,  int  num)  
{  
           p  =  (char  *)malloc(sizeof(char)  *  num); //传过来的是P所指的地址,并不是P的地址,所以改变S不会改变P
}  
void  Test(void)  
{  
           char  *str  =  NULL;  
           GetMemory(str,  100);            //  str  仍然为  NULL              
           strcpy(str,  "hello");            //  运行错误  
}  

毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是  _p,编译器使  _p  =  p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。

这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

myMalloc(p)的执行过程:  

  分配一个临时变量char  *s,s的值等于p,也就是NULL,但是s占用的是与p不同的内存空间。此后函数的执行与p一点关系都没有了!只是用p的值来初始化s .然后s=(char  *)  malloc(100),把s的值赋成malloc的地址,对p的值没有任何影响。p的值还是NULL。   注意指针变量只是一个特殊的变量,实际上它存的是整数值,但是它是内存中的某个地址。通过它可以访问这个地址。  

二:二级指针动态申请内存

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”

void  GetMemory2(char  **p,  int  num)  
{  
           *p  =  (char  *)malloc(sizeof(char)  *  num);  //S指向的是P的地址,所以改变了P所指的内存单元
void  Test2(void)  
{  
           char  *str  =  NULL;  
           GetMemory2(&str,  100);            //  注意参数是  &str,而不是str  
           strcpy(str,  "hello");              
           cout<<  str  <<  endl;  
           free(str);              
}  

myMalloc(&p);

  将p的地址传入函数,假设存储p变量的地址是0x5555,则0x5555这个地址存的是指针变量p的值,也就是Ox5555指向p。   调用的时候同样分配一个临时变量char  **s,此时s  的值是&p的值也就是0x5555,但是s所占的空间是另外的空间,只不过它所指向的值是一个地址:Ox5555。   *s=(char  *)  malloc(100);这一句话的意思是将s所指向的值,也就是0x5555这个位置上的变量的值赋为(char  *)  malloc(100),而0x5555这个位置上存的是恰好是指针变量p,这样p的值就变成了(char  *)  malloc(100)的值。即p的值是新分配的这块内存的起始地址。     这个问题理解起来有点绕,关键是理解变量作函数形参调用的时候都是要分配一个副本,不管是传值还是传址。传入后就和形参没有关系了,它不会改变形参的值。myMalloc(p)不会改变p的值,p的值当然是 NULL,它只能改变p所指向的内存地址的值。但是myMalloc(&p)为什么就可以了,它不会改变(&p)的值也不可能改变,但是它可以改变(&p)所指向内存地址的值,即p的值

由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存

char  *GetMemory3(int  num)  
{  
           char  *p  =  (char  *)malloc(sizeof(char)  *  num);  
           return  p;  
}  
void  Test3(void)  
{  
           char  *str  =  NULL;  
           str  =  GetMemory3(100);              
           strcpy(str,  "hello");  
           cout<<  str  <<  endl;  
           free(str);              
}  

用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡

char  *GetString(void)  
{  
           char  p[]  =  "hello  world";  
           return  p;            //  编译器将提出警告  return语句返回指向“栈内存”的指针 
}  
void  Test4(void)  
{  
char  *str  =  NULL;  
str  =  GetString();            //  str  的内容是垃圾  
cout<<  str  <<  endl;  
}  

 return语句返回常量字符串     函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello  world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。

无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。 

char  *GetString2(void)  
{  
           char  *p  =  "hello  world";  
           return  p;  
}  
void  Test5(void)  
{  
           char  *str  =  NULL;  
           str  =  GetString2();  
           cout<<  str  <<  endl;  
}  

 

posted on 2018-09-22 10:01  tianzeng  阅读(872)  评论(0编辑  收藏  举报

导航