第一章:C语言概述与环境
核心知识点
- C语言的特点:结构化、高级语言与低级语言结合、可移植性好等。
- C程序的基本结构:
main函数是程序的入口,由函数构成。 - C程序的编译和执行过程:编辑 -> 编译 -> 链接 -> 运行。
典型习题
习题1: 编写一个简单的C程序,在屏幕上输出 "Hello, World!"。

解答:
#include <stdio.h> // 1. 包含标准输入输出库,提供 printf 函数的声明
// 2. 定义主函数,程序从这里开始执行
int main() {
// 3. 调用 printf 函数,在屏幕上打印字符串
// \n 是一个转义字符,表示换行
printf("Hello, World!\n");
// 4. main 函数返回 0,表示程序正常结束
return 0;
}
解题思路与知识点:
#include <stdio.h>:这是预处理器指令。stdio.h是 "standard input output header" 的缩写,包含了与输入输出相关的函数(如printf)的声明,没有它,编译器就不知道printf是什么。int main():每个C程序都必须有一个名为main的函数。int表示这个函数执行完毕后会返回一个整数值。return 0;是一个约定,返回0表示程序成功执行,没有发生错误。printf("Hello, World!\n");:printf是一个标准库函数,用于格式化输出,双引号内的字符串是它要打印的内容。\n是一个换行符,它会让光标移动到下一行的开头,使下一次输出从新行开始。
第二章:数据类型、运算符与表达式
核心知识点
- 基本数据类型:
int(整型),float(单精度浮点型),double(双精度浮点型),char(字符型)。 - 常量与变量:常量的值不能改变,变量的值可以改变,使用变量前必须先声明。
- 运算符:
- 算术运算符:, , , , (取模)。
- 赋值运算符:, , 等。
- 自增/自减:, (注意前置和后置的区别)。
- 关系运算符:
>,<, , 等。 - 逻辑运算符:
&&(与), (或), (非)。
- 类型转换:自动类型转换(隐式)和强制类型转换(显式)。
典型习题
习题2: 编写程序,声明一个整型变量 a 和一个浮点型变量 b,给它们赋值,然后计算并输出它们的和、差、积、商。
解答:

#include <stdio.h>
int main() {
// 声明并初始化变量
int a = 10;
float b = 3.14f; // f 后缀表示这是一个 float 常量
// 计算并输出
printf("a = %d, b = %f\n", a, b);
printf("a + b = %f\n", (float)a + b); // 将 a 转换为 float 以得到精确结果
printf("a - b = %f\n", (float)a - b);
printf("a * b = %f\n", (float)a * b);
printf("a / b = %f\n", (float)a / b); // 注意:整数除以浮点数结果是浮点数
return 0;
}
解题思路与知识点:
- 变量声明与初始化:
int a = 10;声明了一个整型变量a并赋初值为10。float b = 3.14f;声明了一个浮点型变量b。f后缀告诉编译器14是一个float类型,而不是默认的double。 printf的格式化输出:%d用于输出整型,%f用于输出浮点型。printf函数会按照格式字符串中的占位符顺序,用后面提供的变量值来替换它们。- 类型转换:当
int和float进行算术运算时,int类型的a会被自动提升为float类型,然后再进行计算,为了清晰和避免潜在的精度问题,我们使用(float)a进行强制类型转换,使代码意图更明确。
习题3: 分析下面代码的输出结果,并解释原因。
int main() {
int i = 5;
int j = ++i + i++;
printf("i = %d, j = %d\n", i, j);
return 0;
}
解答与解析:
输出结果:

