首页 > 代码库 > 《C与指针》第十四章练习

《C与指针》第十四章练习

本章问题

1.预处理器定义了5个符号,给出了进行编译的文件名、文件行的当前行号,当前日期和时间以及编译器是否为ANSI C编译器。为每个符号举出一种可能的用途。

answer:在打印错误信息时,文件名和行号可能是很有用的,尤其是在调试的早期阶段。事实上,assert宏使用它们来实现自己的功能。_DATA_和_TIME_可以把版本的信息编译到程序中。最后,_STDC_可以用于条件编译中,用于在必须由两种类型的编译器进行编译的源代码中选择ANSI C和前ANSI的结构。

 

2.说出两个使用#define定义的名字代替字面值常量的优点。

answer:First,a well chosen name gives the reader some idea of the meaning of a quantity,whereas a literal constant conveys only its value,Second,literal constants that are used more than once are easier to change if they are defined in a single place.

(首先,一个合适的名字能给读者更多关于数量的信息,然而一个字面值常量仅仅传达它的值,其次,定义在一个地方的字面值常量在多次出现时更容易更改)

 

3.编写一个用于调试的宏,打印出任意的表达式。它被调用时应该接受两个参数。第一个是printf格式码,第二个是需要打印的表达式。

answer:

#define PRINT(format, value) \
    printf("file: %s,line: %d, %s = "format"\n",           __FILE__, __LINE__,#value, value);
//调用方式
DEBUG_PRINT("%d", x * y + 3);

//输出
file: main.c, line: 25: x * y + 3 = -4

 

4.下面的程序将打印出什么?在展开#define内容时必须非常小心!

#define MAX(a,b) (a)>(b)?(a):(b)
#define SQUARE(x) x*x
#define DOUBLE(x) x+x

main()
{
    int x,y,z;
    y = 2, z = 3;
    x = MAX(y,z);
    printf("%d %d %d\n",x, y, z);

    y = 2, z = 3;
    x = MAX(++y, ++z);
    printf("%d %d %d\n",x, y, z);

    x = 2;
    y = SQUARE(x);
    z = SQUARE(x+6);
    printf("%d %d %d\n",x, y, z);

    x = 2;
    y = 3;
    z = MAX(5*DOUBLE(x),++y);
    printf("%d %d %d\n",x, y, z);
}

answer:

3 2 3

5 3 5

2 4 20

2 4 12

 

5.putchar函数定义于文件stdio.h中,尽管它的内容比较长,但它作为一个宏实现,你认为它为什么以这种方式定义? 

answer:Because putchar is invoked(调用) so often,speed of invocation was considered of primary importance,Implementing it as a macro eliminates the overhead of a function call.

(因为puchar被调用的次数比较多,调用的速度是考虑的一个重要因素,执行宏比函数需要的开销更小)

 

6.下列代码是否有错?如果有,错在何处?

 技术分享

answer:我们无法通过给出的源代码判断进行判断。如果process以宏的方式实现,并且对它的参数求值超过一次,增加下标值的副作用可能会导致不正确的结果。

 

7.下列代码是否有错?如果有,错在何处?

技术分享

answer:这段代码有几个地方存在错误,其中几处比较微妙。它的主要问题是这个宏具有依赖于副作用(增加下标值)的参数,这种依赖性是非常危险的,由于宏的名字并没有提示它实际所执行的任务(这是第二个问题),这种危险性进一步加大了,假定循环后来改为:

技术分享

尽管看上去相同,但程序此时会失败。最后一个问题是:由于宏始终访问数组中的两个元素,所以如果SIZE是个奇数值,程序就会失败。

(为什么代码改为上述代码,程序还是会失败呢?不是已经没有副作用了吗?首先我们来看看这个程序的功能“Sum all the value in the array”,意思是计算数组的所有值的总和,源代码的意思是每次添加两个连续数组的值,也就是SUM(value) 的意思就是要计算 array[i] + array[i+1]的值,所以说“依赖”于宏的副作用,它就是故意利用宏的副作用来实现,把代码改掉之后,可以把宏直接去掉计算数组总值)

 

8.下列代码是否有错?如果有,错在何处?

在文件header1.h中

技术分享

在文件header2.h中

技术分享

answer:

answer:Nothing is wrong,They each include the other,and it first appears that the compiler will read them alternately until its include nesting limit is reached.In fact this does not happen because of the conditional compilation directives,Whichever file is included first defines its own symbol and then causes the other to be included,When it tries to include the first one again,the entire file is skipped.

(没有错,它们互相包括对方,它第一次出现编译器将交替读取它们知道嵌套达到限制,事实上这并不会发生因为条件编译指令,任何一个文件在第一次定义时产生了一个标志符号,所以导致在其他文件中再次被包含时会跳过它)

 

9.在一次提高程序可读性的尝试中,一位程序员编写了下面的声明。

技术分享

这段代码是否有错?如果有,错在何处?

answer:

 Unfortunately,sizeof is evaluated(求值) after the preprocessor(预处理器) has finished its work,which means that this won‘t work,An alternate approach would be use the values defined in the limits.h include file.

(很不幸,sizeof的求值是在预处理器完成工作之后,这意味着这个程序将不会工作,一个可用来代替的方法是使用limits这个头文件来判断值)

其中sizeof(int) == 2这个表达式是在判断int的值是否是两个字节,在limits这个头文件中定义了类型的大小,int值在INT_MIN到INT_MAX之间,所以可以用limits.h中定义的值来代替sizeof这个语句

 

本章练习

1.你所在的公司向市场投放了一个程序,用于处理金融交易并打印出它们的报表。为了扩展潜在市场,这个程序以几个不同的版本进行销售,每个版本有不同选项的组合--选项越多,价格就越高,你的任务是为一个打印函数实现代码,这样它可以很容易进行编译,产生程序的不同版本。你的函数名是print_ledger。它接受一个int参数,没有返回值,它应该调用一个或多个下面的函数,具体取决于该函数被编译时哪个符号(如果有的话)被定义。

技术分享

每个函数都接受单个int参数,把你收到的值传递给你该调用的函数。

answer:

void print_ledger(int arg)
{
    #if OPTION_LONG
        print_ledger_long(arg);
    #elif OPTION_DETAILED
        print_ledger_detailed(arg);
    #else
        print_ledger_default(arg);
    #endif
}

如果你也像我一样写出上述代码的话,那么,你也跟我一样,没看懂题目昂,因为两个选项都可能被选择,这种可能性说明不能使用#elif指令,让我们看正确答案

void print_ledger(int arg)
{
#ifdef OPTION_LONG
    #define OK 1
    print_ledger_long(arg);
#endif
    
#ifdef OPTION_DETAILED
    #define OK 1
    print_ledger_detailed(arg);
#endif
    
#ifndef OK
    print_ledger_default(arg);
#endif
}

 

2.编写一个函数,返回一个值,提示运行这个函数的计算机类型。这个函数将由一个能够运行与许多不同计算机的程序使用。

我们将使用条件编译来实现这个魔术,你的函数应该叫做cpu_type,它不接受任何参数,当你的函数被编译时,在下面表中“已定义”列中的符号之一可能会被定义,你的函数应该从“返回值”列中返回对应的符号,如果左边列中的所有符号均未定义,那么函数就返回CPU_UNKNOWN这个值,如果超过一个符号被定义,那么其结果就是未定义的。

技术分享

“返回值”列中的符号将被#define定义为各种不同的整形值,其内容位于头文件cpu_type.h中。

answer:

#include <stdio.h>
#include "cpu_type.h"

int cpu_type(void)
{
#if defined(VAX)
    return CPU_VAX;
#elif defined(M68000)
    return CPU_68000;
#elif defined(M68020)
    return CPU_68020;
#elif defined(I80386)
    return CPU_80386;
#elif defined(X6809)
    return CPU_6809;
#elif defined(X6502)
    return CPU_6502;
#elif defined(U3B2)
    return CPU_3B2;
#else
    return CPU_UNKNOWN;
#endif
}

 

《C与指针》第十四章练习