C语言程序设计习题与实验指导
本指导分为四个主要部分:

(图片来源网络,侵删)
- 学习路线图:为你规划一个清晰的学习路径。
- 核心实验与习题:按照学习阶段,提供具体的实验任务和配套练习题。
- 常见问题与调试技巧:解决初学者常遇到的困惑。
- 学习资源推荐:提供优质的书籍和在线资源。
第一部分:C语言学习路线图
遵循以下步骤,可以让你系统地掌握C语言。
| 阶段 | 学习目标 | 关键实验/习题 | |
|---|---|---|---|
| 第一阶段:入门基础 | C语言简介与环境搭建 基本数据类型 ( int, float, char)变量与常量 运算符 (, , , , , , ) 基本输入输出 ( printf, scanf) |
- 成功搭建并使用VSCode/Dev-C++/Clang等开发环境。 - 理解程序的基本结构。 - 能编写简单的计算器程序。 |
实验1:第一个C程序 - "Hello, World!" - 计算两个数的和、差、积、商。 - 计算一个数的平方和立方。 |
| 第二阶段:流程控制 | if-else 条件语句switch 开关语句for 循环while 循环do-while 循环break 与 continue |
- 掌握分支结构,能解决逻辑判断问题。 - 掌握循环结构,能处理重复性任务。 - 能综合运用分支和循环解决复杂问题。 |
实验2:分支结构 - 判断一个数是奇数还是偶数。 - 根据成绩输出等级(A/B/C/D/E)。 实验3:循环结构 - 打印九九乘法表。 - 计算1到100的和。 - 判断一个数是否为素数。 |
| 第三阶段:数组与字符串 | 一维数组的定义、初始化和使用 二维数组的定义、初始化和使用 字符串与字符数组 常用字符串处理函数 ( strlen, strcpy, strcmp, strcat) |
- 理解数组在内存中的连续存储。 - 能用数组批量处理数据。 - 掌握字符串的基本操作。 |
实验4:一维数组 - 找出数组中的最大值和最小值。 - 对数组进行升序/降序排序(冒泡法)。 实验5:二维数组 - 打印杨辉三角。 - 矩阵转置。 实验6:字符串 - 字符串反转。 - 统计字符串中某个字符出现的次数。 |
| 第四阶段:函数 | 函数的定义、声明与调用 函数参数(值传递) 函数返回值 递归函数 变量的作用域与生命周期 |
- 学会将复杂问题分解为多个小函数(模块化编程)。 - 理解函数调用的底层机制(栈)。 - 掌握递归思想,能解决如阶乘、斐波那契数列等问题。 |
实验7:基础函数 - 编写一个函数判断素数,在 main函数中调用。- 编写一个函数实现数组排序。 实验8:递归 - 用递归实现汉诺塔问题。 - 用递归计算斐波那契数列。 |
| 第五阶段:指针 | 指针变量的定义与使用 (, &)指针与数组 指针与函数(指针作为参数) const 与指针指针数组与指向指针的指针 |
- C语言的精髓,理解指针就是内存地址。 - 能用指针高效地操作数组、字符串和函数参数。 - 理解指针是C语言实现动态内存和复杂数据结构的基础。 |
实验9:指针基础 - 通过指针交换两个变量的值。 - 使用指针遍历数组并求和。 实验10:指针与函数 - 使用指针作为参数,在函数内修改数组的值。 - 实现一个 strlen函数。 |
| 第六阶段:结构体与文件 | 结构体的定义与使用 (struct)结构体数组 结构体指针 文件读写 ( fopen, fclose, fscanf, fprintf, fgets, fputs) |
- 能将不同类型的数据组合成一个自定义的数据类型。 - 掌握文件I/O操作,实现程序数据的持久化存储。 |
实验11:结构体 - 定义一个学生结构体,包含姓名、学号、成绩,并实现一个简单的学生信息管理系统(增、删、改、查)。 实验12:文件操作 - 将学生信息写入一个文本文件。 - 从文件中读取学生信息并显示在屏幕上。 |
第二部分:核心实验与习题详解
实验1:入门基础
任务:简易计算器 编写一个程序,提示用户输入两个整数和一个运算符(, , , ),然后输出计算结果。
参考代码:
#include <stdio.h>
int main() {
int num1, num2;
char op;
printf("请输入两个整数和一个运算符 ( 10 + 5): ");
scanf("%d %c %d", &num1, &op, &num2);
switch(op) {
case '+':
printf("结果是: %d\n", num1 + num2);
break;
case '-':
printf("结果是: %d\n", num1 - num2);
break;
case '*':
printf("结果是: %d\n", num1 * num2);
break;
case '/':
if (num2 != 0) {
printf("结果是: %d\n", num1 / num2);
} else {
printf("错误:除数不能为0!\n");
}
break;
default:
printf("无效的运算符!\n");
}
return 0;
}
习题:

