先来看C语言创建链表、插入节点和遍历链表的一段代码:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node{ ElemType elem; struct Node *next; }Node, *LinkedList; //void init_linkedlist(LinkedList *list) { void init_linkedlist(LinkedList *list) { *list = (LinkedList)malloc(sizeof(Node)); (*list)->next = NULL; } void insert(LinkedList list, ElemType elem) { Node *p, *q; q = list; p = (Node *)malloc(sizeof(Node)); p->elem = elem; p->next = NULL; while(q->next != NULL) q = q->next; q->next = p; } void main() { LinkedList list, p; init_linkedlist(&list); insert(list, 3); insert(list, 4); insert(list, 5); p = list->next; while(p != NULL) { printf("%4d", p->elem); p = p->next; } printf("\n"); }
这个小程序完成的功能很简单,创建一个链表,然后插入3,4,5这三个整数,最后遍历链表输出每个节点中的整数。但是大家注意到没有,在main函数中,初始化链表的函数参数的是一个二级指针,为什么要使用一个二级指针作为参数呢?在任何一本C语言的教材上,都会写C语言的函数参数传递是值传递方式,所有的参数都是通过值传递的,使用指针作为参数可以在函数中改变参数的值(此处感觉表达有误,但是想不到更好的表达方式)。可能有人会有疑问了,在上面代码的main函数中初始化链表的调用函数代码
init_linkedlist(&list);
list已经是一个指针了,为什么要传递一个指针的地址,直接使用指针不行吗?确实不行。
为什么?那么我们来看看不使用二级指针会发生什么情况,先来看看不使用二级指针的代码:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node{ ElemType elem; struct Node *next; }Node, *LinkedList; void init_linkedlist(Node *list) { list = (LinkedList)malloc(sizeof(Node)); list->next = NULL; } void insert(LinkedList list, ElemType elem) { Node *p, *q; q = list; p = (Node *)malloc(sizeof(Node)); p->elem = elem; p->next = NULL; while(q->next != NULL) q = q->next; q->next = p; } void main() { LinkedList list, p; printf("%d\n", list); init_linkedlist(list); printf("%d\n", list); }
这段代码的main函数中有两处输出,分别是在初始化链表函数之前和之后,如果运行这段代码,会发现两处输出都是一样的值:
但是如果在使用二级指针的代码(本文的第一段代码)中插入相同的两处输出代码,会发现输出的两个值是不同的:
为什么会这样,原因就在于本文的第一段代码使用的是二级指针作为参数传递,而第二段代码使用的是一级指针作为参数传递。这个问题最终还是回归到了C语言参数传递是值传递了。
如果是使用一级参数传递,首先是main函数中定义一个Node类型的指针,这个指针用list表示,C语言在定义指针的时候也会分配一块内存,一般会占用2个字节或4个字节,现在在大部分的编译器中占用4个字节,这里用4个字节算。在这4个字节的内存中,没有任何值,所以这个指针不指向任何值。然后传递到函数init_linkedlist中,在init_linkedlist函数中,编译器首先为形参list分配一个临时指针内存块,而语句
list = (LinkedList)malloc(sizeof(Node));
中函数malloc分配一块内存,并向该程序返回一个指向这块内存的指针,这样形参list就有值了,在list所表示的临时内存区域中填入刚刚分配的内存的这块内存的地址,假设为0x10086,这样使用(*list)就可以访问这块内存(0x10086)中的内容了。此时在函数init_linkedlist中list所代表的这块内存中的内容是有值的,为0x10086,用图可表示为:
但是现在的list只是占据了一个零时的内存空间,这种改变并不能反映到main函数中,init_linkedlist函数执行完了,临时的list内存块就被回收了,这样刚刚分配的内存块的地址0x10086没有被记录下来。而我们如果要初始化main函数中的链表list的话,就必须记录记录下这块内存空间(0x10086)。
下面我们来考虑使用二级指针的代码,在使用二级指针的代码(本文第一段代码)中,首先定义了一个指针
LinkedList list
然后调用init_linkedlist,调用代码是
init_linkedlist(&list);
这样传递的参数就是一个指针的地址,在函数的参数列表中就表现为指针的指针,函数的定义为
void init_linkedlist(LinkedList *L)
函数的参数是一个二级指针,同样,在执行调用这个函数的时候,临时分配一个指针,这个指针占据一个占用4个字节的内存块(函数执行完要回收的),同时这个临时指针L指向主函数main中定义的list指针,这里假设主函数main中的list指针在内存中的地址为0x12306,用图表示为
其中L是一块临时内存,list是主函数main的中定义的一个指针,此时list代表的内存块中还没有内容(貌似这样说法有误,或者说这块内存还没有初始化)。下面执行内存分配的代码
*L = (LinkedList)malloc(sizeof(Node));
malloc函数分配了一块内存空间,假设地址为0x10010,由于L指向list所代表的内存块,所以*L等价于list,这样将malloc函数分配的内存块赋值给*L就相当于执行语句
list = 0x10010
那么在list代表的内存块中所存储的值就为0x10010,用图表示为
这样在函数init_linkedlist中分配的一段内存也就能在main函数中反映出来了,main函数中list代表的内存块的就指向了新分配的内存,链表初始化完成。
相关推荐
C语言用指针处理链表 链表是一种重要的数据结构,它可以用来表示复杂的数据结构,并且可以动态地存储分配。链表的特点是每个数据元素在内存中单独分配存储空间,构成一个结点;各数据元素之间通过指针相连,构成一...
- 初始化三个链表指针变量`h1`, `h2`, 和`h`。 - 调用`create()`创建第一个链表`h1`,并显示其内容。 - 调用`create()`创建第二个链表`h2`,并显示其内容。 - 调用`merge()`归并`h1`和`h2`,结果存储在`h`中。 ...
了解如何声明、初始化和操作数组,以及处理字符串(如使用strlen、strcpy、strcat等函数)是必备技能。 5. **结构体与联合** 结构体是C语言中的复合数据类型,可以组合多种不同类型的元素。联合与结构体类似,但其...
1. 创建链表:初始化头节点,并根据需要动态添加节点。 2. 插入节点:在指定位置插入新节点,需要修改前后节点的指针。 3. 删除节点:找到要删除的节点,修改前后节点的指针使其连接。 4. 遍历链表:通过跟随节点的...
二、指针的初始化 指针在声明时应尽量初始化,避免指向未定义的内存区域。初始化指针的方式通常是让它指向某个已存在的变量,例如: ```c int num = 10; int *p = # ``` 在这里,`&num`是取num变量的地址,赋值...
考生应理解指针的声明、初始化、运算以及指针与数组、函数的关系。 3. **数组和字符串**:数组是存储同类型数据的集合,字符串是字符数组的特殊形式。考生需要了解数组的声明、初始化、遍历以及字符串处理函数(如...
结合这两个函数,我们可以在初始化一个链表后,先调用`reverseList`函数反转链表,然后调用`printListReverse`函数打印链表。这样,就能实现从尾到头的打印。 在实际编程中,还需要考虑一些边界情况,比如链表为空...
在C语言中,指针是一种存储...最后,需要注意的是,在使用指向指针的指针时,一定要确保对指针进行适当的初始化和检查,避免野指针(即未指向任何有效内存区域的指针)的出现,这样可以防止程序出现不可预测的错误。
1. **创建链表**:初始化头节点,并根据需要添加节点。 2. **插入节点**:可以在链表的开头(头部插入)、结尾(尾部插入)或中间插入新节点。 3. **删除节点**:根据节点的值或位置找到并删除节点。 4. **遍历链表*...
- **流程**:读取用户输入的数据`x`,初始化链表,构建单向循环链表,转换为双向循环链表,查找并输出节点`x`的前驱节点数据。 以上就是关于“单双向循环链表的转换(C语言)”的相关知识点总结。通过这些知识点的...
在使用指针时,需要注意一些问题,例如,指针的初始化、指针的越界访问、指针的赋值等问题。如果不正确地使用指针,可能会导致程序崩溃或出现未定义的行为。 本资源涵盖了C语言指针的基本概念、指针运算、指针与...
二、指针的初始化与解引用 在使用指针前,应对其进行初始化,赋予其一个有效的内存地址。例如,我们可以用&运算符获取变量的地址,然后赋值给指针,如int a = 10; int *p = &a;。解引用(*)操作符用于访问指针所...
其中,创建链表涉及如何初始化头节点;插入和删除元素需要正确处理指针关系;查找元素需要遍历链表;反转链表需要改变相邻节点的指针方向;而合并已排序链表则需要使用到合并排序算法,结合链表特性实现高效合并。 ...
下面是一个链表初始化的示例代码: ```c LinkStack *Init_LinkStack(){ LinkStack *S; S = (LinkStack*)malloc(sizeof(LinkStack)); S->next = NULL; return S; } ``` 在上面的代码中,我们首先定义了一个链表...
1. **创建链表**:创建一个空链表,通常涉及到初始化头节点,头节点通常不存储数据,仅用于指向第一个元素。 2. **插入节点**:在链表的特定位置(如开头、末尾或中间)插入新节点。插入操作需要更新前后节点的指针...
这些函数包括初始化链表,处理蛇的移动,检测碰撞,以及更新游戏状态。比如,`move_snake`函数会根据玩家输入更新蛇的位置,`check_collision`函数则会检查蛇是否撞到自身或边界。 6. **main.c文件** 主程序文件,...
2. **初始化链表**:创建一个空的双链表,通常从NULL开始。我们需要一个头节点,它只起到标识链表的作用,不存储实际数据。初始化代码可能如下: ```c Node* head = NULL; ``` 3. **插入节点**:在双链表中插入...
初始化堆栈时,将堆栈顶指针设置为NULL,表示堆栈为空: ```c Stack* createStack() { Stack* stack = (Stack*)malloc(sizeof(Stack)); if (stack == NULL) { // 错误处理 } stack->top = NULL; stack->size ...
2. **初始化链表**:创建一个空链表,其头节点的next指针指向自己,形成循环。 ```c ListNode* initCircularList() { ListNode* head = createNode(0); // 创建一个虚拟头节点,数据部分可忽略 head->next = head...