C-FAQ整理

费了好大的劲调了下格式,将问答放在了代码块里,问题用的红色,结果编辑下的是红色,发表后,就显示了html标签,那么多,fack!就这样吧,鼠标点的手都累了,将就看吧。

1、from smth

int a=1,b=2,c=3,d=4;
if( a=a>c&&b=c>d ) printf("yes\n");
else printf("no\n");
编译出现:lvalue required as left operand of assignment
原因是什么。(判断条件里面,不用==,而用=,不行?)

先弄清楚运算符优先级,再说你后面帖子的一系列问题。
你下面的写法相当于:
if (a = ((a > c) && b) = (c > d)) printf("yes\n");

2、c-faq

如何向接受结构参数的函数传入常数值?

传统的 C 没有办法生成匿名结构值; 你必须使用临时结构变量或一个小的结构生成函数。

C99 标准引入了 ``复合常量'' (compound literals); 复合常量的一种形式就可以允许结构常量。例如, 向假想 plotpoint() 函数传入一个坐标对常数, 可以调用

    plotpoint((struct point){1, 2});

与 ``指定初始值'' (designated initializers) (C99 的另一个功能) 结合, 也可以用成员名称确定成员值:

    plotpoint((struct point){.x=1, .y=2});

 3、c-faq

我有一个函数 extern int f(int *); 它接受指向  int 型的指针。我怎样用引用方式传入一个常数?下面这样的调用 f(&5); 似乎不行。

在 C99 中, 你可以使用 ``复合常量":

    f((int[]){5});

在 C99 之前, 你不能直接这样做; 你必须先定义一个临时变量, 然后把它的地址传给函数:

    int five = 5;
    f(&five);

4、c-faq

如何确定域在结构中的字节偏移?

ANSI C 在 <stddef.h> 中定义了  offsetof() 宏, 用 offsetof(struct s, f) 可以计算出域 f 在结构 s 中的偏移量。 如果出于某种原因, 你需要自己实现这个功能, 可以使用下边这样的代码:

    #define offsetof(type, f) ((size_t) \
        ((char *)&((type *)0)->f - (char *)(type *)0))

这种实现不是 100% 的可移植; 某些编译器可能会合法地拒绝接受。

5、c-faq

我可否用括号来强制执行我所需要的计算顺序?

一般来讲, 不行。运算符优先级和括弧只能赋予表达是计算部分的顺序. 在如下的代码中

    f() + g() * h()

尽管我们知道乘法运算在加法之前, 但这并不能说明这三个函数哪个会被首先调用。

如果你需要确保子表达式的计算顺序, 你可能需要使用明确的临时变量和独立的语句。

6、c-faq

我需要根据条件把一个复杂的表达式赋值给两个变量中的一个。可以用下边这样的代码吗?  ((condition) ? a : b) = complicated_expression;

不能。? : 操作符, 跟多数操作符一样, 生成一个值, 而不能被赋值。换言之, ? : 不能生成一个 ``左值"。如果你真的需要, 你可以试试下面这样的代码:

    *((condition) ? &a : &b) = complicated_expression;

尽管这毫无优雅可言。

7、c-faq

我有一个 char * 型指针正巧指向一些 int 型变量, 我想跳过它们。为什么如下的代码 ((int *)p)++; 不行?

在 C 语言中, 类型转换意味着 ``把这些二进制位看作另一种类型, 并作相应的对待"; 这是一个转换操作符, 根据定义它只能生成一个右值 (rvalue)。而右值既不能赋值, 也不能用 ++ 自增。(如果编译器支持这样的扩展, 那要么是一个错误, 要么是有意作出的非标准扩展。) 要达到你的目的可以用:

    p = (char *)((int *)p + 1);

或者,因为 p 是 char * 型, 直接用

    p += sizeof(int);

但是, 在可能的情况下, 你还是应该首先选择适当的指针类型, 而不是一味地试图李代桃僵。

8、c-faq

我有个函数,它应该接受并初始化一个指针  void f(int *ip) { static int dummy = 5; ip = &dummy;} 但是当我如下调用时: int *ip; f(ip); 调用者的指针却没有任何变化。

你确定函数初始化的是你希望它初始化的东西吗?请记住在 C 中, 参数是通过值传递的。被调函数仅仅修改了传入的指针副本。你需要传入指针的地址 (函数变成接受指针的指针), 或者让函数返回指针。

9、c-faq

我看到了用指针调用函数的不同语法形式。到底怎么回事?

最初, 一个函数指针必须用 * 操作符 (和一对额外的括弧) ``转换为" 一个 ``真正的" 函数才能调用:

    int r, func(), (*fp)() = func;
    r = (*fp)();

而函数总是通过指针进行调用的, 所有 ``真正的" 函数名总是隐式的退化为指针 (在表达式中, 正如在初始化时一样。参见问题 1.14)。这个推论表明无论 fp 是函数名和函数的指针

    r = fp();

ANSI C 标准实际上接受后边的解释, 这意味着 * 操作符不再需要, 尽管依然允许。

 10、c-faq

我在一个源文件中定义了 char a[6], 在另一个中声明了  extern char *a 。为什么不行 ?

你在一个源文件中定义了一个字符串, 而在另一个文件中定义了指向字符的指针。 extern char * 的申明不能和真正的定义匹配。 类型  T 的指针和类型 T 的数组并非同种类型。 请使用 extern char a[ ]。

11、c-faq

可是我听说 char a[ ] 和 char *a 是一样的。

并非如此。(你所听说的应该跟函数的形式参数有关;参见问题  6.4) 数组不是指针。 数组定义 char a[6] 请求预留 6 个字符的位置, 并用名称 ``a" 表示。也就是说, 有一个称为 ``a" 的位置, 可以放入 6 个字符。 而指针申明 char *p, 请求一个位置放置一个指针,  用名称 ``p" 表示。 这个指针几乎可以指向任何位置: 任何字符和任何连续的字符, 或者哪里也不指(参见问题 5.1 和  1.10)。

一个图形胜过千言万语。声明

    char a[] = "hello";
    char *p = "world";

将会初始化下图所示的数据结果:

       +---+---+---+---+---+---+
    a: | h | e | l | l | o |\0 |
       +---+---+---+---+---+---+
       +-----+     +---+---+---+---+---+---+
    p: |  *======> | w | o | r | l | d |\0 |
       +-----+     +---+---+---+---+---+---+

根据 x 是数组还是指针, 类似 x[3] 这样的引用会生成不同的代码。认识到这一点大有裨益。以上面的声明为例, 当编译器看到表达式  a[3] 的时候, 它生成代码从 a 的位置开始跳过 3 个, 然后取出那个字符. 如果它看到 p[3], 它生成代码找到 ``p" 的位置, 取出其中的指针值, 在指针上加 3 然后取出指向的字符。换言之, a[3] 是 名为 a 的对象 (的起始位置) 之后 3 个位置的值,
而 p[3] 是  p 指向的对象的 3 个位置之后的值. 在上例中, a[3] 和  p[3] 碰巧都是 'l' , 但是编译器到达那里的途径不尽相同。
本质的区别在于类似 a 的数组和类似 p 的指针一旦在表达式中出现就会按照不同的方法计算, 不论它们是否有下标。下一问题继续深入解释。
参见问题 1.13。

 12、c-faq

