程序设计题|建立3结点学生链表,输出第一个学生信息
题目:编程建立一个三个结点的链表,存放三个学生的数据,并输出第一个学生的数据信息。假定学生数据结构中只有学号、姓名、成绩三项。可编写一个建立链表的函数creat()。(可自行定义结构体类型)
完整C代码(可直接编译运行)
#include<stdio.h>#include<stdlib.h>#include<string.h>// 定义学生链表节点typedefstructStudent{intid;// 学号charname[20];// 姓名floatscore;// 成绩structStudent*next;}StuNode;// 创建3个节点链表,返回链表头指针StuNode*creat(){StuNode*p1,*p2,*p3;// 开辟3个节点内存p1=(StuNode*)malloc(sizeof(StuNode));p2=(StuNode*)malloc(sizeof(StuNode));p3=(StuNode*)malloc(sizeof(StuNode));// 学生1赋值p1->id=1001;strcpy(p1->name,"Zhang");p1->score=92.5;// 学生2赋值p2->id=1002;strcpy(p2->name,"Li");p2->score=88.0;// 学生3赋值p3->id=1003;strcpy(p3->name,"Wang");p3->score=79.3;// 串联节点p1->next=p2;p2->next=p3;p3->next=NULL;returnp1;// 返回第一个节点地址}intmain(void){StuNode*head;head=creat();// 输出第一个学生信息printf("=====第一个学生信息=====\n");printf("学号:%d\n",head->id);printf("姓名:%s\n",head->name);printf("成绩:%.1f\n",head->score);// 释放内存(从head出发,逐个节点释放)StuNode*p=head,*q;while(p!=NULL){q=p;// 记录当前节点地址p=p->next;// 先移到下一个节点free(q);// 再释放当前节点}return0;}运行输出:
=====第一个学生信息===== 学号:1001 姓名:Zhang 成绩:92.5逐行小白解析
1. 结构体定义
typedefstructStudent{intid;// 学号charname[20];// 姓名,字符数组保存字符串floatscore;// 成绩structStudent*next;// 指针,存下一个节点地址}StuNode;typedef:给struct Student起别名StuNode,简化后续代码。- 每个节点 = 学生数据 +
next指针,用来链接下一个学生。
2. creat() 创建链表函数
StuNode*creat()- 返回值
StuNode*:返回节点的内存地址(链表起点)。
StuNode*p1,*p2,*p3;p1=(StuNode*)malloc(sizeof(StuNode));p1,p2,p3:指针变量(相当于纸条,只存地址,本身不是节点)malloc:在堆内存开辟一块空间,生成真正的学生节点。
strcpy(p1->name,"Zhang");重点:字符数组不能直接用=赋值字符串,要用strcpy,需要头文件<string.h>。
p1->next=p2;p2->next=p3;p3->next=NULL;链表串联:p1 → p2 → p3 → NULLNULL标记链表末尾,代表后面没有节点。
returnp1;把第一个学生节点的地址返回给main函数。
3. main主函数
StuNode*head;head=creat();调用creat()创建链表,head保存链表第一个节点地址。
printf("学号:%d\n",head->id);printf("姓名:%s\n",head->name);printf("成绩:%.1f\n",head->score);head直接指向第一个学生节点,用->读取数据打印。
✅ 重点详解:链表内存释放(为什么 free(q) 能释放 p 的节点)
第一步:理解 p 和 q 到底是什么
先把内存分成两部分想清楚:
关键点:
- 节点本身在堆区(malloc开出来的),它是一间"房间",里面住着 1001、Zhang、92.5 这些数据。
- p 和 q 在栈区,它们不是节点,只是两张"小纸条",纸条上写的是房间号(内存地址)。
q = p;这句话,不是把整个节点复制一份给 q,只是把 p 纸条上的房间号抄写到 q 纸条上。- 结果:p 纸条 = 房间号0x100,q 纸条 = 房间号0x100,两张纸条指向同一间房间。
第二步:free 到底做了什么
free(q);free不是"清空 q 这个变量"!free是:看括号里那个地址(房间号),去堆区把那个房间整个拆掉。free(q)=free(0x100),就是把 0x100 这间房间拆掉。- q 纸条本身还在,还在栈区,只是它指向的房间没了。
打个比方:q 是一张写着"幸福小区3栋101"的纸条,
free(q)是照着这个门牌号去把101房间拆掉,不是撕掉纸条本身。
第三步:为什么释放前要先移动 p
q=p;// q 先记住当前房间号p=p->next;// p 先跑到下一个房间(提前拿到下一个房间号)free(q);// 现在才拆当前房间如果不先移动 p,直接 free:
// ❌ 错误写法while(p!=NULL){free(p);// 当前房间被拆掉了p=p->next;// 还想去已拆掉的房间里拿 next 地址 → 崩溃!}房间都没了,里面的 next 纸条也一起没了,p 再也拿不到下一个房间号,程序就崩了。
所以顺序必须是:先记下下一个房间号(移动p),再拆当前房间(free(q))。
第四步:三轮循环完整模拟(对照代码看)
链表:p1(地址0x100) → p2(地址0x200) → p3(地址0x300) → NULL
- 第1轮:p 在 p1,q 记住 p1 地址;p 先跑到 p2;free 按 q 上的地址拆掉 p1。
- 第2轮:p 在 p2,q 记住 p2 地址;p 先跑到 p3;free 按 q 上的地址拆掉 p2。
- 第3轮:p 在 p3,q 记住 p3 地址;p 先跑到 NULL;free 按 q 上的地址拆掉 p3。
- 第4轮:p == NULL,循环结束,三个节点全部释放。
第五步:为什么 main 里不能直接 free(p1)
StuNode*creat(){StuNode*p1,*p2,*p3;// 这三个变量是 creat 的局部变量...returnp1;}- p1/p2/p3 是 creat 函数内部的局部变量,函数一结束就"消失"了。
- main 里根本认不到 p1/p2/p3 这个名字,写了 free(p1) 会编译报错。
- 但节点本身是 malloc 在堆区开的,没有消失,只能靠 head 顺着链表一个个找到并释放。
Mermaid内存图(可直接放进Obsidian)
小白易错点
- 忘记头文件:
<stdlib.h>用于malloc,<string.h>用于strcpy - 不能写
p1->name="Zhang",字符数组禁止直接字符串赋值 - 尾节点一定要写
next=NULL,否则链表没有结束标记 - 指针不等于节点:
StuNode *p只是地址纸条,malloc之后才有真实节点 p1/p2/p3是creat里的局部变量,main里不能直接用,释放内存要从head出发遍历- 释放链表节点必须先保存下一个节点地址(移动p),再free,顺序颠倒会造成程序崩溃
- free 释放的是堆内存里的节点(按地址拆房间),不是删除 p/q 这两个指针变量,指针变量还在栈区