【C数据结构】从链表中看到的常见问题:一级指针*和二级指针**的使用、头结点的作用及解释、注意不能解引用空指针





配合食用更佳:【C数据结构】用链表的最简结构单链表来初识链表


一、什么时候用一级指针*,什么时候用二级指针**。

1、从int类型看传地址和传值

我们学函数的时候都知道形参是实参的临时拷贝,传实参的值,形参的改变不会影响实参。
但如果传的实参是地址呢?这时候形参是指针,指针没有改变,指针指向的值改变了,从而做到改变值。
以下情况Swap1 a,b不会交换成功,Swap2 a,b会交换成功

void Swap1(int x,int y)
{
	int z = 0;
	z = x;
	x = y;
	y = z;
}
void Swap2(int* px, int* py)
{
	int z = *px;//z=a
	*px = *py;//a=b
	*py = z;  //b=a
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	//交换
	printf("交换前:a=%d b=%d\n", a, b);
	//a和b叫实参
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);//不会交换成功
	
	Swap2(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);//交换成功

	return 0;
}

2、一级指针*和 二级指针** 同样的原理

这是一个单链表尾插的操作和打印操作,可以不用管如何实现。

SLNode是一个结构体类型
已知sl是一个指针变量,类型是SLNode*,这个sl的值是一个地址。
我们将sl的值的地址&sl,作为尾插函数的实参。
所以在形参那里就是SLNode**,这个有两种理解:

  1. 地址(指针)类型用*,地址的地址(指针的指针)类型**
  2. 先解引用第一个地址(*pphead),然后得到一个值(值是一个地址)类型是 SLNode *,所以是SLNode *( *phead )

为什么要传指针变量的地址?
因为我们要通过函数改变值
比如下面的:*pphead = newnode;
(*pphead 这个通过解引用第一个地址得到的值)
所以我们要传值的地址。

//尾插函数
void SListPushBack(SLNode** pphead, DateType x){
	SLNode* newnode = SListCreatNode(x);
	if (*pphead == NULL) {//如果开始*pphead指向空,让新结点覆盖NULL
		*pphead = newnode;
	}
	else {//正常尾插
		SLNode* cur = *pphead;
		while (cur->next != NULL) {
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

//链表打印
void SListPrint(SLNode* pphead) {
	while (pphead != NULL) {
		printf("%d->", pphead->data);
		pphead = pphead->next;
	}
	printf("NULL");
	printf("\n");
}

void Test1() {
	SLNode *sl= NULL;
	SListPushBack(&sl, 1);
	SListPrint(sl);
}
int main() {

	Test1();//操作的测试
	return 0;

}

所以为什么调用打印函数的时候,可以不用传指针变量的值的地址呢?
因为不用改变这个值。


二、链表头结点的两个作用及解释

在这里插入图片描述

首先头结点的作用:

  1. 在有些操作下,可以不需要分头结点和其他结点的两种情况处理,可以统一一种情况处理
  2. 在指针变量传参时,可以传指针变量的值,而不是地址。

先看作用一:

这是没有头结点的尾插
它讨论了两种情况,一种开始指针指向空,一种有多个结点的情况的尾插。

void SListPushBack(SLNode** pphead, DateType x){
	SLNode* newnode = SListCreatNode(x);
	if (*pphead == NULL) {//如果开始*pphead指向空,让新结点覆盖NULL
		*pphead = newnode;
	}
	else {//正常尾插
		SLNode* cur = *pphead;
		while (cur->next != NULL) {
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

这是有头结点的尾插
是不是更简单了

void SListPushBack(SLNode** pphead, DateType x){
	SLNode* newnode = SListCreatNode(x);
	SLNode* cur = *pphead;//cur指向头结点
	while (cur->next != NULL) {
		cur = cur->next;
	}
	cur->next = newnode;
}


作用二:
没有头结点的时候,假设有个指针为plist

//LTNode 为结构体类型
LTNode* plist=NULL;

plist一开始指向的是NULL,为了建立链表,plist指向的NULL需要改变成一个结点,所以会改变plist的值,所以需要传plist的值的地址。
在这里插入图片描述
有头结点的时候,
可以通过一个初始化让plist一开始指向一个头结点

LTNode*plist=ListInit();

在这里插入图片描述

这个时候若在这个头结点后面链接其他的结点,并不会改变这个指针的指向,不会改变指针的值,所以不用传指针值的地址。


三、注意不能解引用空指针

这个很好理解。
假设有一个快指针fast和一个慢指针low,快指针比慢指针快一步

fast=fast->next->next;
low=low->next;

这个时候要注意fast->next是否为NULL,以防对NULL引用报错。
在很多情况下,对于访问一个指针都要确定一下这个指针是否为空。

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值