C/C++细节摘录

 


指针与声明:  
// a) 一个整型数(An integer) 
int  a;
// b) 一个指向整型数的指针(A pointer to an integer) 
int   * b;
// c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer) 
int   ** c;
// d) 一个有10个整型数的数组(An array of 10 integers) 
int  d[ 10 ];
// e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers) 
( int   * )e[ 10 ];
// f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers) 
int  ( * a)[ 10 ];
// g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) 
int  ( * g)( int  a);
// h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
int  ( * h[ 10 ])( int  a);

 

 

补码加、减运算规则及溢出判断
1、运算规则

[X+Y]= [X]+ [Y]
[X-Y]= [X]+ [-Y]

若已知[Y],求[-Y]的方法是:将[Y]的各位(包括符号位)逐位取反再在最低位加1即可。
例如:[Y]= 101101 [-Y]= 010011 

2、溢出判断,一般用双符号位进行判断:

符号位00 表示正数 11 表示负数
结果的符号位为01时,称为上溢;为10时,称为下溢

例题:设x=0.1101,y=-0.0111,符号位为双符号位
用补码求x+y,x-y 
[x]补+[y]补=00 1101+11 1001=00 0110 
[x-y]补=[x]补+[-y]补=00 1101+00 0111=01 0100
结果错误,正溢出

http://61.50.188.200/jsj/jsjccyl/jy/yemian/3.1.1.htm

 

 

Promotion, Conversion and Casts

Promotion

  • char or short values (signed or unsigned) are promoted to int (or unsigned) before anything else happens
    • this is done because int is assumed to be the most efficient integral datatype, and it is guaranteed that no information will be lost by going from a smaller datatype to a larger one
    • examples:
      • even if ch is a char, the 'z' value in ch = 'z' is converted to datatype int before being assigned to ch
      • as a more graphic illustration, sizeof('z') returns 4 (on a machine with 8 bit bytes and 32 bit ints)
      • similarly, if stuff is a short and a is a char, stuff = a causes the value from a to be promoted to int
      • promotion doesn't change the size of a variable:
        {
        	char ch;
        	int before, after;
        
        	ch = 'a';
        	before = sizeof(ch);
        	ch = ch + 1;
        	after = sizeof(ch);
        }
        
        sets both before and after to 1 even though, when evaluating ch = ch + 1, the value of ch is promoted to int before adding 1

