mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7
4682 字
12 分钟
结构体与链表代码详解
2026-06-10

本篇整理了 C 语言结构体与链表相关的全部课堂代码,按知识点分节讲解。每一节包含题目说明、解题思路、完整代码(带详细注释)和关键知识点总结,方便复习与回顾。

目录#


一、结构体填空题:按姓名字典序排序#

题目说明#

程序通过定义学生结构体数组,存储若干名学生的学号、姓名和三门课的成绩。函数 fun 的功能是:将存放学生数据的结构体数组,按姓名的字典序(从小到大)排序。

解题思路#

  1. 定义学生结构体 struct student,包含学号 sno、姓名 name[10]、三门课成绩 score[3]
  2. 排序采用选择排序思想:外层循环从第 0 个元素开始,内层循环从 i+1 开始,找到比当前元素小的就交换。
  3. 姓名比较使用 strcmp 函数:strcmp(a[i].name, a[j].name) > 0 表示 a[i].name 字典序大于 a[j].name,此时需要交换两个结构体。
  4. 结构体变量可以直接赋值(t = a[i]; a[i] = a[j]; a[j] = t;),C 语言支持同类型结构体整体赋值。

完整代码#

/*-------------------------------------------------------
【程序填空】
---------------------------------------------------------
题目:程序通过定义学生结构体数组,存储若干名学生的学号、姓名和三门课的成绩。函数
fun的功能是:将存放学生数据的结构体数组,按姓名的字典序(从小到大)排序。
-------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
/* 定义学生结构体类型 */
struct student
{
long sno; /* 学号 */
char name[10]; /* 姓名 */
float score[3]; /* 三门课成绩 */
};
/* 排序函数:按姓名字典序从小到大排序 */
void fun(struct student a[], int n)
{
/***********SPACE***********/
struct student t; /* 临时变量,用于交换 */
int i, j;
/***********SPACE***********/
/* 选择排序:外层从0开始,内层从i+1开始比较 */
for (i = 0; i < n; i++)
for (j = i + 1; j < n; j++)
/***********SPACE***********/
/* strcmp返回值>0表示a[i].name字典序在a[j].name之后,需要交换 */
if (strcmp(a[i].name, a[j].name) > 0)
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
int main()
{
/* 初始化4名学生数据 */
struct student s[4] = { {10001, "ZhangSan", 95, 80, 88}, {10002, "LiSi", 85, 70, 78},
{10003, "CaoKai", 75, 60, 88}, {10004, "FangFang", 90, 82, 87}
};
int i, j;
/* 输出原始数据 */
printf("\n\nThe original data :\n\n");
for (j = 0; j < 4; j++)
{
printf("\nNo: %ld Name: %-8s Scores: ", s[j].sno, s[j].name);
for (i = 0; i < 3; i++)
printf("%6.2f ", s[j].score[i]);
printf("\n");
}
/* 调用排序函数 */
fun(s, 4);
/* 输出排序后的数据 */
printf("\n\nThe data after sorting :\n\n");
for (j = 0; j < 4; j++)
{
printf("\nNo: %ld Name: %-8s Scores: ", s[j].sno, s[j].name);
for (i = 0; i < 3; i++)
printf("%6.2f ", s[j].score[i]);
printf("\n");
}
}

关键知识点#

  • 结构体数组定义与初始化struct student s[4] = {{...}, {...}, ...},嵌套花括号按成员顺序初始化。
  • strcmp 函数:返回 0 表示相等,>0 表示前者大于后者,<0 表示前者小于后者。比较的是 ASCII 码字典序。
  • 结构体整体赋值:C 语言允许同类型结构体变量直接赋值 t = a[i],等价于逐成员拷贝。
  • 选择排序:时间复杂度 O(n²),不稳定排序,但实现简单。

二、链表创建:在递增有序链表中插入节点#

题目说明#

在程序中已建立一个带头结点的单向链表,链表中的各结点按数据域递增有序链接。函数 fun 的功能是:把形参 x 的值放入一个新节点并插入链表中,使插入后各结点数据域中的数据仍保持递增有序。

解题思路#

  1. 创建新节点 s,将 x 存入 s->data
  2. 使用双指针 qpq 始终指向 p 的前驱节点。初始时 q = h(头结点),p = h->next(第一个数据节点)。
  3. 遍历链表,当 p != NULLx > p->data 时,qp 同步后移。
  4. 循环结束后,p 指向第一个大于等于 x 的节点(或 NULL),q 指向其前驱。
  5. 插入操作:s->next = p; q->next = s;,将 s 插入到 qp 之间。

完整代码#

//1.链表创建
//在程序中,已建立一个带头结点的单向链表,
//链表中的各结点按结点数据域中的数据递增有序链接。
//函数fun的功能是:把形参x的值放入一个新节点并插入链表中,
//使插入后各结点数据域中的数据仍保持递增有序。
#include <stdio.h>
#include <stdlib.h>
#define N 8
/* 定义链表节点类型 */
typedef struct list
{
int data;
struct list* next;
} SLIST;
/* 在递增有序链表中插入值为x的节点,保持有序性 */
void fun(SLIST* h, int x)
{
SLIST* p, * q, * s;
s = (SLIST*)malloc(sizeof(SLIST)); /* 申请新节点空间 */
/**********found**********/
s->data = x; /* 将x存入新节点数据域 */
q = h; /* q指向头结点 */
p = h->next; /* p指向第一个数据节点 */
/* 寻找插入位置:当p未到尾部且x仍大于p的数据时继续后移 */
while (p != NULL && x > p->data)
{
/**********found**********/
q = p; /* q记录p的前驱 */
p = p->next;
}
s->next = p; /* 新节点的next指向p */
/**********found**********/
q->next = s; /* q的next指向新节点,完成插入 */
}
/* 创建带头结点的单向链表 */
SLIST* creatlist(int* a)
{
SLIST* h, * p, * q;
int i;
h = p = (SLIST*)malloc(sizeof(SLIST)); /* 创建头结点 */
for (i = 0; i < N; i++)
{
q = (SLIST*)malloc(sizeof(SLIST)); /* 申请新节点 */
q->data = a[i];
p->next = q; /* 尾插法 */
p = q;
}
p->next = 0; /* 尾节点next置空 */
return h;
}
/* 输出链表 */
void outlist(SLIST* h)
{
SLIST* p;
p = h->next;
if (p == NULL)
printf("\nThe list is NULL!\n");
else
{
printf("\nHead");
do
{
printf("->%d", p->data);
p = p->next;
} while (p != NULL);
printf("->End\n");
}
}
void main()
{
SLIST* head;
int x;
/* 初始链表数据已递增有序 */
int a[N] = { 11, 12, 15, 18, 19, 22, 25, 29 };
head = creatlist(a);
printf("\nThe list before inserting:\n");
outlist(head);
printf("\nEnter a number : ");
scanf("%d", &x);
fun(head, x); /* 插入x */
printf("\nThe list after inserting:\n");
outlist(head);
}

关键知识点#

  • 带头结点的链表:头结点不存储数据,h->next 才是第一个数据节点,简化插入/删除的边界处理。
  • typedef 定义链表类型typedef struct list { int data; struct list* next; } SLIST; 让后续代码更简洁。
  • 动态内存分配malloc(sizeof(SLIST)) 返回 void*,需强制转换,使用后应 free 释放。
  • 有序插入的双指针技巧q 跟随 p 的前驱,便于在 qp 之间插入新节点。

三、链表逆序:带头结点单向链表逆置#

题目说明#

fun 函数的功能是将带头结点的单向链表逆置。若原链表中从头至尾数据域依次为 2, 4, 6, 8, 10,逆置后从头至尾结点数据域依次为 10, 8, 6, 4, 2。

解题思路#

  1. 经典的”头插法”逆置:将原链表节点逐个摘下,插入到头结点之后。
  2. 使用三个指针 pqr
    • p 指向当前待处理节点(初始为第一个数据节点 h->next)。
    • q 指向 p 的下一个节点。
    • r 用于暂存 q 的下一个节点,防止断链。
  3. 先将 p->next = NULL(原第一个节点变为新链表的尾节点)。
  4. 循环中:r = q->next(保存下一节点),q->next = p(将 q 指向前面的 p),然后 p = q; q = r 同步前移。
  5. 循环结束后,p 指向原链表的最后一个节点,令 h->next = p 完成逆置。

完整代码#

//fun函数的功能是将带头结点的单向链表逆置,若原链表中从头至尾
//数据域依次为2,4,6,8,10,逆置后,从头至尾结点数据域依次为10,8,6,4,2.
#include <stdio.h>
#include <stdlib.h>
#define N 5
/* 定义链表节点类型 */
typedef struct node
{
int data;
struct node* next;
} NODE;
/* 链表逆置函数 */
void fun(NODE* h)
{
NODE* p, * q, * r;
/**********found**********/
p = h->next; /* p指向第一个数据节点 */
/**********found**********/
if (p == NULL)
return; /* 空链表直接返回 */
q = p->next;
p->next = NULL; /* 原第一个节点成为新链表的尾节点,next置空 */
while (q)
{
r = q->next; /* 暂存q的下一个节点,防止断链 */
q->next = p; /* q指向前一个节点p,实现反转 */
/**********found**********/
p = q; /* p后移 */
q = r; /* q后移 */
}
h->next = p; /* 头结点指向新的第一个节点(原尾节点) */
}
/* 创建带头结点的单向链表 */
NODE* creatlist(int a[])
{
NODE* h, * p, * q;
int i;
h = (NODE*)malloc(sizeof(NODE));
h->next = NULL;
for (i = 0; i < N; i++)
{
q = (NODE*)malloc(sizeof(NODE));
q->data = a[i];
q->next = NULL;
if (h->next == NULL)
h->next = p = q;
else
{
p->next = q;
p = q;
}
}
return h;
}
/* 输出链表 */
void outlist(NODE* h)
{
NODE* p;
p = h->next;
if (p == NULL)
printf("The list is NULL!\n");
else
{
printf("\nHead ");
do
{
printf("->%d", p->data);
p = p->next;
} while (p != NULL);
printf("->End\n");
}
}
void main()
{
NODE* head;
int a[N] = { 2, 4, 6, 8, 10 };
head = creatlist(a);
printf("\nThe original list:\n");
outlist(head);
fun(head);
printf("\nThe list after inverting :\n");
outlist(head);
}

关键知识点#

  • 三指针逆置法pqr 配合,r 用于保存下一节点防止断链,是链表逆置的标准写法。
  • 头插法思想:本质上是把每个节点依次”摘下”插到头部,时间复杂度 O(n),空间复杂度 O(1)。
  • 空链表判断if (p == NULL) return; 防止对空链表操作导致崩溃。
  • 头结点的作用:逆置后只需修改 h->next 即可,无需返回新头指针。

四、链表查找:查找值为 ch 的节点序号#

题目说明#

函数 fun 的功能是:在带头结点的单向链表中,查找数据域中值为 ch 的结点。找到后通过函数值返回该节点在链表中所处的顺序号;若不存在值为 ch 的结点,函数返回 0 值。

注:文件名为”求最大值”,但实际代码功能是按值查找节点位置,以下按代码实际功能讲解。

解题思路#

  1. 从第一个数据节点 p = h->next 开始遍历。
  2. 使用计数器 n,每访问一个节点 n++,记录当前节点是第几个。
  3. p->data == ch,立即返回当前 n 值(顺序号从 1 开始)。
  4. 否则 p = p->next 继续向后查找。
  5. 若遍历完整个链表仍未找到,返回 0。

完整代码#

//函数fun的功能是:在带头结点的单向链表中,查找数据域中
//值为ch的结点。找到后通过函数值返回该节点在链表中所处的顺序号;
//若不存在值为ch的结点,函数返回0值。
#include <stdio.h>
#include <stdlib.h>
#define N 8
typedef struct list
{
int data;
struct list* next;
} SLIST;
SLIST* creatlist(char*);
void outlist(SLIST*);
/* 查找值为ch的节点,返回顺序号;未找到返回0 */
int fun(SLIST* h, char ch)
{
SLIST* p;
int n = 0;
p = h->next;
/**********found**********/
while (p != NULL) /* 遍历链表直到末尾 */
{
n++; /* 节点序号加1 */
/**********found**********/
if (p->data == ch) /* 找到匹配的节点 */
return n; /* 返回当前序号 */
else
p = p->next; /* 否则继续后移 */
}
return 0; /* 未找到返回0 */
}
void main()
{
SLIST* head;
int k;
char ch;
char a[N] = { 'm', 'p', 'g', 'a', 'w', 'x', 'r', 'd' };
head = creatlist(a);
outlist(head);
printf("Enter a letter:");
scanf("%c", &ch);
/**********found**********/
k = fun(head, ch); /* 接收查找结果 */
if (k == 0)
printf("\nNot found!\n");
else
printf("The sequence number is : %d\n", k);
}
/* 创建带头结点的单向链表(数据为字符) */
SLIST* creatlist(char* a)
{
SLIST* h, * p, * q;
int i;
h = p = (SLIST*)malloc(sizeof(SLIST));
for (i = 0; i < N; i++)
{
q = (SLIST*)malloc(sizeof(SLIST));
q->data = a[i];
p->next = q;
p = q;
}
p->next = 0;
return h;
}
/* 输出链表(字符型) */
void outlist(SLIST* h)
{
SLIST* p;
p = h->next;
if (p == NULL)
printf("\nThe list is NULL!\n");
else
{
printf("\nHead");
do
{
printf("->%c", p->data);
p = p->next;
} while (p != NULL);
printf("->End\n");
}
}

关键知识点#

  • 链表遍历模板p = h->next; while (p != NULL) { ...; p = p->next; } 是链表遍历的标准模式。
  • 顺序号计数:从 1 开始计数(n++ 在判断前),未找到返回 0 以区分”找到第 0 个”和”未找到”。
  • 字符型链表datachar 类型,输出用 %c,比较直接用 ==
  • 函数返回值传递信息:用返回值同时表达”是否找到”和”位置”两种信息。

五、链表删除相同节点:保留递增链表中的唯一值#

题目说明#

已建立一个带头结点的单向链表,链表中的各个节点按数据域递增有序连接。函数 fun 的功能是:删除链表中数据域值相同的结点,使之只保留一个。

解题思路#

  1. 由于链表已递增有序,相同值的节点必然相邻。
  2. 使用双指针 pqp 指向当前保留的节点,q 始终指向 p 的下一个节点。
  3. 比较 p->dataq->data
    • 若相等,说明 q 是重复节点:p->next = q->next 跳过 qfree(q) 释放内存,然后 q = p->next 取下一个待比较节点。
    • 若不等,说明 q 不是 p 的重复:p = q 后移,q = q->next 后移。
  4. 循环直到 q == NULL

完整代码#

//已建立一个带头结点的单向链表,链表中的各个节点按数据域
//递增有序连接。函数fun的功能是:删除链表中数据域值相同的结点,
//使之只保留一个。
#include <stdio.h>
#include <stdlib.h>
#define N 8
typedef struct list
{
int data;
struct list* next;
} SLIST;
/* 删除递增有序链表中数据域相同的重复节点 */
void fun(SLIST* h)
{
SLIST* p, * q;
p = h->next; /* p指向第一个数据节点 */
if (p != NULL) /* 链表非空才处理 */
{
q = p->next; /* q指向p的下一个节点 */
while (q != NULL)
{
if (p->data == q->data) /* 发现重复节点 */
{
p->next = q->next; /* 跳过q节点 */
/**********found**********/
free(q); /* 释放q节点内存 */
/**********found**********/
q = p->next; /* q指向新的下一个节点,继续与p比较 */
}
else /* 不重复,p和q同步后移 */
{
p = q;
/**********found**********/
q = q->next;
}
}
}
}
/* 创建带头结点的单向链表 */
SLIST* creatlist(int* a)
{
SLIST* h, * p, * q;
int i;
h = p = (SLIST*)malloc(sizeof(SLIST));
for (i = 0; i < N; i++)
{
q = (SLIST*)malloc(sizeof(SLIST));
q->data = a[i];
p->next = q;
p = q;
}
p->next = 0;
return h;
}
/* 输出链表 */
void outlist(SLIST* h)
{
SLIST* p;
p = h->next;
if (p == NULL)
printf("\nThe list is NULL!\n");
else
{
printf("\nHead");
do
{
printf("->%d", p->data);
p = p->next;
} while (p != NULL);
printf("->End\n");
}
}
void main()
{
SLIST* head;
/* 注意:含重复数据2,2和4,4,4 */
int a[N] = { 1, 2, 2, 3, 4, 4, 4, 5 };
head = creatlist(a);
printf("\nThe list before deleting :\n");
outlist(head);
fun(head);
printf("\nThe list after deleting :\n");
outlist(head);
}

关键知识点#

  • 利用有序性简化去重:有序链表中重复节点必相邻,只需一次遍历 O(n) 即可去重;无序链表去重需 O(n²) 或借助哈希。
  • 删除节点的三步骤:① 跳过节点(修改前驱的 next);② free 释放内存;③ 更新指针指向新的下一节点。
  • free 的重要性:删除节点后必须 free,否则造成内存泄漏。
  • 双指针遍历pq 一前一后,是链表操作的经典模式。

六、结构体实践任务#

任务 1-1:计算学生 8 门课的平均成绩#

题目说明#

定义学生结构体 STREC,包含学号 num[10]、8 门课成绩 s[N] 和平均成绩 ave。编写函数 fun,计算该学生 8 门课的平均成绩并存入 ave 字段。

解题思路#

  1. 结构体成员 s[N] 存储 8 门课成绩,ave 存储平均分。
  2. 函数接收结构体指针 STREC *a,先累加 a->s[i]a->ave,最后除以 N 得到平均值。
  3. 注意初始化 a->ave = 0.0,避免累加随机值。

完整代码#

/*
作者:wyl
时间:2026-07-07 08:19:42
输出:
GA005
85.5 76.0 69.5 85.0 91.0 72.0 64.5 87.5 78.875
*/
#include<stdio.h>
#define N 8
/* 学生结构体:学号、8门课成绩、平均分 */
typedef struct stu
{
char num[10]; //学号
double s[N]; //8门课成绩
double ave; //平均成绩
}STREC;
/* 计算平均成绩,通过指针修改结构体成员 */
void fun(STREC *a)
{
int i=0;
a->ave=0.0; /* 初始化累加和为0 */
for(i=0;i<N;i++)
{
a->ave+=a->s[i]; /* 累加8门课成绩 */
}
a->ave/=N; /* 求平均值 */
}
int main()
{
/* 初始化一个学生:学号GA005,8门课成绩 */
STREC s ={"GA005", 85.5,76,69.5, 85,91,72,64.5,87.5};
int i;
fun(&s); /* 传地址,函数内修改ave */
printf("%s \n", s.num);
for(i=0;i<N;i++)
{
printf("%4.1f ", s.s[i]); /* 输出8门课成绩,保留1位小数 */
}
printf("%7.3f ", s.ave); /* 输出平均分,保留3位小数 */
return 0;
}

关键知识点#

  • 结构体指针访问成员:使用 -> 运算符,等价于 (*a).ave
  • 传地址修改实参:函数形参为 STREC *a,实参传 &s,函数内对 a->ave 的修改直接影响主函数的 s
  • 浮点数格式化输出%4.1f 表示总宽 4 位、1 位小数;%7.3f 表示总宽 7 位、3 位小数。

任务 1-2:找出总分最高的学生#

题目说明#

输入 n 个学生信息(学号、姓名、3 门课成绩),编程找出总分最高的学生,并输出其学号、姓名和总分。如果有多个相同最高分,则输出所有最高分学生的信息。

解题思路#

  1. 定义结构体 STREC,含学号、姓名、3 门成绩、总分 sum
  2. 读入每个学生数据时,同步计算 sum = score[0]+score[1]+score[2]
  3. 编写 findMaxScore 函数遍历数组,返回最高总分。
  4. 再次遍历数组,输出所有 sum == max 的学生信息(处理并列最高分的情况)。

完整代码#

/*
作者:wyl
时间:2026-07-07 08:19:42
题目:
输入样例:
编程找出总分最高的学生,
并输出其学号、姓名和总分。
如果有多个相同最高分,则输出所有最高分学生的信息。
【输入样例】
5
2109001 HuangJie 78 83 79
2109002 Liuhaipeng 79 80 77
2109003 Wangqiang 87 86 76
2109004 Liangfeng 92 89 79
2109005 Chengmeng 80 82 75
【输出样例】
2109004 Liangfeng 260
*/
#include<stdio.h>
#define N 10
/* 学生结构体:学号、姓名、3门成绩、总分 */
typedef struct
{
char num[10]; //学号
char name[20]; //姓名
int score[3]; //分数
int sum; //总分
}STREC;
/* 找最高分的学生总分,返回最高分值 */
int findMaxScore(STREC st[], int n)
{
int i,j;
int max=st[0].sum; /* 假设第一个学生总分最高 */
for(i=1;i<n;i++)
{
if(max<st[i].sum)
max=st[i].sum; /* 更新最高分 */
}
return max;
}
int main()
{
STREC stu[N];
int n=0, max=0;
scanf("%d", &n); /* 读入学生人数 */
for(int i=0;i<n;i++)
{
scanf("%s %s %d %d %d", &stu[i].num,
&stu[i].name, &stu[i].score[0], &stu[i].score[1], &stu[i].score[2]);
/* 同步计算总分 */
stu[i].sum = stu[i].score[0]+stu[i].score[1]+stu[i].score[2];
}
max=findMaxScore(stu, n); /* 找到最高分 */
/* 输出所有总分等于最高分的学生(处理并列情况) */
for(int i=0;i<n;i++)
{
if(stu[i].sum==max)
printf("%s %s %d", stu[i].num,stu[i].name, stu[i].sum);
}
return 0;
}

关键知识点#

  • 结构体数组STREC stu[N] 一次性存储多个学生记录。
  • 求最值的通用模式:假设第一个为最值,遍历比较更新。
  • 处理并列情况:先求出最值,再遍历一次输出所有等于最值的元素,避免遗漏。
  • scanf 读入字符串%s 遇空格结束,适合读入无空格的学号和姓名。

任务 2:筛选高于等于平均分的学生#

题目说明#

学生的记录由学号和成绩组成,N 名学生的数据已放入主函数的结构体数组 s 中。编写函数 fun,把高于等于平均分的学生数据放在 b 所指的数组中,高于等于平均分的学生人数通过形参 n 传回,平均分通过函数值返回。

注:原题描述为”高于等于”,但参考答案代码中使用 a[i].s > avg(严格大于),以下以答案代码为准。

解题思路#

  1. 第一遍遍历:累加所有学生成绩,求出平均分 avg
  2. 第二遍遍历:将成绩 > avg 的学生拷贝到数组 b,同时通过 (*n)++ 计数。
  3. 函数返回平均分 avg
  4. 注意指针参数 int *n 用于”带回”人数,需要用 *n 解引用。

完整代码#

/**
*
* 把高于等于平均分的学生数据放在b所指的数组中,
* 高于等于平均分的学生人数通过形参n传回,
* 平均分通过函数值返回。
*
* 输出样例:
The 6 student data which is higher than 77.583:
GA05 85
GA04 85
GA01 91
GA06 87
GA11 79
GA10 90
*/
#include <stdio.h>
#include <string.h>
#define N 12
/* 学生结构体:学号、成绩 */
typedef struct
{
char num[10];
int s;
} STREC;
/* 筛选高于平均分的学生,返回平均分;人数通过指针n带回 */
double fun(STREC *a, STREC *b, int *n)
{
/**********Program**********/
float avg=0.0;
/* 第一遍:求总分 */
for(int i=0;i<N;i++)
{
avg+=a[i].s;
}
avg/=N; /* 求平均分 */
/* 第二遍:筛选高于平均分的学生 */
for(int i=0, j=0;i<N;i++)
{
if(a[i].s>avg) /* 严格大于平均分 */
{
b[j++]=a[i]; /* 结构体整体赋值拷贝到b */
(*n)++; /* 人数加1,通过指针带回主函数 */
}
}
return avg; /* 平均分通过函数值返回 */
/********** End **********/
}
void main()
{
STREC s[N] = {{"GA05", 85}, {"GA03", 76}, {"GA02", 69}, {"GA04", 85}, {"GA01", 91}, {"GA07", 72}, {"GA08", 64}, {"GA06", 87}, {"GA09", 60}, {"GA11", 79}, {"GA12", 73}, {"GA10", 90}};
STREC h[N];
int i, n;
double ave;
ave = fun(s, h, &n); /* 传s和h数组,n传地址 */
printf("The %d student data which is higher than %7.3f:\n", n, ave);
for (i = 0; i < n; i++)
printf("%s %d\n", h[i].num, h[i].s);
printf("\n");
}

关键知识点#

  • 指针参数带回多个结果:C 语言函数只能返回一个值,需”带回”的额外结果通过指针参数实现(如 int *n)。
  • 两遍遍历策略:第一遍求统计量(平均分),第二遍根据统计量筛选,思路清晰不易出错。
  • 结构体数组拷贝b[j++] = a[i] 直接整体赋值,等价于 memcpy 逐字节拷贝。
  • 函数返回值与指针参数的配合:平均分用 return,人数用 *n,是 C 语言常见的”多结果返回”模式。

任务 3:按分数降序排列学生记录#

题目说明#

学生的记录由学号和成绩组成,N 名学生的数据已放入主函数中的结构体数组 s 中。请编写函数 fun,其功能是:按分数降序排列学生的记录,高分在前,低分在后。

解题思路#

  1. 采用冒泡排序:相邻元素两两比较,若前者分数小于后者则交换。
  2. 外层循环 i 从 0 到 N-2,内层循环 j 从 0 到 N-i-2
  3. 比较条件 a[j].s < a[j+1].s:前小后大则交换,实现降序(大的前移)。
  4. 结构体整体交换 temp = a[j]; a[j] = a[j+1]; a[j+1] = temp;

完整代码#

/**
* 学生的记录由学号和成绩组成,N 名学生的数据已放入主函数中的结构体数组 s 中。
请编写函数 fun,其功能是:按分数降序排列学生的记录,高分在前,低分在后。
The data after sorted :
GA001 91 GA013 91 GA014 91 GA006 87
GA005 85 GA004 85 GA015 85 GA003 76
GA007 72 GA016 72 GA002 69 GA011 66
GA008 64 GA012 64 GA017 64 GA018 64
*/
#include <stdio.h>
#define N 16
typedef struct
{
char num[10];
int s;
} STREC;
/* 冒泡排序:按分数降序排列 */
void fun(STREC a[])
{
/**********Program**********/
int i,j;
STREC temp;
for(i=0;i<N-1;i++)
{
for(j=0;j<N-i-1;j++)
{
if(a[j].s<a[j+1].s) /* 前者分数小于后者,交换使大的前移 */
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
/********** End **********/
}
}
}
void main()
{
STREC s[N] = {{"GA005", 85}, {"GA003", 76}, {"GA002", 69}, {"GA004", 85}, {"GA001", 91}, {"GA007", 72}, {"GA008", 64}, {"GA006", 87}, {"GA015", 85}, {"GA013", 91}, {"GA012", 64}, {"GA014", 91}, {"GA011", 66}, {"GA017", 64}, {"GA018", 64}, {"GA016", 72}};
int i;
fun(s);
printf("The data after sorted :\n");
for (i = 0; i < N; i++)
{
if ((i) % 4 == 0)
printf("\n"); /* 每4个换行 */
printf("%s %4d ", s[i].num, s[i].s);
}
printf("\n");
}

关键知识点#

  • 冒泡排序:相邻元素两两比较交换,每轮把最小(或最大)元素”沉”到末尾。时间复杂度 O(n²),稳定排序。
  • 降序 vs 升序:降序用 a[j] < a[j+1](前小后大则交换),升序用 a[j] > a[j+1]
  • 冒泡排序内层循环边界j < N-i-1,因为每轮结束后末尾 i 个元素已就位,无需再比较。
  • 格式化输出%4d 表示占 4 位右对齐,if (i % 4 == 0) printf("\n") 实现每行 4 个。

任务 4:动态链表的创建、插入、删除#

题目说明#

实现动态链表的完整操作:创建链表、按值有序插入节点、删除指定值节点、遍历输出、销毁整个链表。

解题思路#

  1. 创建链表 createLink:遍历数组,逐个 malloc 新节点,尾插法链接。需维护 head(头指针)和 qr(尾指针)。
  2. 插入节点 insertNode:按数据值有序插入(升序)。需处理三种情况:
    • 空链表:新节点直接作为头节点。
    • 插入到表头:新节点的 next 指向原头节点,更新头指针。
    • 插入到中间或末尾:找到合适位置后插入。
  3. 删除节点 DeleteNode:遍历找到目标节点,分”删除头节点”和”删除中间/末尾节点”两种情况处理。删除后 free 释放内存。
  4. 销毁链表 DeleteLink:遍历逐个 free 所有节点,防止内存泄漏。

完整代码#

/*
创建动态链表
*/
#include<stdio.h>
#include<stdlib.h>
#define N 5
typedef struct Node
{
int data;
struct Node * next;
}NODE;
/* 创建链表:尾插法,返回头指针 */
NODE* createLink(int x[], int n)
{
int i=0;
NODE * p=NULL, *head=NULL, * qr=NULL;
for(i=0; i<n; i++)
{
p=(NODE *)malloc(sizeof(NODE)); /* 申请新节点 */
p->data=x[i];
p->next=NULL;
if(head==NULL) /* 第一个节点,设为头 */
{
head=p;
qr=p;
}
else /* 后续节点,尾插 */
{
qr->next = p;
qr=qr->next;
}
}
return head;
}
/* 有序插入节点(升序),返回头指针 */
NODE * insertNode(NODE* head,int data)
{
NODE * pr=head, * temp=NULL;
NODE * p =(NODE *)malloc(sizeof(NODE));
if (p==NULL)
{
printf("No enough memory!\n");
exit(0);
}
p->next =NULL;
p->data=data;
if (head==NULL) /* 空链表,新节点为头 */
{
head=p;
}
else
{
/* 寻找插入位置:pr->data < data 时继续后移 */
while(pr->data <data && pr->next !=NULL)
{
temp = pr; /* temp记录pr的前驱 */
pr = pr->next;
}//找到数据
if( pr->data >= data) /* 插入到pr之前 */
{
if(pr==head) /* 插入到表头 */
{
p->next = head;
head=p;
}
else /* 插入到中间 */
{
pr=temp;
p->next = pr->next;
pr->next =p;
}
}
else /* 插入到末尾 */
{
pr->next=p;
}
}
return head;
}
/* 删除指定data的结点,返回head */
NODE * DeleteNode(NODE *head,int data)
{
NODE * p=head, *pr=head;
if(head==NULL)
return head;
else
{
/* 寻找待删除节点 */
while(p->data !=data && p->next !=NULL)
{
pr = p; /* pr记录p的前驱 */
p = p->next;
}//找到数据
if(data==p->data) /* 找到目标节点 */
{
if(p==head) /* 删除头节点 */
head=p->next;
else /* 删除中间/末尾节点 */
pr->next=p->next;
free(p); /* 释放内存 */
}
else
printf("Not found\n");
}
return head;
}
/* 遍历输出链表 */
void printNode(NODE *head)
{
NODE *p;
for(p=head;p!=NULL;p=p->next)
{
printf("%d,", p->data);
}
printf("\n");
}
/* 销毁整个链表,释放所有节点内存 */
void DeleteLink(NODE *head)
{
NODE * p=head, *pr=NULL;
while(p)
{
pr=p;
p=p->next;
free(pr); /* 逐个释放 */
}
}
int main()
{
NODE *head, *p;
int dataArry[5]={1,3,5,7,9};
head = createLink(dataArry, N); /* 创建:1,3,5,7,9 */
printNode(head);
head = insertNode(head, 10); /* 插入10:1,3,5,7,9,10 */
printNode(head);
head = DeleteNode(head, 3); /* 删除3:1,5,7,9,10 */
printNode(head);
return 0;
}

关键知识点#

  • 不带头结点的链表:与前面例子不同,此链表无头结点,head 直接指向第一个数据节点,因此插入/删除头节点时需特殊处理并更新 head
  • 尾插法建表:维护尾指针 qr,每次新节点链接到 qr->next,然后 qr 后移。
  • 有序插入的边界处理:需分别考虑空表、表头插入、中间插入、表尾插入四种情况。
  • 删除节点的边界处理:需分别考虑删除头节点和删除中间/末尾节点。
  • 内存管理malloc 申请的内存必须 free 释放;DeleteLink 销毁整个链表防止内存泄漏。
  • 函数返回头指针:插入/删除可能改变头指针,因此函数返回新的 head,调用方需接收返回值。

七、课前测:结构体数组#

课前测 1:职工实发工资计算#

题目说明#

输入一个整数 n(3 ≤ n ≤ 10),然后输入 n 个职工的信息(姓名、基本工资、津贴、奖金、支出),要求计算并输出每位职工的实发工资。实发工资 = 基本工资 + 津贴 + 奖金 - 支出。

解题思路#

  1. 定义结构体 struct emp,包含姓名 name[10]、基本工资 jbgz、津贴 jt、奖金 jj、支出 zc
  2. 定义结构体数组 s[10],循环读入 n 个职工数据。
  3. 循环计算并输出每个职工的实发工资:jbgz + jt + jj - zc

注意:原代码中实发工资公式写为 s[i].jbgz + s[i].jt + s[i].jt - s[i].zc津贴 jt 被加了两次,漏加了奖金 jj。这看起来是一个 bug,但由于期望输出与该公式吻合,可能是题目本身的设计。正确公式应为 jbgz + jt + jj - zc。下方代码保留原写法,并在注释中标注。

完整代码#

/*
输入一个整数n(3<=n<=10),然后输入n个职工的信息
要求计算每位职工的实发工资,实发工资=基本工资+津贴+奖金-支出
输入:
n=3
Zhao 3450 2500 3000 895
Qian 3680 3000 4000 1068
Wang 4350 3500 5000 1275
输出:
Zhao:7555.00
Qian:8612.00
Wang:10075.00
*/
#include <stdio.h>
int main (void )
{
/* 定义职工结构体 */
struct emp{
char name[10];
double jbgz, jt, jj, zc; /* 基本工资、津贴、奖金、支出 */
};
struct emp s[10];
int i, n;
printf("n=");
scanf("%d", &n);
/* 读入n个职工数据 */
for (i = 0; i < n; i++){
scanf("%s%lf%lf%lf%lf", s[i].name, &s[i].jbgz, &s[i].jt, &s[i].jj, &s[i].zc);
}
/* 计算并输出实发工资 */
for (i = 0; i < n; i++){ /* 计算时设置断点调试 */
/* 注意:原代码为 jbgz+jt+jt-zc(jt加两次),与期望输出吻合;
按题目描述正确公式应为 jbgz+jt+jj-zc,此处保留原写法 */
printf ("%s:%.2f\n", s[i].name, s[i].jbgz+s[i].jt+ s[i].jt -s[i].zc);
}
return 0;
}

关键知识点#

  • 结构体定义在函数内部struct emp 定义在 main 函数内,作用域局限于该函数。
  • scanf 读入 double:必须用 %lf,不能用 %f%f 用于 float)。
  • printf 输出 double:用 %f%.2f(保留 2 位小数)即可,doublefloatprintf 中都用 %f
  • 数组名作为 scanf 参数s[i].name 是数组名,本身就是地址,无需加 &;而 &s[i].jbgz 等普通变量需加 &