(图片来源网络,侵删)
- 输入一个华氏温度,将其转换为摄氏温度,公式:
C = (F - 32) * 5 / 9。 - 输入一个圆的半径,计算其周长和面积。
实验2 & 3:流程控制
任务:猜数字游戏 程序随机生成一个1到100之间的整数,用户输入猜测的数字,程序提示“猜大了”、“猜小了”或“猜对了”,直到猜中为止。
参考代码思路:
#include <stdio.h>
#include <stdlib.h> // 用于 rand() 和 srand()
#include <time.h> // 用于 time()
int main() {
int secret, guess;
// 用当前时间作为随机数种子,确保每次运行结果不同
srand((unsigned)time(NULL));
secret = rand() % 100 + 1; // 生成1-100的随机数
printf("我已经想好了一个1到100之间的数字,请你来猜猜看!\n");
do {
printf("请输入你的猜测: ");
scanf("%d", &guess);
if (guess > secret) {
printf("猜大了!\n");
} else if (guess < secret) {
printf("猜小了!\n");
} else {
printf("恭喜你,猜对了!\n");
}
} while (guess != secret); // 直到猜对才循环
return 0;
}
习题:
- 打印100到200之间所有能被3整除但不能被5整除的数。
- 输入一行字符,分别统计其中英文字母、空格、数字和其他字符的个数。
实验4 & 5:数组与字符串
任务:学生成绩管理 假设有5个学生,每个学生有3门课程的成绩,计算每个学生的平均分,并找出所有学生中的最高分。

