引言
C语言作为一门经典的编程语言,以其高效、灵活和接近硬件的特性,在系统编程、嵌入式开发以及算法竞赛中占据重要地位。上机实验是学习C语言不可或缺的环节,通过实际编写和调试代码,可以加深对语法、数据结构和算法的理解。本文精选了从基础语法到复杂算法的典型上机实验题目,提供详细的解析和完整的代码示例,帮助读者进行实战演练。每个题目都包含问题描述、解题思路、代码实现和关键点分析,旨在提升编程能力和调试技巧。
1. 基础语法实验:输入输出与算术运算
1.1 题目:计算两个整数的和、差、积、商和余数
问题描述:编写一个C程序,从键盘输入两个整数a和b,计算并输出它们的和(a+b)、差(a-b)、积(a*b)、商(a/b)和余数(a%b)。注意处理除数为零的情况。
解题思路:
- 使用
scanf函数读取输入。 - 使用条件语句检查b是否为零,如果是,则输出错误信息。
- 计算并使用
printf输出结果,格式化输出以提高可读性。 - 关键点:整数除法会截断小数部分,余数运算仅适用于整数。
代码实现:
#include <stdio.h>
int main() {
int a, b;
printf("请输入两个整数(用空格分隔):");
scanf("%d %d", &a, &b);
if (b == 0) {
printf("错误:除数不能为零!\n");
return 1;
}
printf("和:%d\n", a + b);
printf("差:%d\n", a - b);
printf("积:%d\n", a * b);
printf("商:%d\n", a / b);
printf("余数:%d\n", a % b);
return 0;
}
关键点分析:
scanf("%d %d", &a, &b):从标准输入读取两个整数,&操作符用于获取变量地址。if (b == 0):防止除零错误,这是编程中的常见安全检查。- 输出使用换行符
\n分隔,便于阅读。运行示例:输入3 2,输出和5、差1、积6、商1、余数1。
1.2 题目:判断素数(质数)
问题描述:编写程序输入一个正整数n,判断它是否为素数。素数是大于1且只能被1和自身整除的数。
解题思路:
- 从2到sqrt(n)遍历,检查是否有因子。
- 如果有因子,则不是素数;否则是素数。
- 优化:只需检查到sqrt(n),因为如果n有大于sqrt(n)的因子,必有小于sqrt(n)的对应因子。
- 关键点:处理n<=1的特殊情况。
代码实现:
#include <stdio.h>
#include <math.h>
int main() {
int n, i;
int isPrime = 1; // 假设是素数
printf("请输入一个正整数:");
scanf("%d", &n);
if (n <= 1) {
isPrime = 0;
} else {
for (i = 2; i <= sqrt(n); i++) {
if (n % i == 0) {
isPrime = 0;
break;
}
}
}
if (isPrime) {
printf("%d 是素数。\n", n);
} else {
printf("%d 不是素数。\n", n);
}
return 0;
}
关键点分析:
#include <math.h>:使用sqrt函数计算平方根,需要链接数学库(编译时加-lm)。isPrime标志变量:简化逻辑,避免多层if嵌套。break:一旦找到因子,立即退出循环,提高效率。示例:输入17,输出是素数;输入9,输出不是素数。
2. 控制结构实验:循环与条件
2.1 题目:计算1到100的偶数和
问题描述:使用循环计算1到100之间所有偶数的和,并输出结果。
解题思路:
- 使用
for循环遍历1到100。 - 通过
if判断是否为偶数(n % 2 == 0),累加到sum。 - 替代方案:直接循环偶数(i=2; i<=100; i+=2)。
- 关键点:初始化sum为0,避免垃圾值。
代码实现:
#include <stdio.h>
int main() {
int sum = 0;
int i;
for (i = 1; i <= 100; i++) {
if (i % 2 == 0) {
sum += i;
}
}
printf("1到100的偶数和为:%d\n", sum);
return 0;
}
关键点分析:
sum += i:等价于sum = sum + i,是累加的标准写法。- 循环变量i从1开始,确保覆盖所有数。运行结果:2450。
- 扩展:如果使用
while循环,可改为i=1; while(i<=100){... i++;},展示不同循环结构。
2.2 题目:斐波那契数列前n项
问题描述:输入n,输出斐波那契数列的前n项。斐波那契数列:F(1)=1, F(2)=1, F(n)=F(n-1)+F(n-2)(n>2)。
解题思路:
- 使用迭代方法,避免递归的低效。
- 初始化前两项为1,然后循环计算后续项。
- 关键点:n较小时直接输出,n较大时注意整数溢出(使用long long)。
代码实现:
#include <stdio.h>
int main() {
int n;
long long a = 1, b = 1, temp;
printf("请输入n(n>=1):");
scanf("%d", &n);
if (n <= 0) {
printf("n必须大于0!\n");
return 1;
}
printf("斐波那契数列前%d项:\n", n);
if (n >= 1) printf("%lld ", a);
if (n >= 2) printf("%lld ", b);
for (int i = 3; i <= n; i++) {
temp = a + b;
printf("%lld ", temp);
a = b;
b = temp;
}
printf("\n");
return 0;
}
关键点分析:
- 使用
long long防止大数溢出,斐波那契增长很快。 temp变量用于交换a和b,模拟递推关系。- 示例:n=5,输出1 1 2 3 5。迭代比递归高效,避免栈溢出。
3. 数组与字符串实验
3.1 题目:求数组最大值和最小值
问题描述:定义一个整型数组,输入5个元素,找出最大值和最小值并输出。
解题思路:
- 使用
scanf输入数组元素。 - 初始化max和min为数组第一个元素,然后遍历更新。
- 关键点:数组下标从0开始,遍历时从1开始比较。
代码实现:
#include <stdio.h>
int main() {
int arr[5];
int max, min;
int i;
printf("请输入5个整数:\n");
for (i = 0; i < 5; i++) {
scanf("%d", &arr[i]);
}
max = min = arr[0];
for (i = 1; i < 5; i++) {
if (arr[i] > max) max = arr[i];
if (arr[i] < min) min = arr[i];
}
printf("最大值:%d\n", max);
printf("最小值:%d\n", min);
return 0;
}
关键点分析:
arr[5]:固定大小数组,C语言中数组大小必须在编译时确定。- 循环从i=1开始,避免与自身比较。示例输入:3 5 1 8 2,输出max=8, min=1。
3.2 题目:字符串反转
问题描述:输入一个字符串,将其反转后输出。例如,输入”hello”,输出”olleh”。
解题思路:
- 使用字符数组存储字符串。
- 双指针法:一个从头,一个从尾,交换字符直到相遇。
- 关键点:字符串以’\0’结束,反转时不包括它。
代码实现:
#include <stdio.h>
#include <string.h>
int main() {
char str[100];
int len, i;
char temp;
printf("请输入一个字符串:");
gets(str); // 注意:gets不安全,实际中用fgets
len = strlen(str);
for (i = 0; i < len / 2; i++) {
temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
printf("反转后:%s\n", str);
return 0;
}
关键点分析:
strlen(str):获取字符串长度,不包括’\0’。- 交换使用临时变量temp。示例:输入”world”,输出”dlrow”。
- 安全提示:
gets易导致缓冲区溢出,生产环境用fgets(str, 100, stdin)。
4. 函数与递归实验
4.1 题目:计算阶乘(递归实现)
问题描述:编写递归函数计算n的阶乘(n! = n(n-1)…*1),输入n输出结果。
解题思路:
- 递归基:n=0或1时返回1。
- 递归式:n! = n * factorial(n-1)。
- 关键点:防止栈溢出,n不宜过大(通常n<20)。
代码实现:
#include <stdio.h>
long long factorial(int n) {
if (n < 0) return -1; // 错误处理
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
int main() {
int n;
printf("请输入n:");
scanf("%d", &n);
long long result = factorial(n);
if (result == -1) {
printf("n不能为负数!\n");
} else {
printf("%d! = %lld\n", n, result);
}
return 0;
}
关键点分析:
- 函数返回
long long,因为阶乘增长快(20! ≈ 2.4e18)。 - 递归深度为n,示例:n=5,输出120。递归简洁但效率低,迭代版本可作为对比。
4.2 题目:最大公约数(GCD)和最小公倍数(LCM)
问题描述:输入两个正整数a和b,计算GCD和LCM。GCD用欧几里得算法(辗转相除法)。
解题思路:
- GCD:a % b == 0 ? b : gcd(b, a % b)。
- LCM:a * b / gcd(a, b)。
- 关键点:确保a >= b,否则交换。
代码实现:
#include <stdio.h>
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
int main() {
int a, b;
printf("请输入两个正整数:");
scanf("%d %d", &a, &b);
if (a <= 0 || b <= 0) {
printf("输入必须为正整数!\n");
return 1;
}
int g = gcd(a, b);
int l = lcm(a, b);
printf("GCD:%d\n", g);
printf("LCM:%d\n", l);
return 0;
}
关键点分析:
- 欧几里得算法高效,时间复杂度O(log min(a,b))。
- 示例:a=12, b=18,GCD=6, LCM=36。递归实现简洁,但可改为迭代以避免递归开销。
5. 指针实验
5.1 题目:使用指针交换两个变量的值
问题描述:编写函数使用指针交换两个整数的值。
解题思路:
- 函数参数为指针,通过解引用交换。
- 关键点:指针必须指向有效内存,避免空指针。
代码实现:
#include <stdio.h>
void swap(int *p, int *q) {
int temp = *p;
*p = *q;
*q = temp;
}
int main() {
int a = 5, b = 10;
printf("交换前:a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("交换后:a=%d, b=%d\n", a, b);
return 0;
}
关键点分析:
&a获取地址,*p解引用访问值。- 示例:输出交换后a=10, b=5。指针函数常用于修改原变量。
5.2 题目:动态分配数组并求和
问题描述:使用malloc动态分配数组,输入n个元素,求和后释放内存。
解题思路:
malloc分配空间,检查是否成功。- 输入元素,求和,
free释放。 - 关键点:内存泄漏检查,使用
free。
代码实现:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n, *arr, sum = 0;
printf("请输入元素个数:");
scanf("%d", &n);
arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
printf("请输入%d个整数:\n", n);
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
sum += arr[i];
}
printf("和:%d\n", sum);
free(arr); // 释放内存
return 0;
}
关键点分析:
malloc(n * sizeof(int)):分配n个int的空间。- 检查NULL防止崩溃。示例:n=3, 输入1 2 3, 输出6。动态数组适合大小不确定时。
6. 结构体与文件操作实验
6.1 题目:定义学生结构体,存储并输出信息
问题描述:定义学生结构体(姓名、学号、成绩),输入信息,输出平均成绩。
解题思路:
- 使用
struct定义结构体。 - 输入到结构体数组,计算平均。
- 关键点:字符串成员用字符数组。
代码实现:
#include <stdio.h>
struct Student {
char name[50];
int id;
float score;
};
int main() {
struct Student stu[3];
float avg = 0;
for (int i = 0; i < 3; i++) {
printf("输入学生%d的姓名、学号、成绩:", i+1);
scanf("%s %d %f", stu[i].name, &stu[i].id, &stu[i].score);
avg += stu[i].score;
}
avg /= 3;
printf("平均成绩:%.2f\n", avg);
return 0;
}
关键点分析:
.操作符访问成员。- 示例:输入3个学生,输出平均。结构体组织相关数据。
6.2 题目:文件读写:写入并读取整数
问题描述:将1到10写入文件,然后读取并求和。
解题思路:
fopen打开文件,fprintf写入,fscanf读取。- 关键点:检查文件打开成功,关闭文件。
代码实现:
#include <stdio.h>
int main() {
FILE *fp;
int i, num, sum = 0;
// 写入
fp = fopen("numbers.txt", "w");
if (fp == NULL) {
printf("无法打开文件写入!\n");
return 1;
}
for (i = 1; i <= 10; i++) {
fprintf(fp, "%d\n", i);
}
fclose(fp);
// 读取
fp = fopen("numbers.txt", "r");
if (fp == NULL) {
printf("无法打开文件读取!\n");
return 1;
}
while (fscanf(fp, "%d", &num) == 1) {
sum += num;
}
fclose(fp);
printf("文件中1到10的和:%d\n", sum);
return 0;
}
关键点分析:
- “w”模式覆盖写入,”r”只读。
fclose释放资源。运行后生成numbers.txt,内容为1到10,输出和55。
7. 复杂算法实验:排序与搜索
7.1 题目:冒泡排序
问题描述:输入n个整数,使用冒泡排序升序输出。
解题思路:
- 双重循环:外层控制轮数,内层相邻比较交换。
- 优化:如果一轮无交换,提前结束。
- 关键点:时间复杂度O(n^2),适合小数据。
代码实现:
#include <stdio.h>
void bubbleSort(int arr[], int n) {
int i, j, swapped;
for (i = 0; i < n - 1; i++) {
swapped = 0;
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1;
}
}
if (!swapped) break;
}
}
int main() {
int n, arr[100];
printf("输入n(<=100):");
scanf("%d", &n);
printf("输入%d个整数:\n", n);
for (int i = 0; i < n; i++) scanf("%d", &arr[i]);
bubbleSort(arr, n);
printf("排序后:");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
关键点分析:
swapped优化避免无谓循环。- 示例:输入5 3 2 4 1,输出1 2 3 4 5。冒泡稳定但慢,可扩展到快速排序。
7.2 题目:二分查找
问题描述:在已排序数组中查找元素,返回索引或-1。
解题思路:
- 二分:low=0, high=n-1, mid=(low+high)/2。
- 比较arr[mid]与目标,调整low/high。
- 关键点:数组必须有序,时间复杂度O(log n)。
代码实现:
#include <stdio.h>
int binarySearch(int arr[], int n, int target) {
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // 防溢出
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
int main() {
int arr[] = {1, 3, 5, 7, 9};
int n = 5, target;
printf("输入要查找的数:");
scanf("%d", &target);
int index = binarySearch(arr, n, target);
if (index != -1) printf("找到,索引:%d\n", index);
else printf("未找到\n");
return 0;
}
关键点分析:
mid = low + (high - low) / 2:防止(low+high)溢出。- 示例:查找5,输出索引2。二分高效,适用于大数据。
8. 综合实战:链表基础
8.1 题目:单链表创建与遍历
问题描述:创建单链表存储整数,输入数据,遍历输出。
解题思路:
- 定义节点结构体(数据+指针)。
- 尾插法创建链表,遍历打印。
- 关键点:动态内存分配,释放链表避免泄漏。
代码实现:
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
struct Node* createList(int n) {
struct Node *head = NULL, *tail = NULL, *newNode;
int val;
for (int i = 0; i < n; i++) {
printf("输入第%d个数据:", i+1);
scanf("%d", &val);
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = val;
newNode->next = NULL;
if (head == NULL) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
}
return head;
}
void printList(struct Node *head) {
struct Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
void freeList(struct Node *head) {
struct Node *temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}
int main() {
int n;
printf("输入节点数:");
scanf("%d", &n);
struct Node *head = createList(n);
printList(head);
freeList(head);
return 0;
}
关键点分析:
malloc创建节点,->访问成员。- 示例:输入3个数1 2 3,输出1 -> 2 -> 3 -> NULL。链表是动态数据结构基础,可扩展到插入/删除。
结语
通过以上从基础到复杂的实验题目,读者可以系统练习C语言的核心概念。每个代码都可直接编译运行(使用gcc编译器,如gcc program.c -o program -lm链接数学库)。建议在实际环境中调试,遇到问题使用gdb或打印调试。持续练习将显著提升编程能力,为更高级的算法和项目打下坚实基础。如果需要更多题目或特定扩展,请提供反馈。