Conversion

  • after integral promotion, the arguments to an operator are checked
  • if both are the same datatype, evaluation proceeds
  • if the arguments are of different datatypes, conversion will occur:
    • otherwise, if either operand is a long double, the other operand is converted to long double
    • otherwise, if either operand is a double, the other operand is converted to double
    • otherwise, if either operand is a float, the other operand is converted to float
    • otherwise, if either operand is an unsigned long, the other operand is converted to unsigned long
    • otherwise, if one operand is a long and the other is unsigned, one of two things can happen:
      • if sizeof(long) == sizeof(unsigned) (and therefore long has a maximum value less than the maximum value of an unsigned, then both operands are converted to unsigned long
      • if sizeof(long) > sizeof(unsigned), the unsigned operand is converted to long
    • otherwise, if either operand is a long, the other operand is converted to long
    • otherwise, if either operand is an unsigned, the other operand is converted to unsigned
    • otherwise, both operands are evaluated as ints
  • conversion spreads from left to right to raise an expression to the largest datatype:
    • in an entirely int context, 40 / 17 * 13 / 3 would evaluate to 8 (40 / 17 rounds to 2, 2 * 13 = 26, 26 / 3 rounds to 8)
    • to evaluate 40 / 17 * 13 / 3.0
      • 40 / 17 again rounds to 2
      • 2 * 13 = 26
      • but the 3.0 forces the final division into a double context and thus 26.0 / 3.0 = 8.666...
    • if we move the decimal point to the 13 (40 / 17 * 13.0 / 3), the result will still be 8.666... because:
      • 40 / 17 rounds to 2
      • the 13.0 forces the multiplication to a double but 2.0 * 13.0 still equals 26.0
      • and the 26.0 still forces the final division into a double context and 26.0 / 3.0 = 8.666...
    • if we move the decimal point to the 17 (40 / 17.0 * 13 / 3), the result now becomes 10.196... because:
      • the 17.0 forces the initial division into a double context and 40.0 / 17.0 = 2.352...
      • 2.352... * 13.0 = 30.588...
      • and 30.588... / 3.0 = 10.196...

Casts

  • the type of an expression can be forced using casts.
  • a cast is simply any valid datatype enclosed in parentheses and placed next to a constant, variable or expression
  • examples:
    • (float)11 / 3 forces the entire expression to be evaluated in a floating point context, producing a result of 3.6666...
    • to evaluate ((int)7.5 * 2) / (long double)5
      • first, the cast forces 7.5 to an int 7
      • the multiplication is done in an integer context yielding 14
      • the cast forces 5 to a long double 5.0
      • which forces the division to be done in a long double context
      • leading to a final result of 14.0 / 5.0 or 2.8
    • note that casts (as in other conversions) only force conversion at their level, so (float)(40 / 17 * 13 / 3) would still evaluate to 8.0 because the entire expression inside the parentheses takes place in an int context, after which it is cast to a float

 

============================================================================

 3.6 我遇到这样声明结构的代码: struct name { int namelen; char namestr[1];}; 然后又使用一些内存分配技巧使 namestr  数组用起来好像有多个元素。这样合法和可移植吗? 这种技术十分普遍, 尽管 Dennis Ritchie 称之为 ``和C 实现的无保证的亲密接触"。官方的解释认定它没有严格遵守 C 标准, 尽管它看来在所有的实现中都可以工作。仔细检查数组边界的编译器可能会发出警告。

另一种可能是把变长的元素声明为很大, 而不是很小; 在上例中:

    ...
    char namestr[MAXSIZE];

MAXSIZE 比任何可能存储的 name 值都大。但是, 这种技术似乎也不完全符合标准的严格解释。这些 ``亲密'' 结构都必须小心使用, 因为只有程序员知道它的大小, 而编译器却一无所知。

C99 引入了 ``灵活数组域'' 概念, 允许结构的最后一个域省略数组大小。这为类似问题提供了一个圆满的解决方案。

 


 

3.8 如何向接受结构参数的函数传入常数值? 传统的 C 没有办法生成匿名结构值; 你必须使用临时结构变量或一个小的结构生成函数。

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

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

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

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

 


 

.2 使用我的编译器,下面的代码  int i=7; printf("%d/n", i++ * i++); 返回 49?不管按什么顺序计算, 难道不该打印出56吗? 尽管后缀自加和后缀自减操作符 ++ 和 -- 在输出其旧值之后才会执行运算, 但这里的``之后"常常被误解。没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行。也不能保证变量的更新会在表达式 ``完成" (按照 ANSI C 的术语, 在下一个 ``序列点" 之前, 参见问题 3.7) 之前的某个时刻进行。本例中, 编译器选择使用变量的旧值相乘以后再对二者进行自增运算。

包含多个不确定的副作用的代码的行为总是被认为未定义。(简单而言, ``多个不确定副作用" 是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增, 自减和赋值操作符的任何组合。这是一个粗略的定义; 严格的定义参见问题 3.7, ``未定义" 的含义参见问题 11.32。) 甚至都不要试图探究这些东西在你的编译器中是如何实现的 (这与许多 C 教科书上的弱智练习正好相反); 正如  K&R 明智地指出, ``如果你不知道它们在不同的机器上如何实现, 这样的无知可能恰恰会有助于保护你。begintex2html_deferred

 


  4. 3 对于代码  int i = 3; i = i++; 不同编译器给出不同的结果, 有的为 3, 有的为 4, 哪个是正确的? 没有正确答案;这个表达式无定义。参见问题  3.1, 3.7 和   11.32。 同时注意, i++ 和 ++i 都不同于 i+1。如果你要使 i 自增 1, 使用 i=i+1, i+=1, i++ 或 ++i, 而不是任何组合, 参见问题  3.10

 

 


 

4.4 这是个巧妙的表达式:  a ^= b ^= a ^= b  它不需要临时变量就可以交换 a 和 b 的值。 这不具有可移植性。它试图在序列点之间两次修改变量 a, 而这是无定义的。

例如,有人报告如下代码:

    int a = 123, b = 7654;
    a ^= b ^= a ^= b;

在 SCO 优化 C 编译器 (icc) 下会把 b 置为 123, 把 a 置为 0。

参见问题 3.13.7 和 20.14

 


 

4.5 我可否用括号来强制执行我所需要的计算顺序? 一般来讲, 不行。运算符优先级和括弧只能赋予表达是计算部分的顺序. 在如下的代码中

    f() + g() * h()

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

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

参考资料: [K&R1, Sec. 2.12 p. 49, Sec. A.7 p]; [K&R2, Sec. 2.12 pp. 52-3, Sec. A.7 p. 200.]。

 


 

4.12 我需要根据条件把一个复杂的表达式赋值给两个变量中的一个。可以用下边这样的代码吗?  ((condition) ? a : b) = complicated_expression; 不能。? : 操作符, 跟多数操作符一样, 生成一个值, 而不能被赋值。换言之, ? : 不能生成一个 ``左值"。如果你真的需要, 你可以试试下面这样的代码:

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

尽管这毫无优雅可言。

参考资料: [ISO, Sec. 6.3.15]; [H&S, Sec. 7.1 pp. 179-180]。

 


 

5.2 *p++ 自增 p 还是 p 所指向的变量? 后缀 ++ 和 -- 操作符本质上比前缀一目操作的优先级高, 因此 *p++ 和 *(p++) 等价, 它自增 p 并返回  p 自增之前所指向的值。要自增 p 指向的值, 使用  (*p)++, 如果副作用的顺序无关紧要也可以使用 ++*p。

参考资料: [K&R1, Sec. 5.1 p. 91]; [K&R2, Sec. 5.1 p. 95]; [ISO, Sec. 6.3.2, Sec. 6.3.3]; [H&S, Sec. 7.4.4 pp. 192-3, Sec. 7.5 p. 193, Secs. 7.5.7,7.5.8 pp. 199-200]。

 


 

5.5 我能否用 void** 指针作为参数, 使函数按引用接受一般指针? 不可移植。C 中没有一般的指针的指针类型。void* 可以用作一般指针只是因为当它和其它类型相互赋值的时候, 如果需要, 它可以自动转换成其它类型; 但是, 如果试图这样转换所指类型为 void* 之外的类型的  void** 指针时, 这个转换不能完成。

 


 

5.8 我看到了用指针调用函数的不同语法形式。到底怎么回事? 最初, 一个函数指针必须用 * 操作符 (和一对额外的括弧) ``转换为" 一个 ``真正的" 函数才能调用:

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

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

    r = fp();

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

参见问题 1.14

参考资料: [K&R1, Sec. 5.12 p. 116]; [K&R2, Sec. 5.11 p. 120]; [ISO, Sec. 6.3.2.2]; [Rationale, Sec. 3.3.2.2]; [H&S, Sec. 5.8 p. 147, Sec. 7.4.3 p. 190]。

 


 

7.2 可是我听说 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

参考资料: [K&R2, Sec. 5.5 p. 104]; [CT&P, Sec. 4.5 pp. 64-5]。

 


 

7.3 那么, 在 C 语言中 ``指针和数组等价" 到底是什么意思 ? 在 C 语言中对数组和指针的困惑多数都来自这句话。说数组和指针 ``等价"  不表示它们相同, 甚至也不能互换。它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组。

特别地, 等价的基础来自这个关键定义:

一个 T 的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况); 结果指针的类型是 T 的指针。

这就是说, 一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了 &a[0] 一样。例外的情况是, 数组为  sizeof 或 & 操作符的操作数, 或者为字符数组的字符串初始值。

作为这个这个定义的后果, 编译器并那么不严格区分数组下标操作符和指针。在形如 a[i] 的表达式中, 根据上边的规则, 数组蜕化为指针然后按照指针变量的方式如 p[i] 那样寻址, 如问题 6.2 所述, 尽管最终的内存访问并不一样。 如果你把数组地址赋给指针:

    p = a;

那么 p[3] 和 a[3] 将会访问同样的成员。

参见问题 6.6 和 6.11

参考资料: [K&R1, Sec. 5.3 pp. 93-6]; [K&R2, Sec. 5.3 p. 99]; [ISO, Sec. 6.2.2.1, Sec. 6.3.2.1, Sec. 6.3.6]; [H&S, Sec. 5.4.1 p. 124]。

 


 

7.4 那么为什么作为函数形参的数组和指针申明可以互换呢 ? 这是一种便利。

由于数组会马上蜕变为指针, 数组事实上从来没有传入过函数。允许指针参数声明为数组只不过是为让它看起来好像传入了数组, 因为该参数可能在函数内当作数组使用。特别地, 任何声明 ``看起来象" 数组的参数, 例如

    void f(char a[])
    { ... }

在编译器里都被当作指针来处理, 因为在传入数组的时候,那正是函数接收到的.

    void f(char *a)
    { ... }

这种转换仅限于函数形参的声明, 别的地方并不适用。如果这种转换令你困惑, 请避免它; 很多程序员得出结论, 让形参声明 ``看上去象" 调用或函数内的用法所带来的困惑远远大于它所提供的方便。

参见问题 6.18

参考资料: [K&R1, Sec. 5.3 p. 95, Sec. A10.1 p. 205]; [K&R2, Sec. 5.3 p. 100, Sec. A8.6.3 p. 218, Sec. A10.1 p. 226]; [ISO, Sec. 6.5.4.3, Sec. 6.7.1, Sec. 6.9.6]; [H&S, Sec. 9.3 p. 271]; [CT&P, Sec. 3.3 pp. 33-4]。

 


 

7.8 我遇到一些 ``搞笑" 的代码, 包含 5["abcdef"]  这样的 ``表达式"。 这为什么是合法的 C 表达式呢 ?

是的, 弗吉尼亚7.1, 数组和下标在 C 语言中可以互换。这个奇怪的事实来自数组下标的指针定义, 即对于任何两个表达式 a 和 e, 只要其中一个是指针表达式而另一个为整数, 则 a[e] 和 *((a)+(e)) 完全一样。这种交换性在许多 C 语言的书中被看作值得骄傲的东西, 但是它除了在混乱 C 语言竞赛之外, 其实鲜有用武之地。

参考资料: [Rationale, Sec. 3.3.2.1]; [H&S, Sec. 5.4.1 p. 124, Sec. 7.4.1 pp. 186-7]。  

#include  < stdio.h >

int  main()
{
    printf(
" %c " , 5 [ " abcdef " ] );
    system(
" pause " );
}

输出f

 


 

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

    #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 中你可以使用变长数组。

所有这些技术都可以延伸到三维或更多的维数。

参考资料: [C9X, Sec. 6.5.5.2]。

 


 

7.15 当我向一个接受指针的指针的函数传入二维数组的时候, 编译器报错了。 数组蜕化为指针的规则 (参见问题 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 (对于三维或多维数组, 相关的维度) 必须保留。

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

参见问题 6.9 和 6.12

参考资料: [K&R1, Sec. 5.10 p. 110]; [K&R2, Sec. 5.9 p. 113]; [H&S, Sec. 5.4.3 p. 126]。

 


 

7.17 我怎样在函数参数传递时混用静态和动态多维数组 ? 没有完美的方法。假设有如下声明

	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() 的形式。

 

 13.1 这样的代码有什么问题? char c; while((c = getchar()) != EOF) ... 第一, 保存 getchar 的返回值的变量必须是 int 型。getchar() 可能返回任何字符值, 包括 EOF。如果把 getchar 的返回值截为 char 型, 则正常的字符可能会被错误的解释为 EOF, 或者 EOF 可能会被修改 (尤其是 char 型为无符号的时候), 从而永不出现。

参考资料: [K&R1, Sec. 1.5 p. 14]; [K&R2, Sec. 1.5.1 p. 16]; [ISO, Sec. 6.1.2.5, Sec. 7.9.1, Sec. 7.9.7.5]; [H&S, Sec. 5.1.3 p. 116, Sec. 15.1, Sec. 15.6]; [CT&P, Sec. 5.1 p. 70]; [PCS, Sec. 11 p. 157]。

.2 我有个读取直到 EOF 的简单程序, 但是我如何才能在键盘上输入那个  ``EOF" 呢? 其实, 你的 C 程序看到的 EOF 的值和你用键盘发出的文件结束按键组合之间没有任何直接联系。根据你的操作系统, 你可能使用不同的按键组合来表示文件结束, 通常是 Control-D 或 Control-Z。

 

 


 

8.3 但是 strcat 的手册页说它接受两个 char * 型参数。我怎么知道 (空间) 分配的事情呢? 一般地说, 使用指针的时候, 你必须总是考虑内存分配, 除非明确知道编译器替你做了此事。如果一个库函数的文档没有明确提到内存分配, 那么通常需要调用者来考虑。

 

Unix 型的手册页顶部的大纲段落或 ANSI C 标准有些误导作用。 那里展示的程序片段更像是实现者使用的函数定义而不是调用者使用的形式。特别地, 很多接受指针 (如结构指针或串指针) 的函数通常在调用时都用到某个由调用者分配的对象 (结构, 或数组 --- 参见问题 6.3  和 6.4) 的指针。其它的常见例子还有 time() (参见问题  13.10) 和 stat()。  

8. 26 calloc() 和 malloc() 有什么区别?利用 calloc 的零填充功能安全吗? free() 可以释放 calloc() 分配的内存吗, 还是需要一个 cfree()? calloc(m, n) 本质上等价于
    p = malloc(m * n);
    memset(p, 0, m * n);

填充的零是全零, 因此不能确保生成有用的空指针值或浮点零值  (参见第 5 章)。 free() 可以安全地用来释放 calloc() 分配的内存。

参考资料: [ISO, Sec. 7.10.3 to 7.10.3.2]; [H&S, Sec. 16.1 p. 386, Sec. 16.2 p. 386]; [PCS, Sec. 11 pp. 141,142]。

 

9.5 我认为我的编译器有问题: 我注意到 sizeof('a') 是 2 而不是  1 (即, 不是 sizeof(char))。 可能有些令人吃惊, C 语言中的字符常数是 int 型, 因此 sizeof('a') 是  sizeof(int), 这是另一个与 C++ 不同的地方。 参见问题 7.11

 


 

13.8 对于 size_t 那样的类型定义, 当我不知道它到底是 long  还是其它类型的时候, 我应该使用什么样的 printf 格式呢? 把那个值转换为一个已知的长度够大的类型, 然后使用与之对应的 printf 格式。例如, 输出某种类型的长度, 你可以使用

	printf("%lu", (unsigned long)sizeof(thetype));
 
 
13. 7 有人告诉我在 printf 中使用 %lf 不正确。那么, 如果 scanf()  需要 %lf, 怎么可以用在 printf() 中用 %f 输出双精度数呢? printf 的 %f 标识符的确既可以输出浮点数又可以输出双精度数。根据  ``缺省参数扩展" 规则, 不论范围内有没有原形都会在在类似 printf 的可变长度参数列表中采用, 浮点型的变量或扩展为双精度型, 因此 printf()  只会看到双精度数。printf() 的确接受 %Lf, 用于输出长双精度数。参见问题  12.11 和  15.2

参考资料: [K&R1, Sec. 7.3 pp. 145-47, Sec. 7.4 pp. 147-50]; [K&R2, Sec. 7.2 pp. 153-44, Sec. 7.4 pp. 157-59]; [ISO, Sec. 7.9.6.1, Sec. 7.9.6.2]; [H&S, Sec. 15.8 pp. 357-64, Sec. 15.11 pp. 366-78]; [CT&P, Sec. A.1 pp. 121-33]。

 


 

13.16 我用 scanf %d 读取一个数字, 然后再用 gets() 读取字符串, 但是编译器好像跳过了 gets() 调用! scanf %d 不处理结尾的换行符。如果输入的数字后边紧接着一个换行符, 则换行符会被 gets() 处理。

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

参见问题 12.18 和 12.20

参考资料: [ISO, Sec. 7.9.6.2]; [H&S, Sec. 15.8 pp. 357-64]。

 

 


19. 5 哪里可以找到兼容 ANSI 的 lint? PC-Lint 和 FlexeLint 是 Gimpel Software 公司的产品 ( http://www.gimpel.com/)。

Unix System V 版本 4 的 lint 兼容 ANSI。可以从 UNIX Support Labs 或 System V 的销售商单独得到 (和其它 C 工具捆绑在一起)。

另外一个兼容 ANSI 的 lint 是 Splint (以前叫 lclint, http://www.splint.org/)。它可以作一些高级别的正式检验。

如果没有 lint, 许多现代的编译器可以作出几乎和 lint 一样多的诊断。许多网友推荐 gcc -Wall -pedantic。

 


 

19.1 常用工具列表。

工具程序名 (参见问题 18.18)
C 交叉引用生成器cflow, cxref, calls, cscope, xscope, ixfw
C 源代码美化器/美化打印cb, indent, GNU indent, vgrind
版本控制和管理工具CVS, RCS, SCCS
C 源代码扰乱器 (遮蔽器)obfus, shroud, opqcp
``make" 从属关系生成器makedepend, cc -M 或 cpp -M
计算源代码度规工具ccount, Metre, lcount, csize; McCable and Associates 也有一个商业包出售
C 源代码行数计数器可以用 UNIX 的标准工具 wc 作个大概的计算, 比用 grep -c ";" 要好
C 说明帮助 (cdecl)见 comp.sources.unix 第14卷  (参见问题 18.18) 和 [K&R2]
原型发生器参见问题 11.30
malloc 问题抓捕工具参见问题 18.2
``选择性" 的 C 预处理器参见问题 10.16
语言翻译工具参见问题 11.30 和 20.23
C 校对机 (lint)参见问题 18.5
C 编译器参见问题 18.3

这个工具列表并不完全, 如果你知道有没列出的工具, 欢迎联系本表的维护者。

其它工具列表和关于它们的讨论可以在 Usenet 的新闻组  comp.compilers 和 comp.software-eng 找到。

参见问题 18.3 和 18.18

 


 

char * GetStr()
{
char *tmp;
char s[] = "abc";
tmp = "123"
return tmp;
}

void main()
{
printf("%s", GetStr());
}

会输出123吗?123创建在堆上还是栈上呢?123的空间是什么时候释放的?

我的答案:是在堆上创建的.而abc是在栈上创建的.


字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么?

 

#include  < stdio.h >

int  main()
{
    
int  ( * p)( int  );
    printf(
" %d,%d,%d,%d " sizeof ( int   * ), sizeof ( char * ), sizeof ( double * ), sizeof (p));
    system(
" pause " );
}

/*
 *输出:4,4,4,4, 
 
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值