引言:C语言实验教程的重要性与学习价值
C语言作为计算机科学与技术专业的基础核心课程,其实验环节对于巩固理论知识、培养编程思维具有不可替代的作用。魏英教授编写的《C语言程序设计实验教程第二版》是众多高校广泛采用的实验教材,该书通过系统化的实验项目设计,帮助学生从基础语法到复杂算法逐步掌握C语言编程技能。
本教程的特点在于:
- 循序渐进:从简单的输入输出到复杂的数据结构应用
- 理论与实践结合:每个实验都对应理论课的知识点
- 项目驱动:通过实际案例让学生理解编程的实际应用价值
- 错误处理:详细分析常见错误类型及调试方法
实验一:C语言开发环境与基础语法实验
实验目标
- 熟悉C语言开发环境(Visual Studio/Code::Blocks/Dev-C++)
- 掌握C程序的基本结构
- 理解变量定义、数据类型和基本输入输出
- 编写并调试第一个C程序
实验内容详解
1. 开发环境配置
推荐环境:Visual Studio Code + MinGW-w64 或 Visual Studio Community
配置步骤:
# 1. 安装MinGW-w64编译器
# 下载地址:https://sourceforge.net/projects/mingw-w64/
# 安装时选择架构:x86_64, 线程模型:posix
# 2. 配置环境变量
# 将bin目录添加到PATH,例如:C:\mingw64\bin
# 3. 验证安装
gcc --version
2. 第一个C程序:Hello World
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
代码详解:
#include <stdio.h>:包含标准输入输出头文件int main():主函数,程序执行的入口点printf():格式化输出函数return 0:正常退出程序,返回状态码0
3. 变量与数据类型实验
#include <stdio.h>
int main() {
// 整型变量
int age = 20;
long bigNumber = 1000000L;
// 浮点型变量
float height = 1.75f;
double pi = 3.1415926535;
// 字符型变量
char grade = 'A';
// 基本输入输出
printf("年龄:%d\n", age);
printf("身高:%.2f米\n", height);
printf("等级:%c\n", grade);
return 0;
}
常见错误分析:
- 错误1:忘记分号
;int a = 5 // 编译错误:expected ';' before 'return' - 错误2:变量未初始化
int x; // x的值是随机的,可能导致程序异常 printf("%d", x); // 未定义行为 - 错误3:格式化字符串与参数不匹配
int num = 10; printf("数字:%f", num); // 错误:%f用于float/double,应该用%d
实验技巧分享
- 调试技巧:使用
printf打印中间变量值 - 代码规范:变量命名要有意义,如
studentAge而不是a - 版本控制:即使实验简单,也建议使用Git管理代码
- 注释习惯:在复杂逻辑处添加注释,解释”为什么”而不是”做什么”
实验二:分支结构程序设计
实验目标
掌握if-else语句和switch语句的使用,理解条件判断的逻辑。
实验内容详解
1. 成绩等级判断程序
#include <stdio.h>
int main() {
int score;
printf("请输入成绩(0-100):");
scanf("%d", &score);
if (score < 0 || score > 100) {
printf("输入错误!\n");
} else if (score >= 90) {
printf("优秀\n");
} else if (score >= 80) {
printf("良好\n");
} else if (score >= 70) {
printf("中等\n");
} else if (C >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
return 0;
}
注意:上面代码有个故意设置的错误,C >= 60应该是score >= 60,这是学生常犯的变量名拼写错误。
2. Switch语句实现计算器
#include <stdio.h>
int main() {
char operator;
double num1, num2, result;
printf("输入表达式(如:2 + 3):");
scanf("%lf %c %lf", &num1, &operator, &num2);
switch(operator) {
case '+':
result = num1 + num2;
printf("%.2lf + %.2lf = %.2lf\n", num1, num2, result);
break;
case '-':
result = num1 - num2;
printf("%.2lf - %.2lf = %.2lf\n", num1, num2, result);
break;
case '*':
result = num1 * num2;
printf("%.2lf * %.2lf = %.2lf\n", num1, num2, result);
break;
case '/':
if (num2 != 0) {
result = num1 / num2;
printf("%.2lf / %.2lf = %.2lf\n", num1, num2, result);
} else {
printf("错误:除数不能为零!\n");
}
break;
default:
printf("错误:未知运算符!\n");
break;
}
return 0;
}
3. 逻辑运算符实验
#include <stdio.h>
int main() {
int year;
printf("请输入年份:");
scanf("%d", &year);
// 判断闰年:能被4整除但不能被100整除,或能被400整除
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
printf("%d是闰年\n", year);
} else {
printf("%d不是闰年\n", year);
}
return 0;
}
实验技巧分享
- 缩进规范:使用4个空格或1个Tab,保持代码层次清晰
- 边界测试:测试0、负数、极大值等边界情况
- 避免深层嵌套:超过3层嵌套时考虑重构代码
- 使用括号:即使运算符优先级明确,也建议用括号明确意图
实验三:循环结构程序设计
实验目标
掌握for、while、do-while循环,理解循环控制语句(break、continue)。
实验内容详解
1. 求1到100的和
#include <stdio.h>
int main() {
int sum = 0;
// 方法1:for循环
for (int i = 1; i <= 100; i++) {
sum += i;
}
printf("1到100的和:%d\n", sum);
// 方法2:while循环
sum = 0;
int i = 1;
while (i <= 100) {
sum += i;
i++;
}
printf("1到100的和:%d\n", sum);
// 方法3:do-while循环
sum = 0;
i = 1;
do {
sum += i;
i++;
} while (i <= 100);
printf("1到100的和:%d\n", sum);
return 0;
}
2. 打印九九乘法表
#include <stdio.h>
int main() {
printf("九九乘法表\n");
printf("================================\n");
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%d×%d=%-4d", i, j, i*j);
}
printf("\n");
}
return 0;
}
3. 百钱买百鸡问题
#include <stdio.h>
int main() {
int x, y, z; // 公鸡、母鸡、小鸡
printf("百钱买百鸡的所有方案:\n");
for (x = 0; x <= 20; x++) { // 公鸡最多20只
for (y = 0; y <= 33; y++) { // 母鸡最多33只
z = 100 - x - y; // 小鸡数量
if (5*x + 3*y + z/3 == 100 && z % 3 == 0) {
printf("公鸡:%d只,母鸡:%d只,小鸡:%d只\n", x, y, z);
}
}
}
return 0;
}
4. break和continue的使用
#include <stdio.h>
int main() {
// break:立即退出整个循环
printf("break示例:\n");
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 当i=5时,退出整个循环
}
printf("%d ", i);
}
printf("\n");
// continue:跳过本次循环的剩余代码
printf("continue示例:\n");
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i);
}
printf("\n");
return 0;
}
实验技巧分享
- 循环变量初始化:在循环开始前正确初始化循环变量
- 循环条件:确保循环条件最终会变为false,避免死循环
- 循环变量更新:在循环体内更新循环变量
- 调试循环:在循环内部打印关键变量值,观察循环过程
实验四:数组与字符串实验
实验目标
掌握一维数组、二维数组的定义和使用,理解字符串与字符数组的关系。
实验内容详解
1. 一维数组:统计成绩
#include <stdio.h>
int main() {
int scores[5];
int sum = 0;
float average;
printf("请输入5个学生的成绩:\n");
for (int i = 0; i < 5; i++) {
printf("学生%d:", i+1);
scanf("%d", &scores[i]);
sum += scores[i];
}
average = sum / 5.0;
printf("\n统计结果:\n");
printf("总分:%d\n", sum);
printf("平均分:%.2f\n", average);
// 查找最高分
int max = scores[0];
for (int i = 1; i < 5; i++) {
if (scores[i] > max) {
max = scores[i];
}
}
printf("最高分:%d\n", max);
return 0;
}
2. 二维数组:矩阵运算
#include <stdio.h>
#define ROWS 3
#define COLS 3
int main() {
int matrix[ROWS][COLS];
int transpose[COLS][ROWS];
printf("请输入3×3矩阵:\n");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("matrix[%d][%d]:", i, j);
scanf("%d", &matrix[i][j]);
}
}
// 计算转置矩阵
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
transpose[j][i] = matrix[i][j];
}
}
// 输出原矩阵
printf("\n原矩阵:\n");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}
// 输出转置矩阵
printf("\n转置矩阵:\n");
for (int i = 0; i < COLS; i++) {
for (int j = 0; j < ROWS; j++) {
printf("%4d", transpose[i][j]);
}
printf("\n ");
}
return 0;
}
3. 字符串处理
#include <stdio.h>
#include <string.h>
int main() {
char str1[50] = "Hello";
char str2[50] = "World";
char str3[100];
// 字符串连接
strcpy(str3, str1);
strcat(str3, " ");
strcat(str3, str2);
printf("连接结果:%s\n", str3);
// 字符串长度
printf("长度:%d\n", strlen(str3));
// 字符串比较
if (strcmp(str1, str2) < 0) {
printf("%s 小于 %s\n", str1, str2);
}
// 字符串复制
char copy[50];
strcpy(copy, str3);
printf("复制结果:%s\n", copy);
// 字符串查找
char *pos = strchr(str3, 'W');
if (pos != NULL) {
printf("找到字符'W',位置:%ld\n", pos - str3);
}
return 0;
}
4. 冒泡排序算法
#include <stdio.h>
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-1-i; j++) {
if (arr[j] > arr[j+1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
bubbleSort(arr, n);
printf("排序后:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
实验技巧分享
- 数组边界:始终检查数组索引,避免越界访问
- 字符串结束符:字符数组要预留空间给’\0’
- 二维数组行列:使用宏定义或常量,便于修改
- 数组作为函数参数:需要传递数组大小
实验五:函数与模块化编程
实验目标
掌握函数的定义、调用和参数传递,理解模块化编程思想。
实验内容详解
1. 基本函数:计算阶乘
#include <stdio.h>
// 函数声明
long factorial(int n);
int main() {
int num;
printf("请输入一个正整数:");
scanf("%d", &num);
if (num < 0) {
printf("错误:请输入正整数!\n");
} else {
long result = factorial(num);
printf("%d! = %ld\n", num, result);
}
return 0;
}
// 函数定义
long factorial(int n) {
if (n == 0 || n == 1) {
return 1;
}
return n * factorial(n - 1); // 递归调用
}
2. 函数参数传递:值传递与地址传递
#include <stdio.h>
// 值传递:函数内修改不影响原值
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("函数内交换:a=%d, b=%d\n", a, b);
}
// 地址传递:函数内修改会影响原值
void swapByAddress(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("函数内交换:a=%d, b=%d\n", *a, *b);
}
int main() {
int x = 10, y = 20;
printf("原始值:x=%d, y=%d\n", x, y);
// 值传递测试
swapByValue(x, y);
printf("值传递后:x=%d, y=%d\n", x, y);
// 地址传递测试
swapByAddress(&x, &y);
printf("地址传递后:x=%d, y=%d\n", x, y);
return 0;
}
3. 数组作为函数参数
#include <stdio.h>
// 计算数组平均值
float calculateAverage(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (float)sum / size;
}
// 查找数组最大值
int findMax(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
int main() {
int scores[] = {85, 92, 78, 96, 88};
int size = sizeof(scores) / sizeof(scores[0]);
float avg = calculateAverage(scores, size);
int max = findMax(scores, size);
printf("数组:");
for (int i = 0; i < size; i++) {
printf("%d ", scores[i]);
}
printf("\n");
printf("平均值:%.2f\n", avg);
printf("最大值:%d\n", max);
return 0;
}
4. 变量的作用域与存储类别
#include <stdio.h>
int globalVar = 100; // 全局变量
void testFunction() {
static int staticVar = 0; // 静态局部变量
int autoVar = 0; // 自动局部变量
staticVar++;
autoVar++;
printf("静态变量:%d,自动变量:%d\n", staticVar, autoVar);
}
int main() {
printf("全局变量:%d\n", globalVar);
testFunction(); // 输出:静态变量:1,自动变量:1
testFunction(); // 输出:静态变量:2,自动变量:1
testFunction(); // 输出:静态变量:3,自动变量:1
// 局部变量覆盖全局变量
int globalVar = 200;
printf("局部覆盖后:%d\n", globalVar);
return 0;
}
实验技巧分享
- 函数单一职责:每个函数只做一件事,保持简洁
- 参数检查:在函数开始检查参数有效性
- 返回值处理:明确函数返回值的含义和错误处理
- 递归深度:注意递归可能导致栈溢出,考虑迭代替代
实验六:指针与动态内存分配
实验目标
掌握指针的基本概念、指针与数组的关系、动态内存分配。
实验内容详解
1. 指针基础
#include <stdio.h>
int main() {
int num = 100;
int *p = # // 指针p指向num的地址
printf("num的值:%d\n", num);
printf("num的地址:%p\n", &num);
printf("指针p的值:%p\n", p);
printf("指针p指向的值:%d\n", *p);
// 通过指针修改变量
*p = 200;
printf("通过指针修改后,num的值:%d\n", num);
return 0;
}
2. 指针与数组
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // 数组名是首元素地址
printf("数组遍历(指针方式):\n");
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, 地址:%p\n", i, *(p + i), p + i);
}
// 指针运算
printf("\n指针运算:\n");
printf("p = %p\n", p);
printf("p+1 = %p\n", p+1);
printf("*(p+1) = %d\n", *(p+1));
return 0;
}
3. 动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n;
printf("请输入要创建的数组大小:");
scanf("%d", &n);
// 动态分配内存
arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 初始化数组
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
// 使用数组
printf("动态数组内容:\n");
for (int i = 0; i < n; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// 释放内存
free(arr);
arr = NULL; // 防止悬空指针
return 0;
}
4. 字符串与指针
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello World";
char *p = str;
printf("字符串:%s\n", str);
printf("指针遍历:\n");
while (*p != '\0') {
printf("%c", *p);
p++;
}
printf("\n");
// 字符串数组
char *names[] = {"Alice", "Bob", "Charlie"};
for (int i = 0; i < 3; i++) {
printf("%s ", names[i]);
}
printf("\n");
return 0;
}
实验技巧分享
- 指针初始化:指针使用前必须初始化,避免野指针
- 内存释放:malloc/free必须配对使用
- 空指针检查:分配内存后检查是否为NULL
- 悬空指针:释放内存后将指针设为NULL
实验七:结构体与共用体
实验目标
掌握结构体的定义、使用,理解结构体与指针、数组的关系。
实验内容详解
1. 学生信息管理
#include <stdio.h>
#include <string.h>
// 定义结构体
struct Student {
int id;
char name[50];
float score;
};
int main() {
struct Student stu1 = {1001, "张三", 85.5};
struct Student stu2;
// 输入学生信息
printf("请输入学生信息:\n");
printf("学号:");
scanf("%d", &stu2.id);
printf("姓名:");
scanf("%s", stu2.name);
printf("成绩:");
scanf("%f", &stu2.score);
// 输出信息
printf("\n学生信息:\n");
printf("学号:%d,姓名:%s,成绩:%.1f\n", stu1.id, stu1.name, stu1.score);
printf("学号:%d,姓名:%s,成绩:%.1f\n", stu2.id, stu2.name, stu2.score);
return 0;
}
2. 结构体数组与函数
#include <stdio.h>
#include <string.h>
struct Student {
int id;
char name[50];
float score;
};
// 打印学生信息
void printStudent(struct Student stu) {
printf("学号:%d,姓名:%s,成绩:%.1f\n", stu.id, stu.name, stu.score);
}
// 按成绩排序
void sortStudents(struct Student arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-1-i; j++) {
if (arr[j].score < arr[j+1].score) { // 降序
struct Student temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
struct Student students[] = {
{1001, "张三", 85.5},
{1002, "李四", 92.0},
{1003, "王五", 78.5}
};
int n = sizeof(students) / sizeof(students[0]);
printf("排序前:\n");
for (int i = 0; i < n; i++) {
printStudent(students[i]);
}
sortStudents(students, n);
printf("\n排序后(按成绩降序):\n");
for (int i = 0; i < n; i++) {
printStudent(students[i]);
}
return 0;
}
3. 结构体指针
#include <stdio.h>
struct Point {
int x;
int y;
};
// 使用结构体指针作为函数参数
void movePoint(struct Point *p, int dx, int dy) {
p->x += dx; // 等价于 (*p).x
p->y += dy;
}
int main() {
struct Point pt = {10, 20};
printf("原始点:(%d, %d)\n", pt.x, pt.y);
movePoint(&pt, 5, -3);
printf("移动后:(%d, %d)\n", pt.x, pt.y);
// 结构体指针动态分配
struct Point *p = malloc(sizeof(struct Point));
if (p != NULL) {
p->x = 100;
p->y = 200;
printf("动态结构体:(%d, %d)\n", p->x, p->y);
free(p);
}
return 0;
}
4. 共用体(Union)
#include <stdio.h>
// 共用体:所有成员共享同一内存空间
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
printf("共用体大小:%lu字节\n", sizeof(data));
data.i = 10;
printf("赋值整数后:i=%d\n", data.i);
data.f = 220.5;
printf("赋值浮点后:f=%.1f\n", data.f);
printf("此时i=%d(被覆盖)\n", data.i); // 值已改变
strcpy(data.str, "C Language");
printf("赋值字符串后:%s\n", data.str);
printf("此时f=%.1f(被覆盖)\n", data.f); // 值已改变
return 0;
}
实验技巧分享
- 结构体对齐:使用
#pragma pack控制内存对齐 - 结构体赋值:可以直接赋值,但数组成员需要逐个复制
- 共用体使用:用于节省内存,但需注意当前存储的是哪个成员
- 结构体大小:使用sizeof计算,考虑内存对齐
实验八:文件操作实验
实验目标
掌握文件的打开、读写、关闭操作,理解文本文件与二进制文件的区别。
实验内容详解
1. 文本文件读写
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
char filename[] = "test.txt";
char buffer[100];
// 写文件
fp = fopen(filename, "w");
if (fp == NULL) {
printf("无法打开文件:%s\n", filename);
return 1;
}
fprintf(fp, "Hello, File!\n");
fprintf(fp, "This is a test file.\n");
fprintf(fp, "Line number: %d\n", 3);
fclose(fp);
printf("文件写入完成。\n");
// 读文件
fp = fopen(filename, "r");
if (fp == NULL) {
printf("无法打开文件:%s\n", filename);
return 1;
}
printf("\n文件内容:\n");
while (fgets(buffer, 100, fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
return 0;
}
2. 二进制文件读写
#include <stdio.h>
#include <stdlib.h>
struct Student {
int id;
char name[50];
float score;
};
int main() {
FILE *fp;
struct Student stu1 = {1001, "张三", 85.5};
struct Student stu2 = {1002, "李四", 92.0};
struct Student stu_read;
// 二进制写入
fp = fopen("students.dat", "wb");
if (fp == NULL) {
printf("无法创建文件!\n");
return 1;
}
fwrite(&stu1, sizeof(struct Student), 1, fp);
fwrite(&stu2, sizeof(struct Student), 1, fp);
fclose(fp);
printf("二进制文件写入完成。\n");
// 二进制读取
fp = fopen("students.dat", "rb");
if (fp == NULL) {
printf("无法打开文件!\n");
return 1;
}
printf("\n从二进制文件读取:\n");
while (fread(&stu_read, sizeof(struct Student), 1, fp) == 1) {
printf("学号:%d,姓名:%s,成绩:%.1f\n",
stu_read.id, stu_read.name, stu_read.score);
}
fclose(fp);
return 0;
}
3. 文件定位与随机访问
#include <stdio.h>
int main() {
FILE *fp;
char data[] = "0123456789";
// 写入数据
fp = fopen("position.txt", "w+");
fprintf(fp, "%s", data);
// 移动文件指针
fseek(fp, 5, SEEK_SET); // 从文件开头移动5字节
printf("位置5的字符:%c\n", fgetc(fp));
fseek(fp, -2, SEEK_END); // 从文件末尾前移2字节
printf("倒数第2个字符:%c\n", fgetc(fp));
fseek(fp, 0, SEEK_SET); // 回到开头
printf("文件开头字符:%c\n", fgetc(fp));
fclose(fp);
return 0;
}
4. 文件错误处理
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
printf("错误代码:%d\n", errno);
printf("错误信息:%s\n", strerror(errno));
return 1;
}
// 文件操作...
fclose(fp);
return 0;
}
实验技巧分享
- 文件打开模式:区分文本模式和二进制模式
- 检查返回值:每次文件操作后检查返回值
- 及时关闭:文件使用完毕立即关闭
- 路径问题:注意相对路径和绝对路径的区别
实验九:高级数据结构——链表
实验目标
掌握链表的创建、遍历、插入、删除等操作。
实验内容详解
1. 单链表的基本操作
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
struct Node {
int data;
struct Node *next;
};
// 创建新节点
struct Node* createNode(int data) {
struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表末尾添加节点
void appendNode(struct Node **head, int data) {
struct Node *newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct Node *temp = *head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
}
// 打印链表
void printList(struct Node *head) {
struct Node *temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
// 在指定位置插入节点
void insertNode(struct Node **head, int pos, int data) {
struct Node *newNode = createNode(data);
if (pos == 1) {
newNode->next = *head;
*head = newNode;
return;
}
struct Node *temp = *head;
for (int i = 1; i < pos-1 && temp != NULL; i++) {
temp = temp->next;
}
if (temp == NULL) {
printf("位置超出范围!\n");
free(newNode);
return;
}
newNode->next = temp->next;
temp->next = newNode;
}
// 删除指定位置的节点
void deleteNode(struct Node **head, int pos) {
if (*head == NULL) {
printf("链表为空!\n");
return;
}
struct Node *temp = *head;
if (pos == 1) {
*head = temp->next;
free(temp);
return;
}
for (int i = 1; i < pos-1 && temp != NULL; i++) {
temp = temp->next;
}
if (temp == NULL || temp->next == NULL) {
printf("位置超出范围!\n");
return;
}
struct Node *toDelete = temp->next;
temp->next = toDelete->next;
free(toDelete);
}
// 释放链表内存
void freeList(struct Node **head) {
struct Node *temp = *head;
while (temp != NULL) {
struct Node *next = temp->next;
free(temp);
temp = next;
}
*head = NULL;
}
int main() {
struct Node *head = NULL;
// 创建链表
appendNode(&head, 10);
appendNode(&head, 20);
appendNode(&head, 30);
printf("初始链表:");
printList(head);
// 插入节点
insertNode(&head, 2, 15);
printf("在位置2插入15:");
printList(head);
// 删除节点
deleteNode(&head, 3);
printf("删除位置3的节点:");
printList(head);
// 释放内存
freeList(&head);
return 0;
}
2. 链表应用:学生成绩管理
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
int id;
char name[50];
float score;
};
struct Node {
struct Student data;
struct Node *next;
};
// 按成绩排序链表
void sortList(struct Node *head) {
if (head == NULL) return;
struct Node *i, *j;
struct Student temp;
for (i = head; i->next != NULL; i = i->next) {
for (j = i->next; j != NULL; j = j->next) {
if (i->data.score < j->data.score) {
temp = i->data;
i->data = j->data;
j->data = temp;
}
}
}
}
实验技巧分享
- 内存管理:每次malloc都要检查返回值
- 指针操作:使用二级指针修改头指针
- 边界检查:插入删除时检查位置有效性
- 调试技巧:打印链表时显示地址便于调试
实验十:综合项目——学生管理系统
实验目标
综合运用所学知识,开发一个完整的学生信息管理系统。
项目需求
- 学生信息包括:学号、姓名、成绩
- 功能:添加、删除、修改、查询、排序、统计、文件存储
- 使用链表存储数据
- 支持文件持久化
完整代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 学生结构体
struct Student {
int id;
char name[50];
float score;
};
// 链表节点
struct Node {
struct Student data;
struct Node *next;
};
// 全局变量
struct Node *head = NULL;
char filename[] = "students.dat";
// 函数声明
void addStudent();
void deleteStudent();
void modifyStudent();
void searchStudent();
void sortStudents();
void displayAll();
void saveToFile();
void loadFromFile();
void freeMemory();
int menu();
int main() {
loadFromFile();
while (1) {
switch (menu()) {
case 1: addStudent(); break;
case 2: deleteStudent(); break;
case 3: modifyStudent(); break;
case 4: searchStudent(); break;
case 5: sortStudents(); break;
case 6: displayAll(); break;
case 7: saveToFile(); break;
case 8: freeMemory(); printf("感谢使用!\n"); return 0;
default: printf("无效选择!\n");
}
}
return 0;
}
int menu() {
printf("\n========== 学生管理系统 ==========\n");
printf("1. 添加学生\n");
printf("2. 删除学生\n");
printf("3. 修改学生\n");
printf("4. 查询学生\n");
printf("5. 按成绩排序\n");
printf("6. 显示所有\n");
printf("7. 保存到文件\n");
printf("8. 退出系统\n");
printf("请选择:");
int choice;
scanf("%d", &choice);
return choice;
}
void addStudent() {
struct Student stu;
printf("\n添加学生\n");
printf("学号:"); scanf("%d", &stu.id);
printf("姓名:"); scanf("%s", stu.name);
printf("成绩:"); scanf("%f", &stu.score);
// 检查学号是否重复
struct Node *temp = head;
while (temp != NULL) {
if (temp->data.id == stu.id) {
printf("错误:学号%d已存在!\n", stu.id);
return;
}
temp = temp->next;
}
// 添加到链表末尾
struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
return;
}
newNode->data = stu;
newNode->next = NULL;
if (head == NULL) {
head = newNode;
} else {
temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
}
printf("添加成功!\n");
}
void deleteStudent() {
int id;
printf("\n删除学生\n");
printf("请输入要删除的学号:");
scanf("%d", &id);
if (head == NULL) {
printf("系统中没有学生记录!\n");
return;
}
struct Node *temp = head;
struct Node *prev = NULL;
// 查找要删除的节点
while (temp != NULL && temp->data.id != id) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) {
printf("未找到学号%d的学生!\n", id);
return;
}
// 删除节点
if (prev == NULL) {
head = temp->next;
} else {
prev->next = temp->next;
}
free(temp);
printf("删除成功!\n");
}
void modifyStudent() {
int id;
printf("\n修改学生\n");
printf("请输入要修改的学号:");
scanf("%d", &id);
struct Node *temp = head;
while (temp != NULL) {
if (temp->data.id == id) {
printf("当前信息:学号:%d,姓名:%s,成绩:%.1f\n",
temp->data.id, temp->data.name, temp->data.score);
printf("请输入新姓名:"); scanf("%s", temp->data.name);
printf("请输入新成绩:"); scanf("%f", &temp->data.score);
printf("修改成功!\n");
return;
}
temp = temp->next;
}
printf("未找到学号%d的学生!\n", id);
}
void searchStudent() {
int id;
printf("\n查询学生\n");
printf("请输入要查询的学号:");
scanf("%d", &id);
struct Node *temp = head;
while (temp != NULL) {
if (temp->data.id == id) {
printf("查询结果:\n");
printf("学号:%d\n", temp->data.id);
printf("姓名:%s\n", temp->data.name);
printf("成绩:%.1f\n", temp->data.score);
return;
}
temp = temp->next;
}
printf("未找到学号%d的学生!\n", id);
}
void sortStudents() {
if (head == NULL) {
printf("没有学生记录!\n");
return;
}
// 使用选择排序
struct Node *i, *j;
struct Student temp;
for (i = head; i->next != NULL; i = i->next) {
for (j = i->next; j != NULL; j = j->next) {
if (i->data.score < j->data.score) {
temp = i->data;
i->data = j->data;
j->data = temp;
}
}
}
printf("排序完成!\n");
displayAll();
}
void displayAll() {
if (head == NULL) {
printf("没有学生记录!\n");
return;
}
printf("\n所有学生信息:\n");
printf("学号\t姓名\t成绩\n");
printf("----------------------------\n");
struct Node *temp = head;
while (temp != NULL) {
printf("%d\t%s\t%.1f\n", temp->data.id, temp->data.name, temp->data.score);
temp = temp->next;
}
}
void saveToFile() {
FILE *fp = fopen(filename, "wb");
if (fp == NULL) {
printf("无法打开文件进行保存!\n");
return;
}
struct Node *temp = head;
while (temp != NULL) {
fwrite(&temp->data, sizeof(struct Student), 1, fp);
temp = temp->next;
}
fclose(fp);
printf("数据已保存到文件:%s\n", filename);
}
void loadFromFile() {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("未找到数据文件,将创建新系统。\n");
return;
}
struct Student stu;
while (fread(&stu, sizeof(struct Student), 1, fp) == 1) {
// 添加到链表
struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
break;
}
newNode->data = stu;
newNode->next = NULL;
if (head == NULL) {
head = newNode;
} else {
struct Node *temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
}
}
fclose(fp);
printf("数据加载完成!\n");
}
void freeMemory() {
struct Node *temp = head;
while (temp != NULL) {
struct Node *next = temp->1
free(temp);
temp = next;
}
head = NULL;
}
实验技巧分享
- 模块化设计:每个功能独立函数,便于维护
- 错误处理:每个操作都要考虑失败情况
- 数据持久化:使用二进制文件保存结构体数组
- 用户体验:提供清晰的菜单和提示信息
- 内存管理:程序退出前释放所有动态内存
通用实验技巧与调试方法
1. 编译错误处理
常见编译错误:
- 语法错误:缺少分号、括号不匹配
- 类型错误:赋值类型不兼容
- 未定义符号:忘记包含头文件或拼写错误
解决方法:
- 从第一个错误开始修正,后面的错误可能由第一个引起
- 使用
gcc -Wall开启所有警告 - 仔细阅读错误信息,定位到具体行号
2. 运行时错误调试
常见运行时错误:
- 段错误(Segmentation Fault):访问非法内存
- 除零错误:除数为0
- 浮点异常:非法浮点运算
调试技巧:
// 使用printf调试
int main() {
int arr[5] = {1,2,3,4,5};
int *p = arr;
// 调试代码
printf("数组地址:%p\n", arr);
printf("指针值:%p\n", p);
printf("指针指向的值:%d\n", *p);
return 0;
}
3. 内存泄漏检测
工具推荐:
- Linux: Valgrind
- Windows: Visual Studio调试器
- macOS: Instruments
Valgrind使用示例:
gcc -g program.c -o program
valgrind --leak-check=full ./program
4. 代码规范建议
// 好的命名
int studentCount;
char studentName[50];
float examScore;
// 避免的命名
int a;
char b[50];
float c;
// 函数命名
void calculateAverage(); // 动词+名词,清晰表达功能
int findMaxValue(); // 返回值类型明确
// 常量定义
#define MAX_STUDENTS 100
const int MAX_SIZE = 100;
5. 性能优化技巧
// 避免不必要的数组拷贝
void processArray(int arr[], int size) { // 传递指针,不拷贝
// ...
}
// 使用位运算优化
// 判断奇偶
if (n & 1) { // 比 n % 2 == 1 快
// 奇数
}
// 交换变量(无临时变量)
a ^= b;
b ^= a;
a ^= b;
常见问题解答(FAQ)
Q1: 为什么我的程序运行结果正确但评测系统报错?
A: 可能是输出格式不完全匹配,检查空格、换行、大小写。
Q2: 动态分配的内存忘记释放会怎样?
A: 程序运行时内存占用持续增加,长时间运行可能导致系统内存耗尽。
Q3: 指针和数组有什么区别?
A: 数组是连续内存空间,大小固定;指针是变量,存储地址,可以指向任意内存。
Q4: 如何处理输入缓冲区残留?
A: 使用getchar()清空缓冲区,或在scanf格式字符串前加空格。
Q5: 结构体和共用体的主要区别?
A: 结构体每个成员有独立内存,共用体所有成员共享同一内存。
总结
通过本教程的系统学习,你应该能够:
- 熟练掌握C语言基础语法和程序结构
- 理解并运用各种控制结构和函数
- 掌握数组、字符串、结构体等数据结构
- 理解指针概念并正确使用
- 进行文件操作和动态内存管理
- 开发简单的综合应用程序
记住:编程是实践性技能,多写代码、多调试、多思考是提高的关键。遇到问题时,先自己尝试解决,再查阅资料或请教他人。祝你在C语言学习中取得优异成绩!
