
C语言习题集及答案:从基础语法到函数与数组练习
这组练习适合已经安装 C 编译器、正在学习 C 语言基础语法的读者。题目从输入输出和表达式开始,逐步练习条件判断、循环、函数与数组。每题都附有可单独编译运行的示例,并说明适用场景、边界条件和容易犯的错误。
示例采用 C11,可用 GCC 编译。将代码保存为 main.c,在终端执行:
gcc -std=c11 -Wall -Wextra -pedantic main.c -o main
./main
-Wall、-Wextra 和 -pedantic 用于启用常见警告与标准检查。编译器有警告时,应先理解原因,不要只为让程序通过而忽略它们。下面每个代码块都是独立程序;分别复制到文件中编译,不要把多个示例的 main 函数直接拼在一起。
一、输入输出与整数运算
练习 1:读取两个整数并计算和
题目:输入两个整数,输出它们的和。这个练习用于熟悉变量、格式说明符和标准输入输出。
#include <stdio.h>
int main(void)
{
int a;
int b;
printf("请输入两个整数:");
if (scanf("%d %d", &a, &b) != 2) {
fprintf(stderr, "输入无效。\n");
return 1;
}
printf("和为:%d\n", a + b);
return 0;
}
答案说明:scanf 返回成功赋值的项目数。这里要求读到两个整数,因此应检查返回值是否为 2。如果输入字母、文件提前结束,或只输入一个整数,程序会报告输入无效,而不是继续使用未正确读取的变量。
边界与错误:int 能表示的范围由实现决定,通常可通过 INT_MIN 和 INT_MAX 查看。两个合法的 int 相加仍可能超出 int 范围;有符号整数溢出在 C 中属于未定义行为,不能假定结果会自动回绕。此示例面向一般的小整数练习,不适合处理任意大数。需要可靠处理大范围数据时,应在运算前检查范围,或设计能检测溢出的计算方式。
练习 2:计算矩形面积
题目:输入矩形的长和宽,输出面积。这里用 double 演示浮点输入与计算。
#include <stdio.h>
int main(void)
{
double length;
double width;
printf("请输入长和宽:");
if (scanf("%lf %lf", &length, &width) != 2) {
fprintf(stderr, "输入必须是两个数字。\n");
return 1;
}
if (length < 0.0 || width < 0.0) {
fprintf(stderr, "长和宽不能为负数。\n");
return 1;
}
printf("面积为:%.2f\n", length * width);
return 0;
}
答案说明:在 scanf 中读取 double 要使用 %lf;在 printf 中输出 double 通常使用 %f。%.2f 表示显示两位小数,不代表内部结果只保留两位。
边界与错误:题目规定长、宽不能为负数,但是否允许零要按实际需求决定。浮点数只能近似表示许多十进制小数,不能把输出的小数位数误认为计算精度。若输入可能来自不可信来源,还需明确允许的数值范围;对非常大的浮点输入,仅检查是否成功读入并不足以保证结果符合业务要求。
二、条件判断与循环
练习 3:判断奇数或偶数
题目:读取整数,判断它是奇数还是偶数。该题适合练习取余运算和 if 分支。
#include <stdio.h>
int main(void)
{
int number;
printf("请输入一个整数:");
if (scanf("%d", &number) != 1) {
fprintf(stderr, "输入无效。\n");
return 1;
}
if (number % 2 == 0) {
printf("%d 是偶数。\n", number);
} else {
printf("%d 是奇数。\n", number);
}
return 0;
}
答案说明:整数除以 2 的余数为 0 时是偶数,否则是奇数。这个判断对负数也成立,例如 -3 % 2 不等于 0,所以仍会被判断为奇数。
常见错误:不要把比较运算符 == 写成赋值运算符 =。if (number = 2) 会给变量赋值,而不是判断它是否等于 2;编译器通常会对此发出警告。
练习 4:计算 1 到 n 的整数和
题目:输入非负整数 n,使用循环计算从 1 到 n 的和;当 n 为 0 时,结果为 0。
#include <stdio.h>
int main(void)
{
int n;
int i;
long long sum = 0;
printf("请输入非负整数 n:");
if (scanf("%d", &n) != 1 || n < 0) {
fprintf(stderr, "请输入有效的非负整数。\n");
return 1;
}
for (i = 1; i <= n; ++i) {
sum += i;
}
printf("1 到 %d 的和为:%lld\n", n, sum);
return 0;
}
答案说明:循环变量从 1 开始,每次递增,直到大于 n。使用 long long 作为累加器,比常见的 32 位 int 能容纳更大的结果,但仍有上限,并不意味着可以累加任意大的数。
边界与常见错误:当输入范围很大时,循环耗时和累加结果都可能超出预期。此处还需要注意,当 n 接近 INT_MAX 时,最后一次循环后的 ++i 可能使 int 溢出。实际程序应给输入设置合理上限,或采用不会发生该问题的循环变量类型与循环条件。对初学练习而言,不要用极大输入测试这个版本。
练习 5:求两个整数中的较大值
题目:输入两个整数,输出较大的一个;相等时输出相同的值。
#include <stdio.h>
int main(void)
{
int a;
int b;
int maximum;
printf("请输入两个整数:");
if (scanf("%d %d", &a, &b) != 2) {
fprintf(stderr, "输入无效。\n");
return 1;
}
if (a > b) {
maximum = a;
} else {
maximum = b;
}
printf("较大值为:%d\n", maximum);
return 0;
}
答案说明:当 a 和 b 相等时,程序会进入 else 分支,此时 b 与最大值相等,因此结果仍正确。也可以使用条件运算符 a > b ? a : b,但初学时先熟悉完整的分支结构更容易理解。
三、函数:拆分计算与复用逻辑
练习 6:用函数判断素数
题目:编写函数判断一个整数是否为素数。小于 2 的整数不是素数。
#include <stdio.h>
int is_prime(int n)
{
int divisor;
if (n < 2) {
return 0;
}
for (divisor = 2; divisor <= n / divisor; ++divisor) {
if (n % divisor == 0) {
return 0;
}
}
return 1;
}
int main(void)
{
int n;
printf("请输入一个整数:");
if (scanf("%d", &n) != 1) {
fprintf(stderr, "输入无效。\n");
return 1;
}
if (is_prime(n)) {
printf("%d 是素数。\n", n);
} else {
printf("%d 不是素数。\n", n);
}
return 0;
}
答案说明:若一个合数存在大于 1 的因数,那么至少有一个因数不大于它的平方根。因此只需从 2 开始试除到平方根附近。条件写作 divisor <= n / divisor,避免直接计算 divisor * divisor 时发生整数乘法溢出。
适用场景与边界:该方法适合演示和判断单个、范围不大的整数。它的试除次数仍会随输入增大,不适合高性能的大整数素性测试。函数返回 1 表示真、0 表示假,是 C 程序常见写法;调用者应按约定理解返回值。
练习 7:用函数计算阶乘
题目:使用函数计算非负整数 n 的阶乘,约定 0! = 1。
#include <stdio.h>
int main(void)
{
int n;
int i;
unsigned long long result = 1;
printf("请输入 0 到 20 之间的整数:");
if (scanf("%d", &n) != 1 || n < 0 || n > 20) {
fprintf(stderr, "输入必须在 0 到 20 之间。\n");
return 1;
}
for (i = 2; i <= n; ++i) {
result *= (unsigned long long)i;
}
printf("%d! = %llu\n", n, result);
return 0;
}
答案说明:循环从 2 开始累乘,输入为 0 或 1 时,初始值 1 就是正确结果。这里为突出边界检查,将计算放在主函数中;练习时可以进一步将累乘部分封装成函数。
边界与常见错误:阶乘增长非常快,固定宽度整数很快就会溢出。对于常见的 64 位 unsigned long long,20! 可表示,而 21! 超出其范围;C 标准保证该类型至少能表示到 18446744073709551615,但准确位宽仍由实现决定。无符号整数溢出按模数回绕,不会自动给出数学上的正确阶乘。因此程序显式限制输入范围,不能删除检查后就认为结果仍可靠。
四、数组:存储一组同类型数据
练习 8:输入 5 个数并计算平均值
题目:将 5 个整数存入数组,计算总和与平均值。
#include <stdio.h>
int main(void)
{
int values[5];
int i;
long long sum = 0;
printf("请输入 5 个整数:\n");
for (i = 0; i < 5; ++i) {
if (scanf("%d", &values[i]) != 1) {
fprintf(stderr, "输入无效。\n");
return 1;
}
sum += values[i];
}
printf("总和:%lld\n", sum);
printf("平均值:%.2f\n", (double)sum / 5);
return 0;
}
答案说明:values[5] 有 5 个元素,下标依次是 0、1、2、3、4。因此循环条件应为 i < 5,而不是 i <= 5。平均值计算前将总和转换为 double,否则整数除法会丢弃小数部分。
边界与安全:数组本身不会自动检查下标。访问 values[5] 属于越界访问,行为未定义,可能导致错误结果、崩溃或其他问题。C 的数组也不会在运行时自动记录长度;使用数组的函数通常需要同时接收元素个数。此例固定读取 5 个数,因此容量是明确的。
练习 9:遍历数组并找最大值
题目:读取 6 个整数,找出其中的最大值。
#include <stdio.h>
int main(void)
{
int values[6];
int i;
int maximum;
printf("请输入 6 个整数:\n");
for (i = 0; i < 6; ++i) {
if (scanf("%d", &values[i]) != 1) {
fprintf(stderr, "输入无效。\n");
return 1;
}
}
maximum = values[0];
for (i = 1; i < 6; ++i) {
if (values[i] > maximum) {
maximum = values[i];
}
}
printf("最大值为:%d\n", maximum);
return 0;
}
答案说明:先用第一个元素初始化最大值,再从第二个元素开始比较。这种做法能正确处理全为负数的数组。常见错误是把最大值初始化为 0;若输入全部为负数,程序就会错误地输出 0。这里数组长度固定为 6,因此访问 values[0] 有效;如果数组可能为空,就不能这样初始化,必须先检查长度。
练习 10:用函数求数组元素的和
题目:编写函数接收一个整数数组和元素个数,返回数组元素之和。
#include <stdio.h>
long long sum_array(const int values[], int count)
{
int i;
long long sum = 0;
for (i = 0; i < count; ++i) {
sum += values[i];
}
return sum;
}
int main(void)
{
int values[] = {3, 7, -2, 10};
int count = (int)(sizeof values / sizeof values[0]);
printf("数组和为:%lld\n", sum_array(values, count));
return 0;
}
答案说明:函数参数中的 const int values[] 表示函数不应通过该参数修改数组元素;在函数形参中,数组形式会调整为指针形式,因此函数本身无法仅凭 values 得知数组长度,必须另传 count。
在定义数组的作用域内,sizeof values / sizeof values[0] 可以计算元素个数。不要把同一写法直接搬进 sum_array 里:在那里 values 是指针,sizeof values 得到的是指针大小,而不是原数组占用的大小。
边界与调用约定:本函数假设 count 非负,并且当 count 大于 0 时,values 指向至少包含 count 个元素的有效数组。调用者必须保证这些条件,C 不会替函数自动验证指针对应的内存长度。若要支持零长度数组,可在接口约定中明确:count == 0 时不读取数组元素;如果外部输入能决定长度,还应检查长度非负且不超过实际容量。返回类型虽比 int 更宽,累加足够多的极大整数仍可能超出范围。
五、怎样把练习代码写得更可靠
1. 为输入设定范围,而不只检查格式
scanf 的返回值适合检查成功转换了多少项,但它不等于完整的输入校验方案。例如,整数转换超出目标类型可表示范围时,不能依赖程序得到一个可移植的“截断值”;面对不可信或复杂输入,较可靠的做法是用 fgets 读取一行,再用 strtol 等函数解析,并检查是否有数字、是否溢出、尾部是否只有允许的空白,以及结果是否落在业务范围内。
数组长度、循环次数和文件大小尤其需要上限。不要根据未经验证的输入直接创建超大数组、申请超大内存或执行可能很久的循环。输入限制应来自实际需求,并在使用前显式检查。
2. 分清表达式类型与格式说明符
格式说明符与参数类型不匹配会造成未定义行为,不能靠“看起来能输出”来确认正确。常见对应关系包括:int 在 printf 中使用 %d;long long 使用 %lld;unsigned long long 使用 %llu;double 在 scanf 中使用 %lf,在 printf 中使用 %f。编译时打开格式检查相关警告,有助于发现不匹配。
3. 理解数组、字符串与缓冲区边界
整数数组和字符数组都需要在有效下标范围内访问。C 风格字符串还要求末尾有空字符 '\0';存储长度为 n 的字符串至少需要 n+1 个字符的空间。不要使用不限制写入长度的字符串输入方式。处理一行文本时,fgets 可以指定缓冲区容量,但仍要考虑输入是否完整读入、末尾换行如何处理,以及如何检测过长输入。
4. 区分“本地练习可用”与“生产环境足够安全”
本文示例用于学习语法和基本防错,不是完整的安全输入框架。它们没有处理所有可能的数值范围、超长输入、并发、文件权限或业务规则。若程序接收网络数据、文件内容或用户提交的数据,应将这些数据视为不可信:验证类型、长度和范围;在使用数组或指针前确认容量与有效性;检查运算溢出和资源消耗;对错误输入明确失败,而不是继续使用不完整状态。
安全编程不能只靠某个单独的函数或编译选项保证。可以结合编译器警告、地址与未定义行为检测工具进行测试,例如在支持的 GCC 或 Clang 环境中尝试 -fsanitize=address,undefined。这些工具能帮助发现部分问题,但不能证明程序没有缺陷,也不能替代边界检查和正确的接口设计。
六、练习建议
完成题目后,可以逐项改变条件进行自测:输入负数、零、边界附近的值、非数字内容和不完整输入;把数组内容全部设为负数;尝试空数组接口的约定;将计算逻辑拆成函数,并为函数写清楚参数范围和返回值含义。遇到结果异常时,先检查循环边界、变量类型、格式说明符和输入返回值,再检查算法本身。
掌握这些练习后,可以继续学习字符数组与字符串处理、指针、结构体、动态内存、文件读写和排序查找。后续内容仍应沿用同一原则:明确数据范围,验证输入,保证数组与指针访问在有效边界内,并确认每种整数运算都能由所选类型表示。









