问题来源
在研究构造链表节点插入函数,使用结构体指针作为函数传入参数的时候发现的问题。
在链表指针还没有开辟内存空间的时候就作为函数的传入函数的时候,不使用函数回传的结构体给链表指针重新赋值,链表的节点插入无效。而给链表指针开辟内存空间之后,作为函数的传入参数,不需要函数的回传的结构体给链表指针重新赋值,链表的节点插入也能正常插入。
这就引起了我的好奇,为什么在给函数传入参数之前一定需要给参数指针开辟内存空间才行呢?
先看节点插入函数
//插入节点,参数:插入那个链表,插入节点的数据是多少
struct Node* insertNodeByHead(struct Node* headNode, int data){
struct Node* newNode = createNode(data);
if(headNode==NULL){
headNode = (struct Node*)malloc(sizeof(struct Node));
headNode->data = 9;
headNode->next = NULL;
}
newNode->next = headNode->next; //此时两个节点共同指向下一个节点
headNode->next = newNode; //现在headNode节点指向newNode节点
return headNode;
}
我学习的教程是没有判断改链表是否为空的情况,我考虑到如果链表是空的话,那我先给链表分配内存(也就是问题的关键点)。然后问题出现:
这里没有给传入的链表指针参数开辟内存空间,也没有将函数的返回结果给链表指针重新赋值,结果是插入节点失败:
int main(){
struct Node* List = NULL; //创建一个链表变量 重点:有很多编译器都需要初始化为空,不然会出错
insertNodeByHead(List, 4);
insertNodeByHead(List, 3);
insertNodeByHead(List, 5);
insertNodeByHead(List, 6);
printList(List);
return 0;
}
没有给传入的链表指针参数开辟内存空间,但将函数的返回结果给链表指针重新赋值,结果是插入节点成功。
int main(){
struct Node* List = NULL; //创建一个链表变量 未初始化
List = insertNodeByHead(List, 4);
List = insertNodeByHead(List, 3);
List = insertNodeByHead(List, 5);
List = insertNodeByHead(List, 6);
printList(List);
return 0;
}
问题分析
其实这个是指针的问题,指针变量是存放了数据的地址。
在这里List是一个链表指针,它存放了第一个节点的地址,所以当我们还没有给链表指针开辟一个空间的时候,也就是这个链表指针还没有指向任何一个地址。我们暂且将这种还没开辟空间的链表指针叫为空指针,当我们将空指针作为函数的传入参数时, List只能讲一个空地址传给insertNodeByHead(struct Node* headNode, int data)函数的链表指针变量headNode,headNode也成为一个空指针,而在这个函数里面,按代码逻辑,将会给链表指针变量headNode开辟一个内存空间,那现在headNode将不再是一个空指针,它存放了一个地址,这个地址就是新开辟的一个节点空间。但是这只是headNode链表指针变量指向了第一个节点而已,而函数外面的List链表指针变量还是空指针变量。在insertNodeByHead函数里后面的插入节点代码都只是对headNode链表指针变量的操作,而List并没有一点改变,如果没有将insertNodeByHead函数的返回指针值给List重新辅助的操作,也就是List没有“继承”insertNodeByHead函数对headNode的一切操作。当将insertNodeByHead的返回指针值给List重新赋值,也就是headNode变量存放的地址“分享”给List变量,那么List也会指向第一个节点了。
int main(){
struct Node* List = NULL; //创建一个链表变量 未初始化
insertNodeByHead(List, 4);
List = insertNodeByHead(List, 3);
List = insertNodeByHead(List, 5);
List = insertNodeByHead(List, 6);
printList(List);
return 0;
}
从实验结果可以看出,第一次插入节点并赋值为4的操作并没有成功。
结论
通过上述的推论和实验可知,使用指针变量作为函数的传入参数时,我们仅是利用了这个指针变量存放的地址而已,如果这个指针变量是一个空指针,那么这个传入参数将毫无用处。如果我们使用了空指针作为传入参数,我们需要使用函数返回的指针值给原来的指针变量重新赋值才可以获得函数的操作结果。那么按结论来说,只有我们的指针变量List获得第一个节点后,我们就不需要再使用函数返回的指针值给List重新赋值了。
int main(){
struct Node* List = NULL; //创建一个链表变量 未初始化
insertNodeByHead(List, 4);
List = insertNodeByHead(List, 3);
insertNodeByHead(List, 5);
insertNodeByHead(List, 6);
printList(List);
return 0;
}
实验正确。
使用空指针作为传入参数的另一种解决办法
使用二级指针变量就可以解决这个问题,也就是使用指针的指针作为函数的传入参数,这样就不需要使用返回指针值重新赋值了。关于使用二级指针的代码我还没尝试,这是知道这个办法可以解决,等我会了再贴代码出来。
关于链表的学习,可以看这篇文章哦:LeetCode算法学习——链表