虽然我无法直接提供一本“郑莉《C语言程序设计》实验指导书”的完整扫描版或PDF(这涉及版权问题),但我可以为您构建一个高度符合其教学风格和知识体系、循序渐进的实验指导框架,这个框架可以作为您完成实验的“路线图”和“备忘录”,涵盖了从入门到核心知识点的几乎所有关键实验。

(图片来源网络,侵删)
郑莉《C语言程序设计》实验指导框架
本实验指导按照教材的章节顺序,分为多个实验模块,每个模块包含实验目的、实验内容、实验指导与提示、以及思考与拓展。
C语言程序设计初步
实验目的:
- 熟悉C语言程序的开发环境(如Visual Studio, Dev-C++, Code::Blocks, VS Code等)。
- 掌握C语言程序的基本结构,包括头文件、主函数、注释等。
- 学习简单的输入/输出函数
printf和scanf的使用。 - 理解编译、链接、运行的基本概念。 **
- 经典入门: 编写一个程序,在屏幕上输出 "Hello, World!"。
- 简单计算器: 编写一个程序,提示用户输入两个整数,然后计算并输出它们的和、差、积、商。
- 个人信息打印: 编写一个程序,使用
scanf接收用户输入的姓名、年龄和身高,然后使用printf将这些信息格式化地打印出来。
实验指导与提示:
-
环境搭建: 选择一个你熟悉的IDE并完成安装,确保可以成功创建、编译、运行一个C源文件(
.c)。
(图片来源网络,侵删) -
程序结构: 一个最简单的C程序结构是:
#include <stdio.h> // 包含标准输入输出库 int main() { // 主函数,程序的入口 // 在这里写你的代码 printf("Hello, World!\n"); return 0; // 返回0,表示程序正常结束 } -
printf和scanf:printf用于格式化输出。%d用于整数,%f用于浮点数,%c用于字符,%s用于字符串,\n是换行符。scanf用于格式化输入。注意:scanf的变量名前必须加上取地址符&,scanf("%d", &a);。
-
常见错误:
- 忘记写
#include <stdio.h>。 main函数拼写错误或返回值类型错误。printf或scanf中的格式控制符与变量类型不匹配(用%d读取一个浮点数)。- 忘记在
scanf的变量名前加&。
- 忘记写
思考与拓展:

