由于市面上《C语言程序设计》的教材版本繁多(如谭浩强、C Primer Plus、K&R C等),具体章节和习题编号可能略有不同,我将按照C语言学习的核心知识点模块,并提供每个模块的典型例题、答案和深度解析。

C语言基础与数据类型
这个模块是C语言的基石,重点在于理解变量、常量、基本数据类型以及如何进行简单的输入输出。
典型例题1:华氏温度转摄氏温度
* 编写一个程序,将用户输入的华氏温度转换为摄氏温度,转换公式为:`C = (F - 32) 5 / 9`。
答案与代码:
#include <stdio.h>
int main() {
// 1. 定义变量
// float 用于存储带小数的温度
float fahrenheit, celsius;
// 2. 提示用户输入
printf("请输入华氏温度: ");
// 3. 从键盘读取用户输入的浮点数,并存储到 fahrenheit 变量中
scanf("%f", &fahrenheit);
// 4. 根据公式进行计算
// 注意:为了得到精确的小数,分子或分母中至少有一个应该是浮点数
// (fahrenheit - 32.0) 或 5.0
celsius = (fahrenheit - 32.0) * 5.0 / 9.0;
// 5. 输出结果
// %.2f 表示输出一个浮点数,并保留两位小数
printf("对应的摄氏温度是: %.2f\n", celsius);
return 0; // 程序正常结束
}
知识点解析:

