C程序设计语言习题答案,如何高效解题?

99ANYc3cd6
预计阅读时长 40 分钟
位置: 首页 C语言 正文

c 程序设计语言习题答案
(图片来源网络,侵删)
  1. 学习目的:提供答案是为了帮助您检查自己的思路、理解难点,并作为学习的辅助工具。切勿直接抄袭,这会严重阻碍您对C语言核心概念的理解和编程能力的提升。
  2. 版本问题:K&R C有两个主要版本:经典的第一版(1978)和更新的第二版(1988)。绝大多数现代课程和开发环境都基于第二版,以下答案主要针对第二版
  3. 代码风格:K&R以其简洁、优雅的代码风格著称,这里的答案也尽量遵循其风格,但为了清晰性和可读性,在某些地方可能会加入注释或稍作调整。
  4. 并非唯一解:编程题通常没有唯一的“标准答案”,以下提供的是一种或几种常见且正确的解法,您可能想探索其他实现方式。

第1章:教程引言

这一章的习题相对简单,旨在帮助您熟悉C语言的基本语法和结构。

习题 1-1printf 函数的调用中,错误地将第二个参数 c 写成了 x,观察会发生什么。

答案

#include <stdio.h>
int main()
{
    printf("hello, world\n"); // 第一版代码
    printf("hello, world\n"); // 第二版代码
    printf("hello, world\n"); // 第三版代码
    printf("hello, world\n"); // 第四版代码
    printf("hello, world\n"); // 第五版代码
    printf("hello, world\n"); // 第六版代码
    printf("hello, world\n"); // 第七版代码
    printf("hello, world\n"); // 第八版代码
    printf("hello, world\n"); // 第九版代码
    printf("hello, world\n"); // 第十版代码
    int x = 10;
    printf("x = %d\n", x); // 正确的用法
    // printf("x = %d\n", c); // 错误的用法,c 未定义,编译器会报错
    return 0;
}

解析

  • c 是一个已经定义的变量,但类型不匹配(cchar,而 %d 需要一个 int),可能会导致输出错误或警告。
  • c 根本没有定义,编译器(如 gcc)会报错,提示 "undeclared variable 'c'"(未声明的变量 'c'),这是最常见的错误。
  • 编译器会检测到这个错误,并阻止程序生成可执行文件。

习题 1-2做实验,看看当 printf 函数的参数多于格式字符串中指定的数量时会发生什么。

答案

c 程序设计语言习题答案
(图片来源网络,侵删)
#include <stdio.h>
int main()
{
    printf("Hello, world\n"); // 0个参数
    printf("Value: %d\n", 123); // 1个参数
    printf("Values: %d and %f\n", 123, 456.789); // 2个参数
    printf("Extra arguments: %d, %f, %s\n", 1, 2.0, "three", 4); // 4个参数,格式字符串只要求3个
    return 0;
}

解析

  • 行为是未定义的,这意味着程序可能会崩溃,或者输出一些看起来毫无意义的垃圾数据。
  • 原因printf 函数根据格式字符串中的格式说明符(如 %d, %f)从栈上按顺序读取参数,如果参数过多,printf 会读取到不属于它的数据,导致不可预测的行为,这是一个典型的缓冲区溢出风险,非常危险。
  • 永远要确保 printf 的参数数量和类型与格式字符串完全匹配。

习题 1-3修改温度转换程序,以打印出逆序的表格(即从300度到0度)。

答案

#include <stdio.h>
int main()
{
    float fahr, celsius;
    int lower, upper, step;
    lower = 0;   // 下限
    upper = 300; // 上限
    step = 20;   // 步长
    printf("Fahrenheit to Celsius (Reverse)\n");
    printf("--------------------------------\n");
    printf("Fahr\tCelsius\n");
    printf("--------------------------------\n");
    // 从300度开始,每次减20,直到小于0
    for (fahr = upper; fahr >= lower; fahr -= step) {
        celsius = (5.0 / 9.0) * (fahr - 32.0);
        printf("%3.0f\t%6.1f\n", fahr, celsius);
    }
    return 0;
}

解析

  • 关键修改在于 for 循环。
  • 初始化fahr = upper (从300开始)。
  • 条件fahr >= lower (只要fahr大于等于0就继续)。
  • 步进fahr -= step (每次减20)。
  • 这样循环就会从高到低依次打印。