i = 7, j = 12
解析(C语言标准规定: 一个变量在一个表达式中只能被求值一次,但编译器实现可能不同,此分析基于常见行为):
int j = ++i + i++;:这一行是关键,涉及到副作用和求值顺序。++i(前置自增):i的值先自增1(变为6),然后这个新值6用于加法运算。i++(后置自增):i的当前值(现在是6)被用于加法运算,然后i的值再自增1(变为7)。- 计算过程:
++i的结果是6。i++的结果是6。j = 6 + 6 = 12。- 在整个表达式计算完毕后,
i的值因为i++而变成了7。
i的值为7,j的值为12。
易错点:这种混合使用前置和后置自增/自减的表达式是C语言中著名的“坑”,在实际编程中,应尽量避免编写这样的代码,因为它可读性差,且在不同编译器下可能产生不同结果。
第三章:顺序与选择结构
核心知识点
if-else语句:实现单分支、双分支或多分支选择结构。switch语句:基于一个变量的值进行多路分支。- 关系运算符与逻辑运算符的结合:构建复杂的条件表达式。
典型习题
习题4: 编写一个程序,要求用户输入一个整数,然后判断该数是正数、负数还是零,并输出相应的信息。
解答:
#include <stdio.h>
int main() {
int num;
// 提示用户输入
printf("请输入一个整数: ");
// 从键盘读取一个整数并存入 num 变量
scanf("%d", &num);
// 使用 if-else if-else 结构进行判断
if (num > 0) {
printf("%d 是一个正数,\n", num);
} else if (num < 0) {
printf("%d 是一个负数,\n", num);
} else {
printf("你输入的是零,\n");
}
return 0;
}
解题思路与知识点:
scanf("%d", &num);:scanf是标准输入函数,用于从键盘读取数据。%d表示要读取一个整数,&num是变量num的地址,scanf需要知道把读到的数据存放到哪里去。地址运算符&是初学者最容易忘记的。if-else if-else结构:这是一个阶梯式的判断结构。if的条件为真,则执行其后的代码块,并跳过所有else if和else。if为假,则判断else if,以此类推,如果所有条件都为假,则执行else块,这确保了只有一个分支会被执行。
第四章:循环结构
核心知识点
for循环:适用于循环次数已知的情况。while循环:适用于循环次数未知,但循环条件明确的情况。do-while循环:至少执行一次循环体,然后再判断条件。break和continue:break用于跳出整个循环,continue用于跳过本次循环的剩余语句,直接进入下一次循环。
典型习题
习题5: 使用 for 循环,计算并输出 1 到 100 之间所有偶数的和。
解答:
#include <stdio.h>
int main() {
int sum = 0; // 用于存储累加和
// for 循环:初始化 i=1; 条件 i<=100; 每次循环后 i++
for (int i = 1; i <= 100; i++) {
// 使用 if 语句判断 i 是否为偶数
if (i % 2 == 0) {
sum = sum + i; // 或者写成 sum += i;
}
}
printf("1 到 100 之间所有偶数的和是: %d\n", sum);
return 0;
}
解题思路与知识点:
- 循环变量初始化:
int i = 1;在循环开始前定义并初始化计数器i。 - 循环条件:
i <= 100;只要i小于或等于100,循环就会继续。 - 循环后操作:
i++每次循环结束后,i的值增加1。 - 取模运算符 :
i % 2计算i除以2的余数,如果余数为0,说明i能被2整除,就是偶数。 - 累加:
sum = sum + i;是一个累加操作,每次将符合条件的i加到sum上。
第五章:数组
核心知识点
- 一维数组:相同类型数据的线性集合,声明方式:
类型 数组名[大小];。 - 数组下标:从0开始,到
大小-1结束。 - 数组初始化:可以在声明时直接初始化。
- 字符串:以
'\0'(空字符) 结尾的字符数组。
典型习题
习题6: 定义一个包含10个整数的数组,使用循环为其赋值为1到10,然后逆序输出数组中的所有元素。
解答:
#include <stdio.h>
int main() {
int arr[10]; // 声明一个有10个元素的整型数组
// 使用 for 循环为数组赋值
for (int i = 0; i < 10; i++) {
arr[i] = i + 1; // 数组下标从0开始,arr[0] 存 1
}
printf("数组逆序输出: ");
// 使用另一个 for 循环逆序输出
for (int i = 9; i >= 0; i--) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
解题思路与知识点:
- 数组声明与下标:
int arr[10];创建了一个可以存放10个int的数组,第一个元素是arr[0],最后一个元素是arr[9]。初学者最容易犯的错误是下标越界,例如访问arr[10]。 - 循环与数组:
for (int i = 0; i < 10; i++)是遍历数组的经典模式。i既作为循环计数器,也作为数组下标。 - 逆序遍历:第二个
for循环的初始化部分int i = 9;,条件i >= 0;,以及递减i--,共同实现了从数组末尾到开头的遍历。
第六章:函数
核心知识点
- 函数定义:
返回类型 函数名(参数列表) { 函数体 }。 - 函数声明:
返回类型 函数名(参数列表);(告诉编译器这个函数存在)。 - 函数调用:
函数名(实际参数);。 - 参数传递:C语言中,函数参数传递是值传递,函数内部无法修改主调函数中普通变量的值,但可以修改指针指向的值。
典型习题
习题7: 编写一个函数 is_prime(int num),用于判断一个数是否为素数(质数),然后在 main 函数中调用该函数,判断用户输入的数字。
解答:
#include <stdio.h>
#include <stdbool.h> // 为了使用 bool, true, false
// 函数声明
bool is_prime(int num);
int main() {
int number;
printf("请输入一个正整数: ");
scanf("%d", &number);
if (is_prime(number)) {
printf("%d 是一个素数,\n", number);
} else {
printf("%d 不是一个素数,\n", number);
}
return 0;
}
// 函数定义:判断 num 是否为素数
bool is_prime(int num) {
// 素数必须大于1
if (num <= 1) {
return false;
}
// 从 2 到 num-1 遍历,看是否有能整除 num 的数
for (int i = 2; i < num; i++) {
if (num % i == 0) {
return false; // 如果找到,说明不是素数
}
}
// 如果循环结束都没找到,说明是素数
return true;
}
解题思路与知识点:
- 函数声明与定义分离:
bool is_prime(int num);在main函数之前声明,这样main函数在调用它时,编译器已经知道它的存在。 #include <stdbool.h>:这是C99标准引入的头文件,提供了布尔类型bool以及其值true和false,使代码更易读。- 函数实现逻辑:
- 小于等于1的数不是素数。
- 从2开始到
num-1,num能被其中任何一个数整除,它就不是素数,函数立即返回false。 - 如果循环正常结束,说明没有找到能整除它的数,它就是素数,返回
true。
main函数中的调用:if (is_prime(number)),函数调用可以放在任何表达式可以出现的地方,因为它返回一个bool值。
第七章:指针
核心知识点
- 指针变量:存放内存地址的变量。
&(取地址运算符):获取变量的内存地址。- *`` (解引用/间接寻址运算符)**:获取指针所指向地址处的值。
- 指针与数组:数组名在表达式中会“退化”为其首元素的地址,因此指针和数组关系密切。
典型习题
习题8: 使用指针,实现两个整数的值交换。
解答:
#include <stdio.h>
// 函数声明,参数是指针
void swap(int *ptr1, int *ptr2);
int main() {
int a = 10, 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 指向的值(a的值)存入 temp
*ptr1 = *ptr2; // 将 ptr2 指向的值(b的值)赋给 ptr1 指向的内存(a的内存)
*ptr2 = temp; // 将 temp 中原来的值(a的值)赋给 ptr2 指向的内存(b的内存)
}
解题思路与知识点:
- 为什么必须用指针? 因为C语言是值传递。
swap函数的参数是int a, int b,那么函数内部交换的是a和b的副本,对main函数中的原始变量没有影响。 - 传递地址:通过传递
&a和&b(a和b的地址),swap函数中的指针ptr1和ptr2就能够直接访问到main函数中a和b所在的内存空间。 - 解引用操作:
*ptr1的意思是“获取ptr1这个地址上存放的值”,通过修改这个值,就等于修改了main函数中a的值。
第八章:结构体
核心知识点
- 结构体:将不同类型的数据组合成一个有机的整体。
struct是关键字。 - 结构体成员访问:使用点运算符 。
student.name。 - 结构体指针访问成员:使用箭头运算符
->。p_student->name。
典型习题
习题9: 定义一个结构体 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 = "Zhang San";,因为 name 是字符数组
// 需要使用 strcpy 函数来复制字符串
strcpy(s1.name, "Zhang San");
s1.score = 95.5f;
// 4. 输出结构体成员的值
printf("学号: %d\n", s1.id);
printf("姓名: %s\n", s1.name);
printf("成绩: %.1f\n", s1.score); // %.1f 表示输出一位小数
return 0;
}
解题思路与知识点:
struct关键字:定义结构体类型时必须使用struct Student。- 字符串赋值:C语言中没有字符串类型,字符串是用字符数组表示的,不能直接用赋值符 给字符数组赋值,必须使用
strcpy(string copy) 函数从字符串常量复制到字符数组中。 - 成员访问:使用 运算符来访问结构体变量的成员。
s1.id、s1.name、s1.score。
总结与学习建议
- 多动手敲代码:理论看懂了不代表会写,一定要亲自把每个例子敲一遍,修改参数,观察结果变化。
- 调试是关键:学会使用调试器(如 GDB, VS Code 的调试功能)或简单的
printf语句来跟踪程序执行过程和变量值的变化,这是解决 Bug 的最有效方法。 - 理解底层原理:特别是指针、内存、函数调用栈等概念,理解了它们,C语言的水平会有质的飞跃。
- 多读优秀代码:阅读一些开源的、高质量的C语言项目,学习别人的编程风格和解决问题的思路。
- 循序渐进:不要急于求成,把基础(数据类型、流程控制、函数)打牢,再学习指针、结构体等高级内容。
希望这份详细的习题解答对你有帮助!祝你学习顺利!