(图片来源网络,侵删)
参考代码思路:
#include <stdio.h>
#define STUDENTS 5
#define COURSES 3
int main() {
int scores[STUDENTS][COURSES];
float averages[STUDENTS];
int i, j;
float max_avg = 0.0;
// 输入成绩
for (i = 0; i < STUDENTS; i++) {
printf("请输入第%d个学生的3门课程成绩: ", i + 1);
for (j = 0; j < COURSES; j++) {
scanf("%d", &scores[i][j]);
}
}
// 计算平均分并找出最高平均分
for (i = 0; i < STUDENTS; i++) {
int sum = 0;
for (j = 0; j < COURSES; j++) {
sum += scores[i][j];
}
averages[i] = (float)sum / COURSES;
printf("第%d个学生的平均分是: %.2f\n", i + 1, averages[i]);
if (averages[i] > max_avg) {
max_avg = averages[i];
}
}
printf("所有学生中的最高平均分是: %.2f\n", max_avg);
return 0;
}
习题:
- 将一个字符串中的所有小写字母转换为大写字母。
- 实现一个简单的字符串匹配功能,查找子字符串在主字符串中首次出现的位置。
实验6 & 7:函数
任务:排序函数封装
将冒泡排序算法封装成一个函数void bubble_sort(int arr[], int len),然后在main函数中调用它来对一个数组进行排序。
参考代码:
#include <stdio.h>
// 函数声明
void bubble_sort(int arr[], int len);
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int len = sizeof(arr) / sizeof(arr[0]);
printf("排序前的数组: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
bubble_sort(arr, len);
printf("排序后的数组: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
// 函数定义:冒泡排序
void bubble_sort(int arr[], int len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
习题:
- 编写一个递归函数计算阶乘
n!。 - 编写一个函数,实现两个字符串的连接(不使用
strcat)。
实验8 & 9:指针
任务:使用指针交换数组元素
编写一个函数void reverse_array(int *arr, int len),使用指针操作将数组元素逆序存放。
参考代码:
#include <stdio.h>
void reverse_array(int *arr, int len);
int main() {
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
printf("逆序前的数组: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
reverse_array(arr, len);
printf("逆序后的数组: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
void reverse_array(int *arr, int len) {
int *start = arr; // 指向数组首元素
int *end = arr + len - 1; // 指向数组末元素
while (start < end) {
// 交换 start 和 end 指向的值
int temp = *start;
*start = *end;
*end = temp;
// 移动指针
start++;
end--;
}
}
习题:
- 编写一个函数
int my_strlen(const char *str),使用指针计算字符串的长度。 - 理解并解释值传递和指针传递的区别,并用代码示例说明。
实验10 & 11:结构体与文件
任务:简单的通讯录
定义一个联系人结构体,包含姓名、电话,实现一个功能,可以将一个联系人信息写入到文件contacts.txt中。
参考代码:
#include <stdio.h>
#include <string.h>
// 定义联系人结构体
struct Contact {
char name[50];
char phone[20];
};
int main() {
struct Contact c;
printf("请输入联系人姓名: ");
scanf("%s", c.name);
printf("请输入联系人电话: ");
scanf("%s", c.phone);
// 以写入模式打开文件
FILE *fp = fopen("contacts.txt", "w");
if (fp == NULL) {
printf("无法打开文件!\n");
return 1;
}
// 将结构体数据写入文件
fprintf(fp, "姓名: %s\n电话: %s\n", c.name, c.phone);
// 关闭文件
fclose(fp);
printf("联系人信息已成功保存到 contacts.txt 文件中,\n");
return 0;
}
习题:
- 修改上述程序,实现从
contacts.txt文件中读取联系人信息并打印到屏幕上。 - 扩展通讯录功能,使其能够添加多个联系人,并将所有联系人信息存储在文件中。
第三部分:常见问题与调试技巧
常见问题
-
scanf的坑:- 问题:
scanf("%d", &num);后,如果输入了非数字(如字母),程序会进入死循环。 - 原因:
scanf读取失败时,错误的输入会留在输入缓冲区中,导致下一次scanf再次读取失败,无限循环。 - 解决:在读取前清空输入缓冲区,或者使用更健壮的输入方式(如
fgets+sscanf)。
- 问题:
-
数组越界:
- 问题:定义
int a[5];,却访问a[5]或a[-1]。 - 原因:C语言不检查数组边界,越界访问会覆盖内存中的其他数据,导致程序崩溃或产生不可预测的结果。
- 解决:始终确保数组索引在
0到size-1的范围内。
- 问题:定义
-
忘记
return 0;:- 问题:在
main函数末尾忘记写return 0;。 - 原因:在C99标准之前,
main函数不写return语句是未定义行为,现代编译器通常能处理,但养成好习惯总是好的。 - 解决:在
main函数的最后总是加上return 0;表示程序正常退出。
- 问题:在
-
指针未初始化:
- 问题:
int *p; *p = 10;导致程序崩溃。 - 原因:指针
p只是一个野指针,它指向一个随机的内存地址,向该地址写入数据会破坏内存,引发段错误。 - 解决:指针在使用前必须初始化(如
int a = 10; int *p = &a;)或置为NULL。
- 问题:
调试技巧
printf大法:这是最简单、最直接的调试方法,在怀疑出错的地方前后加上printf语句,打印变量的值,观察程序流程和数据变化。- 使用调试器:强烈推荐学习使用GDB (Linux/macOS) 或 Visual Studio Debugger (Windows)。
- 设置断点:在代码的某一行设置断点,程序运行到此处会暂停。
- 单步执行:逐行或逐函数地执行代码,观察每一步的变化。
- 查看变量:在断点处可以查看所有局部变量和全局变量的当前值。
- 代码审查:逐行阅读自己的代码,像计算机一样去“执行”它,检查逻辑是否正确。
第四部分:学习资源推荐
-
经典书籍:
- 《C Primer Plus》:最适合初学者的C语言圣经,内容详尽,例子丰富。
- 《C程序设计语言(The C Programming Language, K&R)》:C语言之父写的书,非常经典,但可能对新手稍显简略。
- 《C陷阱与缺陷》:帮你避开C语言中常见的坑,适合有一定基础后阅读。
-
在线教程与网站:
- 菜鸟教程:入门快,有在线环境可以直接练习。
- Runoob (W3Schools):和菜鸟教程类似,也是很好的入门选择。
- CS50 (哈佛大学公开课):计算机科学导论,其中C语言部分讲解得非常生动有趣,强烈推荐。
- GitHub:阅读优秀的开源C语言项目,是提升水平的最佳途径之一。
也是最重要的建议: 多写,多练,多思考。 编程是一门实践性极强的学科,只有亲手敲代码、解决问题,才能真正掌握它,祝你学习顺利!