习题 1-4编写一个程序打印摄氏温度到华氏温度的转换表。

答案

c 程序设计语言习题答案
(图片来源网络,侵删)
#include <stdio.h>
int main()
{
    float fahr, celsius;
    int lower, upper, step;
    lower = 0;   // 摄氏温度下限
    upper = 300; // 摄氏温度上限
    step = 20;   // 步长
    printf("Celsius to Fahrenheit\n");
    printf("---------------------\n");
    printf("Celsius\tFahrenheit\n");
    printf("---------------------\n");
    // 从0度开始,每次加20,直到300
    for (celsius = lower; celsius <= upper; celsius += step) {
        fahr = (9.0 / 5.0) * celsius + 32.0;
        printf("%3.0f\t%6.1f\n", celsius, fahr);
    }
    return 0;
}

解析

  • 这与原题(华氏转摄氏)的逻辑完全相同,只是转换公式和打印的标签反了过来。
  • 转换公式:F = (9/5)C + 32

习题 1-5修改温度转换程序,以打印出华氏温度对应的摄氏温度的浮点值,并保持对齐美观。

答案

#include <stdio.h>
int main()
{
    float fahr, celsius;
    int lower, upper, step;
    lower = 0;
    upper = 300;
    step = 20;
    printf("Fahrenheit\tCelsius\n");
    printf("------------------------\n");
    for (fahr = lower; fahr <= upper; fahr += step) {
        celsius = (5.0 / 9.0) * (fahr - 32.0);
        // %3.0f: 整数部分至少占3位,无小数
        // %6.1f: 总宽度至少6位,小数点后1位
        printf("%3.0f\t\t%6.1f\n", fahr, celsius);
    }
    return 0;
}

解析

  • 关键在于 printf 的格式化字符串。
  • %3.0f:打印 fahr,它是一个浮点数,但我们只关心整数部分。.0 表示不显示小数部分。3 表示总共至少占3个字符宽度,这样能保证数字对齐。
  • %6.1f:打印 celsius,总共至少占6个字符宽度,小数点后保留1位,这样也能保证所有小数点对齐。
  • \t (制表符) 也可以用于简单的对齐,但当数字位数变化时(如从100到200),\t 的对齐效果可能不如指定宽度来得精确。

习题 1-6验证 getchar() != EOF 的值是0还是1。

答案

#include <stdio.h>
int main()
{
    int c;
    printf("Enter some characters, then press Ctrl+D (Unix) or Ctrl+Z (Windows) to signal EOF.\n");
    c = getchar() != EOF; // 将比较的结果(0或1)赋值给c
    printf("The value of (getchar() != EOF) is: %d\n", c);
    return 0;
}

解析

  • 是一个关系运算符,它的结果是一个整型:如果表达式为真,结果为 1;如果为假,结果为 0
  • EOF (End of File) 是一个在 stdio.h 中定义的宏,通常值为 -1
  • 当用户输入一个可打印字符(如 'A')并回车时,getchar() 读取该字符(其ASCII码不为-1),getchar() != EOF 的结果为 1
  • 当用户输入EOF信号时,getchar() 返回 EOF (即-1),getchar() != EOF 的结果为 0
  • 表达式的值是 1(真)或 0(假),而不是 getchar() 返回的那个字符本身。

习题 1-7编写一个打印 EOF 值的程序。

答案

#include <stdio.h>
int main()
{
    printf("The value of EOF is: %d\n", EOF);
    return 0;
}

解析

  • 这是一个非常直接的题目。EOF 是一个宏,我们只需要用 printf 打印它的整数值即可。
  • 在大多数系统上,输出会是 -1

习题 1-8编写一个统计空格、制表符和换行符数量的程序。

答案

#include <stdio.h>
int main()
{
    int c, nb, nt, nl;
    nb = 0; // 空格数
    nt = 0; // 制表符数
    nl = 0; // 换行符数
    printf("Enter text (Ctrl+D or Ctrl+Z to end):\n");
    while ((c = getchar()) != EOF) {
        if (c == ' ') {
            ++nb;
        } else if (c == '\t') {
            ++nt;
        } else if (c == '\n') {
            ++nl;
        }
    }
    printf("\n--- Summary ---\n");
    printf("Blanks: %d\n", nb);
    printf("Tabs:   %d\n", nt);
    printf("Newlines: %d\n", nl);
    return 0;
}