我如何声明大小和传入的数组一样的局部数组 ?

直到最近以前, 你都不能; C 语言的数组维度一直都是编译时常数。但是, C99 引入了变 长数组(VLA), 解决了这个问题;
局部数组的大小可以用变量或其它表达式设置, 可能也包括函数参数。 ( gcc 提供参数化数组作为扩展已经有些时候了。)
如果你不能使用 C99 或 gcc, 你必须使用 malloc(), 并在函数返回之前调用 free()。参见问题 6.11, 6.13, 6.16, 7.19 和 7.27。

13、c-faq

我该如何动态分配多维数组 ?

传统的解决方案是分配一个指针数组, 然后把每个指针初始化为动态分配的 ``列"。 以下为一个二维的例子:

    #include <stdlib.h>

    int **array1 = malloc(nrows * sizeof(int *));
    for(i = 0; i < nrows; i++)
        array1[i] = malloc(ncolumns * sizeof(int));

当然, 在真实代码中, 所有的 malloc 返回值都必须检查。你也可以使用 sizeof(*array1)  和 sizeof(**array1) 代替 sizeof(int *) 和 sizeof(int)。

你可以让数组的内容连续, 但在后来重新分配列的时候会比较困难, 得使用一点指针算术:

    int **array2 = malloc(nrows * sizeof(int *));
    array2[0] = malloc(nrows * ncolumns * sizeof(int));
    for(i = 1; i < nrows; i++)
        array2[i] = array2[0] + i * ncolumns;

在两种情况下, 动态数组的成员都可以用正常的数组下标 arrayx[i][j] 来访问  (for 0 <= i <nrows 和 0 <= j <ncolumns)。

如果上述方案的两次间接因为某种原因不能接受, 你还可以同一个单独的动态分配的一维数组来模拟二维数组:

    int *array3 = malloc(nrows * ncolumns * sizeof(int));

但是, 你现在必须手工计算下标, 用 array3[i * ncolumns + j] 访问第 i, j 个成员。使用宏可以隐藏显示的计算, 但是调用它的时候要使用括号和逗号, 这看起来不太象多维数组语法, 而且宏需要至少访问一维。参见问题 6.16。
另一种选择是使用数组指针:
    int (*array4)[NCOLUMNS] = malloc(nrows * sizeof(*array4));
但是这个语法变得可怕而且运行时最多只能确定一维。
当然, 使用这些技术, 你都必须记住在不用的时候释放数组 (这可能需要多个步骤; 参见问题 7.20)。 而且你可能不能混用动态数组和传统的静态分配数组。参见问题 6.17 和 6.15。
最后, 在 C99 中你可以使用变长数组。
所有这些技术都可以延伸到三维或更多的维数。

14、c-faq

当我向一个接受指针的指针的函数传入二维数组的时候, 编译器报错了。

数组蜕化为指针的规则 (参见问题 6.3) 不能递归应用。数组的数组 (即 C 语言中的二维数组) 蜕化为数组的指针, 而不是指针的指针。数组指针常常令人困惑, 需要小心对待; 参见问题 6.10。

如果你向函数传递二位数组:

    int array[NROWS][NCOLUMNS];
    f(array);

那么函数的声明必须匹配:

    void f(int a[][NCOLUMNS])
    { ... }

或者

    void f(int (*ap)[NCOLUMNS]) /* ap 是个数组指针 */
    { ... }

在第一个声明中, 编译器进行了通常的从 ``数组的数组" 到 ``数组的指针"  的隐式转换 (参见问题 6.3 和 6.4); 第二种形式中的指针定义显而易见。因为被调函数并不为数组分配地址, 所以它并不需要知道总的大小, 所以行数 NROWS 可以省略。但数组的宽度依然重要, 所以列维度  NCOLUMNS (对于三维或多维数组, 相关的维度) 必须保留。

如果一个函数已经定义为接受指针的指针, 那么几乎可以肯定直接向它传入二维数组毫无意义。

15、c-faq

我怎样在函数参数传递时混用静态和动态多维数组 ?

没有完美的方法。假设有如下声明

    int array[NROWS][NCOLUMNS];
    int **array1;        /* 不齐的   */
    int **array2;        /* 连续的   */
    int *array3;         /* "变平的" */
    int (*array4)[NCOLUMNS];

指针的初始值如问题 6.13 的程序片段, 函数声明如下

    void f1a(int a[][NCOLUMNS], int nrows, int ncolumns);
    void f1b(int (*a)[NCOLUMNS], int nrows, int ncolumns);
    void f2(int *aryp, int nrows, int ncolumns);
    void f3(int **pp, int nrows, int ncolumns);

其中 f1a() 和 f1b() 接受传统的二维数组, f2() 接受 ``扁平的" 二维数组, f3() 接受指针的指针模拟的数组 (参见问题 6.15 和 6.16), 下面的调用应该可以如愿运行:

    f1a(array, NROWS, NCOLUMNS);
    f1b(array, NROWS, NCOLUMNS);
    f1a(array4, nrows, NCOLUMNS);
    f1b(array4, nrows, NCOLUMNS);
    f2(&array[0][0], NROWS, NCOLUMNS);
    f2(*array, NROWS, NCOLUMNS);
    f2(*array2, nrows, ncolumns);
    f2(array3, nrows, ncolumns);
    f2(*array4, nrows, NCOLUMNS);
    f3(array1, nrows, ncolumns);
    f3(array2, nrows, ncolumns);

下面的调用在大多数系统上可能可行, 但是有可疑的类型转换, 而且只有动态  ncolumns 和静态 NCOLUMNS 匹配才行:

    f1a((int (*)[NCOLUMNS])(*array2), nrows, ncolumns);
    f1a((int (*)[NCOLUMNS])(*array2), nrows, ncolumns);
    f1b((int (*)[NCOLUMNS])array3, nrows, ncolumns);
    f1b((int (*)[NCOLUMNS])array3, nrows, ncolumns);

同时必须注意向 f2() 传递 &array[0][0] (或者等价的 *array) 并不完全符合标准; 参见问题 6.16。

如果你能理解为何上述调用可行且必须这样书写, 而未列出的组合不行, 那么你对 C  语言中的数组和指针就有了很好的理解了。

为免受这些东西的困惑, 一种使用各种大小的多维数组的办法是令它们 ``全部" 动态分配, 如问题 6.13 所述。如果没有静态多维数组 --- 如果所有的数组都按问题 6.13 的 array1 和 array2 分配 --- 那么所有的函数都可以写成  f3() 的形式。

 16、c-faq

当数组是函数的参数时, 为什么 sizeof 不能正确报告数组的大小 ?

编译器把数组参数当作指针对待 (参见问题 6.4), 因而报告的时指针的大小。

17、c-faq

为什么这段代码不行?char *answer; printf("Type something:\n"); gets(answer); printf("You typed \"%s\"\n", answer);

指针变量 answer, 传入 gets(), 意在指向保存得到的应答的位置, 但却没有指向任何合法的位置。换言之, 我们不知道指针 answer 指向何处。因为局部变量没有初始化, 通常包含垃圾信息, 所以甚至都不能保证 answer  是一个合法的指针。参见问题 1.10 和 5.1。

