一级指针与二级指针在动态链表中的应用
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; }