每个程序都包含 题目描述、核心思路、代码示例 和 知识点总结。

第一部分:基础语法与流程控制
"Hello, World!" 程序描述**:编写一个程序,在屏幕上打印 "Hello, World!"。
核心思路:这是所有编程语言的第一个程序,目的是让你成功搭建并运行一个C语言环境,使用 printf 函数进行标准输出。
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
知识点总结:
#include <stdio.h>:包含标准输入输出库,以便使用printf函数。int main():C程序的入口函数。printf():格式化输出函数。\n:换行符。return 0;:表示程序正常结束。
两数交换描述**:交换两个变量的值。
核心思路:不能直接交换,需要一个临时变量作为“中转站”,这是最基本、最安全的交换方法。
#include <stdio.h>
int main() {
int a = 10, b = 20, temp;
printf("交换前: a = %d, b = %d\n", a, b);
temp = a; // 1. 将a的值存入temp
a = b; // 2. 将b的值赋给a
b = temp; // 3. 将temp的值(原a的值)赋给b
printf("交换后: a = %d, b = %d\n", a, b);
return 0;
}
知识点总结:
- 变量声明与赋值。
- 使用临时变量进行数据交换。
printf的格式化输出。
判断奇偶数描述**:输入一个整数,判断它是奇数还是偶数。
核心思路:利用取模运算 ,一个数如果能被2整除(即 num % 2 == 0),就是偶数,否则是奇数。
#include <stdio.h>
int main() {
int num;
printf("请输入一个整数: ");
scanf("%d", &num);
if (num % 2 == 0) {
printf("%d 是偶数,\n", num);
} else {
printf("%d 是奇数,\n", num);
}
return 0;
}
知识点总结:
scanf函数用于从键盘读取输入。- (取模/求余)运算符。
if-else条件判断语句。
判断闰年描述**:输入一个年份,判断是否是闰年。
核心思路:闰年的判断规则:
- 能被4整除,但不能被100整除,或者
- 能被400整除。
#include <stdio.h>
int main() { int year; printf("请输入一个年份: "); scanf("%d", &year);
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
printf("%d 是闰年,\n", year);
} else {
printf("%d 不是闰年,\n", year);
}
return 0;
**知识点总结**:
* 复杂的 `if-else` 条件判断。
* 逻辑运算符 `&&` (与)、`||` (或)、`!` (非)。
---
### 第二部分:循环结构
#### 5. 乘法口诀表描述**:在屏幕上打印出9x9的乘法口诀表。
**核心思路**:使用双重循环(嵌套循环),外层循环控制行(1到9),内层循环控制列(1到当前行数)。
```c
#include <stdio.h>
int main() {
for (int i = 1; i <= 9; i++) { // 外层循环,控制行
for (int j = 1; j <= i; j++) { // 内层循环,控制列
printf("%d*%d=%-2d ", j, i, i * j); // %-2d 用于对齐
}
printf("\n"); // 每行结束后换行
}
return 0;
}
知识点总结:
for循环和嵌套循环。printf的格式化输出,%-2d表示左对齐,占2个字符宽度。
求最大公约数描述**:输入两个正整数,求它们的最大公约数。
核心思路:使用 辗转相除法(欧几里得算法),用较大数除以较小数,将余数作为新的除数,原来的除数作为新的被除数,直到余数为0,此时的除数就是最大公约数。
#include <stdio.h>
int main() {
int a, b, temp;
printf("请输入两个正整数: ");
scanf("%d %d", &a, &b);
// 确保a是较大的数
if (a < b) {
temp = a;
a = b;
b = temp;
}
while (b != 0) { // 辗转相除
temp = a % b;
a = b;
b = temp;
}
printf("最大公约数是: %d\n", a);
return 0;
}
知识点总结:
while循环。- 辗转相除法算法。
- 变量交换技巧。
求最小公倍数描述**:输入两个正整数,求它们的最小公倍数。
核心思路:利用公式:最小公倍数 = 两数之积 / 最大公约数,所以可以先求出GCD,再计算LCM。
#include <stdio.h>
int main() {
int a, b, gcd, lcm, temp;
printf("请输入两个正整数: ");
scanf("%d %d", &a, &b);
int original_a = a, original_b = b; // 保存原始值用于计算LCM
// 求GCD
if (a < b) {
temp = a;
a = b;
b = temp;
}
while (b != 0) {
temp = a % b;
a = b;
b = temp;
}
gcd = a;
// 求LCM
lcm = (original_a * original_b) / gcd;
printf("最大公约数是: %d\n", gcd);
printf("最小公倍数是: %d\n", lcm);
return 0;
}
知识点总结:
- GCD和LCM的关系。
- 算法的组合使用。
水仙花数描述**:找出所有3位的水仙花数,水仙花数是指一个3位数,其各位数字立方和等于它本身。
核心思路:遍历所有的3位数(100-999),对每个数,分离出其百位、十位和个位,然后计算立方和并判断是否等于原数。
#include <stdio.h>
#include <math.h> // 使用pow函数需要此头文件
int main() {
int i, a, b, c;
printf("3位的水仙花数有:\n");
for (i = 100; i < 1000; i++) {
a = i / 100; // 百位
b = (i / 10) % 10; // 十位
c = i % 10; // 个位
if (i == pow(a, 3) + pow(b, 3) + pow(c, 3)) {
printf("%d\n", i);
}
}
return 0;
}
知识点总结:
for循环遍历。- 利用整数除法和取模运算分离数字的各位。
math.h库中的pow()函数(或自己写a*a*a)。
第三部分:数组与字符串
数组元素求和与平均值描述**:定义一个整型数组,计算数组中所有元素的和及平均值。
核心思路:遍历数组,用一个累加器变量把所有元素加起来,最后用总和除以元素个数得到平均值。
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = 0;
double average;
int n = sizeof(arr) / sizeof(arr[0]); // 计算数组元素个数
for (int i = 0; i < n; i++) {
sum += arr[i];
}
average = (double)sum / n; // 注意类型转换
printf("数组元素的和为: %d\n", sum);
printf("数组元素的平均值为: %.2f\n", average);
return 0;
}
知识点总结:
- 数组的定义和初始化。
sizeof操作符计算数组大小和元素个数。for循环遍历数组。- 数据类型转换
(double),避免整数除法。
数组元素查找(线性查找)描述**:在数组中查找一个指定的数,如果找到则输出其位置,否则输出“未找到”。
核心思路:线性查找,从数组的第一个元素开始,依次与目标值比较,直到找到或遍历完整个数组。
#include <stdio.h>
int main() {
int arr[] = {15, 2, 7, 9, 12, 5};
int n = sizeof(arr) / sizeof(arr[0]);
int target, found = 0; // found作为标志位
printf("请输入要查找的数: ");
scanf("%d", &target);
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
printf("找到了!数字 %d 在数组的第 %d 个位置,\n", target, i + 1);
found = 1;
break; // 找到后立即退出循环
}
}
if (!found) {
printf("未找到数字 %d,\n", target);
}
return 0;
}
知识点总结:
- 数组的遍历和比较。
break语句的使用。- 使用标志位(
found)来控制流程。
数组排序(冒泡排序)描述**:使用冒泡排序算法对数组进行升序排序。
核心思路:重复地遍历数组,比较相邻的两个元素,如果它们的顺序错误(前一个比后一个大)就交换它们,这个过程就像气泡一样,大的数会“浮”到数组的末端。
#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;
}
知识点总结:
- 冒泡排序算法的实现。
- 双重嵌套循环。
- 函数的定义与调用,传递数组作为参数。
- 代码模块化思想。
字符串反转描述**:输入一个字符串,将其反转后输出。
核心思路:使用两个指针,一个指向字符串开头,一个指向结尾,交换两个指针所指的字符,然后让两个指针分别向中间移动,直到它们相遇或交错。
#include <stdio.h>
#include <string.h> // 使用strlen需要此头文件
void reverseString(char str[]) {
int length = strlen(str);
int i, j;
char temp;
for (i = 0, j = length - 1; i < j; i++, j--) {
temp = str[i];
str[i] = str[j];
str[j] = temp;
}
}
int main() {
char str[100];
printf("请输入一个字符串: ");
fgets(str, sizeof(str), stdin); // 安全地读取一行,包括空格
// 去掉fgets可能读取的换行符
str[strcspn(str, "\n")] = 0;
reverseString(str);
printf("反转后的字符串: %s\n", str);
return 0;
}
知识点总结:
- 字符数组和字符串处理。
strlen()函数求字符串长度。fgets()和scanf("%s")的区别(fgets更安全,能读取空格)。- 双指针算法在字符串处理中的应用。
第四部分:函数与递归
判断素数(质数)描述**:编写一个函数,判断一个数是否为素数。
核心思路:素数是指只能被1和它本身整除的大于1的自然数,一个简单的优化是,只需判断从2到 sqrt(num) 之间是否有能整除 num 的数即可。
#include <stdio.h>
#include <math.h> // 使用sqrt函数
// 函数声明
int isPrime(int num);
int main() {
int num;
printf("请输入一个正整数: ");
scanf("%d", &num);
if (isPrime(num)) {
printf("%d 是素数,\n", num);
} else {
printf("%d 不是素数,\n", num);
}
return 0;
}
// 函数定义
int isPrime(int num) {
if (num <= 1) {
return 0; // 1及以下的数不是素数
}
for (int i = 2; i <= sqrt(num); i++) {
if (num % i == 0) {
return 0; // 如果能被整除,则不是素数
}
}
return 1; // 是素数
}
知识点总结:
- 函数的定义、声明和调用。
- 素数的数学定义和优化算法。
math.h库中的sqrt()函数。- 函数返回值(0代表假,1代表真)。
斐波那契数列描述**:打印斐波那契数列的前n项,斐波那契数列:1, 1, 2, 3, 5, 8, 13...
核心思路:有两种经典方法:
- 迭代法(循环):从第3项开始,每一项都是前两项之和,效率高。
- 递归法:
F(n) = F(n-1) + F(n-2),代码简洁,但效率低,有大量重复计算。 这里展示两种方法。// 方法一:迭代法(推荐) void fibonacci_iterative(int n) { int t1 = 1, t2 = 1, nextTerm; printf("斐波那契数列前 %d 项 (迭代法): \n", n); for (int i = 1; i <= n; ++i) { printf("%d ", t1); nextTerm = t1 + t2; t1 = t2; t2 = nextTerm; } printf("\n"); }
// 方法二:递归法 int fibonacci_recursive(int n) { if (n <= 1) { return n; } else { return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2); } }
int main() { int n; printf("请输入要打印的项数: "); scanf("%d", &n);
// 调用迭代法
fibonacci_iterative(n);
// 调用递归法(打印前n项)
printf("斐波那契数列前 %d 项 (递归法): \n", n);
for (int i = 0; i < n; i++) {
printf("%d ", fibonacci_recursive(i));
}
printf("\n");
return 0;
**知识点总结**:
* 迭代算法(循环)。
* **递归算法**:函数调用自身。
* 递归的边界条件和递推关系。
* 理解递归的效率问题。
---
### 第五部分:指针与内存管理
#### 15. 使用指针实现数组排序描述**:使用指针变量来访问和交换数组元素,实现冒泡排序。
**核心思路**:与普通数组版本类似,但所有对数组的访问都通过指针来完成,这能让你更深刻地理解指针和数组的关系。
```c
#include <stdio.h>
void bubbleSortWithPointer(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");
bubbleSortWithPointer(arr, n); // 传递数组名(即首元素地址)
printf("排序后的数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
知识点总结:
- 指针与数组的关系:
arr[i]等价于*(arr + i)。 - 通过指针操作数组元素。
- 指针作为函数参数。
动态内存分配描述**:在程序运行时,根据用户输入的大小动态地创建一个数组,并对其进行操作,最后释放内存。
核心思路:使用 malloc (memory allocate) 函数在堆上申请内存空间,使用完毕后,必须用 free 函数释放,否则会造成内存泄漏。
#include <stdio.h>
#include <stdlib.h> // 使用malloc和free需要此头文件
int main() {
int n;
int *arr; // 声明一个指针变量
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]);
}
printf("你输入的数组是: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 释放内存
free(arr);
arr = NULL; // 好习惯,防止悬垂指针
printf("内存已释放,\n");
return 0;
}
知识点总结:
malloc函数:在堆上动态分配内存。free函数:释放动态分配的内存。- 内存泄漏的概念及其危害。
- 指针在动态内存管理中的核心作用。
第六部分:结构体与文件操作
结构体数组与排序描述**:定义一个包含学生姓名和成绩的结构体,创建一个结构体数组,并按成绩从高到低排序。
核心思路:定义 struct,创建结构体数组,在排序时,比较的是结构体成员(成绩),交换的是整个结构体。
#include <stdio.h>
#include <string.h>
// 定义学生结构体
struct Student {
char name[50];
float score;
};
// 按成绩降序排序的函数
void sortStudents(struct Student students[], int n) {
struct Student temp;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (students[j].score < students[j + 1].score) {
// 交换整个结构体
temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
}
int main() {
struct Student students[3] = {
{"张三", 85.5},
{"李四", 92.0},
{"王五", 78.5}
};
int n = sizeof(students) / sizeof(students[0]);
printf("排序前的学生信息:\n");
for (int i = 0; i < n; i++) {
printf("姓名: %s, 成绩: %.1f\n", students[i].name, students[i].score);
}
sortStudents(students, n);
printf("\n按成绩排序后的学生信息:\n");
for (int i = 0; i < n; i++) {
printf("姓名: %s, 成绩: %.1f\n", students[i].name, students[i].score);
}
return 0;
}
知识点总结:
struct的定义和使用。- 结构体数组的创建和初始化。
- 结构体成员的访问( 操作符)。
- 结构体变量的赋值和交换。
文件的读写描述**:将一组数据写入一个文本文件,然后再从该文件中读取并显示出来。
核心思路:
- 写入文件:使用
fopen以写模式("w")打开文件,fprintf写入数据,fclose关闭文件。 - 读取文件:使用
fopen以读模式("r")打开文件,fscanf读取数据,fclose关闭文件。#include <stdio.h>
int main() { // --- 写入文件 --- FILE *writeFile; writeFile = fopen("data.txt", "w"); // "w" 表示写模式,如果文件不存在则创建 if (writeFile == NULL) { printf("无法打开文件进行写入!\n"); return 1; } fprintf(writeFile, "Hello, File!\n"); fprintf(writeFile, "这是一个测试文件,\n"); fprintf(writeFile, "数字: %d\n", 12345); fclose(writeFile); printf("数据已成功写入 data.txt 文件,\n");
// --- 读取文件 ---
FILE *readFile;
char buffer[100];
int number;
readFile = fopen("data.txt", "r"); // "r" 表示读模式
if (readFile == NULL) {
printf("无法打开文件进行读取!\n");
return 1;
}
printf("\n--- 从文件读取的数据 ---\n");
while (fgets(buffer, sizeof(buffer), readFile) != NULL) { // 逐行读取
printf("%s", buffer);
}
// 也可以使用 fscanf 读取特定格式
// rewind(readFile); // 将文件指针重置到开头
// fscanf(readFile, "数字: %d", &number);
// printf("读取到的数字是: %d\n", number);
fclose(readFile);
printf("\n文件读取完毕,\n");
return 0;
**知识点总结**:
* `FILE` 指针的概念。
* `fopen`, `fclose`, `fprintf`, `fscanf`, `fgets` 等文件操作函数。
* 文件打开模式(`"r"`, `"w"`, `"a"` 等)。
* 文件操作的错误检查。
---
### 总结与建议
这18个程序是C语言学习的基石,当你能独立、熟练地写出并理解它们时,说明你已经掌握了C语言的核心知识。
**学习建议**:
1. **亲手敲**:不要只看,一定要亲手在编译器里敲出来,运行并观察结果。
2. **多思考**:理解每个程序背后的逻辑和算法,而不仅仅是记住代码。
3. **勤修改**:尝试修改这些程序,比如改变输入、调整算法、增加功能,看看会发生什么。
4. **重实践**:在理解的基础上,尝试自己不看答案,独立完成这些程序。
祝你学习顺利!