改正提问程序的最简单方案是使用局部数组, 而不是指针, 让编译器考虑分配的问题:

    #include <stdio.h>
    #include <string.h>

    char answer[100], *p;
    printf("Type something:\n");
    fgets(answer, sizeof answer, stdin);
    if((p = strchr(answer, '\n')) != NULL)
        *p = '\0';
    printf("You typed \"%s\"\n", answer);

本例中同时用 fgets() 代替 gets(), 以便 array 的结束符不被改写。参见问题 12.20。不幸的是, 本例中的 fgets() 不会象 gets()  那样自动地去掉结尾的 \n。 也可以用 malloc() 分配  answer 缓冲区。

18、c-faq

我用一行这样的代码分配一个巨大的数组, 用于数字运算:  double *array = malloc(300 * 300 * sizeof( double ));
malloc() 并没有返回 null, 但是程序运行得有些奇怪, 好像改写了某些内存, 或者 malloc() 并没有分配我申请的那么多内存, 云云。

注意 300 * 300 是 90,000, 这在你乘上 sizeof(double) 以前就已经不能放入 16 位的 int 中了。 如果你需要分配这样大的内存空间, 你可得小心为妙。如果在你的机器上 size_t (malloc() 接受的类型) 是 32位, 而 int 为 16 位,
你可以写 300 * (300 * sizeof(double)) 来避免这个问题。 (参见问题 3.11)。否则, 你必须把你的数据结构分解为更小的块,
或者使用 32 位的机器或编译器, 或者使用某种非标准的内存分配函数。参见问题 19.27。

 19、c-faq

我的程序总是崩溃, 显然在 malloc 内部的某个地方。 但是我看不出哪里有问题。是 malloc() 有 bug 吗?

很不幸, malloc 的内部数据结构很容易被破坏, 而由此引发的问题会十分棘手。最常见的问题来源是向 malloc 分配的区域写入比所分配的还多的数据;一个常见的 bug 是用 malloc(strlen(s)) 而不是 strlen(s) + 1。 其它的问题还包括使用指向已经释放了的内存的指针,释放未从 malloc 获得的内存, 或者两次释放同一个指针, 或者试图重分配空指针, 参见问题 7.25。

 20、c-faq

我有个程序分配了大量的内存, 然后又释放了。但是从操作系统看, 内存的占用率却并没有回去。

多数 malloc/free 的实现并不把释放的内存返回操作系统, 而是留着供同一程序的后续 malloc() 使用。

21、c-faq

free() 怎么知道有多少字节需要释放?

malloc/free 的实现会在分配的时候记下每一块的大小, 所以在释放的时候就不必再考虑了。

22、c-faq

alloca() 是什么?为什么不提倡使用它?

在调用 alloca() 的函数返回的时候, 它分配的内存会自动释放。也就是说, 用 alloca 分配的内存在某种程度上局部于函数的 ``堆栈帧"  或上下文中。

alloca() 不具可移植性, 而且在没有传统堆栈的机器上很难实现。当它的返回值直接传入另一个函数时会带来问题, 如  fgets(alloca(100), 100, stdin)。

由于这些原因, alloca() 不合标准, 不宜使用在必须广泛移植的程序中, 不管它可能多么有用。 既然 C99 支持变长数组(VLA), 它可以用来更好的完成 alloca() 以前的任务。

23、c-faq

我认为我的编译器有问题: 我注意到 sizeof('a') 是 2 而不是  1 (即, 不是 sizeof(char))。

可能有些令人吃惊, C 语言中的字符常数是 int 型, 因此 sizeof('a') 是  sizeof(int), 这是另一个与 C++ 不同的地方。 参见问题 7.11。

24、c-faq

C 语言中布尔值的候选类型是什么?为什么它不是一个标准类型?我应该用 #define 或 enum 定义 true 和 false 值吗?

C 语言没有提供标准的布尔类型, 部分因为选一个这样的类型涉及最好由 程序员决定的空间/时间折衷。 (使用 int 可能更快, 而使用 char 可能更节省数据空间。然而, 如果需要和 int 反复转换, 那么小类型也可能生成 更大或更慢的代码。)

使用 #define 还是枚举常数定义 true/false 可以随便, 无关大雅 (参见问题 2.16 和 17.8)。 使用以下任何一种形式

    #define TRUE  1        #define YES 1
    #define FALSE 0        #define NO  0

    enum bool {false, true};    enum bool {no, yes};

或直接使用 1 和 0 , 只要在同一程序或项目中一致即可。 如果你的调试器在查看变量的
时候能够显示枚举常量的名字, 可能使用枚举更好。

有些人更喜欢这样的定义

    #define TRUE (1==1)
    #define FALSE (!TRUE)

或者定义这样的 ``辅助" 宏

    #define Istrue(e) ((e) != 0)

但这样于事无益, 参见下边的问题 9.2, 5.9 和  10.1。

 25、c-faq

sizeof 操作符可以用于 #if 预编译指令中吗?

不行。 预编译在编译过程的早期进行, 此时尚未对类型名称进行分析。 作为替代, 可以考虑使用 ANSI 的 <limits.h> 中定义的常量, 或者使用 ``配置" (configure) 脚本。 更好的办法是, 书写与类型大小无关的代码; 参见问题 1.1。

26、c-faq

为什么声明  extern int f(struct x *p);  报出了一个奇怪的警告信息 ``结构 x 在参数列表中声明"?

与 C 语言通常的作用范围规则大相径庭的是, 在原型中第一次声明 (甚至提到)  的结构不能和同一源文件中的其它结构兼容, 它在原型的结束出就超出了作用范围。

要解决这个问题, 在同一源文件的原型之前放上这样的声明:

    struct x;

它在文件范围内提供了一个不完整的结构 x 的声明, 这样, 后续的用到结构 x  的声明至少能够确定它们引用的是同一个结构 x。

27、c-faq

我不明白为什么我不能象这样在初始化和数组维度中使用常量: const int n = 5; int a[n];

const 限定词真正的含义是 ``只读的"; 用它限定的对象是运行时 (同常)  不能被赋值的对象。
因此用 const 限定的对象的值并不完全是一个真正的常量。在这点上 C 和 C++ 不一样。如果你需要真正的运行时常量,
使用预定义宏 #define (或enum)。

28、 c-faq

``const char *p" 和 ``char * const p" 有何区别?

``const char *p" (也可以写成 ``char const *p") 声明了一个指向字符常量的指针, 因此不能改变它所指向的字符;
``char * const p" 声明一个指向 (可变)  字符的指针常量, 就是说, 你不能修改指针。
``从里到外" 看就可以理解它们; 参见问题 1.7。

29、c-faq

memcpy() 和 memmove() 有什么区别?

如果源和目的参数有重叠, memmove() 提供有保证的行为。而 memcpy()  则不能提供这样的保证, 因此可以实现得更加有效率。如果有疑问, 最好使用 memmove()。

30、c-faq

malloc(0) 有什么用?返回一个控指针还是指向 0  字节的指针?

ANSI/ISO 标准声称它可能返回任意一种; 其行为由实现定义。

31、c-faq

这样的代码有什么问题? char c; while((c = getchar()) != EOF) ...