解析

  • 初始化三个计数器 nb, nt, nl
  • 使用 while 循环持续读取字符,直到遇到 EOF
  • 在循环内部,使用 if-else if 结构判断字符类型,并递增相应的计数器。
  • 循环结束后,打印出最终的统计结果。

习题 1-9编写一个将输入复制到输出的程序,并将连续的多个空格替换为单个空格。

答案

#include <stdio.h>
int main()
{
    int c, prev_was_space;
    prev_was_space = 0; // 标记前一个字符是否是空格
    while ((c = getchar()) != EOF) {
        if (c != ' ') {
            // 如果当前字符不是空格,直接输出
            putchar(c);
            prev_was_space = 0; // 重置标记
        } else {
            // 如果当前字符是空格
            if (!prev_was_space) {
                // 如果前一个字符不是空格,才输出这个空格
                putchar(c);
            }
            // 无论前一个字符是不是空格,都标记当前为空格
            prev_was_space = 1;
        }
    }
    return 0;
}

解析

  • 关键在于需要一个状态标记 prev_was_space
  • 它记录上一个被处理的字符是否是空格。
  • 当遇到一个空格时,检查 prev_was_space
    • 如果为 0(上一个不是空格),说明这是第一个空格,可以输出,并将标记设为 1
    • 如果为 1(上一个也是空格),说明这是连续的空格,不输出,标记保持为 1
  • 当遇到非空格字符时,直接输出,并将标记重置为 0

习题 1-10编写一个将制表符替换为 \t,将回退符替换为 \b,将反斜杠替换为 \\ 的程序。

答案

#include <stdio.h>
int main()
{
    int c;
    while ((c = getchar()) != EOF) {
        if (c == '\t') {
            putchar('\\');
            putchar('t');
        } else if (c == '\b') {
            putchar('\\');
            putchar('b');
        } else if (c == '\\') {
            putchar('\\');
            putchar('\\');
        } else {
            putchar(c);
        }
    }
    return 0;
}

解析

  • 使用 if-else if 结构来识别这三个特殊字符。
  • 对于每个特殊字符,我们输出一个反斜杠 \,然后输出它的替代字符(t, b, \)。
  • 对于所有其他字符,直接原样输出 putchar(c)
  • 注意:反斜杠 \ 本身是一个转义字符,所以在字符串中要写成 \\ 才能表示一个字面的反斜杠。

习题 1-11如何测试 word 统计程序?设计一些输入,使其能覆盖各种边界情况。

答案: 这是一个设计题,没有标准代码答案,关键在于设计测试用例

测试用例设计思路

  1. 空输入:直接按 EOF 信号。

    • 预期:单词数、行数、字符数都应为0。
  2. 只有空白字符的输入:输入多个空格、制表符、换行符。

    • 输入示例" \t\t\n\n \t \n"
    • 预期:单词数应为0,行数和字符数应正确统计。
  3. 单个单词:输入一个单词,以 EOF

    • 输入示例"hello" (然后按EOF)
    • 预期:单词数=1,行数=0或1(取决于实现),字符数=5。
  4. 单个单词后跟换行符:输入一个单词和一个换行符。

    • 输入示例"world\n" (然后按EOF)
    • 预期:单词数=1,行数=1,字符数=6。
  5. 多个单词在同一行:输入多个由空格分隔的单词。

    • 输入示例"this is a test"
    • 预期:单词数=4,行数=0或1,字符数=14。
  6. 单词分布在多行:输入多个单词,并用换行符分隔。

    • 输入示例"first\nsecond third\n"
    • 预期:单词数=3,行数=2或3(取决于末尾换行符是否计为新行),字符数=18。
  7. 连续的非字母数字字符:输入标点符号。

    • 输入示例
    • 预期:单词数=0(取决于程序如何定义单词,K&R的定义是字母、数字、下划线)。
  8. 混合输入:包含单词、空格、制表符、换行符的复杂情况。

    • 输入示例" one\ttwo\n three four\n\n"
    • 预期:单词数=4,行数=3,字符数=24。
  9. 行首和行尾的空格

    • 输入示例" start end \n"
    • 预期:单词数=2。
  10. 只有一个换行符的行

    • 输入示例"\n"
    • 预期:单词数=0,行数=1。