#include <stdio.h>:引入标准输入输出库,没有它,程序无法使用printf和scanf。float:用于定义单精度浮点型变量,适合存储像温度、价格这样的实数。printf:格式化输出函数。"%.2f"是格式控制符,%f表示输出一个浮点数,.2表示保留两位小数。scanf:格式化输入函数。"%f"表示等待用户输入一个浮点数。&是取地址运算符,&fahrenheit表示获取fahrenheit变量的内存地址,scanf需要这个地址来存储读取到的值。return 0:main函数的返回值。0表示程序成功执行并正常退出。
流程控制(选择与循环)
这个模块是程序“智能”的核心,学习如何让程序根据不同条件执行不同代码,或者重复执行某段代码。
典型例题2:判断奇偶数
** 编写一个程序,用户输入一个整数,程序判断该数是奇数还是偶数。
答案与代码:
#include <stdio.h>
int main() {
int number;
printf("请输入一个整数: ");
scanf("%d", &number);
// 使用取模运算符 %
// number 除以 2 的余数为 0,则是偶数
if (number % 2 == 0) {
printf("%d 是一个偶数,\n", number);
} else {
// 否则,就是奇数
printf("%d 是一个奇数,\n", number);
}
return 0;
}
知识点解析:
- (取模运算符):计算两个整数相除的余数,这是判断奇偶数最常用的方法。
if-else语句:这是最基本的选择结构。if后面的条件(number % 2 == 0)为真(成立),则执行if块内的代码;否则,执行else块内的代码。- (等于运算符):用于判断左右两边的值是否相等。注意:千万不要写成 , 是赋值运算符。
典型例题3:打印九九乘法表
** 使用嵌套循环,打印标准的九九乘法表。
答案与代码:
#include <stdio.h>
int main() {
// 外层循环控制行数 (1-9)
for (int i = 1; i <= 9; i++) {
// 内层循环控制每行的列数 (1-当前行号i)
for (int j = 1; j <= i; j++) {
// 打印乘法表达式,%-2d 表示左对齐,占2个字符宽度,使输出整齐
printf("%d*%d=%-2d ", j, i, i * j);
}
// 每行结束后打印一个换行符
printf("\n");
}
return 0;
}
知识点解析:
for循环:for (初始化; 条件判断; 循环后操作),非常适合已知循环次数的场景。- 嵌套循环:一个循环内部包含另一个循环,外层循环执行一次,内层循环会完整地执行一遍。
printf中的格式化:%-2d中的 表示左对齐,2表示该整数至少占2个字符宽度,这使得乘法表的输出非常整齐美观。\n:换行符,printf遇到它就会将光标移动到下一行的开头。
数组
数组是存储一组相同类型数据的集合,是处理批量数据的基础。
典型例题4:数组元素求和与平均值
** 定义一个包含10个整数的数组,计算所有元素的和以及平均值。
答案与代码:
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 定义并初始化数组
int sum = 0;
float average;
// 使用循环遍历数组
for (int i = 0; i < 10; i++) {
// 将每个元素的值累加到 sum 中
sum += arr[i]; // 等价于 sum = sum + arr[i];
}
// 计算平均值
// 注意:sum 是 int,10 也是 int,整数除法会丢失小数部分
// 所以需要将其中一个操作数转换为 float
average = (float)sum / 10;
printf("数组的和是: %d\n", sum);
printf("数组的平均值是: %.2f\n", average);
return 0;
}
知识点解析:
- 数组定义:
int arr[10];定义了一个可以存放10个int类型数据的数组。 - 数组索引:C语言数组的索引从 0 开始。
arr[0]是第一个元素,arr[9]是最后一个元素。 - 遍历数组:使用
for循环从0遍历到数组长度-1是最常见的方式。 - 类型转换:
(float)sum是强制类型转换,它将sum的值临时转换为float类型,然后再进行除法运算,从而保证结果是浮点数,避免整数除法带来的精度丢失。
函数
函数是将一段代码封装起来,通过函数名来调用的代码块,它可以实现代码的复用、模块化和结构化。
典型例题5:使用函数判断素数
** 编写一个函数 isPrime,用于判断一个整数是否为素数(质数),在 main 函数中调用该函数,并打印1到100之间的所有素数。
答案与代码:
#include <stdio.h>
#include <math.h> // 为了使用 sqrt 函数
// 函数声明:告诉编译器后面会有一个名为 isPrime 的函数
int isPrime(int num);
int main() {
printf("1到100之间的素数有:\n");
for (int i = 2; i <= 100; i++) {
// 调用 isPrime 函数,并根据返回值判断
if (isPrime(i)) {
printf("%d ", i);
}
}
printf("\n");
return 0;
}
// 函数定义:实现判断素数的逻辑
int isPrime(int num) {
// 1和0以及负数不是素数
if (num <= 1) {
return 0; // 0 代表假
}
// 2是唯一的偶素数
if (num == 2) {
return 1; // 1 代表真
}
// 排除所有其他偶数
if (num % 2 == 0) {
return 0;
}
// 只需要检查到该数的平方根即可
for (int i = 3; i <= sqrt(num); i += 2) {
if (num % i == 0) {
return 0; // 如果能被整除,说明不是素数
}
}
// 如果循环都结束了还没找到能整除的数,就是素数
return 1;
}
知识点解析:
- 函数声明与定义:
int isPrime(int num);是函数声明,放在main函数之前。int isPrime(int num) { ... }是函数定义,实现了具体功能。 - 参数传递:
isPrime(i)中的i是实参,传递给函数的num(形参)。 - 返回值:函数通过
return语句返回一个值。isPrime函数返回int类型,1代表“是素数”(真),0代表“不是素数”(假)。 - 算法优化:判断素数时,只需检查到
sqrt(num)即可,并且可以跳过所有偶数,这样可以大大提高效率。
指针
指针是C语言的灵魂和难点,它存储的是变量的内存地址,掌握指针才能深入理解C语言的工作机制。
典型例题6:使用指针交换两个变量的值
** 编写一个函数 swap,使用指针作为参数,交换两个整数的值。
答案与代码:
#include <stdio.h>
// 函数声明,参数是两个整型指针
void swap(int *ptr1, int *ptr2);
int main() {
int a = 10;
int b = 20;
printf("交换前: a = %d, b = %d\n", a, b);
// 调用 swap 函数,传入 a 和 b 的地址
swap(&a, &b);
printf("交换后: a = %d, b = %d\n", a, b);
return 0;
}
// 函数定义
void swap(int *ptr1, int *ptr2) {
int temp; //需要一个临时变量来存储值
temp = *ptr1; // *ptr1 是解引用,获取 ptr1 地址上存储的值(即 a 的值)
*ptr1 = *ptr2; // 将 ptr2 地址上的值(b的值)赋给 ptr1 地址上的值
*ptr2 = temp; // 将临时变量中的值(原来的a的值)赋给 ptr2 地址上的值
}
知识点解析:
- 指针变量:
int *ptr;定义了一个指向int类型的指针变量ptr。 &(取地址运算符):&a获取变量a的内存地址。- (解引用/间接寻址运算符):
*ptr获取指针ptr所指向地址上存储的值。 - 为什么必须用指针?:C语言是值传递,在
main函数中调用swap(a, b)时,实际上是把a和b的值复制了一份传给swap函数的形参。swap函数内部交换的是这两份副本,对main函数中的a和b没有任何影响,而传递地址(指针)则可以让函数直接操作到main函数中的变量本身,从而实现真正的交换。
结构体
结构体允许你将不同类型的数据组合成一个单一的、自定义的数据类型。
典型例题7:使用结构体存储学生信息
** 定义一个结构体 Student,包含学号(id)、姓名(name)和成绩(score),创建一个 Student 类型的变量,并为其赋值,然后打印出来。
答案与代码:
#include <stdio.h>
#include <string.h> // 为了使用 strcpy 函数
// 1. 定义结构体类型 Student
struct Student {
int id;
char name[50]; // 字符数组,用于存储字符串
float score;
};
int main() {
// 2. 创建一个 Student 类型的变量 s1
struct Student s1;
// 3. 为结构体变量的成员赋值
s1.id = 1001;
// 不能直接用 s1.name = "张三";,因为 name 是数组名,是常量地址
// 需要使用 strcpy 函数来复制字符串
strcpy(s1.name, "张三");
s1.score = 95.5f;
// 4. 打印结构体变量的成员
printf("学生信息:\n");
printf("学号: %d\n", s1.id);
printf("姓名: %s\n", s1.name);
printf("成绩: %.1f\n", s1.score);
return 0;
}
知识点解析:
- 结构体定义:
struct Student { ... };定义了一个新的数据类型Student。 - 结构体变量创建:
struct Student s1;创建了一个Student类型的变量s1。 - 成员访问:使用 点运算符来访问和修改结构体变量的成员,
s1.id。 - 字符串处理:
char name[50]是一个字符数组,用于存储字符串,赋值字符串需要使用strcpy(string copy) 函数,因为它会把一个字符串完整地复制到字符数组中。
学习建议与常见误区
- 多动手,少看书:C语言是实践性极强的学科,看懂了不等于会写了,一定要把每一个例题都亲手敲一遍、运行一遍、修改一下看看结果。
- 理解底层,不要死记:理解指针就是地址,数组名就是数组首地址,函数调用是压栈过程,这些底层逻辑能让你豁然开朗。
- 善用调试器:学会使用
gdb(Linux) 或 Visual Studio Debugger 等工具,单步执行、查看变量值是解决逻辑错误的最佳方式。 - 重视警告:编译器给出的警告(Warning)往往比错误(Error)更致命,仔细阅读并修复所有警告,这能帮你提前发现很多潜在问题。
- 常见错误:
- 数组越界:访问
arr[10](当数组长度为10时)。 - 忘记
&:scanf("%d", num);应该是scanf("%d", &num);。 - 混淆 和 :
if (a = 5)是赋值,if (a == 5)是判断。 - 指针未初始化:
int *p; *p = 10;(p是一个野指针,指向未知内存,操作它会导致程序崩溃)。
- 数组越界:访问
希望这份详尽的学习指导能对您有所帮助!祝您学习顺利!