课前测 2:找出总分最高的学生#

题目说明#

建立一个有 n(3 ≤ n ≤ 10)个学生成绩的结构记录,包括学号、姓名和 3 门成绩,输出总分最高学生的姓名和总分。

解题思路#

  1. 定义结构体 struct students,含学号 number、姓名 name[20]、3 门成绩 score[3]、总分 sum
  2. 循环读入 n 个学生数据,读入时同步累加计算 sum
  3. 遍历数组找总分最大的学生,用 index 记录其下标。
  4. 输出 student[index] 的姓名和总分。

完整代码#

/*
找出总分最高的学生:建立一个有n(3<=n<=10)个学生成绩的结构记录,
包括学号、姓名和3门成绩,输出总分最高学生的姓名和总分。
输入:
n=5
1 Huang 78 83 75
2 Wang 76 80 77
3 Shen 87 83 76
4 Zhang 92 88 78
5 Liu 80 82 75
输出:
Zhang, 258
*/
#include <stdio.h>
int main (void)
{
int i, index, j, n, max=0;
/* 定义学生结构体 */
struct students{
int number;
char name[20];
int score[3];
int sum;
}student[10];
printf("n=");
scanf("%d", &n);
/* 读入n个学生数据,同步计算总分 */
for (i = 0; i < n; i++){
scanf("%d%s", &student[i].number, student[i].name);
student[i].sum=0;
for(j = 0; j < 3; j++){
scanf("%d", &student[i].score[j]);
student[i].sum += student[i].score[j]; /* 累加3门成绩 */
}
}
/* 查找总分最高的学生 */
index = 0;
max = student[0].sum; /* 假设第一个总分最高 */
for(i = 1; i < n; i++){
if(max < student[i].sum) {
index = i; /* 更新最高分学生下标 */
max=student[i].sum;
}
}
/* 输出最高分学生的姓名和总分 */
printf("%s, %d\n", student[index].name, student[index].sum);
return 0;
}

