关于一级指针和二级指针的简单见解
一级指针形式如: int *p
二级指针形式如: int **p
可能很多初学者会疑惑在函数参数里面到底应该用一级指针还是二级指针。
例子:
第一个是链表
一个链表结构:
typedef struct Node
{
int value;
struct Node *next;
}
对于添加数据到链表尾部这个函数,传入的参数必须是一个二级指针。
在传参的时候发生的赋值动作是:
Node *hNode;
Node **pNode = &hNode;
pNode指向了hNode的地址,因此*pNode的使用效果相当于操作hNode的值。
可以改变hNode的指向。
那如果参数用一级指针会有什么效果。
此时在传参的时候发生的赋值动作是:
Node *hNode;
Node *pNode = hNode;
形参pNode此时和hNode都指向同一块内存(这块内存可能是NULL)。
此时添加一个新的结点到链表尾部,而恰好此时链表为NULL。
添加后形参pNode会指向一块新的内存,而此时hNode仍然是指向原来的那块内存(即NULL)。
在函数返回的时候,hNode仍是指向NULL,形参pNode作为局部变量将会自动销毁,而它所指向的那块内存因为是动态分配的
所以没有被非局部指针指向或被释放,因此发生内存泄漏了。
所以这里必须要用二级指针。
再举一个常见例子。
定义一个函数,给一个整形指针(初始为NULL)分配指定的内存。
错误做法:
void mallocMem(int *p,int size)
{
p = new int[size];
}
许多人会以为函数形参为指针意思就是把原来的指针直接传进去函数中使用,其实即使形参是指针,也会发生复制。
在上面谈及链表的时候我也说过,假如我们在main函数要调用mallocMem传递一个指针 int *s; 那么在函数调用处会发生一个赋值动作,
p = s; 所以事实上 p 和 s 是两个指针,只不过它们指向的内存地址是一样的。
p = new int[size]后 p就相当于抛弃了 s ,而指向了一个新的内存,在函数返回时,s还是指向原来的地方(NULL). 而且还会发生内存泄漏。
正确做法:
void mallocMem(int **p,int size)
{
*p = new int[size]; //这里s也指向了新的内存地址。
}
必须用二级指针的另外几个链表操作函数还有:删除结点 。
所以凡是涉及到会造成指针所指向地方会发生内存动态分配的操作(new delete),都必须用二级指针才能保证安全。
而剩下的就是不必用二级指针的链表函数(遍历,合并等等)。
简单来说:一级指针能做到的事情二级指针都能做到,反之不然。
大家在使用的时候要好好想想到底要对这个指针做什么操作。