第一, 保存 getchar 的返回值的变量必须是 int 型。getchar() 可能返回任何字符值, 包括 EOF。如果把 getchar 的返回值截为 char 型, 则正常的字符可能会被错误的解释为 EOF, 或者 EOF 可能会被修改 (尤其是 char 型为无符号的时候), 从而永不出现。

 32、c-faq

为什么这些代码  while(!feof(infp)) { fgets(buf, MAXLINE, infp); fputs(buf, outfp); }  把最后一行复制了两遍?

在 C 语言中, 只有输入例程试图读并失败以后才能得到文件结束符。换言之, C 的 I/O 和 Pascal 的不一样。通常你只需要检查输入例程的返回值, 例如, fgets() 在遇到文件结束符的时候返回 NULL。实际上, 在任何情况下, 都完全没有必要使用 feof()。

33、c-faq

对于 size_t 那样的类型定义, 当我不知道它到底是 long  还是其它类型的时候, 我应该使用什么样的 printf 格式呢?

把那个值转换为一个已知的长度够大的类型, 然后使用与之对应的 printf 格式。例如, 输出某种类型的长度, 你可以使用
    printf("%lu", (unsigned long)sizeof(thetype));

34、c-faq

我如何用 printf 实现可变的域宽度?就是说, 我想在运行时确定宽度而不是使用 %8d?

printf("%*d", width, x) 就能达到你的要求。

35、c-faq

为什么这些代码  double d; scanf("%f", &d);  不行?

跟 printf() 不同, scanf() 用 %lf 代表双精度数, 用 %f 代表浮点数。参见问题 12.7

36、c-faq

有人告诉我在 printf 中使用 %lf 不正确。那么, 如果 scanf()  需要 %lf, 怎么可以用在 printf() 中用 %f 输出双精度数呢?

printf 的 %f 标识符的确既可以输出浮点数又可以输出双精度数。根据  ``缺省参数扩展" 规则,
不论范围内有没有原形都会在在类似 printf 的可变长度参数列表中采用, 浮点型的变量或扩展为双精度型,
因此 printf()  只会看到双精度数。printf() 的确接受 %Lf, 用于输出长双精度数。参见问题 12.11 和 15.2。

37、c-faq

我用 scanf %d 读取一个数字, 然后再用 gets() 读取字符串, 但是编译器好像跳过了 gets() 调用!

scanf %d 不处理结尾的换行符。如果输入的数字后边紧接着一个换行符, 则换行符会被 gets() 处理。

作为一个一般规则, 你不能混用 scanf() 和 gets(), 或任何其它的输入例程的调用; scanf 对换行符的特殊处理几乎一定会带来问题。要么就用 scanf() 处理所有的输入, 要么干脆不用。

38、c-faq

我发现如果坚持检查返回值以确保用户输入的是我期待的数值, 则 scanf() 的使用会安全很多, 但有的时候好像会陷入无限循环。

