配合食用更佳:【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**,这个有两种理解:
- 地址(指针)类型用*,地址的地址(指针的指针)类型**
- 先解引用第一个地址(*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;
}
所以为什么调用打印函数的时候,可以不用传指针变量的值的地址呢?
因为不用改变这个值。
二、链表头结点的两个作用及解释
首先头结点的作用:
- 在有些操作下,可以不需要分头结点和其他结点的两种情况处理,可以统一一种情况处理
- 在指针变量传参时,可以传指针变量的值,而不是地址。
先看作用一:
这是没有头结点的尾插
它讨论了两种情况,一种开始指针指向空,一种有多个结点的情况的尾插。
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引用报错。
在很多情况下,对于访问一个指针都要确定一下这个指针是否为空。