关键知识点#

  • 结构体数组定义与变量同时声明struct students {...} student[10]; 在定义类型的同时声明数组变量。
  • 边读入边计算:读入成绩时同步累加 sum,避免再写一个循环。
  • 记录下标而非记录整个结构体:用 index 记录最高分学生的下标,比拷贝整个结构体更高效。
  • 求最值的标准模式:假设第一个为最值,遍历比较更新,与任务 1-2 的 findMaxScore 函数思路一致。

总结#

本篇涵盖了结构体与链表的核心操作:

知识点涉及章节核心技巧
结构体定义与初始化填空题、所有实践任务structtypedef、嵌套初始化
结构体数组排序填空题、任务 3选择排序、冒泡排序、strcmp
结构体指针与传参任务 1-1、任务 2-> 运算符、传地址修改实参
链表创建链表创建、任务 4头插法、尾插法、头结点
链表遍历与查找链表查找while(p!=NULL) 模板
链表插入链表创建、任务 4双指针、有序插入、边界处理
链表删除链表删除相同节点、任务 4free 释放、双指针
链表逆置链表逆序三指针法、头插法思想
内存管理所有链表章节malloc/free 配对、防内存泄漏

掌握这些基础操作后,可以进一步学习双向链表、循环链表、链表合并、链表环检测等进阶内容。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

结构体与链表代码详解
https://blog.radarweb.top/posts/c/结构体与链表代码详解/
作者
Sherry
发布于
2026-06-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录