在 scanf() 转换数字的时候, 它遇到的任何非数字字符都会终止转换 并被保留在输入流中。因此, 除非采用了其它的步骤,
那么未预料到的非数字输入会不断 ``阻塞" scanf(): scanf() 永远都不能越过错误的非数字字符而处理后边的合法数字字符。
如果用户在数字格式的 scanf 如 %d 或 %f  中输入字符 `x', 那么提示后并用同样的 scanf() 调用重试的代码会立即遇到同一个 'x'。

39、c-faq

为什么大家都说不要使用 scanf()?那我该用什么来代替呢?

scanf() 有很多问题 --- 参见问题 12.15, 12.16 和  12.17。而且, 它的 %s 格式有着和 gets() 一样的问题 (参见问题  12.20) --- 很难保证接收缓冲不溢出。

更一般地讲, scanf() 的设计使用于相对结构化的, 格式整齐的输入。设计上, 它的名称就是来自于 ``scan formatted"。
如果你注意到, 它会告诉你成功或失败, 但它只能提供失败的大略位置, 至于失败的原因, 就无从得知了。
对 scanf() 多得体的错误恢复几乎是不可能的; 通常先用类似 fgets() 的函数读入整行, 然后再用 sscanf() 或其它技术解释。strtol(), strtok() 和 atoi()  等函数通常有用; 参见问题 13.4。
如果你真的要用任何 scanf 的变体, 你要确保检查返回值, 以确定找到了期待的值。而使用 %s 格式的时候, 一定要小心缓冲区溢出。

40、c-faq

fgetops/fsetops 和 ftell/fseek 之间有什么区别? fgetops()  和 fsetops() 到底有什么用处?

ftell() 和 fseek() 用长整型表示文件内的偏移 (位置), 因此, 偏移量被限制在 20 亿 (231-1) 以内。
而新的 fgetpos() 和 fsetpos() 函数使用了一个特殊的类型定义 fpos_t 来表示偏移量。
这个类型会适当选择, 因此, fgetpos() 和 fsetpos 可以表示任意大小的文件偏移。fgetpos() 和  gsetpos()
也可以用来记录多字节流式文件的状态。参见问题 1.2。

41、c-faq

如何清除多余的输入, 以防止在下一个提示符下读入? fflush(stdin) 可以吗?

fflush() 仅对输出流有效。因为它对 ``flush" 的定义是用于完成缓冲字符的写入, 而对于输入流 fflush 并不是用于放弃剩余的输入。

42、c-faq

既然 fflush() 不能, 那么怎样才能清除输入呢?

这取决于你要做什么。如果你希望丢掉调用 scanf() (参见问题 12.16 - 12.17) 之后所剩下的换行符和未预知的输入,
你可能需要重写你的  scanf() 或者换掉它, 参见问题 12.18。或者你可以用下边这样的代码吃掉一行中多余的字符

    while((c = getchar()) != '\n' && c != EOF)
        /* 丢弃 */ ;

你也可以使用 curses 的 flushinp() 函数。

没有什么标准的办法可以丢弃标准输入流的未读取字符, 即使有, 那也不够, 因为未读取字符也可能来自其它的操作系统级的输入缓冲区。
如果你希望严格丢弃多输入的字符 (可能是预测发出临界提示), 你可能需要使用系统相关的技术; 参加问题 19.1 和 19.2。

43、c-faq

怎样同时向两个地方输出, 如同时输出到屏幕和文件?

直接做不到这点。但是你可以写出你自己的 printf 变体, 把所有的内容都输出两次。下边有个简单的例子:

    #include <stdio.h>
    #include <stdarg.h>

    void f2printf(FILE *fp1, FILE *fp2, char *fmt, ...)
    {
    va_list argp;
    va_start(argp, fmt); vfprintf(fp1, fmt, argp); va_end(argp);
    va_start(argp, fmt); vfprintf(fp2, fmt, argp); va_end(argp);
    }

这里的 f2printf() 就跟 fprintf() 一样, 除了它接受两个文件指针并同时输出到两个文件。

44、c-faq

我需要一些处理正则表达式或通配符匹配的代码。

确保你知道经典的正则表达式和文件名通配符的不同。前者的变体在 Unix 工具 ed 和 grep 等中使用, 后者的变体在多数操作系统中使用。

有许多匹配正则表达式的包可以利用。很多包都是用成对的函数, 一个 ``编译" 正则表达式, 另一个 ``执行" 它,
即用它比较字符串。查查头文件  <regex.h> 或 <regexp.h> 和函数 regcmp/regex, regcomp/regexec,
或 re_comp/re_exec。这些函数可能在一个单独的 regexp 库中。在  ftp://ftp.cs.toronto.edu/pub/regexp.shar.Z 或其它地方可以找到一个  Henry Spencer 开发的广受欢迎的 regexp 包,这个包也可自由再发布。 GNU 工程有一个叫做 rx 的包。参见问题 18.18。

文件名通配符匹配 (有时称之为 ``globbing") 在不同的系统上有不同的实现。在 Unix 上, shell 会在进程调用之前自动扩展通配符, 因此, 程序几乎从不需要专门考虑它们。在 MS-DOS 下的编译器中, 通常都可以在建立 argv 的时候连接一个用来扩展通配符的特殊目标文件。有些系统 (包括 MS-DOS 和 VMS)  会提供通配符指定文件的列表和打开的系统服务。参见问题 19.25  和 20.2。

45、 c-faq

我想用 qsort() 对一个结构数组排序。我的比较函数接受结构指针, 但是编译器认为这个函数对于 qsort() 是错误类型。
我要怎样转换这个函数指针才能避免这样的警告?

这个转换必须在比较函数中进行, 而函数必须定义为接受 ``一般指针" (const void*) 的类型, 就象上文问题 13.6 中所讨论的。比较函数可能像这样:

    int mystructcmp(const void *p1, const void *p2)
    {
        const struct mystruct *sp1 = p1;
        const struct mystruct *sp2 = p2;
        /* 现在比较 sp1->whatever 和 sp2-> ... */

从一般指针到结构 mystruct 指针的转换过程发生在 sp1 = p1 和 sp2 = p2  的初始化中; 由于 p1 和 p2 都是 void 指针, 编译器隐式的进行了类型转换。

另一方面, 如果你对结构的指针进行排序, 则如问题 13.6 所示, 你需要间接使用: sp1 = *(struct mystruct * const *)p1。

一般而言, 为了让编译器 ``闭嘴" 而进行类型转换是一个坏主意。编译器的警告信息通常希望告诉你某些事情, 忽略或轻易去掉会让你陷入危险, 除非你明确知道你在做什么。参见问题 4.5。

46、c-faq

怎样在日期上加 N 天?怎样取得两个日期的时间间隔?

ANSI/ISO 标准 C 函数 mktime() 和 difftime() 对这两个问题提供了一些有限的支持。 mktime() 接受没有规格化的日期,
所以可以用一个日期的 struct tm 结构, 直接在 tm_mday 域进行加或减, 然后调用 mktime()  对年、月、日域进行规格化,
同时也转换成了 time_t 值。可以用 mktime() 来计算两个日期的 time_t 值, 然后用 difftime() 计算两个 time_t 值的秒数差分。

但是, 这些方法只有日期在 time_t 表达范围内才保证工作正常。对于保守的  time_t, 通常范围是从 1970 年到大约 2037 年;
注意有些 time_t 的表达不是按照 Unix 和 Posix 标准的。tm_mday 域是个 int, 所以日偏移量超出  32,736 就会上溢。
还要注意, 在夏令时转换的时候, 一天并不是 24 小时, 所以不要假设可以用 86400 整除。

另一个解决的方法是用 ``Julian 日期", 这可以支持更宽的时间范围。处理 Julian  日期的代码可以在以下地方找到:
Snippets 收集 (参见问题 18.16); Simtel/Oakland 站点 (文件 JULCAL10.ZIP, 参见问题 18.18) 和 文献中提到的文章  ``Date conversionsciteburki。

参见问题 13.11, 20.27 和 20.28。

47、c-faq

怎样获得在一定范围内的随机数?

直接的方法是

    rand() % N      /* 不好 */

试图返回从 0 到 N - 1 的数字。但这个方法不好, 因为许多随机数发生器的低位比特并不随机, 参见问题 13.16。一个较好的方法是:

    (int)((double)rand() / ((double)RAND_MAX + 1) * N)

如果你不希望使用浮点, 另一个方法是:

    rand() / (RAND_MAX / N + 1)

两种方法都需要知道 RAND_MAX, 而且假设 N 要远远小于 RAND_MAX。  RAND_MAX 在 ANSI 里 #define 在 <stdlib.h>。

顺便提一下, RAND_MAX 是个常数, 它告诉你 C 库函数 rand()  的固定范围。你不可以设 RAND_MAX 为其它的值, 也没有办法要求 rand()  返回其它范围的值。

如果你用的随机数发生器返回的是 0 到 1 的浮点值, 要取得范围在 0 到  N - 1 内的整数, 只要将随机数乘以 N 就可以了。

48、c-faq

我需要随机的真/假值, 所以我用直接用 rand() % 2, 可是我得到交替的 0, 1, 0, 1, 0 ……

这是个低劣的伪随机数生成器, 在低位比特中不随机。 很不幸, 某些系统就提供这样的伪随机数生成器。尝试用高位比特; 参见问题 13.14。

49、c-faq

怎样产生标准分布或高斯分布的随机数?

这里有一个由 Marsaglia 首创 Knuth 推荐的方法:

    #include <stdlib.h>
    #include <math.h>

    double gaussrand()
    {
        static double V1, V2, S;
        static int phase = 0;
        double X;

        if(phase == 0) {
        do {
            double U1 = (double)rand() / RAND_MAX;
            double U2 = (double)rand() / RAND_MAX;

            V1 = 2 * U1 - 1;
            V2 = 2 * U2 - 1;
            S = V1 * V1 + V2 * V2;
        } while(S >= 1 || S == 0);

        X = V1 * sqrt(-2 * log(S) / S);
        } else
        X = V2 * sqrt(-2 * log(S) / S);

        phase = 1 - phase;

        return X;
    }

其它的方法参见本文的扩展版本, 参见问题 20.36。

49、c-faq

有什么好的方法来验对浮点数在 ``足够接近" 情况下的等值?

浮点数的定义决定它的绝对精确度会随着其代表的值变化, 所以比较两个浮点数的最好方法就要利用一个精确的阈值。这个阈值和作比较的浮点数值大小有关。不要用下面的代码:

    double a, b;
    ...
    if (a == b)     /* 错! */

要用类似下列的方法:

    #include <math.h>

    if (fabs(a - b)  <= epsilon * fabs(a))

epsilon 被赋为一个选定的值来控制 ``接近度"。你也要确定  a 不会为 0。

50、c-faq

怎样取整数?

最简单、直接的方法:

    (int)(x + 0.5)

这个方法对于负数并不正常工作。可以使用一个类似的方法:

    (int)(x < 0 ? x - 0.5 : x + 0.5)

51、c-faq

为什么 C 不提供乘幂的运算符?

因为提供乘幂指令的处理器非常少。 C 有一个 pow() 标准函数, 原型说明在 <math.h>。而对于小的正整数指数, 直接用乘法一般会更有效。

52、c-faq

为什么我机器上的 <math.h> 没有预定义常数 M_PI?

这个常数不包含在标准内, 它应该是定义准确到机器精度的 $\pi$ 值。如果你需要用到 $\pi$, 你需要自己定义, 或者用 4*atan(1.0) 或  acos(-1.0) 来计算出来。

53、c-faq

我要寻找一些实现以下功能的程序源代码:快速傅立叶变换 (FFT)、矩阵算术 (乘法、倒置等函数)、复数算术。

Ajay Shah 整理了一个免费算术软件列表。这个列表在互联网有广泛的归档。其中一个 URL 是 ftp://ftp.math.psu.edu/pub/FAQ/numcomp-free-c。

54、c-faq

为什么 %f 可以在 printf() 参数中, 同时表示 float 和  double?他们难道不是不同类型吗?

``参数默认晋级" 规则适用于在可变参数中的可变动部分: char 和  short int 晋级到 int, float 晋级到 double。
同样的晋级也适用于在作用域中没有原型说明的函数调用, 即所谓的 ``旧风格" 函数调用, 参见问题 11.4。
所以 printf 的 %f 格式总是得到  double。类似的, %c 总是得到 int, %hd 也是。参见问题 12.7, 12.13。

55、c-faq

为什么当 n 为 long int, printf("%d", n);  编译时没有匹配警告?我以为 ANSI 函数原型可以防止这样的类型不匹配。

当一个函数用可变参数时, 它的原型说明没有也不能提供可变参数的数目和类型。所以通常的参数匹配保护不适用于可变参数中的可变部分。
编译器不能执行内含的转换或警告不匹配问题16.1。
脚注
... 编译器不能执行内含的转换或警告不匹配问题16.1
    译者注: 现代的编译器 (例如  gcc), 如果打开编译警告参数, 编译器对标准中的可变参数函数 (printf, scanf ... 等) 会进行匹配测试。象问题中的源代码, 用 ``gcc -Wall" 进行编译, 会给出这样的警告: ``warning: int format, long int arg (arg 2)"

56、c-faq

怎样写一个有可变参数的函数?

用 <stdarg.h> 提供的辅助设施。

下面是一个把任意个字符串连接起来的函数, 结果存在 malloc 的内存中:

    #include <stdlib.h>          /* 说明 malloc, NULL, size_t */
    #include <stdarg.h>          /* 说明 va_ 相关类型和函数 */
    #include <string.h>          /* 说明 strcat 等 */

    char *vstrcat(const char *first, ...)
    {
        size_t len;
        char *retbuf;
        va_list argp;
        char *p;

        if(first == NULL)
        return NULL;

        len = strlen(first);

        va_start(argp, first);

        while((p = va_arg(argp, char *)) != NULL)
        len += strlen(p);

        va_end(argp);

        retbuf = malloc(len + 1);   /* +1 包含终止符 \0 */

        if(retbuf == NULL)
        return NULL;        /* 出错 */

        (void)strcpy(retbuf, first);

        va_start(argp, first);        /* 重新开始扫描 */

        while((p = va_arg(argp, char *)) != NULL)
        (void)strcat(retbuf, p);

        va_end(argp);

        return retbuf;
    }

调用如下:

    char *str = vstrcat("Hello, ", "world!", (char *)NULL);

注意最后一个参数的类型重置; 参见问题 5.2, 15.3。注意调用者要释放返回的存储空间, 那是用 malloc 分配的。

57、c-faq

怎样写类似 printf() 的函数, 再把参数转传给 printf() 去完成大部分工作?

用 vprintf(), vfprintf() 或 vsprintf()。

下面是一个 error() 函数, 它列印一个出错信息, 在信息前加入字符串  ``error: " 和在信息后加入换行符:

        #include <stdio.h>
        #include <stdarg.h>

        void error(const char *fmt, ...)
        {
        va_list argp;
        fprintf(stderr, "error: ");
        va_start(argp, fmt);
        vfprintf(stderr, fmt, argp);
        va_end(argp);
        fprintf(stderr, "\n");
        }

58、c-faq

我有个接受 float 的可变参函数, 为什么  va_arg(argp, float) 不工作?

``参数默认晋级" 规则适用于在可变参数中的可变动部分: 参数类型为 float 的总是晋级 (扩展) 到 double, char 和 short int 晋级到 int。
所以  va_arg(arpg, float) 是错误的用法。应该总是用  va_arg(arpg, double)。 同理, 要用 va_arg(argp, int) 来取得原来类型是 char, short 或 int 的参数。
基于相同理由, 传给 va_start() 的最后一个 ``固定" 参数项的类型不会被晋级。参见问题 11.4 和 15.2。

59、c-faq

va_arg() 不能得到类型为函数指针的参数。

宏 va_arg() 所用的类型重写不能很好地操作于象函数指针这类过度复杂的类型。但是如果你用 typedef 定义一个函数指针类型, 那就一切正常了。参见问题 1.7。

 60、c-faq

``Segmentation violation", ``Bus error" 和  ``General protection fault"  意味着什么?

通常, 这意味着你的程序试图访问不该访问的内存地址, 一般是由于堆栈出错或是不正确的使用指针。可能的原因有:
局部数组溢出 (用堆栈分配的自动变量); 不小心, 用了空指针 (参见问题 5.2 和 5.15)、未初始化指针、
地址未对齐的指针或其它没有适当分配的指针 (参见问题 7.1 和 7.2); malloc 内部被破坏 (参见问题 7.16);
函数调用参数不匹配, 特别是如果用了指针, 两个可能出错的函数是  scanf() (参见问题 12.11) 和 fprintf() (确定他的第一个参数是 FILE *)。

61、c-faq

原型说明 extern int func __((int, int)); 中, 那些多出来的括号和下划线代表了什么?

这是为了可以在使用 ANSI 以前的编译器时, 关掉说明中的原型部分。这是技巧的一部分。

在别的地方, 宏 __ 被定义为类似下面的代码:

    #ifdef __STDC__
    #define __(proto) proto
    #else
    #define __(proto) ()
    #endif

原型说明中额外的括号是为了让原型列表被当作宏的单一参数。

62、c-faq

网上有哪些 C 的教程或其它资源?

有许多个:

在 http://cprog.tomsweb.net 有个 Tom Torfs 的不错的教程。

Christopher Sawtell 写的《给 C 程序员的便筏》 (Notes for C programmers)。在下面的地址可以得到:  ftp://svr-ftp.eng.cam.ac.uk/misc/sawtell_C.shar,  ftp://garbo.uwasa.fi/pc/c-lang/c-lesson.zip,  http://www.fi.uib.no/Fysisk/Teori/KURS/OTHER/newzealand.html。

Time Love 的《程序员的 C》 (C for Programmers)。  http://www-h.eng.cam.ac.uk/help/tpl/languages/C/teaching_C/

The Coronado Enterprises C 教程在 Simtel 镜像点目录 pub/msdos/c, 或在 http://www.coronadoenterprises.com/tutorials/c/index.html。

Steve Holmes 的在线教程 http://www.strath.ac.uk/IT/Docs/Ccourse/。

Martin Brown 的网页有一些 C 教程的资料  http://www-isis.ecs.soton.ac.uk/computing/c/Welcome.html。

在一些 UNIX 的机器上, 在 shell 命令行, 可以试试 ``learn c"。注意教程可能比较旧了。

最后, 本 FAQ 的作者以前教授一些 C 的课程, 这些笔记都放在了网上  http://www.eskimo.com/~scs/cclass/cclass.html。

【不承诺申明: 我没有检阅这些我收集的教程, 它们可能含有错误。除了那个有我名字的教程, 我不能为它们提供保证。而这些信息会很快的变得过时, 也许, 当你读到这想用上面的地址时, 它们已经不能用了】

这其中的几个教程, 再加上许多其它 C 的信息, 可以从  http://www.lysator.liu.se/c/index.html 得到。

Vinit Carpenter 维护着一个学习 C 和 C++ 的资源列表, 公布在新闻组  comp.lang.c 和 comp.lang.c++, 也归档在本 FAQ 所在  (参见问题 20.36), 或者  http://www.cyberdiem.com/vin/learn.html。

63、c-faq

哪里可以找到好的源代码实例, 以供研究和学习?

这里有几个连接可以参考: ftp://garbo.uwasa.fi/pc/c-lang/00index.txt, http://www.eskimo.com/~scs/src/。

小心, 网上也有数之不尽的非常糟糕的代码。不要从坏代码中``学习", 这是每个人都可以做到的, 你可以做的更好。 参见问题 18.7,  18.10, 18.16 和 18.18。

64、c-faq

哪里可以找到标准 C 函数库的源代码?

GNU 工程有一个完全实现的 C 函数库 (http://www.gnu.org/software/libc/)。另一个来源是由 P.J. Plauger 写的书《The Standard C Library》[Plauger],
然而它不是公共版权的。

65、c-faq

是否有一个在线的 C 参考指南?

提供两个选择:  http://www.cs.man.ac.uk/standard_c/_index.html,
 http://www.dinkumware.com/htm_cl/index.html

66、c-faq

哪里可以得到 ANSI/ISO C 标准?

可以用 18 美元从 www.ansi.org 联机购买一份电子副本 (PDF)。在美国可以从一下地址获取印刷版本

    American National Standards Institute
    11 W. 42nd St., 13th floor
    New York, NY  10036  USA
    (+1) 212 642 4900



    Global Engineering Documents
    15 Inverness Way E
    Englewood, CO  80112  USA
    (+1) 303 397 2715
    (800) 854 7179  (U.S. & Canada)

其它国家, 可以联系适当的国内标准组织, 或 Geneva 的 ISO, 地址是:

    ISO Sales
    Case Postale 56
    CH-1211 Geneve 20
    Switzerland

或者参见 URL http://www.iso.ch 或查阅 comp.std.internat FAQ 列表, Standards.Faq。

由 Herbert Schild 注释的名不副实的《ANSI C 标准注解》包含 ISO 9899 的 多数内容; 这本书由 Osborne/McGraw-Hill 出版, ISBN 为 0-07-881952-0, 在美国售价大约 40 美圆。有人认为这本书的注解并不值它和官方标准的差 价: 里边错漏百出, 有些标准本身的内容甚至都不全。 网上又很多人甚至建议完全忽略里边的注解。在 http://www.lysator.liu.se/c/schildt.html 可以找到 Clive Feather 对该注解的评论 (``注解的注解")。

``ANSI 基本原理" 的原本文本可以从  ftp://ftp.uu.net/doc/standards/ansi/X3.159-1989 匿名 ftp 下载  (参见问题 18.18), 也可以在万维网从  http://www.lysator.liu.se/c/rat/title.html 得到。这本基本原理由 Silicon Press 出版, ISBN为0-929306-07-4。

C9X 的公众评论草稿可以从 ISO/IEC JTC1/SC22/WG14 的网站得到, 地址为  http://www.dkuug.dk/JTC1/SC22/WG14/。

67、c-faq

我需要分析和评估表达式的代码。

有两个软件包可用: ``defunc", 在 1993 年 12 月 公布于新闻组 comp.sources.misc  (V41 i32,33), 1994 年 1 月公布于新闻组 alt.sources。
可以在这个 URL 得到: ftp://sunsite.unc.edu/pub/packages/development/libraries/defunc-1.3.tar.Z; ``parse", 可以从 lamont.ldgo.columbia.edu 得到。其它选择包括 S-Lang 注释器 (http://www.s-lang.org/),
共享软件 Cmm (``C 减减" 或 ``去掉困难部分的C")。参见问题 18.18 和 20.4。

《Software Solutions in C》[Schumacher, ed.]中也有一些分析和评估的代码  (第 12 章, 235 到 255 页)。

 68、c-faq

谁有 C 编译器的测试套件?

Plum Hall (以前在 Cardiff, NJ, 现在在 Hawaii) 有一个套件出售; Ronald Guilmette 的 RoadTestTM 编译器测试套件 (更多信息在
ftp://netcom.com/pub/rfg/roadtest/announce.txt);  Nullstone 的自动编译器性能分析工具 (http://www.nullstone.com)。  
FSF 的 GNU C (gcc) 发布中含有一个许多编译器通常问题的 C 严酷测试。  Kahan 的偏执狂的测试 (ftp://netlib.att.com/netlib/paranoia), 尽其所能的测试 C 实现的浮点能力。

69、c-faq

哪里有一些有用的源代码片段和例子的收集?

Bob Stout 的 ``SNIPPETS" 是个很流行的收集  (ftp://ftp.brokersys.com/pub/snippets 或 http://www.brokersys.com/snippets/)。

Lars Wirzenius 的 ``publib" 函数库  (ftp://ftp.funet.fi/pub/languages/C/Publib/)。

70、c-faq

我需要执行多精度算术的代码。

一些流行的软件包是: ``quad", 函数在 BSD Unix 系统的 libc 中 (ftp.uu.net, /systems/unix/bsd-sources/.../src/lib/libc/quad/*);
GNU MP 函数库  ``libmp"; MIRACL 软件包 (http://indigo.ie/~mscott/); David Bell 和 Landon Curt Noll 写的```calc" 程序; 以及老 Unix 的 libmp.a。

