C语言习题集及答案:从基础语法到函数与数组练习

运营教程11小时前发布 jdkjadmin
0 00
C语言习题集及答案:从基础语法到函数与数组练习

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。这些工具能帮助发现部分问题,但不能证明程序没有缺陷,也不能替代边界检查和正确的接口设计。

六、练习建议

完成题目后,可以逐项改变条件进行自测:输入负数、零、边界附近的值、非数字内容和不完整输入;把数组内容全部设为负数;尝试空数组接口的约定;将计算逻辑拆成函数,并为函数写清楚参数范围和返回值含义。遇到结果异常时,先检查循环边界、变量类型、格式说明符和输入返回值,再检查算法本身。

掌握这些练习后,可以继续学习字符数组与字符串处理、指针、结构体、动态内存、文件读写和排序查找。后续内容仍应沿用同一原则:明确数据范围,验证输入,保证数组与指针访问在有效边界内,并确认每种整数运算都能由所选类型表示。

© 版权声明

相关文章