前言
链表中的二级指针问题经常会让人摸不着头脑,迷迷糊糊。经常搞不清楚什么时候用二级指针,什么时候用一级指针。本文咱们来简单讨论下这个问题。
代码
接口.cpp
#pragma once
#include <assert.h>
#include <string>
#include <iostream>
using namespace std;
typedef struct LinkNode{
string name;
int age;
LinkNode* next;
}MyNode;
// 打印链表
void printLinkList(MyNode* phead)
{
cout << "----------------链表遍历打印----------------" << endl;
if (phead == nullptr)
{
cout << "当前链表为空" << endl;
return;
}
MyNode* ptr = phead;
while (ptr)
{
cout << "当前节点值-" << "Name:" << ptr->name << " Age:" << ptr->age << endl;
ptr = ptr->next;
}
}
// 释放链表
void freeLinkList(MyNode** phead)
{
cout << "----------------链表内存释放----------------" << endl;
if (*phead == nullptr)
{
cout << "当前链表为空,跳过释放" << endl;
return;
}
MyNode* pMov = nullptr;
while (*phead !=nullptr)
{
pMov = *phead;
*phead = pMov->next;
free(pMov);
pMov = nullptr;
}
}
// 链表初始化
void initLinkListBad(MyNode* node, int age, string name)
{
cout << "----------------初始化链表----------------" << endl;
// node形参的修改不影响实参,此种初始化方法是错误的,实参还是nullptr
node = new MyNode;
node->age = age;
node->name = name;
node->next = nullptr;
}
// 链表初始化,需要修改头节点指针本身的值,则需要传递二级指针
void initLinkList(MyNode** pnode, int age, string name)
{
cout << "----------------初始化链表----------------" << endl;
// 修改头节点指针本身的值
*pnode = new MyNode;
(*pnode)->age = age;
(*pnode)->name = name;
(*pnode)->next = nullptr;
}
// 添加节点(尾部)
// pphead需要使用二级指针,入参需要传入指针的地址。如果使用一级指针,则相当于传值,并不会改变实际的值,只是修改了形参临时变量的值。
void addNode(MyNode** pphead, int age, string name)
{
cout << "----------------链表节点添加----------------" << endl;
MyNode* newNode = new MyNode;
newNode->name = name;
newNode->age = age;
newNode->next = nullptr;
// 如果在添加节点中初始化头节点,则参数需要传递二级指针,否则传递一级指针即可
if (*pphead == nullptr)
{
*pphead = newNode;
}
else
{
MyNode* tail = *pphead;
// 寻找链表尾部
while (tail->next != nullptr)
{
tail = tail->next;
}
tail->next = newNode;
}
}
void addNodeBad(MyNode* phead, int age, string name)
{
cout << "----------------链表节点添加----------------" << endl;
MyNode* newNode = new MyNode;
newNode->name = name;
newNode->age = age;
newNode->next = nullptr;
if (phead == nullptr)
{
phead = newNode;
}
else
{
MyNode* tail = phead;
// 寻找链表尾部
while (tail->next != nullptr)
{
tail = tail->next;
}
tail->next = newNode;
}
}
// 删除节点(如果要删除的点为头节点,则需要传二级指针,否则头节点变为野指针)
bool delNode(MyNode** pphead, string name)
{
cout << "----------------链表节点删除----------------" << endl;
if (*pphead == nullptr)
{
cout << "链表为空,无法删除!" << endl;
return false;
}
MyNode* pPre = *pphead;
MyNode* pCur = nullptr;
// 头节点是目标数据
if (pPre->name == name)
{
pCur = pPre;
*pphead = pCur->next;
// 释放内存
free(pCur);
}
else
{
// 当前的下下一个节点不是最后一个节点 而且 当前节点的下一个节点不是目标
while (pPre->next->next != nullptr && pPre->next->name != name)
{
pPre = pPre->next;
}
if (pPre->next->name == name)
{
// 找到目标
pCur = pPre->next;
pPre->next = pCur->next;
free(pCur);
}
else {
// 未找见
cout << "节点" << name << "未找见,无法删除" << endl;
}
}
return true;
}
// 错误的示例,未使用二级指针,如果头节点为目标删除对象,则头指针变为野指针
bool delNodeBad(MyNode* pphead, string name)
{
cout << "----------------链表节点删除----------------" << endl;
if (pphead == nullptr)
{
cout << "链表为空,无法删除!" << endl;
return false;
}
MyNode* pPre = pphead;
MyNode* pCur = nullptr;
// 头节点是目标数据
if (pPre->name == name)
{
pCur = pPre;
pphead = pCur->next;
// 释放内存
free(pCur);
}
else
{
// 当前的下下一个节点不是最后一个节点 而且 当前节点的下一个节点不是目标
while (pPre->next->next != nullptr && pPre->next->name != name)
{
pPre = pPre->next;
}
if (pPre->next->name == name)
{
// 找到目标
pCur = pPre->next;
pPre->next = pCur->next;
free(pCur);
}
else {
// 未找见
cout << "节点" << name << "未找见,无法删除" << endl;
}
}
return true;
}
// 修改节点
void modifyNode(MyNode* phead, string name, int newAge)
{
cout << "----------------链表节点修改----------------" << endl;
if (phead == nullptr)
{
cout << "链表为空,无法修改!" << endl;
return;
}
MyNode* ptr = phead;
// 找见或者查到最后一个元素就跳出循环
while (ptr->name != name && ptr->next!=nullptr)
{
ptr = ptr->next;
}
if (ptr->name == name)
{
// 找到目标
ptr->age = newAge;
cout << "节点" << name << "已找见,现在修改" << endl;
}
else
{
// 未找见
cout << "节点" << name << "未找见,无法修改" << endl;
}
}
// 查找指定节点
void findNode(MyNode* phead, string name, MyNode** dest)
{
cout << "----------------链表节点查找----------------" << endl;
if (phead == nullptr)
{
cout << "链表为空,未查找到目标!" << endl;
return;
}
MyNode* ptr = phead;
while (ptr->name != name && ptr->next != nullptr)
{
ptr = ptr->next;
}
if (ptr->name == name)
{
// 找到目标
*dest = ptr;
cout << "节点" << name << "已找见" << endl;
}
else
{
// 未找见
cout << "节点" << name << "未找见" << endl;
}
}
main.cpp
#include "linknode.h"
// 主函数
int main()
{
#if 1
MyNode *headNode = nullptr;
initLinkList(&headNode, 31, "mazhen");
addNode(&headNode, 28, "meng");
addNode(&headNode, 27, "xiaomeng");
printLinkList(headNode);
delNode(&headNode, "xiaomeng");
delNode(&headNode, "mazhen");
delNode(&headNode, "meng");
cout << "删除节点后打印:" << endl;
printLinkList(headNode);
freeLinkList(&headNode);
// 对比验证不使用二级指针的情况
/*MyNode* headNodeTest = nullptr;
initLinkListBad(headNodeTest, 28, "meng");
printLinkList(headNodeTest);*/
#endif
return 0;
}
讨论
1.应该传递二级指针还是一级指针?
总结:一般传递的指针参数为链表头节点的指针,如果对应的操作里需要修改头指针本身的值,则需要传递二级指针即头指针的地址或称头指针的指针。本质上需要理解最基础的传值和传地址或引用的概念。
a.如打印链表仅传递一级指针即可。如初始化头节点,则需要传递二级指针,因为初始化头节点这个操作需要为一级指针的值去赋值。
b.如添加节点的话,传递一级指针即可,因为例如尾插只需要根据头节点地址找到为最后一个节点,然后插入。哪怕传入一级指针,通过形参赋值也可以找到我们需要的东西。当然传递二级指针也是可以的,只不过没什么必要,如文中代码可能会在添加节点的代码addNode中初始化第一个节点,则使用二级指针能够兼容两种情况。
c.如删除指定节点的话,需要传递二级指针,这里有两种情况,如果需要删除的恰好是头节点,则您如果传递一级指针的话则实参头节点指针会变为野指针,使用二级指针则能避免此问题。如果需要删除的不是头节点,则使用一级指针即可,此时不会出现野指针问题。为了兼顾这两方面,删除节点时需要使用二级指针。
d.如清空链表则需要传递二级指针。试想传递一级指针的话因为传值问题导致出头节点外的其他节点均被释放,但头节点无法释放,形成野指针。如果传递二级指针,则不会存在该问题了。
其余操作分析方法类似,欢迎一起讨论。
相关截图: