以链表的插入为例刨析C语言中指针传参问题

引例1:单链表空头结点的头插

链表(C语言)

我们新建一个空链表并对其进行头插

//定义单链表结构体
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

//申请一个新节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->data = x;
	tmp->next = NULL;
	return tmp;
}

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

// 错误的单链表头插方式
void SListPushFrontfault(SListNode* pplist, SLTDateType x)
{
	SListNode* NewNode = BuySListNode(x);
	if (pplist == NULL)
	{
		pplist = NewNode;
	}
	else
	{
		NewNode->next = pplist;
		pplist = NewNode;
	}
}

//代入main函数进行测试
int main()
{
	SListNode* phead = NULL;
	SListPushFrontfault(phead, 1);
	SListPushFrontfault(phead, 2);
	SListPushFrontfault(phead, 3);
	SListPushFrontfault(phead, 4);
	SListPrint(phead);
	return 0;
}

运行以上代码我们的预期结果是头插1,2,3,4,打印出来应该为

而实际结果却是


 为什么?

类比:交换两个变量的值的函数

例:编写一个swap函数来交换两个变量的值

我们都知道,如果我们编写以下函数

void swap(int a,int b)
{  
   int c=a;
   a=b;
   b=c;
}

这个函数不会对传入的a,b的值产生任何影响,因为函数中的a,b只是一个局部变量,出函数之后a,b便已经销毁,不会改变传入的值

而如果我们需要改变传入的值,我们必须传入a,b的地址,通过对地址的访问来改变a,b的值

void swap(int *a,int *b)
{
    int c=*a;
    *a=*b;
    *b=c;
}

同样,我们在对该函数传参的时候,则必须取其地址传入

swap(&num1,&num2);

 而为什么对于单链表的头插传入地址却无法改变他们的值?

因为对于swap函数,我们想改变的是int变量,故只需要传入int变量的地址

而对于该头插函数,我们需要改变的是int(SLTDataType)*变量,改变的是一个指针变量,故需要传入的是指针变量的地址

通过以下图我们可以更好去了解

 以链表的插入为例刨析C语言中指针传参问题_第1张图片

 通过图片我们可以很清晰看出,如果我们传入的是同级变量,那么传入的都会被视为局部变量

而如果我们传入的是上一级变量,则传入的是全局变量的地址,修改的是全局变量

解答:为什么单链表头插需要传入二级指针

在main函数中,我们先定义了int* phead=NULL;

而在头插的函数中,如果phead为空,则使phead=NewNode;

这里并不是让phead指向NewNode,而是让phead的值等于NewNode,NewNode也是一个指针变量,所以我们的操作实际是修改了phead的值,故改变第一级需要传入第二级,问题得到了解决。

引例2:单链表非空头结点的尾插

我们沿用引例1中的代码,新建一个非空头结点的单链表并对其进行尾插

//定义单链表结构体
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

//申请一个新节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->data = x;
	tmp->next = NULL;
	return tmp;
}

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

// 错误的单链表尾插方式
void SListPushBackfault(SListNode* pplist, SLTDateType x)
{
	SListNode* NewNode = BuySListNode(x);
	SListNode* Lhead = pplist;
	if (pplist == NULL)
	{
		pplist = NewNode;
	}
	else
	{
		while (Lhead->next)
		{
			Lhead = Lhead->next;
		}
		Lhead->next = NewNode;
	}
}

//代入main函数进行测试
int main()
{
	SListNode* phead = NULL;
	SListNode HeadNode;
	HeadNode.data = 0;
	HeadNode.next = NULL;
	phead = &HeadNode;
	SListPushBackfault(phead, 1);
	SListPushBackfault(phead, 2);
	SListPushBackfault(phead, 3);
	SListPushBackfault(phead, 4);
	SListPrint(phead);
	return 0;
}

与引例1一样,如果我们对空链表进行该尾插,同样会因为局部变量问题而尾插失败,其仍为空链表。

但是在该情况下,我们对非空链表进行尾插,最终得到的结果却大相径庭

 为什么在该情况便尾插成功了呢?

解答:为什么对非空链表尾插只需要一级指针

我们注意到尾插函数中的如下一段代码

while (Lhead->next)
		{
			Lhead = Lhead->next;
		}
		Lhead->next = NewNode;

该代码的意义是找到尾结点,然后对尾结点进行尾插,我们不难发现,这里并没有出现plist 

我们需要改变的是结构体变量而非指针变量,通过类比1的图进行理解:

以链表的插入为例刨析C语言中指针传参问题_第2张图片

而Lhead刚好为TailNode的地址,即为*TailNode,故改变了Lhead便改变了TailNode的值 

总结:函数传参时应传入哪种参数?

如果不想改变传入的变量,便只用传入同级的参数,即与图中在同一层

(例如单链表的打印只传入了头结点的指针而非二级指针)

如果目的是改变传入的参数,那么需要传入上一级的指针,即与图中在上一层

你可能感兴趣的:(C语言中疑难点,链表,数据结构,c语言)