71、c-faq

怎样分配大于 64K 的数组或结构?

一台合理的电脑应该可以让你透明地访问所有的有效内存。如果, 你很不幸, 你可能需要重新考虑程序使用内存的方式, 或者用各种针对系统的技巧。

64K 仍然是一块相当大的内存。不管你的电脑有多少内存, 分配这么一大段连续的内存是个不小的要求。标准 C 不保证一个单独的对象可以大于 32K, 或者 C99 的 64K。通常, 设计数据结构时的一个好的思想, 是使它不要求所有的内存都连续。对于动态分配的多维数组, 你可以使用指针的指针, 在问题 6.13 中有举例说明。你可以用链接或结构指针数组来代替一个大的结构数组。

如果你使用的是 PC 兼容机 (基于 8086) 系统, 遇到了  64K 或 640K的限制, 可以考虑使用 ``huge"  (巨大) 内存模型, 或者扩展或延伸内存, 或 malloc 的变体函数  halloc() 和 farmalloc(), 或者用一个 32 比特的 ``平直" 编译器 (例如 djgpp, 参见问题 18.3), 或某种 DOS 扩充器, 或换一个操作系统。

72、c-faq

怎样访问位于某的特定地址的内存 (内存映射的设备或图显内存)?

设置一个适当类型的指针去正确的值, 使用明示的类型重制, 以保证编译器知道这个不可移植转换是你的意图:

    unsigned int *magicloc = (unsigned int *)0x12345678;