(图片来源网络,侵删)
- 如何让程序输出一个浮点数,并保留两位小数?(提示:
printf("%.2f", num);) - 如果输入一个字符,应该用什么格式控制符?
- 如果用户输入的不是数字,
scanf会发生什么?如何处理这种情况?
数据类型、运算符与表达式
实验目的:
- 掌握C语言的基本数据类型(
int,float,double,char)及其特点。 - 熟悉算术运算符、赋值运算符、关系运算符和逻辑运算符的优先级和结合性。
- 学习使用强制类型转换。
- 理解自增()和自减()运算符的使用。 **
- 数据类型大小: 编写程序,使用
sizeof运算符输出int,float,double,char在你使用的编译器中所占的字节数。 - 混合运算与类型转换: 定义一个
int型变量a和一个double型变量b,计算a / b和b / a的值,并观察结果类型的变化,尝试使用强制类型转换(double)a并观察结果。 - 逻辑判断: 编写程序,判断一个输入的年份是否为闰年,闰年的条件是:能被4整除但不能被100整除,或者能被400整除。
- 复杂表达式: 设
int a=3, b=4, c=5;,计算表达式a + b > c && b == c的值。
实验指导与提示:
sizeof:sizeof是一个运算符,不是函数。sizeof(int)或sizeof(a)。- 类型转换: 当参与运算的类型不一致时,C会进行隐式类型转换(也叫“算术转换”),除法运算中,如果两个操作数都是整数,结果也是整数(小数部分被舍弃),强制类型转换
(type)expr可以将expr的结果临时转换为type类型。 - 运算符优先级: , > , , > , >
关系运算符>逻辑与(&&)>逻辑或(||)>赋值运算符,可以使用括号 来明确运算顺序,这是个好习惯。 - 闰年判断: 这是
&&(与), (或), (非), (取模) 运算符的经典应用案例。
思考与拓展:
i++和++i在单独使用时效果一样,但在表达式中(如a = i++和a = ++i)有什么区别?- 如何判断一个字符是数字('0'-'9')还是字母('a'-'z', 'A'-'Z')?(提示:利用字符的ASCII码值和关系运算符)
逻辑控制语句
实验目的:
- 熟练掌握
if-else语句及其嵌套,实现多分支选择结构。 - 掌握
switch语句的使用,理解其与if-else的区别。 - 掌握
for,while,do-while三种循环语句,并能用它们解决重复性问题。 - 学习
break和continue语句在循环中的作用。 ** - 分段函数: 编写程序,根据输入的
x值,计算并输出y的值。y = x(x < 1)y = 2x - 1(1 ≤ x < 10)y = 3x - 11(x ≥ 10)
- 简单菜单系统: 使用
switch语句实现一个简单的计算器菜单,用户可以选择进行加、减、乘、除或退出运算。 - 循环求和:
- 计算 1 到 100 的累加和。
- 计算 1 到 100 之间所有偶数的和。
- 循环嵌套: 打印九九乘法表。
- 素数判断: 输入一个正整数,判断它是否为素数(质数),素数是指只能被1和它本身整除的大于1的自然数。
实验指导与提示:
if-else: 适用于范围判断,注意else总是和它最近的、未配对的if配对。switch: 适用于等值判断。switch后面的表达式必须是整型(或字符型),case后的值必须是常量。break语句用于跳出switch结构,不要忘记写break,否则会发生“case穿透”。forvswhilevsdo-while:for循环:次数确定时首选。while循环:条件控制,循环次数不确定时常用。do-while循环:至少执行一次。
- 循环嵌套: 外层循环控制行,内层循环控制列,九九乘法表是理解嵌套循环的经典案例。
- 素数判断: 最直观的方法是,从
i=2开始到sqrt(n)(或n/2),检查n是否能被i整除,如果能,则n不是素数。
思考与拓展:
- 如何用
do-while循环改写素数判断程序? - 在打印九九乘法表时,如何只打印下三角或上三角?
break和continue有什么区别?break会跳出整个循环,而continue只会结束本次循环,进入下一次循环。
数组
实验目的:
- 掌握一维数组的定义、初始化和引用方法。
- 学习使用数组进行批量数据处理,如排序、查找、统计等。
- 掌握字符串的表示方法(字符数组)和常用字符串处理函数(
strlen,strcpy,strcmp等)。 - 了解二维数组的基本概念和使用。 **
- 数组基本操作: 定义一个包含10个整数的数组,用循环为其赋值(如1到10),然后逆序输出数组中的所有元素。
- 排序算法: 实现一个简单的排序算法(如冒泡排序或选择排序)对一个包含10个随机整数的数组进行升序排序,并输出排序前后的数组。
- 查找与统计:
- 在一个已排序的数组中,使用二分查找法查找一个指定的数字,并输出其位置或“未找到”的提示。
- 统计一个字符串中英文字母、数字、空格和其他字符的个数。
- 二维数组: 使用二维数组实现一个3x3的矩阵,并计算其主对角线元素之和。
实验指导与提示:
- 数组定义:
int arr[10];,C语言中,数组下标从0开始。 - 冒泡排序: 核心思想是相邻元素比较,如果顺序错误就交换,通过多轮遍历,将最大的元素“冒泡”到数组末尾。
- 字符串处理函数: 使用这些函数需要包含头文件
<string.h>。strlen(s):求字符串s的长度(不包括结尾的\0)。strcpy(dest, src):将字符串src复制到dest。strcmp(s1, s2):比较两个字符串,s1 > s2返回正数,s1 == s2返回0,s1 < s2返回负数。
- 二维数组:
int matrix[3][3];,访问元素使用matrix[i][j],主对角线元素是matrix[0][0],matrix[1][1],matrix[2][2]。
思考与拓展:
- 如何将一个字符串反转?(提示:可以交换首尾字符)
- 除了冒泡排序,你还知道哪些排序算法?(如插入排序、选择排序)
- 为什么
strcpy函数不安全?有没有更安全的替代函数?(如strncpy)
函数
实验目的:
- 掌握函数的定义、声明和调用方法。
- 理解函数参数传递(值传递)的机制。
- 学习使用
return语句从函数返回值。 - 掌握递归函数的设计思想和应用场景。 **
- 模块化计算器: 将实验三中的计算器功能用函数实现,编写
add(),subtract(),multiply(),divide()四个函数,main函数通过调用这些函数来完成计算。 - 素数判断函数: 将实验四中的素数判断逻辑封装成一个函数
int isPrime(int num);,该函数返回1(是素数)或0(不是素数),在main函数中调用它来测试多个数字。 - 递归应用:
- 使用递归函数计算一个非负整数
n的阶乘n!。 - 使用递归函数计算斐波那契数列的第
n项。(斐波那契数列:1, 1, 2, 3, 5, 8, ...,即F(n) = F(n-1) + F(n-2))
- 使用递归函数计算一个非负整数
实验指导与提示:
- 函数三要素: 返回值类型、函数名、参数列表。
- 函数声明 vs 定义: 如果函数定义在
main函数之后,需要在main之前进行声明,告诉编译器这个函数的存在。int isPrime(int num);。 - 值传递: C语言中,函数参数传递是值传递,这意味着函数内部操作的是实参的副本,修改副本不会影响外部的实参。数组作为参数是个例外(传递的是数组首地址)。
- 递归:
- 递归出口(基准情形): 必须有一个或多个不需要再次递归调用就能直接返回结果的情形,否则会无限递归导致栈溢出。
- 递归规则(递归情形): 函数调用自身,但参数必须向基准情形逼近。
- 递归与栈: 每次递归调用都会在栈上分配一个新的栈帧,递归过深可能导致栈溢出。
思考与拓展:
- 为什么传递数组不需要加
&?(因为数组名arr会“退化”为其首元素的地址) - 如何通过指针实现地址传递,从而在被调用函数中修改外部变量的值?(这是后续指针实验的内容)
- 递归和循环(迭代)有什么优缺点?递归代码更简洁易读,但通常效率较低且可能栈溢出。
指针
实验目的:
- 掌握指针变量的定义、初始化和基本使用。
- 理解指针与数组的关系,能用指针遍历数组。
- 学习使用指针作为函数参数,实现地址传递。
- 了解指针数组与指向指针的指针(进阶内容)。 **
- 指针基本操作: 定义一个整型变量
a和一个指向a的指针p,通过指针p修改a的值,并分别通过a和p输出a的值和地址。 - 指针与数组: 定义一个整型数组,使用指针(而不是下标)来遍历并打印数组中的所有元素。
- 指针作为函数参数:
- 编写一个函数
void swap(int *x, int *y);,用于交换两个整数的值,在main函数中调用它,验证交换是否成功。 - 编写一个函数
void bubble_sort(int arr[], int n);,使用指针(数组)参数对数组进行排序。
- 编写一个函数
- 动态内存分配(进阶): 使用
malloc函数在堆上动态分配一个大小为10的整型数组,用指针指向它,进行一些操作后,使用free函数释放内存。
实验指导与提示:
- 指针定义:
int *p;。 是指针运算符,&是取地址运算符。p = &a;表示p存储a的地址。 - 指针解引用:
*p表示访问p所指向地址的值,通过*p = 100;可以修改a的值。 - 指针与数组: 在C语言中,数组名
arr和&arr[0]几乎可以互换。arr[i]等价于*(arr + i)。 swap函数: 这是理解指针作为函数参数最经典的例子,因为没有值传递的限制,函数可以直接修改外部变量的内存。malloc和free: 使用malloc需要包含头文件<stdlib.h>。int *p = (int*)malloc(10 * sizeof(int));。动态分配的内存在使用完毕后必须手动free(p),否则会造成内存泄漏。
思考与拓展:
int *p;和int *p = NULL;有什么区别?为什么后者更好?char *str = "hello";和char str[] = "hello";有什么根本区别?(前者指向一个只读字符串字面量,后者是一个可修改的字符数组)- 函数指针是什么?它有什么用?(函数指针可以指向一个函数,作为参数传递或作为返回值,是实现回调等高级功能的基础)
结构体与共用体
实验目的:
- 掌握结构体(
struct)类型的定义和使用。 - 学习结构体变量的初始化、成员访问。
- 理解结构体数组的概念。
- 了解结构体指针的使用。 **
- 学生信息管理:
- 定义一个结构体
Student,包含成员:学号(int id)、姓名(char name[20])、成绩(float score)。 - 定义一个
Student类型的变量,并对其进行初始化和成员访问。 - 定义一个
Student类型的数组,用于存储5个学生的信息,并编写一个函数void printStudents(Student s[], int n);来打印所有学生的信息。
- 定义一个结构体
- 结构体指针: 修改上题,使用
Student类型的指针来遍历和打印结构体数组中的学生信息。
实验指导与提示:
- 结构体定义:
struct Student { int id; char name[20]; float score; }; - 定义变量:
struct Student s1;或struct Student s1 = {1001, "Zhang San", 95.5};。 - 成员访问: 使用点运算符 。
s1.id = 1001;或printf("%s", s1.name);。 - 结构体指针: 定义
struct Student *p = &s1;,通过指针访问成员使用箭头运算符->。p->id等价于(*p).id。
思考与拓展:
- 结构体的大小为什么不一定是其所有成员大小之和?(内存对齐)
- 共用体(
union)和结构体有什么区别?共用体的所有成员共享同一块内存空间。
文件操作
实验目的:
- 掌握文件指针(
FILE *)的概念。 - 学习文件的打开、关闭、读写等基本操作。
- 熟悉常用的文件读写函数(
fopen,fclose,fgetc,fputc,fgets,fputs,fscanf,fprintf)。 - 学习使用二进制方式读写文件。 **
- 文本文件写入与读取:
- 编写程序,将实验七中5个学生的信息(格式化文本)写入到一个名为
students.txt的文件中。 - 编写另一个程序,从
students.txt文件中读取学生信息,并打印到屏幕上。
- 编写程序,将实验七中5个学生的信息(格式化文本)写入到一个名为
- 命令行参数: 编写一个程序,它接受两个命令行参数:第一个是源文件名,第二个是目标文件名,程序将源文件的内容复制到目标文件中。
- 二进制文件(进阶): 将一个结构体数组以二进制形式写入文件,然后再从文件中读出,验证数据是否一致。
实验指导与提示:
- 文件指针: 所有文件操作都通过
FILE *类型的指针来完成。 fopen:FILE *fp = fopen("filename.txt", "r");,第二个参数是打开模式:"r":只读,文件必须存在。"w":只写,文件不存在则创建,存在则清空。"a":追加,文件不存在则创建,存在则在末尾写入。"r+","w+","a+":读写模式。
- 错误检查: 每次调用
fopen后,都应该检查返回值是否为NULL,以判断文件是否成功打开。 - 读写函数:
fprintf(fp, "%d %s", id, name);// 格式化写入fscanf(fp, "%d %s", &id, name);// 格式化读取fputs("Hello", fp);// 写入一个字符串fgets(buffer, 100, fp);// 读取一行
fclose: 使用完文件后,必须调用fclose(fp);来关闭文件并释放资源。
思考与拓展:
- 文本模式和二进制模式有什么区别?在什么情况下必须使用二进制模式?
- 如何定位文件中的读写位置?(使用
fseek函数) - 如何获取文件的大小?
综合项目设计
实验目的:
- 综合运用前面所学的C语言知识。
- 学习设计一个结构完整、功能清晰的程序。
- 培养分析问题、设计算法、编写代码和调试程序的综合能力。 三选一或自定):**
学生成绩管理系统
- 功能要求:
- 从文件中加载学生信息(学号、姓名、多门课程成绩)。
- 添加、删除、修改学生信息。
- 按学号或姓名查找学生。
- 计算每个学生的总分和平均分,并可以按总分排序。
- 将修改后的学生信息保存回文件。
- 涉及知识点: 结构体、数组、文件操作、指针、排序算法、循环、分支。
简易图书管理系统
- 功能要求:
- 管理图书信息(书号、书名、作者、库存数量)。
- 添加、删除、修改图书信息。
- 按书名或作者查找图书。
- 模拟借书和还书功能(减少/增加库存)。
- 可以查看所有图书的列表。
- 涉及知识点: 结构体、数组、文件操作、字符串处理、菜单界面设计。
贪吃蛇游戏
- 功能要求:
- 使用控制台字符界面实现。
- 蛇可以由方向键控制移动。
- 随机生成食物,蛇吃到食物后身体变长,得分增加。
- 撞到墙壁或自己的身体则游戏结束。
- 涉及知识点: (可能需要平台相关的库,如Windows的
conio.h或Linux的ncurses)数组(表示地图和蛇身)、循环(游戏主循环)、键盘输入、随机数生成、延时。
实验指导与提示:
- 模块化设计: 将不同功能封装成独立的函数。
addStudent(),deleteStudent(),saveToFile()等。 - 数据结构选择: 使用结构体数组来存储学生或图书信息,如果数据量可能很大,可以考虑使用链表(这是数据结构课程的内容,但用数组实现项目已足够)。
- 用户界面: 设计一个清晰的菜单循环,让用户可以选择不同的功能。
- 调试: 学会使用
printf在关键位置打印变量值,来追踪程序的执行流程和数据变化,这是程序员最重要的技能之一。
希望这份详细的实验指导框架能对您学习郑莉老师的《C语言程序设计》有所帮助!祝您实验顺利!