习题 1-12编写一个程序,以每行一个单词的形式打印其输入。

答案

#include <stdio.h>
#define IN  1  // 在单词内部
#define OUT 0 // 在单词外部
int main()
{
    int c, state;
    state = OUT;
    while ((c = getchar()) != EOF) {
        if (c == ' ' || c == '\n' || c == '\t') {
            // 如果遇到空白字符,并且之前在单词内部,则打印换行符
            if (state == IN) {
                putchar('\n');
            }
            state = OUT;
        } else {
            // 如果不是空白字符,并且之前在单词外部,说明是新单词的开始
            if (state == OUT) {
                state = IN;
            }
            putchar(c);
        }
    }
    return 0;
}

解析

  • 引入两个状态常量 INOUT,使用一个状态变量 state 来跟踪当前是否在单词内部。
  • 当遇到一个空白字符(空格、制表、换行)时:
    • 检查 statestateIN,说明我们刚刚结束了一个单词,此时打印一个换行符。
    • 无论是否打印换行,都将 state 设置为 OUT
  • 当遇到一个非空白字符时:
    • 检查 statestateOUT,说明这是一个新单词的开始,此时将 state 设置为 IN
    • 打印这个字符。
  • 这个逻辑可以正确处理连续的空白字符,并在每个单词结束后打印换行符。

后续章节习题答案

由于篇幅限制,这里无法一次性列出所有章节的详细答案,但您可以参考以下模式,我将按照同样的方式为您提供后续章节的答案和解析。

第2章(类型、运算符和表达式)的习题 2-3: 编写一个函数 htoi(s),它将一个十六进制数字的字符串(包括可选的前缀 0x0X)转换为等价的整型值。 答案与解析

#include <stdio.h>
#include <ctype.h> // 用于 isxdigit
int htoi(char s[]);
int main()
{
    printf("0x1F -> %d\n", htoi("0x1F"));   // 31
    printf("0X1a -> %d\n", htoi("0X1a"));   // 26
    printf("FF    -> %d\n", htoi("FF"));    // 255
    printf("123   -> %d\n", htoi("123"));   // 291 (1*256 + 2*16 + 3)
    printf("0     -> %d\n", htoi("0"));     // 0
    return 0;
}
int htoi(char s[])
{
    int i, n, digit;
    i = 0;
    // 检查并跳过可选的 0x 或 0X 前缀
    if (s[i] == '0' && (s[i+1] == 'x' || s[i+1] == 'X')) {
        i += 2;
    }
    n = 0;
    // 循环处理每个十六进制字符
    while (isxdigit(s[i])) {
        digit = s[i];
        if (digit >= '0' && digit <= '9') {
            digit = digit - '0';
        } else if (digit >= 'a' && digit <= 'f') {
            digit = digit - 'a' + 10;
        } else if (digit >= 'A' && digit <= 'F') {
            digit = digit - 'A' + 10;
        }
        n = n * 16 + digit;
        i++;
    }
    return n;
}

解析

  1. 跳过前缀:首先检查字符串是否以 0x0X 开头,如果是,则跳过这两个字符。
  2. 核心转换逻辑:使用一个 while 循环来处理字符串中的每一个字符,直到遇到非十六进制字符为止。
  3. 字符转数字:对于每个字符,需要将其转换为对应的整数值(0-15)。
    • 如果是 '0''9',用字符的ASCII码减去 '0' 的ASCII码即可得到数字。
    • 如果是 'a''f',减去 'a' 的ASCII码,然后加10。
    • 如果是 'A''F',减去 'A' 的ASCII码,然后加10。
  4. 累加计算:每次转换得到一个数字后,更新结果 nn = n * 16 + digit 是将当前结果左移(乘以16)并加上新的数字位,这是进制转换的标准方法。
  5. 库函数:使用了 ctype.h 中的 isxdigit() 函数来判断一个字符是否是有效的十六进制字符,这是一种更健壮的方法。
-- 展开阅读全文 --
头像
dede如何批量修改或增加文章的点击数?
« 上一篇 今天
1-1 2 1 3 c语言是什么?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码