
(图片来源网络,侵删)
- 仅供学习参考: 这份指南的核心目的是帮助你理解C语言编程的思想、方法和常见问题的解决思路。直接抄袭代码是学习编程的大忌,你将无法真正掌握编程技能。
- 实验报告是关键: 实验的重点不仅在于写出能运行的代码,更在于完成一份高质量的实验报告,报告应包括:实验目的、实验环境、实验内容、算法设计/思路分析、完整的源代码、运行结果截图、实验总结与心得等。
- 代码非唯一解: C语言解决问题的方法多种多样,这里提供的代码只是其中一种常见且易于理解的实现方式,你的代码只要逻辑正确、能通过测试,就是好代码。
- 与教材/老师沟通: 不同的实验指导书对实验的具体要求可能不同(对输入输出的格式、函数的封装、错误处理等),请务必以你手中实验指导书的具体要求为准,或与你的指导老师沟通确认。
C语言环境搭建与简单程序设计
实验目的:
- 熟悉C语言开发环境(如 Visual Studio, Dev-C++, Code::Blocks, VS Code + MinGW 等)。
- 掌握C语言程序的基本结构,能够编写简单的顺序结构程序。
- 掌握基本数据类型(
int,float,double,char)的使用。 - 掌握基本运算符和表达式的使用。 示例:* 编写一个程序,从键盘输入一个华氏温度,要求输出对应的摄氏温度,转换公式为:`C = (F - 32) 5 / 9`。
参考答案与思路分析:
-
思路分析:
- 需要一个变量来存储用户输入的华氏温度,可以定义为
float或double类型,因为温度可能是小数。 - 需要另一个变量来存储计算出的摄氏温度。
- 使用
printf()函数提示用户输入。 - 使用
scanf()函数从键盘读取用户输入的华氏温度。 - 根据公式进行计算。
- 使用
printf()函数格式化输出最终结果。
- 需要一个变量来存储用户输入的华氏温度,可以定义为
-
参考代码:
(图片来源网络,侵删)#include <stdio.h> // 标准输入输出头文件 int main() { // 1. 定义变量 float fahrenheit, celsius; // 2. 提示用户输入 printf("请输入一个华氏温度: "); // 3. 读取用户输入 scanf("%f", &fahrenheit); // 4. 进行计算 celsius = (fahrenheit - 32) * 5.0f / 9.0f; // 5. 输出结果 // %.2f 表示输出一个浮点数,并保留两位小数 printf("对应的摄氏温度是: %.2f\n", celsius); return 0; // 程序正常结束 } -
常见问题与注意事项:
- 忘记
#include <stdio.h>: 会导致printf和scanf函数无法识别。 - 变量未定义: 直接使用未定义的变量会编译报错。
scanf的格式化字符串与变量类型不匹配: 用%d读取float类型的变量。scanf中忘记使用取地址符&:scanf("%f", fahrenheit)应为scanf("%f", &fahrenheit)。- 整数除法问题: 在
celsius = (fahrenheit - 32) * 5 / 9;中,fahrenheit是整数,5/9的结果是0(整数除法)。解决方案: 将5或9改为0或0,强制进行浮点数运算。
- 忘记
选择结构程序设计
实验目的:
- 掌握
if...else和switch语句的使用。 - 能够根据不同的条件执行不同的程序分支。
- 理解逻辑运算符(
&&, , )的使用。 示例:* 编写一个程序,实现一个简单的计算器,用户输入两个数字和一个运算符(, , `/`),程序输出计算结果,如果输入的运算符不是这四个之一,或除数为0,则给出错误提示。
参考答案与思路分析:
-
思路分析:
(图片来源网络,侵删)- 需要3个变量:两个操作数(
num1,num2)和一个运算符(operator)。 - 使用
scanf读取这三个值,注意,读取字符时,scanf("%c", &operator)前最好加一个getchar()或scanf(" %c", ...)(注意空格) 来吸收回车符。 - 使用
switch语句来判断operator的值,并执行相应的加、减、乘、除运算。 - 在
case '/'中,需要额外判断num2是否为0,如果为0则输出错误信息。 - 使用
default分支来处理非法运算符的情况。
- 需要3个变量:两个操作数(
-
参考代码:
#include <stdio.h> int main() { double num1, num2; char operator; printf("请输入两个数字和一个运算符 ( 5 + 3): "); scanf("%lf %c %lf", &num1, &operator, &num2); switch (operator) { case '+': printf("结果是: %.2lf\n", num1 + num2); break; case '-': printf("结果是: %.2lf\n", num1 - num2); break; case '*': printf("结果是: %.2lf\n", num1 * num2); break; case '/': if (num2 != 0) { printf("结果是: %.2lf\n", num1 / num2); } else { printf("错误:除数不能为0!\n"); } break; default: printf("错误:无效的运算符!\n"); break; } return 0; } -
常见问题与注意事项:
switch语句中忘记break: 这会导致“贯穿”现象,即一个case执行完后会继续执行下一个case。- 字符输入问题: 从键盘输入数字和运算符后按回车,回车符会留在输入缓冲区中,干扰后续字符的读取,使用
scanf(" %c", ...)中的空格可以跳过空白字符(包括回车)。 - 浮点数比较 的问题: 直接用
if (num2 == 0)判断浮点数是否为0在理论上有风险,但在实际应用中,对于用户输入的简单计算,通常是可行的,更严谨的做法是判断if (num2 > -1e-6 && num2 < 1e-6)。
循环结构程序设计
实验目的:
- 掌握
for,while,do...while三种循环语句的使用。 - 理解循环嵌套的概念和应用。
- 掌握
break和continue语句的作用。 示例1:** 使用循环结构打印九九乘法表。
参考答案与思路分析:
-
思路分析:
- 这是一个典型的双重循环问题,外层循环控制行数(从1到9),内层循环控制每行的列数(从1到当前行号)。
- 外层循环变量
i代表被乘数,内层循环变量j代表乘数。 - 内层循环的结束条件应该是
j <= i。 - 使用
printf格式化输出,保持对齐。
-
参考代码:
#include <stdio.h> int main() { int i, j; for (i = 1; i <= 9; i++) { for (j = 1; j <= i; j++) { // \t 表示制表符,用于对齐 // %d*%d=%-2d 的意思是: // %d: 第一个数字 // *: 星号 // %d: 第二个数字 // =: 等号 // %-2d: 结果,占2位,左对齐 printf("%d*%d=%-2d\t", j, i, i * j); } // 每行结束后换行 printf("\n"); } return 0; }示例2:** 判断一个整数是否为素数(质数),素数是指只能被1和它本身整除的大于1的自然数。
参考答案与思路分析:
-
思路分析:
- 定义一个整数
num。 num <= 1,则它不是素数。num == 2,它是素数。- 对于
num > 2的情况,我们只需要检查从2到sqrt(num)(num的平方根) 之间是否有能整除num的数,如果没有,num就是素数。 - 使用一个
for循环,从i = 2循环到i <= sqrt(num),在循环中,num % i == 0,说明num能被i整除,它就不是素数,可以提前用break结束循环。 - 循环结束后,根据循环变量
i的值或设置一个标志变量的值来判断是否为素数。
- 定义一个整数
-
参考代码:
#include <stdio.h> #include <math.h> // 为了使用 sqrt 函数 int main() { int num, isPrime = 1; // isPrime 是一个标志,1代表是素数,0代表不是 printf("请输入一个正整数: "); scanf("%d", &num); if (num <= 1) { isPrime = 0; } else if (num == 2) { isPrime = 1; } else { for (int i = 2; i <= sqrt(num); i++) { if (num % i == 0) { isPrime = 0; // 发现一个因子,不是素数 break; // 无需继续检查,直接跳出循环 } } } if (isPrime) { printf("%d 是一个素数,\n", num); } else { printf("%d 不是一个素数,\n", num); } return 0; }
数组
实验目的:
- 掌握一维数组和二维数组的定义、初始化和引用。
- 学会使用数组处理批量数据。
- 掌握与数组相关的算法,如排序、查找等。 示例:** 编写一个程序,实现一个整型数组的冒泡排序,并输出排序前后的数组。
参考答案与思路分析:
-
思路分析:
- 定义一个整型数组,
int arr[10];。 - 使用循环和
scanf为数组元素赋值。 - 冒泡排序算法:
- 外层循环控制排序的轮数,共
n-1轮。 - 内层循环负责每一轮的比较和交换,每一轮都会将当前未排序部分的最大数“冒泡”到最后。
- 内层循环的次数会随着轮数的增加而减少,因为每轮都会确定一个数的最终位置。
- 比较相邻的两个元素,如果前一个比后一个大,就交换它们。
- 外层循环控制排序的轮数,共
- 使用循环输出排序前后的数组。
- 定义一个整型数组,
-
参考代码:
#include <stdio.h> #define N 10 // 定义数组大小,方便修改 int main() { int arr[N]; int i, j, temp; // 输入数组元素 printf("请输入 %d 个整数:\n", N); for (i = 0; i < N; i++) { scanf("%d", &arr[i]); } // 输出排序前的数组 printf("排序前的数组: "); for (i = 0; i < N; i++) { printf("%d ", arr[i]); } printf("\n"); // 冒泡排序 for (i = 0; i < N - 1; i++) { // 每一轮内层循环的比较次数 // -i 是因为每轮结束后,最后i个元素已经有序 for (j = 0; j < N - 1 - i; j++) { // 如果前一个数比后一个数大,则交换 if (arr[j] > arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } // 输出排序后的数组 printf("排序后的数组: "); for (i = 0; i < N; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
函数
实验目的:
- 掌握函数的定义、声明和调用。
- 理解函数参数传递(值传递)。
- 掌握递归函数的设计和调用。
示例1:**
编写一个函数
is_prime(int num),用于判断一个数是否为素数,在main函数中调用该函数,并打印1到100之间的所有素数。
参考答案与思路分析:
-
思路分析:
- 将判断素数的逻辑封装成一个独立的函数,接收一个整数参数,返回一个整型值(1表示是素数,0表示不是)。
main函数中创建一个循环,从1到100遍历每个数。- 在循环中,调用
is_prime()函数判断当前数。 - 如果函数返回值为1,则打印该数。
-
参考代码:
#include <stdio.h> #include <math.h> // 函数声明 int is_prime(int num); int main() { printf("1到100之间的素数有:\n"); for (int i = 1; i <= 100; i++) { if (is_prime(i)) { printf("%d ", i); } } printf("\n"); return 0; } // 函数定义:判断一个数是否为素数 int is_prime(int num) { if (num <= 1) { return 0; // 不是素数 } if (num == 2) { return 1; // 是素数 } for (int i = 2; i <= sqrt(num); i++) { if (num % i == 0) { return 0; // 发现因子,不是素数 } } return 1; // 是素数 }示例2:** 使用递归函数计算一个非负整数的阶乘。
参考答案与思路分析:
-
思路分析:
- 递归函数的特点是函数内部调用自身。
- 递归的出口(基准情形): 必须有一个条件,当满足该条件时,函数不再调用自身,而是直接返回一个确定的结果,对于阶乘,
0! = 1。 - 递归的递推关系: 将问题分解为更小的同类问题。
n! = n * (n-1)!。 - 函数可以这样设计:
n == 0,返回1;否则,返回n * factorial(n - 1)。
-
参考代码:
#include <stdio.h> // 函数声明 long factorial(int n); int main() { int num; printf("请输入一个非负整数: "); scanf("%d", &num); if (num < 0) { printf("错误:请输入非负整数!\n"); } else { printf("%d 的阶乘是: %ld\n", num, factorial(num)); } return 0; } // 递归函数定义:计算阶乘 long factorial(int n) { // 基准情形 if (n == 0) { return 1; } else { // 递推关系 return n * factorial(n - 1); } }
指针
实验目的:
- 掌握指针变量的定义和使用。
- 理解指针和地址的关系。
- 掌握指针作为函数参数,实现通过函数调用修改主调函数中变量的值(地址传递)。
示例:*
编写一个函数 `swap(int a, int *b)
,用于交换两个整数的值,在main` 函数中调用该函数,验证交换是否成功。
参考答案与思路分析:
-
思路分析:
- C语言中,函数参数是“值传递”,意味着函数内部只能操作主调函数变量的一份拷贝,直接传递变量本身无法在函数内修改主调函数的变量。
- 解决方案: 传递变量的地址,函数接收的是地址(即指针),然后通过指针间接访问和修改主调函数中的变量。
swap函数接收两个整型指针int *a和int *b。- 在函数内部,定义一个临时变量
temp,然后通过*a和*b(解引用操作)来交换内存中地址所对应的值。 main函数中,定义两个整数,并打印它们的初始值,然后调用swap函数,传入这两个变量的地址&x和&y,最后再次打印,查看值是否已交换。
-
参考代码:
#include <stdio.h> // 函数声明 void swap(int *a, int *b); int main() { int x = 10, y = 20; printf("交换前: x = %d, y = %d\n", x, y); // 调用 swap 函数,传入 x 和 y 的地址 swap(&x, &y); printf("交换后: x = %d, y = %d\n", x, y); return 0; } // 函数定义:交换两个整数的值 void swap(int *a, int *b) { int temp; temp = *a; // 将 a 指针指向的值赋给 temp *a = *b; // 将 b 指针指向的值赋给 a 指针指向的内存 *b = temp; // 将 temp 的值赋给 b 指针指向的内存 }
结构体与共用体
实验目的:
- 掌握结构体类型的定义和使用。
- 掌握结构体数组的定义和使用。
- 掌握结构体指针的使用。
示例:**
定义一个结构体
Student,包含学号(id)、姓名(name)和成绩(score),创建一个结构体数组,用于存储3个学生的信息,并找出成绩最高的学生。
参考答案与思路分析:
-
思路分析:
- 使用
struct关键字定义Student结构体。 - 在
main函数中定义一个Student类型的数组,struct Student students[3];。 - 使用循环和
scanf为每个学生的成员赋值,注意,name是字符串,需要使用%s,并且确保数组足够大。 - 假设第一个学生是成绩最高的,然后遍历剩余的学生,如果发现有学生成绩比当前最高分还高,就更新最高分和对应的学生信息。
- 最后输出成绩最高的学生信息。
- 使用
-
参考代码:
#include <stdio.h> #include <string.h> // 为了使用 strcpy 函数 // 定义 Student 结构体 struct Student { int id; char name[50]; float score; }; int main() { struct Student students[3]; int i; int max_index = 0; // 假设第一个学生成绩最高 // 输入学生信息 for (i = 0; i < 3; i++) { printf("请输入第 %d 个学生的学号、姓名和成绩: ", i + 1); scanf("%d %s %f", &students[i].id, students[i].name, &students[i].score); } // 找出成绩最高的学生 for (i = 1; i < 3; i++) { if (students[i].score > students[max_index].score) { max_index = i; } } // 输出成绩最高的学生信息 printf("\n成绩最高的学生是:\n"); printf("学号: %d, 姓名: %s, 成绩: %.2f\n", students[max_index].id, students[max_index].name, students[max_index].score); return 0; }
文件操作
实验目的:
- 掌握文件的基本操作(打开、关闭、读写)。
- 掌握
fopen,fclose,fprintf,fscanf,fgets,fputs等常用文件操作函数的使用。 - 理解文本文件和二进制文件的区别。
示例:**
将一个字符串 "Hello, File I/O!" 写入到一个名为
output.txt的文本文件中,然后再从该文件中读出内容并打印到屏幕上。
参考答案与思路分析:
-
思路分析:
- 写入文件:
- 使用
fopen("output.txt", "w")以“写”模式打开文件,如果文件不存在,会创建它;如果存在,内容会被清空。 - 检查文件指针是否为
NULL,判断是否打开成功。 - 使用
fprintf(fp, "Hello, File I/O!");将字符串写入文件。 - 使用
fclose(fp);关闭文件。
- 使用
- 读取文件:
- 使用
fopen("output.txt", "r")以“读”模式打开文件。 - 检查文件指针是否为
NULL。 - 定义一个字符数组(缓冲区)来存放读出的内容。
- 使用
fgets(buffer, sizeof(buffer), fp);从文件中读取一行。 - 使用
printf("%s", buffer);将读出的内容打印到屏幕。 - 关闭文件。
- 使用
- 写入文件:
-
参考代码:
#include <stdio.h> #include <string.h> int main() { FILE *fp; char buffer[100]; // --- 写入文件 --- fp = fopen("output.txt", "w"); // "w" 表示写模式 if (fp == NULL) { printf("无法打开文件进行写入!\n"); return 1; } fprintf(fp, "Hello, File I/O!"); fclose(fp); printf("内容已成功写入 output.txt\n"); // --- 读取文件 --- fp = fopen("output.txt", "r"); // "r" 表示读模式 if (fp == NULL) { printf("无法打开文件进行读取!\n"); return 1; } // fgets 从文件中读取一行,直到遇到换行符或EOF fgets(buffer, sizeof(buffer), fp); fclose(fp); printf("从文件中读取的内容是: %s\n", buffer); return 0; }
总结与建议
- 多动手,多调试: 编程是实践性极强的技能,不要只看不练,遇到错误时,学会使用编译器的错误提示信息,并学会用调试器单步跟踪程序,观察变量的变化。
- 善用注释: 在代码中写清晰的注释,解释你的思路和关键步骤,这不仅能帮助别人理解,也能在你回顾代码时节省大量时间。
- 阅读优秀代码: 在网上(如 GitHub)阅读一些优秀的开源C语言项目,学习别人的编程风格和解决问题的思路。
- 从模仿到创造: 初期可以模仿范例的代码结构和风格,然后尝试修改它,增加新功能,解决不同的问题,逐步培养自己的编程能力。
希望这份详细的指南能对你的C语言学习之路有所帮助!祝你实验顺利!