那么, *magiloc 就指向你所要的地址。如果地址是个内存映射设备的寄存器, 你大概需要使用限定词 volatile。MS-DOS 下, 在和段、偏移量打交道时, 你会发现像 MK_FP 这类宏非常好用。

73、c-faq

怎样在一个 C 程序中调用另一个程序 (独立可执行的程序, 或系统命令)?

使用库函数 system(), 它的功能正是你所要的。注意, 系统返回的值最多是命令的退出状态值 (但这并不是一定的), 通常和命令的输出无关。
还要注意, system() 只接受一个单独的字符串参数来表述调用程序。如果你要建立复杂的命令行, 可以使用 sprintf()。

跟据你使用的系统, 也许你还可以使用系统函数, 例如 exec 或  spawn (或 execl, execv, spawnl, spawnv 等)。

74、c-faq

怎样调用另一个程序或命令, 同时收集它的输出?

Unix 和其它一些系统提供了 popen() 函数, 它在联通运行命令的进程管道设置了 stdio 流, 所以输出可以被读取 (或提供输入)。记住, 结束使用后, 要调用函数 pclose()。
如果你不能使用 popen(), 你应该可以调用 system(), 并输出到一个你可以打开读取的文件。
如果你使用 Unix, 觉得 popen() 不够用, 你可以学习用 pipe(), dup(), fork() 和 exec()。
顺便提一下, freopen() 可能并不工作。

75、c-faq

一个进程如何改变它的调用者的环境变量?

这有可能完全做不到。不同的系统使用不同的方法来实现像 Unix 系统的全局名字/值功能。环境是否可以被运行的进程有效的改变, 以及如果可以, 又怎样去做, 这些都依赖于系统。

在 Unix 下, 一个进程可以改变自己的环境 (某些系统为此提供了  setenv() 或 putenv() 函数), 被改变的环境通常会被传给子进程, 但是这些改变不会传递到父进程。在 MS-DOS 下, 总环境是可以操作的, 但是这需要晦涩难解的技巧。参见 MS-DOS 的  FAQ。

76、c-faq

怎样抓获或忽略像 control-C 这样的键盘中断?

基本步骤是调用 signal():

    #include <signal.h>
    singal(SIGINT, SIG_IGN);

就可以忽略中断信号, 或者:

    extern void func(int);
    signal(SIGINT, func);

使程序在收到中断信号时, 调用函数 func()。

在多任务系统下 (例如 Unix), 最好使用更加深入的技巧:

    extern void func(int);
    if(signal(SIGINT, SIG_IGN) != SIG_IGN)
        signal(SIGINT, func);

这个测试和额外的调用可以保证前台的键盘中断不会因疏忽而中断了在后台运行的进程, 在所有的系统中都用这种调用 signal 的方法并不会带来负作用。

在某些系统中, 键盘中断处理也是终端输入系统模式的功能, 参见问题 19.1。在某些系统中, 程序只有在读入输入时, 才查看键盘中断,因此键盘中断处理就依赖于调用的输入例程 (以及输入例程是否有效)。在 MS-DOS 下, 可以使用 setcbrk() 或 ctrlbrk()。

77、c-faq

怎样很好地处理浮点异常?

在许多系统中, 你可以定义一个 matherr() 的函数, 当出现某些浮点错误时 (例如 <math> 中的数学例程), 它就会被调用。
你也可以使用 signal() 函数 (参见问题 19.37)  截取 SIGFPE 信号。

78、c-faq

怎样写数据文件, 使之可以在不同字大小、字节顺序或浮点格式的机器上读入?

最可移植的方法是是用文本文件 (通常是 ASCII), 用 fprintf()  写入, 用 fscanf() 读入, 或类似的函数。同理, 这也适用于网络协议。
不必太相信那些说文本文件太大或读写太慢的论点。大多数现实情况下, 操作的效率是可接受的, 而可以在不同机器间交换和用标准工具就可以对其进行操作是个巨大的优势。

如果你必须使用二进制文件, 你可以通过使用某些标准格式来提高可移植性, 还可以利用已经写好的 I/O 函数库。这些格式包括: Sun 的 XDR (RFC 1014)、  OSI 的 ASN.1 (在  CCITT X.409 和 ISO 8825 ``Basic Encoding Rules" 中都有引用)、 CDF、 netCDF 或 HDF。

79、c-faq

怎样调用一个由 char * 指针指向函数名的函数?

最直接的方法就是维护一个名字和函数指针的列表:

    int one_func(), two_func();
    int red_func(), blue_func();

    struct { char *name; int (*funcptr)(); } symtab[] = {
        "one_func", one_func,
        "two_func", two_func,
        "red_func", red_func,
        "blue_func",blue_func,
    };

然后搜索函数名, 就可以调用关联的函数指针。

80、c-faq

怎样实现比特数组或集合?

使用 int 或 char 数组, 再加上访问所需比特的几个宏。这里有一些简单的宏定义, 用于 char 数组:

    #include <limits.h>     /* for CHAR_BIT */

    #define BITMASK(b) (1 << ((b) % CHAR_BIT))
    #define BITSLOT(b) ((b) / CHAR_BIT)
    #define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b))
    #define BITTEST(a, b) ((a)[BITSLOT(b)] & BITMASK(b))

如果你没有 <limits.h>, 可以定义  CHAR_BIT 为 8。

81、c-faq

由一个日期, 怎样知道是星期几?

用 mktime() 或 localtime() (参见问题 13.11  和 13.12, 如果 tm_hour 的值位 0, 要注意 DST (夏时制) 的调整);
或者 Zeller 的 congruence (参阅 sci.math FAQ); 或者这个由 Tomohiko Sakamoto 提供的优雅的代码:

    int dayofweek(int y, int m, int d)  /* 0 = Sunday */
    {
    static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
    y -= m < 3;
    return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
    }

82、c-faq

什么是 ``达夫设备" (Duff's Device)?

这是个很棒的迂回循环展开法, 由 Tom Duff 在 Lucasfilm 时所设计。它的 ``传统" 形态, 是用来复制多个字节:

    register n = (count + 7) / 8;   /* count > 0 assumed */
    switch (count % 8)
    {
    case 0:    do { *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
          } while (--n > 0);
    }

这里 count 个字节从 from 指向的数组复制到 to 指向的内存地址 (这是个内存映射的输出寄存器, 这也是为什么它没有被增加)。
它把  swtich 语句和复制 8 个字节的循环交织在一起, 从而解决了剩余字节的处理问题 (当 count 不是 8 的倍数时)。相信不相信, 象这样的把  case 标志放在嵌套在 swtich 语句内的模块中是合法的。当他公布这个技巧给 C 的开发者和世界时, Duff 注意到 C 的 swtich  语法, 特别是 ``跌落" 行为, 一直是被争议的, 而 ``这段代码在争论中形成了某种论据, 但我不清楚是赞成还是反对"。

83、c-faq

``lvalue" 和 ``rvalue" 代表什么意思?

简单的说, ``lvalue" 是个可以出现在赋值语句左方的表达式; 你也可以把它想象成有地址的对象。有关数组的,
参见问题 6.5。``rvalue" 就是有值的表达式, 所以可以用在赋值语句的右方。

 

转载于:https://my.oschina.net/orion/blog/28071

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值