查看s3某目录占用的空间大小

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
previous up contents next C 语言常见问题集 原著:Steve Summit 翻译:朱群英, 孙 云 修订版 0.9.4, 2005年6月23日 版权所有 © 2005 * 目录 * 1. 前言 * 2. 声明和初始化 o 2.1 我如何决定使用那种整数类型? o 2.2 64 位机上的 64 位类型是什么样的? o 2.3 怎样定义和声明全局变量和函数最好? o 2.4 extern 在函数声明中是什么意思? o 2.5 关键字 auto 到底有什么用途? o 2.6 我似乎不能成功定义一个链表。我试过 typedef struct { char *item; NODEPTR next; } *NODEPTR; 但是编译器报了错误信息。难道在C语言中一个结构不能包含指向自己的指针吗? o 2.7 怎样建立和理解非常复杂的声明?例如定义一个包含 N 个指向返回指向字符的指针的函数的指针的数组? o 2.8 函数只定义了一次, 调用了一次, 但编译器提示非法重定义了。 o 2.9 main() 的正确定义是什么? void main() 正确吗? o 2.10 对于没有初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为 ``零", 它可否作为空指针或浮点零? o 2.11 代码 int f() { char a[] = "Hello, world!";} 不能编译。 o 2.12 这样的初始化有什么问题?char *p = malloc(10); 编译器提示 ``非法初始式" 云云。 o 2.13 以下的初始化有什么区别?char a[] = "string literal"; char *p = "string literal"; 当我向 p[i] 赋值的时候, 我的程序崩溃了。 o 2.14 我总算弄清除函数指针的声明方法了, 但怎样才能初始化呢? * 3. 结构、联合和枚举 o 3.1 声明 struct x1 { ...}; 和 typedef struct { ...} x2; 有什么不同? o 3.2 为什么 struct x { ...}; x thestruct; 不对? o 3.3 一个结构可以包含指向自己的指针吗? o 3.4 在 C 语言中实现抽象数据类型什么方法最好? o 3.5 在 C 中是否有模拟继承等面向对象程序设计特性的好方法? o 3.6 我遇到这样声明结构的代码: struct name { int namelen; char namestr[1];}; 然后又使用一些内存分配技巧使 namestr 数组用起来好像有多个元素。这样合法和可移植吗? o 3.7 是否有自动比较结构的方法? o 3.8 如何向接受结构参数的函数传入常数值? o 3.9 怎样从/向数据文件读/写结构? o 3.10 我的编译器在结构中留下了空洞, 这导致空间浪费而且无法与外部数据文件进行 "二进制" 读写。能否关掉填充, 或者控制结构域的对齐方式? o 3.11 为什么 sizeof 返回的值大于结构的期望值, 是不是尾部有填充? o 3.12 如何确定域在结构中的字节偏移? o 3.13 怎样在运行时用名字访问结构中的域? o 3.14 程序运行正确, 但退出时却 ``core dump''了,怎么回事? o 3.15 可以初始化一个联合吗? o 3.16 枚举和一组预处理的 #define 有什么不同? o 3.17 有什么容易的显示枚举值符号的方法? * 4. 表达式 o 4.1 为什么这样的代码: a[i] = i++; 不能工作? o 4.2 使用我的编译器,下面的代码 int i=7; printf("%d\n", i++ * i++); 返回 49?不管按什么顺序计算, 难道不该打印出56吗? o 4.3 对于代码 in
老资源。 目录 1 声明和初始化1 1.1 我如何决定使用那种整数类型? . . . . . . . . . . . . . . . . . . . 1 1.2 64 位机上的64 位类型是什么样的? . . . . . . . . . . . . . . . . 1 1.3 怎样定义和声明全局变量和函数最好? . . . . . . . . . . . . . . . 2 1.4 extern 在函数声明中是什么意思? . . . . . . . . . . . . . . . . . 2 1.5 关键字auto 到底有什么用途? . . . . . . . . . . . . . . . . . . . 2 1.6 我似乎不能成功定义一个链表。我试过typedef struct f char *item; NODEPTR next; g *NODEPTR; 但是编译器报了错误信 息。难道在C语言中一个结构不能包含指向自己的指针吗? . . . . 3 1.7 怎样建立和理解非常复杂的声明?例如定义一个包含N 个指向返 回指向字符的指针的函数的指针的数组? . . . . . . . . . . . . . . 3 1.8 函数只定义了一次, 调用了一次, 但编译器提示非法重定义了。. . 4 1.9 main() 的正确定义是什么? void main() 正确吗? . . . . . . . . . 4 1.10 对于没有初始化的变量的初始值可以作怎样的假定?如果一个全 局变量初始值为“零”, 它可否作为空指针或浮点零? . . . . . . . 4 1.11 代码int f() f char a[] = "Hello, world!";g 不能编译。. . . . . . . 5 1.12 这样的初始化有什么问题?char *p = malloc(10); 编译器提示“非 法初始式” 云云。. . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.13 以下的初始化有什么区别?char a[] = "string literal"; char *p = "string literal"; 当我向p[i] 赋值的时候, 我的程序崩溃了。. . . . 5 1.14 我总算弄清除函数指针的声明方法了, 但怎样才能初始化呢? . . 5 2 结构、联合和枚举7 2.1 声明struct x1 f . . . g; 和typedef struct f . . . g x2; 有什么不同? . 7 2.2 为什么struct x f . . . g; x thestruct; 不对? . . . . . . . . . . . . . 7 2.3 一个结构可以包含指向自己的指针吗? . . . . . . . . . . . . . . . 7 2.4 在C 语言中实现抽象数据类型什么方法最好? . . . . . . . . . . . 7 2.5 在C 中是否有模拟继承等面向对象程序设计特性的好方法? . . . 7 i 目录ii 2.6 我遇到这样声明结构的代码: struct name f int namelen; char namestr[1];g; 然后又使用一些内存分配技巧使namestr 数组用起 来好像有多个元素。这样合法和可移植吗? . . . . . . . . . . . . 8 2.7 是否有自动比较结构的方法? . . . . . . . . . . . . . . . . . . . . 8 2.8 如何向接受结构参数的函数传入常数值? . . . . . . . . . . . . . . 8 2.9 怎样从/向数据文件读/写结构? . . . . . . . . . . . . . . . . . . . 9 2.10 我的编译器在结构中留下了空洞, 这导致空间浪费而且无法与外 部数据文件进行”二进制” 读写。能否关掉填充, 或者控制结构域 的对齐方式? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.11 为什么sizeof 返回的值大于结构的期望值, 是不是尾部有填充? . . 9 2.12 如何确定域在结构中的字节偏移? . . . . . . . . . . . . . . . . . 9 2.13 怎样在运行时用名字访问结构中的域? . . . . . . . . . . . . . . . 10 2.14 程序运行正确, 但退出时却“core dump”了,怎么回事? . . . . . 10 2.15 可以初始化一个联合吗? . . . . . . . . . . . . . . . . . . . . . . . 10 2.16 枚举和一组预处理的#define 有什么不同? . . . . . . . . . . . . 10 2.17 有什么容易的显示枚举值符号的方法? . . . . . . . . . . . . . . . 11 3 表达式13 3.1 为什么这样的代码: a[i] = i++; 不能工作? . . . . . . . . . . . . 13 3.2 使用我的编译器,下面的代码int i=7; printf("%dnn", i++ * i++); 返回49?不管按什么顺序计算, 难道不该打印出56吗? . . . . . . 13 3.3 对于代码int i = 3; i = i++; 不同编译器给出不同的结果, 有的为 3, 有的为4, 哪个是正确的? . . . . . . . . . . . . . . . . . . . . . 14 3.4 这是个巧妙的表达式: a ˆ= b ˆ= a ˆ= b 它不需要临时变量就可 以交换a 和b 的值。. . . . . . . . . . . . . . . . . . . . . . . . . 14 3.5 我可否用括号来强制执行我所需要的计算顺序? . . . . . . . . . . 14 3.6 可是&& 和|| 运算符呢?我看到过类似while((c = getchar()) != EOF && c != ’nn’) 的代码⋯⋯ . . . . . . . . . . . . . . . . . . 14 3.7 我怎样才能理解复杂表达式?“序列点” 是什么? . . . . . . . . . 15 3.8 那么, 对于a[i] = i++; 我们不知道a[] 的哪一个分量会被改写,但i 的确会增加1, 对吗? . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.9 ++i 和i++ 有什么区别? . . . . . . . . . . . . . . . . . . . . . . 15 3.10 如果我不使用表达式的值, 我应该用++i 或i++ 来自增一个变量 吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.11 为什么如下的代码int a = 100, b = 100; long int c = a * b; 不能 工作? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.12 我需要根据条件把一个复杂的表达式赋值给两个变量中的一 个。可以用下边这样的代码吗? ((condition) ? a : b) = complicated expression; . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 目录iii 4 指针17 4.1 我想声明一个指针并为它分配一些空间, 但却不行。这些代码有 什么问题?char *p; *p = malloc(10); . . . . . . . . . . . . . . . . 17 4.2 *p++ 自增p 还是p 所指向的变量? . . . . . . . . . . . . . . . . 17 4.3 我有一个char * 型指针正巧指向一些int 型变量, 我想跳过它们。 为什么如下的代码((int *)p)++; 不行? . . . . . . . . . . . . . . 17 4.4 我有个函数,它应该接受并初始化一个指针void f(int *ip) f static int dummy = 5; ip = &dummy;g 但是当我如下调用时: int *ip; f(ip); 调用者的指针却没有任何变化。. . . . . . . . . . . . . . . 18 4.5 我能否用void** 指针作为参数, 使函数按引用接受一般指针? . . 18 4.6 我有一个函数extern int f(int *); 它接受指向int 型的指针。我怎 样用引用方式传入一个常数?下面这样的调用f(&5); 似乎不行。. 18 4.7 C 有“按引用传递” 吗? . . . . . . . . . . . . . . . . . . . . . . . 18 4.8 我看到了用指针调用函数的不同语法形式。到底怎么回事? . . . 19 4.9 我怎样把一个int 变量转换为char * 型?我试了类型转换, 但是不 行。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 5 空(null) 指针21 5.1 臭名昭著的空指针到底是什么? . . . . . . . . . . . . . . . . . . . 21 5.2 怎样在程序里获得一个空指针? . . . . . . . . . . . . . . . . . . . 21 5.3 用缩写的指针比较“if(p)” 检查空指针是否可靠?如果空指针的内 部表达不是0 会怎么样? . . . . . . . . . . . . . . . . . . . . . . . 22 5.4 NULL 是什么, 它是怎么定义的? . . . . . . . . . . . . . . . . . . 23 5.5 在使用非全零作为空指针内部表达的机器上, NULL 是如何定义 的? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.6 如果NULL 定义成#define NULL ((char *)0) 难道不就可以向函 数传入不加转换的NULL 了吗? . . . . . . . . . . . . . . . . . . 23 5.7 如果NULL 和0 作为空指针常数是等价的, 那我到底该用哪一个 呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.8 但是如果NULL 的值改变了, 比如在使用非零内部空指针的机器 上, 难道用NULL (而不是0) 不是更好吗? . . . . . . . . . . . . . 24 5.9 用预定义宏#define Nullptr(type) (type *)0 帮助创建正确类型的 空指针。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.10 这有点奇怪。NULL 可以确保是0, 但空(null) 指针却不一定? . . 24 5.11 为什么有那么多关于空指针的疑惑?为什么这些问题如此经常地 出现? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 5.12 我很困惑。我就是不能理解这些空指针一类的东西。. . . . . . . 25 5.13 考虑到有关空指针的所有这些困惑, 难道把要求它们内部表达都 必须为0 不是更简单吗? . . . . . . . . . . . . . . . . . . . . . . . 26 5.14 说真的, 真有机器用非零空指针吗, 或者不同类型用不同的表达? 26 目录iv 5.15 运行时的“空指针赋值” 错误是什么意思? . . . . . . . . . . . . . 26 6 数组和指针27 6.1 我在一个源文件中定义了char a[6], 在另一个中声明了extern char *a 。为什么不行? . . . . . . . . . . . . . . . . . . . . . . . 27 6.2 可是我听说char a[ ] 和char *a 是一样的。. . . . . . . . . . . . . 27 6.3 那么, 在C 语言中“指针和数组等价” 到底是什么意思? . . . . . 28 6.4 那么为什么作为函数形参的数组和指针申明可以互换呢? . . . . . 28 6.5 如果你不能给它赋值, 那么数组如何能成为左值呢? . . . . . . . . 29 6.6 现实地讲, 数组和指针地区别是什么? . . . . . . . . . . . . . . . 29 6.7 有人跟我讲, 数组不过是常指针。. . . . . . . . . . . . . . . . . . 29 6.8 我遇到一些“搞笑” 的代码, 包含5["abcdef"] 这样的“表达式”。 这为什么是合法的C 表达式呢? . . . . . . . . . . . . . . . . . . 29 6.9 既然数组引用会蜕化为指针, 如果arr 是数组, 那么arr 和&arr 又 有什么区别呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 6.10 我如何声明一个数组指针? . . . . . . . . . . . . . . . . . . . . . 30 6.11 我如何在运行期设定数组的大小?我怎样才能避免固定大小的数 组? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 6.12 我如何声明大小和传入的数组一样的局部数组? . . . . . . . . . . 30 6.13 我该如何动态分配多维数组? . . . . . . . . . . . . . . . . . . . . 31 6.14 有个灵巧的窍门: 如果我这样写int realarray[10]; int *array = &realarray[-1]; 我就可以把“array” 当作下标从1 开始的数组。. . 32 6.15 当我向一个接受指针的指针的函数传入二维数组的时候, 编译器 报错了。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.16 我怎样编写接受编译时宽度未知的二维数组的函数? . . . . . . . 32 6.17 我怎样在函数参数传递时混用静态和动态多维数组? . . . . . . . 33 6.18 当数组是函数的参数时, 为什么sizeof 不能正确报告数组的大小? 34 7 内存分配35 7.1 为什么这段代码不行?char *answer; printf("Type something:nn"); gets(answer); printf("You typed n"%sn"nn", answer); . . . . . . . 35 7.2 我的strcat() 不行.我试了char *s1 = "Hello, "; char *s2 = "world!"; char *s3 = strcat(s1, s2); 但是我得到了奇怪的结果。. . . . . . . 35 7.3 但是strcat 的手册页说它接受两个char * 型参数。我怎么知道 (空间) 分配的事情呢? . . . . . . . . . . . . . . . . . . . . . . . . 36 7.4 我刚才试了这样的代码char *p; strcpy(p, "abc"); 而它运行正 常?怎么回事?为什么它没有崩溃? . . . . . . . . . . . . . . . . 36 7.5 一个指针变量分配多少内存? . . . . . . . . . . . . . . . . . . . . 36 7.6 我有个函数, 本该返回一个字符串, 但当它返回调用者的时候, 返 回串却是垃圾信息。. . . . . . . . . . . . . . . . . . . . . . . . . 36 目录v 7.7 那么返回字符串或其它集合的争取方法是什么呢? . . . . . . . . 37 7.8 为什么在调用malloc() 时, 我得到“警告: 整数赋向指针需要类型 转换”? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 7.9 为什么有些代码小心地把malloc 返回的值转换为分配的指针类型。37 7.10 在调用malloc() 的时候, 错误“不能把void * 转换为int *” 是什 么意思? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 7.11 我见到了这样的代码char *p = malloc(strlen(s) + 1); strcpy(p, s); 难道不应该是malloc((strlen(s) + 1) * sizeof(char))? . . . . . 37 7.12 我如何动态分配数组? . . . . . . . . . . . . . . . . . . . . . . . . 38 7.13 我听说有的操作系统程序使用的时候才真正分配malloc 申请的内 存。这合法吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 7.14 我用一行这样的代码分配一个巨大的数组, 用于数字运算: double *array = malloc(300 * 300 * sizeof( double )); malloc() 并没有返 回null, 但是程序运行得有些奇怪, 好像改写了某些内存, 或者 malloc() 并没有分配我申请的那么多内存, 云云。. . . . . . . . . 38 7.15 我的PC 有8 兆内存。为什么我只能分配640K 左右的内存? . . 38 7.16 我的程序总是崩溃, 显然在malloc 内部的某个地方。但是我看不 出哪里有问题。是malloc() 有bug 吗? . . . . . . . . . . . . . . . 38 7.17 动态分配的内存一旦释放之后你就不能再使用, 是吧? . . . . . . 38 7.18 为什么在调用free() 之后指针没有变空?使用(赋值, 比较) 释放 之后的指针有多么不安全? . . . . . . . . . . . . . . . . . . . . . 39 7.19 当我malloc() 为一个函数的局部指针分配内存时, 我还需要用 free() 明确的释放吗? . . . . . . . . . . . . . . . . . . . . . . . . 39 7.20 我在分配一些结构, 它们包含指向其它动态分配的对象的指针。 我在释放结构的时候, 还需要释放每一个下级指针吗? . . . . . . 39 7.21 我必须在程序退出之前释放分配的所有内存吗? . . . . . . . . . . 40 7.22 我有个程序分配了大量的内存, 然后又释放了。但是从操作系统 看, 内存占用率却并没有回去。. . . . . . . . . . . . . . . . . . 40 7.23 free() 怎么知道有多少字节需要释放? . . . . . . . . . . . . . . . 40 7.24 那么我能否查询malloc 包, 可分配的最大块是多大? . . . . . . . 40 7.25 向realloc() 的第一个参数传入空指针合法吗?你为什么要这样做? 40 7.26 calloc() 和malloc() 有什么区别?利用calloc 的零填充功能安全 吗?free() 可以释放calloc() 分配的内存吗, 还是需要一个cfree()? 40 7.27 alloca() 是什么?为什么不提倡使用它? . . . . . . . . . . . . . . 41 8 字符和字符串43 8.1 为什么strcat(string, ’!’); 不行? . . . . . . . . . . . . . . . . . . 43 8.2 我在检查一个字符串是否跟某个值匹配。为什么这样不行?char *string; . . . if(string == "value") f /* string matches ”value” */ . . . g . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 目录vi 8.3 如果我可以写char a[] = "Hello, world!"; 为什么我不能写char a[14]; a = "Hello, world!"; . . . . . . . . . . . . . . . . . . . . . . 43 8.4 我怎么得到对应字符的数字(字符集) 值, 或者相反? . . . . . . . 44 8.5 我认为我的编译器有问题: 我注意到sizeof(’a’) 是2 而不是1 (即, 不是sizeof(char))。. . . . . . . . . . . . . . . . . . . . . . . . . . 44 9 布尔表达式和变量45 9.1 C 语言中布尔值的候选类型是什么?为什么它不是一个标准类 型?我应该用#define 或enum 定义true 和false 值吗? . . . . . 45 9.2 因为在C 语言中所有的非零值都被看作“真”, 是不是把TRUE 定 义为1 很危险?如果某个内置的函数或关系操作符“返回” 不是1 的其它值怎么办? . . . . . . . . . . . . . . . . . . . . . . . . . . 45 9.3 当p 是指针时, if(p) 是合法的表达式吗? . . . . . . . . . . . . . 46 10 C 预处理器47 10.1 这些机巧的预处理宏: #define begin f #define end g 你觉得怎么 样? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 10.2 怎么写一个一般用途的宏交换两个值? . . . . . . . . . . . . . . . 47 10.3 书写多语句宏的最好方法是什么? . . . . . . . . . . . . . . . . . 47 10.4 我第一次把一个程序分成多个源文件, 我不知道该把什么放到.c 文件, 把什么放到.h 文件。(“.h” 到底是什么意思?) . . . . . . . 48 10.5 一个头文件可以包含另一头文件吗? . . . . . . . . . . . . . . . . 48 10.6 #include 和#include "" 有什么区别? . . . . . . . . . . . . 48 10.7 完整的头文件搜索规则是怎样的? . . . . . . . . . . . . . . . . . 49 10.8 我在文件的第一个声明就遇到奇怪的语法错误, 但是看上去没什 么问题。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 10.9 我包含了我使用的库函数的正确头文件, 可是连接器还是说它没 有定义。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 10.10 我在编译一个程序, 看起来我好像缺少需要的一个或多个头文 件。谁能发给我一份? . . . . . . . . . . . . . . . . . . . . . . . . 49 10.11 我怎样构造比较字符串的#if 预处理表达式? . . . . . . . . . . . 49 10.12 sizeof 操作符可以用于#if 预编译指令中吗? . . . . . . . . . . . . 50 10.13 我可以在#include 行里使用#ifdef 来定义两个不同的东西吗? . 50 10.14 对typdef 的类型定义有没有类似#ifdef的东西? . . . . . . . . . 50 10.15 我如何用#if 表达式来判断机器是高字节在前还是低字节在前? . 50 10.16 我得到了一些代码, 里边有太多的#ifdef。我不想使用预处理器 把所有的#include 和#ifdef 都扩展开, 有什么办法只保留一种条 件的代码呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 10.17 如何列出所有的预定义标识符? . . . . . . . . . . . . . . . . . . . 50 目录vii 10.18 我有些旧代码, 试图用这样的宏来构造标识符#define Paste(a, b) a/**/b 但是现在不行了。. . . . . . . . . . . . . . . . . . . . . . 51 10.19 为什么宏#define TRACE(n) printf("TRACE: %dnn", n) 报出警 告“用字符串常量代替宏”?它似乎应该把TRACE(count); 扩展 为printf("TRACE: %dncount", count); . . . . . . . . . . . . . . 51 10.20 使用# 操作符时, 我在字符串常量内使用宏参数有问题。. . . . . 51 10.21 我想用预处理做某件事情, 但却不知道如何下手。. . . . . . . . . 51 10.22 怎样写参数个数可变的宏? . . . . . . . . . . . . . . . . . . . . . 51 11 ANSI/ISO 标准C 53 11.1 什么是“ANSI C 标准”? . . . . . . . . . . . . . . . . . . . . . . . 53 11.2 我如何得到一份标准的副本? . . . . . . . . . . . . . . . . . . . . 53 11.3 我在哪里可以找到标准的更新? . . . . . . . . . . . . . . . . . . . 54 11.4 很多ANSI 编译器在遇到以下代码时都会警告类型不匹配。 extern int func(float); int func(x) float x; f . . . . . . . . . . . . . 54 11.5 能否混用旧式的和新型的函数语法? . . . . . . . . . . . . . . . . 55 11.6 为什么声明extern int f(struct x *p); 报出了一个奇怪的警告信 息“结构x 在参数列表中声明”? . . . . . . . . . . . . . . . . . . 55 11.7 我不明白为什么我不能象这样在初始化和数组维度中使用常量: const int n = 5; int a[n]; . . . . . . . . . . . . . . . . . . . . . . . 55 11.8 既然不能修改字符串常量, 为什么不把它们定义为字符常量的数 组? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 11.9 “const char *p” 和“char * const p” 有何区别? . . . . . . . . . . 56 11.10 为什么我不能向接受const char ** 的函数传入char **? . . . . . 56 11.11 怎样正确声明main()? . . . . . . . . . . . . . . . . . . . . . . . . 56 11.12 我能否把main() 定义为void, 以避免扰人的“main无返回值” 警 告? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 11.13 可main() 的第三个参数envp 是怎么回事? . . . . . . . . . . . . 57 11.14 我觉得把main() 声明为void 不会失败, 因为我调用了exit() 而不 是return , 况且我的操作系统也忽略了程序的退出/返回状态。. . 57 11.15 那么到底会出什么问题?真的有什么系统不支持void main() 吗? 57 11.16 我一直用的那本书《熟练傻瓜C语言》总是使用void main()。. . 57 11.17 从main() 中, exit(status) 和返回同样的status 真的等价吗? . . . 57 11.18 我试图用ANSI “字符串化” 预处理操作符# 向信息中插入符号 常量的值, 但它字符串化的总是宏的名字而不是它的值。. . . . . 58 11.19 警告信息“warning: macro replacement within a string literal” 是 什么意思? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 11.20 在我用#ifdef 去掉的代码里出现了奇怪的语法错误。. . . . . . . 58 11.21 #pragma 是什么, 有什么用? . . . . . . . . . . . . . . . . . . . . 59 11.22 “#pragma once” 是什么意思?我在一些头文件中看到了它。. . 59 11.23 a[3] = "abc"; 合法吗?它是什么意思? . . . . . . . . . . . . . . . 59 11.24 为什么我不能对void* 指针进行运算? . . . . . . . . . . . . . . . 59 11.25 memcpy() 和memmove() 有什么区别? . . . . . . . . . . . . . . 59 11.26 malloc(0) 有什么用?返回一个控指针还是指向0 字节的指针? . 59 11.27 为什么ANSI 标准规定了外部标示符的长度和大小写限制? . . . 60 11.28 我的编译对最简单的测试程序报出了一大堆的语法错误。. . . . . 60 11.29 为什么有些ASNI/ISO 标准库函数未定义?我明明使用的就是 ANSI 编译器。. . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 11.30 谁有把旧的C 程序转化为ANSI C 或相反的工具, 或者自动生成 原型的工具? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 11.31 为什么声称兼容ANSI 的Frobozz Magic C 编译器不能编译这些 代码?我知道这些代码是ANSI 的, 因为gcc 可以编译。. . . . . 60 11.32 人们好像有些在意实现定义(implementation-defin-ed)、未明确 (unspecified) 和无定义(undefined) 行为的区别。它们的区别到底 在哪里? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 11.33 一个程序的“合法”, “有效” 或“符合” 到底是什么意思? . . . . . 61 11.34 我很吃惊, ANSI 标准竟然有那么多没有定义的东西。标准的唯一 任务不就是让这些东西标准化吗? . . . . . . . . . . . . . . . . . 61 11.35 有人说i = i++ 的行为是未定义的, 但是我刚在一个兼容ANSI 的 编译器上测试, 得到了我希望的结果。. . . . . . . . . . . . . . . 62 12 标准输入输出库63 12.1 这样的代码有什么问题?char c; while((c = getchar()) != EOF) ... 63 12.2 我有个读取直到EOF 的简单程序, 但是我如何才能在键盘上输入 那个“EOF” 呢? . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 12.3 为什么这些代码while(!feof(infp)) f fgets(buf, MAXLINE, infp); fputs(buf, outfp); g 把最后一行复制了两遍? . . . . . . . . . . . 63 12.4 我的程序的屏幕提示和中间输出有时显示在屏幕上, 尤其是当我 用管道向另一个程序输出的时候。. . . . . . . . . . . . . . . . . 63 12.5 我怎样不等待回车键一次输入一个字符? . . . . . . . . . . . . . . 64 12.6 我如何在printf 的格式串中输出一个’%’?我试过n%, 但是不行。64 12.7 有人告诉我在printf 中使用%lf 不正确。那么, 如果scanf() 需要 %lf, 怎么可以用在printf() 中用%f 输出双精度数呢? . . . . . . . 64 12.8 对于size t 那样的类型定义, 当我不知道它到底是long 还是其它 类型的时候, 我应该使用什么样的printf 格式呢? . . . . . . . . . 64 12.9 我如何用printf 实现可变的域宽度?就是说, 我想在运行时确定 宽度而不是使用%8d? . . . . . . . . . . . . . . . . . . . . . . . . 64 12.10 如何输出在千位上用逗号隔开的数字?金额数字呢? . . . . . . . 65 12.11 为什么scanf("%d", i) 调用不行? . . . . . . . . . . . . . . . . . . 65 12.12 为什么char s[30]; scanf("%s", s); 不用& 也可以? . . . . . . . . 65 目录ix 12.13 为什么这些代码double d; scanf("%f", &d); 不行? . . . . . . . . 65 12.14 怎样在scanf() 格式串中指定可变的宽度? . . . . . . . . . . . . . 65 12.15 当我用“%dnn” 调用scanf 从键盘读取数字的时候, 好像要多输入 一行函数才返回。. . . . . . . . . . . . . . . . . . . . . . . . . . 65 12.16 我用scanf %d 读取一个数字, 然后再用gets() 读取字符串, 但是 编译器好像跳过了gets() 调用! . . . . . . . . . . . . . . . . . . . 66 12.17 我发现如果坚持检查返回值以确保用户输入的是我期待的数值, 则scanf() 的使用会安全很多, 但有的时候好像会陷入无限循环。. 66 12.18 为什么大家都说不要使用scanf()?那我该用什么来代替呢? . . . 66 12.19 我怎样才知道对于任意的sprintf 调用需要多大的目标缓冲区?怎 样才能避免sprintf() 目标缓冲区溢出? . . . . . . . . . . . . . . . 66 12.20 为什么大家都说不要使用gets()? . . . . . . . . . . . . . . . . . . 67 12.21 为什么调用printf() 之后errno 内有ENOTTY? . . . . . . . . . . 67 12.22 fgetops/fsetops 和ftell/fseek 之间有什么区别? fgetops() 和fsetops() 到底有什么用处? . . . . . . . . . . . . . . . . . . . . . . . 68 12.23 如何清除多余的输入, 以防止在下一个提示符下读入?fflush(stdin) 可以吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 12.24 既然fflush() 不能, 那么怎样才能清除输入呢? . . . . . . . . . . . 68 12.25 对某些路径文件名调用fopen() 总是失败。. . . . . . . . . . . . . 68 12.26 我想用“r+” 打开一个文件, 读出一个字符串, 修改之后再写入, 从 而就地更新一个文件。可是这样不行。. . . . . . . . . . . . . . . 69 12.27 怎样在程序里把stdin 或stdout 重定向到文件? . . . . . . . . . . 69 12.28 一旦使用freopen() 之后, 怎样才能恢复原来的stdout (或stdin)? 69 12.29 怎样同时向两个地方输出, 如同时输出到屏幕和文件? . . . . . . 69 12.30 怎样正确的读取二进制文件?我有时看到0x0a 和0x0d 混淆了, 而且如果数据中包含0x1a 的话, 我好像会提前遇到EOF。. . . . 70 13 库函数71 13.1 怎样把数字转为字符串(与atoi 相反)?有itoa() 函数吗? . . . . 71 13.2 为什么strncpy() 不能总在目标串放上终止符’n0’? . . . . . . . 71 13.3 为什么有些版本的toupper() 对大写字符会有奇怪的反应?为什 么有的代码在调用toupper() 前先调用tolower()? . . . . . . . . . 71 13.4 怎样把字符串分隔成用空白作间隔符的段?怎样实现类似传递给 main() 的argc 和argv? . . . . . . . . . . . . . . . . . . . . . . . 72 13.5 我需要一些处理正则表达式或通配符匹配的代码。. . . . . . . . 72 13.6 我想用strcmp() 作为比较函数, 调用qsort() 对一个字符串数组排 序, 但是不行。. . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 13.7 我想用qsort() 对一个结构数组排序。我的比较函数接受结构指 针, 但是编译器认为这个函数对于qsort() 是错误类型。我要怎样 转换这个函数指针才能避免这样的警告? . . . . . . . . . . . . . . 73 13.8 怎样对一个链表排序? . . . . . . . . . . . . . . . . . . . . . . . . 73 13.9 怎样对多于内存的数据排序? . . . . . . . . . . . . . . . . . . . . 73 13.10 怎样在C 程序中取得当前日期或时间? . . . . . . . . . . . . . . . 73 13.11 我知道库函数localtime() 可以把time t 转换成结构struct tm, 而 ctime() 可以把time t 转换成为可打印的字符串。怎样才能进行 反向操作, 把struct tm 或一个字符串转换成time t? . . . . . . . 74 13.12 怎样在日期上加N 天?怎样取得两个日期的时间间隔? . . . . . . 74 13.13 我需要一个随机数生成器。. . . . . . . . . . . . . . . . . . . . . 75 13.14 怎样获得在一定范围内的随机数? . . . . . . . . . . . . . . . . . 75 13.15 每次执行程序, rand() 都返回相同顺序的数字。. . . . . . . . . . 75 13.16 我需要随机的真/假值, 所以我用直接用rand() % 2, 可是我得到 交替的0, 1, 0, 1, 0 ⋯⋯ . . . . . . . . . . . . . . . . . . . . . . . 76 13.17 怎样产生标准分布或高斯分布的随机数? . . . . . . . . . . . . . . 76 13.18 我不断得到库函数未定义错误, 但是我已经#inlude 了所有用到 的头文件了。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 13.19 虽然我在连接时明确地指定了正确的函数库, 我还是得到库函数 未定义错误。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 13.20 连接器说end 未定义代表什么意思? . . . . . . . . . . . . . . . . 77 13.21 我的编译器提示printf 未定义!这怎么可能? . . . . . . . . . . . 77 14 浮点运算79 14.1 一个float 变量赋值为3.1 时, 为什么printf 输出的值为3.0999999? 79 14.2 执行一些开方根运算, 可是得到一些疯狂的数字。. . . . . . . . . 79 14.3 做一些简单的三角函数运算, 也引用了#include , 可是 一直得到编译错误“undefined: sin” (函数sin 未定义)。. . . . . . 79 14.4 浮点计算程序表现奇怪, 在不同的机器上给出不同的结果。. . . . 79 14.5 有什么好的方法来验对浮点数在“足够接近” 情况下的等值? . . . 80 14.6 怎样取整数? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 14.7 为什么C 不提供乘幂的运算符? . . . . . . . . . . . . . . . . . . 80 14.8 为什么我机器上的 没有预定义常数M PI? . . . . . . 80 14.9 怎样测试IEEE NaN 以及其它特殊值? . . . . . . . . . . . . . . . 81 14.10 在C 中如何很好的实现复数? . . . . . . . . . . . . . . . . . . . . 81 14.11 我要寻找一些实现以下功能的程序源代码:快速傅立叶变换 (FFT)、矩阵算术(乘法、倒置等函数)、复数算术。. . . . . . . 81 14.12 Turbo C 的程序崩溃, 显示错误为“floating point formats not linked” (浮点格式未连接)。. . . . . . . . . . . . . . . . . . . . . 81 15 可变参数83 15.1 为什么调用printf() 前, 必须要用#include ? . . . . . 83 15.2 为什么%f 可以在printf() 参数中, 同时表示float 和double?他们 难道不是不同类型吗? . . . . . . . . . . . . . . . . . . . . . . . . 83 15.3 为什么当n 为long int, printf("%d", n); 编译时没有匹配警告? 我以为ANSI 函数原型可以防止这样的类型不匹配。. . . . . . . 83 15.4 怎样写一个有可变参数的函数? . . . . . . . . . . . . . . . . . . . 83 15.5 怎样写类似printf() 的函数, 再把参数转传给printf() 去完成大部 分工作? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 15.6 怎样写类似scanf() 的函数, 再把参数转传给scanf() 去完成大部 分工作? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 15.7 怎样知道实际上有多少个参数传入函数? . . . . . . . . . . . . . . 85 15.8 为什么编译器不让我定义一个没有固定参数项的可变参数函数? . 86 15.9 我有个接受float 的可变参函数, 为什么va arg(argp, float) 不工作? 86 15.10 va arg() 不能得到类型为函数指针的参数。. . . . . . . . . . . . . 86 15.11 怎样实现一个可变参数函数, 它把参数再传给另一个可变参数函 数? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 15.12 怎样调用一个参数在执行是才建立的函数? . . . . . . . . . . . . 87 16 奇怪的问题89 16.1 遇到不可理解的不合理语法错误, 似乎大段的程序没有编译。. . 89 16.2 为什么过程调用不工作?编译器似乎直接跳过去了。. . . . . . . 89 16.3 程序在执行用之前就崩溃了, 用调试器单步跟进, 在main() 之前 就死了。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 16.4 程序执行正确, 但退出时崩溃在main() 最后一个语句之后。为什 么会这样? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 16.5 程序在一台机器上执行完美, 但在另一台上却得到怪异的结果。 更奇怪的是, 增加或去除调试的打印语句, 就改变了症状⋯⋯ . . . 90 16.6 为什么代码: char *p = "hello, worl!"; p[0] = ’H’; 会崩溃? . . . 90 16.7 “Segmentation violation”, “Bus error” 和“General protection fault” 意味着什么? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 17 风格93 17.1 什么是C 最好的代码布局风格? . . . . . . . . . . . . . . . . . . 93 17.2 用if(!strcmp(s1, s2)) 比较两个字符串等值,是否是个好风格? . . . 93 17.3 为什么有的人用if (0 == x) 而不是if (x == 0)? . . . . . . . . . 93 17.4 原型说明extern int func ((int, int)); 中, 那些多出来的括号和下 划线代表了什么? . . . . . . . . . . . . . . . . . . . . . . . . . . 94 17.5 为什么有些代码在每次调用printf() 前, 加了类型转换(void)? . . 94 17.6 什么是“匈牙利标志法” (Hungarian Notation)?是否值得用? . . 94 17.7 哪里可以找到“印第安山风格指南” (Indian Hill Style Guide) 及 其它编码标准? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 17.8 有些人说goto 是邪恶的, 我应该永不用它。那是否太极端了? . . 95 18 工具和资源97 18.1 常用工具列表。. . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 18.2 怎样抓捕棘手的malloc 问题? . . . . . . . . . . . . . . . . . . . . 98 18.3 有什么免费或便宜的编译器可以使用? . . . . . . . . . . . . . . . 98 18.4 刚刚输入完一个程序, 但它表现的很奇怪。你可以发现有什么错 误的地方吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 18.5 哪里可以找到兼容ANSI 的lint? . . . . . . . . . . . . . . . . . . 99 18.6 难道ANSI 函数原型说明没有使lint 过时吗? . . . . . . . . . . . 99 18.7 网上有哪些C 的教程或其它资源? . . . . . . . . . . . . . . . . . 99 18.8 哪里可以找到好的源代码实例, 以供研究和学习? . . . . . . . . . 100 18.9 有什么好的学习C 的书?有哪些高级的书和参考? . . . . . . . . 100 18.10 哪里可以找到标准C 函数库的源代码? . . . . . . . . . . . . . . . 101 18.11 是否有一个在线的C 参考指南? . . . . . . . . . . . . . . . . . . 101 18.12 哪里可以得到ANSI/ISO C 标准? . . . . . . . . . . . . . . . . . 101 18.13 我需要分析和评估表达式的代码。. . . . . . . . . . . . . . . . . 101 18.14 哪里可以找到C 的BNF 或YACC 语法? . . . . . . . . . . . . . 101 18.15 谁有C 编译器的测试套件? . . . . . . . . . . . . . . . . . . . . . 102 18.16 哪里有一些有用的源代码片段和例子的收集? . . . . . . . . . . . 102 18.17 我需要执行多精度算术的代码。. . . . . . . . . . . . . . . . . . . 102 18.18 在哪里和怎样取得这些可自由发布的程序? . . . . . . . . . . . . 102 19 系统依赖105 19.1 怎样从键盘直接读入字符而不用等RETURN 键?怎样防止字符 输入时的回显? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 19.2 怎样知道有未读的字符, 如果有, 有多少?如果没有字符, 怎样使 读入不阻断? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 19.3 怎样显示一个百分比或“转动的短棒” 的进展表示器? . . . . . . 106 19.4 怎样清屏?怎样输出彩色文本?怎样移动光标到指定位置? . . . 106 19.5 怎样读入方向键, 功能键? . . . . . . . . . . . . . . . . . . . . . . 107 19.6 怎样读入鼠标输入? . . . . . . . . . . . . . . . . . . . . . . . . . 107 19.7 怎样做串口(“comm”) 的输入输出? . . . . . . . . . . . . . . . . 107 19.8 怎样直接输出到打印机? . . . . . . . . . . . . . . . . . . . . . . . 107 19.9 怎样发送控制终端或其它设备的逃逸指令序列? . . . . . . . . . . 108 19.10 怎样直接访问输入输出板? . . . . . . . . . . . . . . . . . . . . . 108 19.11 怎样做图形? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 19.12 怎样显示GIF 和JPEG 图象? . . . . . . . . . . . . . . . . . . . 108 19.13 怎样检验一个文件是否存在? . . . . . . . . . . . . . . . . . . . . 108 19.14 怎样在读入文件前, 知道文件大小? . . . . . . . . . . . . . . . . . 109 19.15 怎样得到文件的修改日期和时间? . . . . . . . . . . . . . . . . . 109 19.16 怎样缩短一个文件而不用清除或重写? . . . . . . . . . . . . . . . 109 19.17 怎样在文件中插入或删除一行(或记录)? . . . . . . . . . . . . . . 109 19.18 怎样从一个打开的流或文件描述符得到文件名? . . . . . . . . . . 110 19.19 怎样删除一个文件? . . . . . . . . . . . . . . . . . . . . . . . . . 110 19.20 怎样复制一个文件? . . . . . . . . . . . . . . . . . . . . . . . . . 110 19.21 为什么用了详尽的路径还不能打开文件? fopen("c:n newdir nfile.dat", "r") 返回错误。. . . . . . . . . . . . . . . . . . . . . . 110 19.22 fopen() 不让我打开文件: "$HOME/.profile" 和"˜/ .myrcfile"。. 111 19.23 怎样制止MS-DOS 下令人担忧的“Abort, Retry, Ignore?” 信息? 111 19.24 遇到“Too many open files (打开文件太多)” 的错误, 怎样增加同 时打开文件的允许数目? . . . . . . . . . . . . . . . . . . . . . . . 111 19.25 怎样在C 中读入目录? . . . . . . . . . . . . . . . . . . . . . . . . 111 19.26 怎样找出系统还有多少内存可用? . . . . . . . . . . . . . . . . . 111 19.27 怎样分配大于64K 的数组或结构? . . . . . . . . . . . . . . . . . 111 19.28 错误信息“DGROUP data allocation exceeds 64K (DGROUP 数 据分配内存超过64K)” 说明什么?我应该怎么做?我以为使用了 大内存模型, 那我就可以使用多于64K 的数据! . . . . . . . . . . 112 19.29 怎样访问位于某的特定地址的内存(内存映射的设备或图显内存)? 112 19.30 怎样在一个C 程序中调用另一个程序(独立可执行的程序, 或系统 命令)? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 19.31 怎样调用另一个程序或命令, 同时收集它的输出? . . . . . . . . . 113 19.32 怎样才能发现程序自己的执行文件的全路径? . . . . . . . . . . . 113 19.33 怎样找出和执行文件在同一目录的配置文件? . . . . . . . . . . . 113 19.34 一个进程如何改变它的调用者的环境变量? . . . . . . . . . . . . 113 19.35 怎样读入一个对象文件并跳跃到其中的地址? . . . . . . . . . . . 114 19.36 怎样实现精度小于秒的延时或记录用户回应的时间? . . . . . . . 114 19.37 怎样抓获或忽略像control-C 这样的键盘中断? . . . . . . . . . . 114 19.38 怎样很好地处理浮点异常? . . . . . . . . . . . . . . . . . . . . . 115 19.39 怎样使用socket?网络化?写客户/服务器程序? . . . . . . . . . 115 19.40 怎样调用BIOS 函数?写ISR?创建TSR? . . . . . . . . . . . . 115 19.41 编译程序, 编译器出示“union REGS” 未定义错误信息, 连接器出 示“int86()” 的未定义错误信息。. . . . . . . . . . . . . . . . . . 115 19.42 什么是“near” 和“far” 指针? . . . . . . . . . . . . . . . . . . . . 116 19.43 我不能使用这些非标准、依赖系统的函数, 程序需要兼容ANSI! . 116 20 杂项117 20.1 怎样从一个函数返回多个值? . . . . . . . . . . . . . . . . . . . . 117 20.2 怎样访问命令行参数? . . . . . . . . . . . . . . . . . . . . . . . . 117 20.3 怎样写数据文件, 使之可以在不同字大小、字节顺序或浮点格式 的机器上读入? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 20.4 怎样调用一个由char * 指针指向函数名的函数? . . . . . . . . . 117 20.5 怎样实现比特数组或集合? . . . . . . . . . . . . . . . . . . . . . 118 20.6 怎样判断机器的字节顺序是高字节在前还是低字节在前? . . . . . 118 20.7 怎样掉换字节? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 20.8 怎样转换整数到二进制或十六进制? . . . . . . . . . . . . . . . . 119 20.9 我可以使用二进制常数吗?有printf() 的二进制的格式符吗? . . 119 20.10 什么是计算整数中比特为1 的个数的最有效的方法? . . . . . . . 119 20.11 什么是提高程序效率的最好方法? . . . . . . . . . . . . . . . . . 119 20.12 指针真得比数组快吗?函数调用会拖慢程序多少? ++i 比i = i +1 快吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 20.13 人们说编译器优化的很好, 我们不在需要为速度而写汇编了, 但我 的编译器连用移位代替i/=2 都做不到。. . . . . . . . . . . . . . 120 20.14 怎样不用临时变量而交换两个值? . . . . . . . . . . . . . . . . . 120 20.15 是否有根据字符串做切换的方法? . . . . . . . . . . . . . . . . . 121 20.16 是否有使用非常量case 标志的方法(例如范围或任意的表达式)? 121 20.17 return 语句外层的括号是否真的可选择? . . . . . . . . . . . . . . 121 20.18 为什么C 注释不能嵌套?怎样注释掉含有注释的代码?引用字符 串内的注释是否合法? . . . . . . . . . . . . . . . . . . . . . . . . 121 20.19 C 是个伟大的语言还是别的?哪个其它语言可以写象a+++++b 这样的代码? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 20.20 为什么C 没有嵌套函数? . . . . . . . . . . . . . . . . . . . . . . 122 20.21 assert() 是什么?怎样用它? . . . . . . . . . . . . . . . . . . . . . 122 20.22 怎样从C 中调用FORTRAN (C++, BASIC, Pascal, Ada, LISP) 的函数?反之亦然? . . . . . . . . . . . . . . . . . . . . . . . . . 122 20.23 有什么程序可以做从Pascal 或Fortran (或LISP, Ada, awk, “老” C) 到C 的转换? . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 20.24 C++ 是C 的超集吗?可以用C++ 编译器来编译C 代码吗? . . 123 20.25 需要用到“近似” 的strcmp, 比较两个字符串的近似度, 并不需要 完全一样。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 20.26 什么是散列法? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 20.27 由一个日期, 怎样知道是星期几? . . . . . . . . . . . . . . . . . . 124 20.28 (year%4 == 0) 是否足够判断润年?2000 年是闰年吗? . . . . . . 124 20.29 一个难题: 怎样写一个输出自己源代码的程序? . . . . . . . . . . 124 20.30 什么是“达夫设备” (Duff’s Device)? . . . . . . . . . . . . . . . . 125 20.31 下届国际C 混乱代码竞赛(IOCCC) 什么时候进行?哪里可以找 到当前和以前的获胜代码? . . . . . . . . . . . . . . . . . . . . . 125 20.32 [K&R1] 提到的关健字entry 是什么? . . . . . . . . . . . . . . . . 126 20.33 C 的名字从何而来? . . . . . . . . . . . . . . . . . . . . . . . . . 126 20.34 “char” 如何发音? . . . . . . . . . . . . . . . . . . . . . . . . . . 126 20.35 “lvalue” 和“rvalue” 代表什么意思? . . . . . . . . . . . . . . . . 126 20.36 哪里可以取得本FAQ (英文版) 的额外副本? . . . . . . . . . . . 126
1. C语言和汇编语言在开发单片机时各有哪些优缺点? 答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言可能有所差异,所以不易移植。 C语言是一种结构化的高级语言。其优点是可读性好,移植容易,是普遍使用的一种计算机语言。缺点是占用资源较多,执行效率没有汇编高。 对于目前普遍使用的RISC架构的8bit MCU来说,其内部ROM、RAM、STACK等资源都有限,如果使用C语言编写,一条C语言指令编译后,会变成很多条机器码,很容易出现ROM空间不够、堆栈溢出等问题。而且一些单片机厂家也不一定能提供C编译器。而汇编语言,一条指令就对应一个机器码,每一步执行什幺动作都很清楚,并且程序大小和堆栈调用情况都容易控制,调试起来也比较方便。所以在单片机开发中,我们还是建议采用汇编语言比较好。 如果对单片机C语言有兴趣,HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK的单片机就有提供C编译器,可以到HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK的网站(www.holtek.com.cn )免费下载使用。 2. C或汇编语言可以用于单片机,C++能吗? 答:在单片机开发中,主要是汇编和C,没有用C++的。 3. 搞单片机开发,一定要会C吗? 答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言可能有所差异,所以不易移植。 对于目前普遍使用的RISC架构的8bit MCU来说,其内部ROM、RAM、STACK等资源都有限,如果使用C语言编写,一条C语言指令编译后,会变成很多条机器码,很容易出现ROM空间不够、堆栈溢出等问题。而且一些单片机厂家也不一定能提供C编译器。而汇编语言,一条指令就对应一个机器码,每一步执行什么动作都很清楚,并且程序大小和堆栈调用情况都容易控制,调试起来也比较方便。所以在资源较少单片机开发中,我们还是建议采用汇编语言比较好。 而C语言是一种编译型程序设计语言,它兼顾了多种高级语言的特点,并具备汇编语言的功能。C语言有功能丰富的库函数、运算速度快、编译效率高、有良好的可移植性,而且可以直接实现对系统硬件的控制。C语言是一种结构化程序设计语言,它支持当前程序设计中广泛采用的由顶向下结构化程序设计技术。此外,C语言程序具有完善的模块程序结构,从而为软件开发中采用模块化程序设计方法提供了有力的保障。因此,使用C语言进行程序设计已成为软件开发的一个主流。用C语言来编写目标系统软件,会大大缩短开发周期,且明显地增加软件的可读性,便于改进和扩充,从而研制出规模更大、性能更完备的系统。 综上所述,用C语言进行单片机程序设计是单片机开发与应用的必然趋势。所以作为一个技术全面并涉足较大规模的软件系统开发的单片机开发人员最好能够掌握基本的C语言编程。 4. 当开发一个较复杂而又开发时间短的项目时,用C还是用汇编开发好? 答:对于复杂而开发时间紧的项目时,可以采用C语言,但前提是要求对该MCU系统的C语言和C编译器非常熟悉,特别要注意该C编译系统所能支持的数据类型和算法。虽然C语言是最普遍的一种高级语言,但不同的MCU厂家其C语言编译系统是有所差别的,特别是在一些特殊功能模块的操作上。如果对这些特性不了解,那调试起来就有的烦了,到头来可能还不如用汇编来的快。 5. 在教学中要用到8088和196芯片单片机教材,请问那里可以找到关于这方面的书或资料? 答:有关这方面的教材,大学里常用的一本是《IBM-PC汇编语言程序设计》清华大学出版社出版的,在网上以及书店都是可以找到的,另外网上还可以搜索到很多其他的教材如:《微机原理及汇编语言教程》(杨延双 张晓冬 等编著 )和《16/32 位微机原理、汇编语言及接口技术》(作者: 钟晓捷 陈涛 ,机械工业出版社 出版)等,可以在较大型的科技书店里查找或者直接从网上订购。 6. 初学者到底是应该先学C还是汇编? 答:对于单片机的初学者来说,应该从汇编学起。因为汇编语言是最接近机器码的一种语言,可以加深初学者对单片机各个功能模块的了解,从而打好扎实的基础。 7. 我是一名武汉大学电子科技大3的学生,学了电子线路、数字逻辑、汇编和接口、C语言,但是总是感觉很迷茫,觉好象什么都不会。怎么办? 答:大学过程是一个理论过程,实践的机会比较少,往往会造成理论与实践相脱节,这是国内大学教育系统的通病,不过对于学生来说切不可好高骛远。一般从大三会开始接触到一些专业课程,电子相关专业会开设相关的单片机应用课程并且会有简单的实验项目,那么要充分把握实验课的机会,多多地实际上机操作练习。平时可以多看看相关的电子技术杂志网站,看看别人的开发经验,硬件设计方案以及他人的软件设计经验。有可能的话,还可以参加一些电子设计大赛,借此机会2--3个人合作做一个完整系统,会更有帮助。到了大四毕业设计阶段,也可以选择相关的课题作些实际案例增长经验。做什么事情都有个经验的积累过程,循序渐进。 8. 请问作为学生,如何学好单片机? 答:学习好单片机,最主要的是实践,在实践中增长经验。在校学生的话,实践机会的确会比较少,但是有机会的话,可以毕业实习选择相关的课题,这样就可以接触到实际的项目。而且如果单片机微机原理是一门主课的话,相信学校会安排比较多的实践上机机会。有能力的话,可以找一些相关兼职工作做做,会更有帮助。而且单片机开发应用需要软硬件结合,所以不能只满足于编程技巧如何完美,平时也要注意硬件知识的积累,多上上电子论坛网站,买一些相关杂志。可能的话,可以到ic37去买一些小零件,自己搭一个小系统让它工作起来。 HOTLEK的单片机是RISC结构的8位单片机,它可以广泛应用在家用电器、安全系统、掌上游戏等方面。大概来说可以分成I/O型单片机、LCD型单片机、A/D型单片机、A/D with LCD型单片机等等。这些单片机的中文资料我们都公开在HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK网站www.holtek.com.cn 。 HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK各类单片机的使用手册下载地址: http://www.holtek.com.cn/referanc/htk_book.htm HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK单片机软件/硬件应用范例下载地址: http://www.holtek.com.cn/tech/appnote/appnote.htm HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK单片机支持工具下载地址: http://www.holtek.com.cn/tech/tool/tool.htm 9. 如何才能才为单片机的高手啊? 答:要成为单片机高手,应该多实践,时常关注单片机的发展趋势;经常上一些相关网站,从那里可以找到许多有用的资料。 10. 女性是否适合单片机软件编程这个行业? 答:要根据自己的兴趣,配合自己对软件编程的耐性,男女皆适合这个行业。 11. Holtek的数据手册在哪里下载? 答:如果对Holtek的IC感兴趣的话,相应的数据手册可以到网站上http://www.holtek.com.cn/products/index.htm去选IC资料下载。 12. 8位机还能延续多久! 答:以现在MCU产品主力还是在8位领域,主要应用于汽车应用、消费性电子、电脑及PC周边、电信与通讯、办公室自动化、工业控制等六大市场,其中车用市场多在欧、美地区,而亚太地区则以消费性电子为主, 并以量大低单价为产品主流,目前16位MCU与8位产品,还有相当幅度的价差,新的应用领域也仍在开发,业界预计,至少在2005年前8位的MCU仍是MCU产品的主流。 13. 学习ARM及嵌入式系统是否比学习其它一般单片机更有使用前景?对于一个初学者应当具备哪些相关知识? 答:一般在8位单片机与ARM方面的嵌入式系统是有层次上的差别,ARM适用于系统复杂度较大的高级产品,如PDA、手机等应用。而8位单片机因架构简单,硬件资源相对较少,适用于一般的工业控制、消费性家电等等。对于一个单片机方面的软件编程初学者,应以HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK系列或8051等8位单片机来做入门练习。而初学者应当具备软件编程相关知识,单片机一般软件编程是以汇编语言为主,各家有各家的语法,但大都以RISC的MCU架构为主,其中 RISC (Reduced Instruction Set Computer) 代表MCU的所有指令。都是利用一些简单的指令组成的,简单的指令代表 MCU 的线路可以尽量做到最佳化,而提高执行速率。另外初学者要具备单片机I/O接口的应用知识,这在于周边应用电路及各种元器件的使用,须配合自己所学的电子学及电路学等。 14. 符合44PIN的80系列8位单片机的MCU有哪些? 答:符合44PIN的80系列8位单片机有Z8674312FSC、Z86E2112FSC、Z86E2116FSC。 15. 请介绍一下MCU的测试方法。 答: MCU从生产出来到封装出货的每个不同的阶段会有不同的测试方法,其中主要会有两种:中测和成测。 所谓中测即是WAFER的测试,它会包含产品的功能验证及AC、DC的测试。项目相当繁多,以HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK产品为例最主要的几项如下:  接续性测试:检测每一根I/OPIN内接的保护用二极管是否功能无误。  功能测试:以产品设计者所提供测试资料(TEST PATTERN)灌入IC,检查其结果是否与当时SIMULATION时状态一样。  STANDBY电流测试:测量IC处于HALT模式时即每一个接点(PAD)在1态0态或Z态保持不变时的漏电流是否符合最低之规格。  耗电测试:整颗IC的静态耗电与动态耗电。  输入电压测试:测量每个输入接脚的输入电压反应特性。  输出电压测试:测量每个输出接脚的输出电压位准。  相关频率特性(AC)测试,也是通过外灌一定频率,从I/O口来看输出是否与之匹配。  为了保证IC生产的长期且稳定品质,还会做产品的可靠性测试,这些测试包括ESD测试,LATCH UP测试,温度循环测试,高温贮存测试,湿度贮存测试等。 成测则是产品封装好后的测试,即PACKAGE测试。即是所有通过中测的产品封装后的测试,方法主要是机台自动测试,但测试项目仍与WAFER TEST相同。PACKAGE TEST的目的是在确定IC在封装过程中是否有任何损坏。 16. 能否利用单片来检测手机电池的充放电时间及充放电时的电压电流变化,并利用一个I/O端口使检测结果在电脑上显示出来? 答:目前市场上的各类智能充电器,大部分都采用MCU进行充电电流和电压的控制。至于要在电脑上显示,好象并不实用,可能只有在一些专门的电池检测仪器中才会用到;对于一般的手机用户来说,谁会在充电时还需要用一台电脑来做显示呢?要实现单片机与电脑的连接,最简单的方式就是采用串口通讯,但需要加一颗RS-232芯片。 17. 在ARM编程中又应当如何? 答:就以嵌入式系统观念为例,一般嵌入式处理器可以分为三类:嵌入式微处理器、嵌入式微控制器、嵌入式DSP(Digital Signal Processor)。 嵌入式微处理器就是和通用计算机的微处理器对应的CPU。在应用中,一般是将微处理器装配在专门设计的电路板上,在母板上只保留和嵌入式相关的功能即可,这样可以满足嵌入式系统体积小和功耗低的要求。目前的嵌入式处理器主要包括:PowerPC、Motorola 68000、ARM系列等等。 嵌入式微控制器又称为单片机,它将CPU、存储器(少量的RAM、ROM或两者都有)和其它接口I/O封装在同一片集成电路里。常见的有HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK MCU系列、Microchip MCU系列及8051等。 嵌入式DSP专门用来处理对离散时间信号进行极快的处理计算,提高编译效率和执行速度。在数字滤波、FFT(Fast Fourier Transform)、频谱分析、图像处理的分析等领域,DSP正在大量进入嵌入式市场。 18. MCU在射频控制时,MCU的时钟(晶振)、数据线会辐射基频或基频的倍频,被低噪放LNA放大后进入混频,出现带内的Spur,无法滤除。除了用layout、选择低辐射MCU的方法可以减少一些以外,还有什么别的方法? 答:在设计高频电路用电路板有许多注意事项,尤其是GHz等级的高频电路,更需要注意各电子组件pad与印刷pattern的长度对电路特性所造成的影响。最近几年高频电路与数位电路共享相同电路板,构成所谓的混载电路系统似乎有增加的趋势,类似如此的设计经常会造成数位电路动作时,高频电路却发生动作不稳定等现象,其中原因之一是数位电路产生的噪讯,影响高频电路正常动作所致。为了避免上述问题除了设法分割两电路block之外,设计电路板之前充分检讨设计构想,才是根本应有的手法,基本上设计高频电路用电路板必需掌握下列三大原则:  高质感。  不可取巧。  不可仓促抢时间。 以下是设计高频电路板的一些建议: (1)印刷pattern的长度会影响电路特性。尤其是传输速度为GHz高速数位电路的传输线路,通常会使用strip line,同时藉由调整配线长度补正传输延迟时间,其实这也意味着电子组件的设置位置对电路特性具有绝对性的影响。 (2)Ground作大better。铜箔面整体设置ground层,而连接via的better ground则是高频电路板与高速数位电路板共同的特征,此外高频电路板最忌讳使用幅宽细窄的印刷pattern描绘ground。 (2)电子组件的ground端子,以最短的长度与电路板的ground连接。具体方法是在电子组件的ground端子pad附近设置via,使电子组件能以最短的长度与电路板的ground连接。 (3)信号线作短配线设计。不可任意加大配线长度,尽量缩短配线长度。 (4)减少电路之间的结合。尤其是filter与amplifier输出入之间作电路分割非常重要,它相当于audio电路的cross talk对策。 (5)MCU回路Layout考量:震荡电路仅可能接近IC震荡脚位;震荡电路与VDD & VSS保持足够的距离;震荡频率大于1MHz时不需加 osc1 & osc2 电容;电源与地间要最短位置并尽量拉等宽与等距的线,于节点位置加上104/103/102等陶瓷电容。 19. Intel系列的96单片机80c196KB开发系统时,都有那些注意事项? 答:一个即时系统的软体由即时操作系统加上应用程序构成。应用程序与作业系统的接口通过系统调用来实现。用80C196KB-p.htm" target="_blank" title="80C196KB货源和PDF资料">80C196KB作业系统的MCU,只能用内部RAM作为TCB和所有系统记忆体(含各种控制表)以及各个任务的工作和资料单元。因此一定要注意以下几点: (1)对各个任务分配各自的堆迭区,该堆迭区既作为任务的工作单元,也作为任务控制块的保护单元。 (2)系统的任务控制块只存放各任务的堆迭指标,而任务的状态均存放于任务椎栈中。在一个任务退出运行时,通过中断把它的状态进栈,然后把它的堆迭指标保存于系统的TCB中;再根据优先取出优先顺序最高的已就绪任务的堆迭指标SP映象值送入SP中;最后执行中断返回指令转去执行新任务。 (3)各任务的资料和工作单元尽量用堆迭实现,这样可以允许各任务使用同一个子程序。使用堆迭实现参数传递并作为工作单元,而不使用绝对地址的RAM,可实现可重入子程序。该子程序既可为各个任务所调用,也可实现递回调用。 20. 在demo板上采样电压时,不稳定,采样结果有波动,如何消除? 答:一般来说,仿真器都是工作在一个稳压的环境(通常为5V)。如果用仿真器的A/D时,要注意其A/D参考电压是由仿真器内部给出,还是需要外部提供。A/D转换需要一个连续的时钟周期,所以在仿真时不能用单步调试的方法,否则会造成A/D采样值不准。至于A/D采样不稳定,可以在A/D输入口加一电容,起到滤波作用;在软件处理时采用中值滤波的方法。 21. 在车载DVD系统中,如何设计电子防震系统? 答:在车载DVD系统,最好选择高档DVD机,因为高档DVD机都采用电子防震系统(ADVANCEDESP),当记忆缓冲区内的读数降低,先进的电子防震设计会以双速读数系统,做出比正常速度快两倍的读数速率,以减低噪声,即使连续震荡仍可避免跳线情况出现,现在就说说什幺叫电子防震。简单地说:电子防震就是一个信号的储存--释放过程,首先CD要先把信号进行提前读取,也就是我们见到机子的加速,再把信号储存在RAM中,而我们在开防震的时候所听到的就是经过RAM的声音,这样就是它的过程。当没有防震时是由于信号是1比1读取的,所以当受到冲击后,就会出现跳音。而当开了防震时,机子受到冲击后,由RAM释放出来的声音使音乐不停地播放,而与此同时,光头迅速进行复位检索,当检索到信号后立即补充,所以不会出现跳音。大概的情况就是这样。但是这样还没有满足用家的要求,由于这种的方法带来的时间短,通常只有3秒,所以跳音的机会还是蛮高,如果增大RAM又带来造价的增高因为RAM这东西价格较贵,尤其是质量好的。 22. 在电子防震技术中,有那些IC或器件可供选择? 答:在电子防震技术中,最重要的技术之一要数是RAM技术,而一直以来都是因为它的成本问题,所以防震时间都一直不能增加,也就是说RAM本身就有限制,RAM的容量越大,造价就越高。而许多厂家就如何在RAM的限制里得到最大限度的记忆时间展开了开发研究。 23. 如何进行编程可以减少程序的bug? 答:在此提供一些建议,因系统中实际运行的参数都是有范围的。系统运行中要考虑的超范围管理参数有:  物理参数。这些参数主要是系统的输入参数,它包括激励参数、采集处理中的运行参数和处理结束的结果参数。合理设定这些边界,将超出边界的参数都视为非正常激励或非正常回应进行出错处理。  资源参数。这些参数主要是系统中的电路、器件、功能单元的资源,如记忆体容量、存储单元长度、堆迭深度。在程序设计中,对资源参数不允许超范围使用。  应用参数。这些应用参数常表现为一些单片机、功能单元的应用条件。如E2PROM的擦写次数与资料存储时间等应用参数界限。  过程参数。指系统运行中的有序变化的参数。 在上述参数群对一程序编写者而言,须养成良好习惯,在程序的开头,有顺序的用自己喜欢文字参数对应列表来替代,然后用自己定义的文字参数来编写程序,这样在做程序的修改及维护时只在程序的开头做变动即可,不用修改到程序段,才比较容易且不会出错。 24. 有人认为单片机将被ARM等系列结构的嵌入式系统所取代。单片机的生命期还有多长? 答:因为8位单片机与嵌入式系统的ARM在功能结构和单价的差异,故应用层次上就有很大的不同。 ARM适用于系统复杂度较大的高级产品,如PDA、手机等应用。 而8位单片机因架构简单,硬件资源相对较少,适用于一般的工业控制,消费性家电……等等。评估单片机近期是否会给ARM取代,要观察两个因素:  芯片成本 因ARM的工作频率较高,电路较庞大,所需的芯片制造工艺要求在0。25U以上,成本较高。8位单片机工作频率相对较低,电路较小,所需的芯片制造工艺在0。5U 即可,成本较低。  功能定位 ARM的功能较单片机强,但两者定位不同。就如现阶段不会有人用ARM去作一个简单的工业定时开关。当然,如果两者单价相同也无不可,但现实是有很大的单价差距。 至于将来,因芯片制造成本会不断下降,上述的成本差异影响愈来愈少!但我估计在往后5年单片机仍有价格优势,仍能存活!但ARM是否会精简架构,降低成本,抢夺低阶市场?我想可能性不大,ARM应该会向上发展。同样,单片机也只能向上发展,如16位,高功能……等。 原因就是因为芯片制造工艺进步太快。压迫芯片设计往高集成发展。 25. 在单片机C编成时,如何才能使生成的代码具有和汇编一样的效率? 答:如果是使用C语言编程时,不太可能生成的代码具有1:1和汇编一样的效率。 C语言命令要被硬件识别并执行,必须通过编译器编译。编译器分为前端、中端、后端。前端与各种计算机语言写的程序打交道,后端与处理器的基本指令集接轨。所以如果使用C编程时,要达到最高的效率,最好能够很了解所使用的C编译器。先试验一下每条C语言编译以后对应的汇编语言的语句行数,这样就可以很明确的知道效率。在今后编程的时候,使用编译效率最高的语句,这样就能确保单片机C编程的时候同样的功能不同的C程序,编译效率最高。但是各家的C编译器都会有一定的差异,优秀的嵌入式系统C编译器代码长度和执行时间仅比以汇编语言编写的同样功能程度长5-20%,所以不同厂家的C编译器的编译效率也会有所不同。 26. ARM单片机和哪种内核的单片机比较接近? 答:严格的说,ARM不是单片机,是一个嵌入式的实时操作系统。ARM(Advanced RISC Machines)是微处理器行业的一家知名企业,设计了大量高性能、廉价、耗能低的RISC处理器、相关技术及软件。ARM将其技术授权给世界上许多著名的半导体、软件和OEM厂商,每个厂商得到的都是一套独一无二的ARM相关技术及服务。所以市场上像Intel、IBM、LG半导体、NEC、SONY、菲利浦和国半这样的大公司都有ARM系列,现在不存在什幺ARM单片机和哪种内核的单片机比较接近的问题。而且由于厂家购买内核后会根据自己芯片应用方向的不同,自行添加不同的外挂功能模块,所以,同样内核的芯片其提供的功能是不同的。 27. 从51转到ARM会有困难吗? 答:从51转到ARM,其实编程之类的原理都是一样的,但是要注意的是ARM是一个RISC的架构,在ARM的应用开放源代码的程序很多,要想提高自己,就要多看别人的程序,linux,uc/os-II等等这些都是很好的源码。 28. 我学过MCS51单片机教材,很有兴趣,但缺乏实践经验,手头没有任何道具可供演练,资金又有限,请问该怎么办? 答:在没有任何条件进行实践时,如果真的有兴趣,可以下载一些具有软件仿真功能仿真软件进行一些编程,像一些做得比较好的51仿真软件应该具有这种功能。HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK的仿真软件HT-IDE3000也具有相应的功能,同时它还具有LCD软件仿真,周边电路的软件仿真。有兴趣的话,也可以去免费下载使用:http://www.holtek.com.cn/tech/tool/ide.htm。同时可以到一些ic37去购买一些简单器件自己练习搭一下电路以加强硬件方面的知识。 29. 如果已经有了针对某MCU的C实现的某个算法,保持框架不变,对核心的部分用汇编优化,有没有一些比较通用的原则? 答:每个人的编程都有自己的风格与习惯,如果要利用别人的程序,在其中修修改改,如果他的程序并没有很好的模块化的话,建议最好不要这幺做,否则本来预期达到事倍功半,说不定反而事半功倍了。要参考他人的程序当然可以,但是首要是要看懂并理解他人程序的算法精髓,而不是在他的基础上打补丁。而关于算法方面的优化,可以购买一些数据结构的书籍,上面有比较详细的说明。 30. 如果准备估计一个算法的MIPS,有什么好的途径? 答:算法的运行时间是指一个算法在计算机上运算所花费的时间。它大致等于计算机执行简单操作(如赋值操作,比较操作等)所需要的时间与算法中进行简单操作次数的乘积。通常把算法中包含简单操作次数的多少叫做算法的时间复杂性。它是一个算法运行时间的相对量度,一般用数量级的形式给出。度量一个程序的执行时间通常有两种方法:  一种是事后统计的方法。因为很多计算机内部都有计时功能,不同算法的程序可通过一组或若干组相同的统计数据以分辨优劣。但这种方法有两个缺陷:一是必须先运行依据算法编制的程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用另一种事前分析估算的方法。  一种是事前分析估算的方法。一个程序在计算机上运行时所消耗的时间取决于下列因素: (1)依据的算法选用何种策略; (2)问题的规模。例如求100以内还是1000以内的素数; (3)书写程序的语言。对于同一个算法,实现语言的级别越高,执行效率就越低; (4)编译程序所产生的机器代码的质量。这个跟编译器有关; (5)机器执行指令的速度。 显然,同一个算法用不同的语言实现,或者用不同的编译程序进行编译,或者在不同的计算机上运行时,效率均不相同。这表明使用绝对的时间单位衡量算法的效率是不合适的。撇开这些与计算机硬件、软件有关的因素,可以认为一个特定算法"运行工作量"的大小,只依赖于问题的规模(通常用整数量n表示),或者说,它是问题规模的函数。 一个算法是由控制结构(顺序、分支和循环三种)和原操作(指固有数据类型的操作)构成的,则算法时间取决于两者的综合效果。为了便于比较同一问题的不同算法,通常的做法是,从算法中选取一种对于所研究的问题(或算法类型)来说是基本运算的原操作,以该基本操作重复执行的次数作为算法的时间度量。 算法的MIPS有专门的一门学问,可以去好好参考相关的数据结构书籍。 31. 遥控的编解码思路和设计流程是怎样的? 答:一般来说完整的遥控码分为头码、地址码、数据码和校验码四个组成部分。头码根据不同的厂家各不相同,地址码和数据码都由逻辑“1”和逻辑“0”组成。编码的设计目的,就是按照编码规则发送不同的码值。我们最常见的码型有SONY、松下、NEC等厂家型号。遥控编码芯片最常用的是在空调、DVD、车库门等遥控器上。 设计编码程序可以分为三个部分。 第一部分是了解码型的特性。遥控码的头码和地址码(也称为客户码)是固定不变的,数据码和校验码根据不同的键值而改变。 第二部分是计算发码时间。遥控码大部分都是由逻辑“1”和逻辑“0”组成,也就是由一串固定占空比、固定周期的方波所组成。通常这些方波的周期是毫秒甚至微秒等级,需要在时间上计算的比较精确。所以选择发码单片机型号的时候,就要考虑到单片机的运行速度是不是够快,以及程序运行时间够不够。 第三部分就是程序的编写。选定单片机型号之后,开始设计程序流程。一般来说我们使用I/O口就可以做发码的输出端口。发码程序一般由几个子程序组成,头码子程序、逻辑1子程序,逻辑0子程序以及校验码的算法子程序。一旦我们得到要发送码的命令后,首先调用头码子程序,然后根据客户码和键值调用逻辑1子程序或者逻辑0子程序,最后调用校验码算法子程序输出校验码。 HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK公司的HT48CA0/HT48RA0、HT48CA3/HT48RA3和HT48CA6是专为遥控器设计的单片机,它们具有专门红外输出口,可以实现绝大部分发码的要求。 设计解码程序也可以分为三部分。 第一部分了解编码波形特性。从分析编码的高、低脉冲宽度入手,了解逻辑“1”和逻辑“0”的波形占空比、周期。了解头码的特性。 第二部分确定接收方式。一般我们可以用I/O口查询方法或者INT口中断响应方法来接收编码。这两者的区别是I/O口查询方式比较耗费单片机的运行时间资源,需要不断的去侦测I/O的电平变化,以免漏掉有效的码值;而INT口中断接收方式则比较节省资源,当外部有电平变化时,单片机才需要去处理,不需要时刻进行侦测。但是INT口中断接收方式不能辨别相同周期不同占空比的波形特性,当编码所携带的逻辑“1”和逻辑“0”具有这种特性时,就无法通过INT口中断接收方式来辨别了,因为INT中断只是在上升沿或者下降沿的时候才触发。 第三部分将接收的码值存储并分析执行。根据判断高低电平的宽度(定时器或者延时),可以得到码值,也就是我们所说的解码。一般我们连续收到3个相同的完整码值,就确认此码的确被发出,并接收成功。当解码结束,根据码值我们可以判断出是哪个按键被按下,由此去执行相对的按键功能。 HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK公司的HT48以及HT49(带LCD)系列单片机,都可以符合大多数解码的任务。 32. 在学习单片机的过程中,如何理解预分频,12时钟模式(6时钟模型)等概念? 答:预分频器的英文是prescaler。它就是将输入的频率信号分频,然后再输出。HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK公司有一款最基本的8位I/O型单片机HT48R05A-1,我们就以这款单片机为例说明。HT48R05A-1有一个8位向上计数的定时器Counter。系统时钟Fsys(4MHz)进入八阶预分频器(8-stage Prescaler)进行分频,再进入定时计数器Counter计数。根据软件设置,预分频器可以将Fsys进行2的n次方分频(n=1~8)。举例来说,如果软件设置为预分频器2分频,那幺预分频器输出的频率就是Fsys/2=2MHz,这个2MHz信号再进入定时计数器Counter。 如果需要HT48R05A-1或者其它各类HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK单片机的详细资料,可以在如下地址下载:http://www.holtek.com.cn/referanc/htk_book.htm 。 12时钟模式(6时钟模型)应该就是在MCS51系列中,12个系统时钟为一个机器周期,2个系统时钟为一个状态,即一个机器周期有6个状态。 33. A/D、D/A的采样速率与其它单片机相比有什么优势? 答:HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK A/D Tyep MCU内嵌逐位逼近的A/D转换电路,精度有8bit/9bit/10bit,A/D转换时间最快为76us。 至于D/A,一般是指PWM输出,HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK A/D Type MCU都带有8bit的PWM输出,但HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK PWM的特点是其输出频率由系统频率决定(既系统频率选定后,PWM频率也就定了),其占空比通过对[PWM]寄存器赋值进行控制,不需要占用定时/计数器资源。 34. 采用AT89S51时,出现了按了复位按钮,RAM中的数据被修改了。这是怎么回事?注:数据放在特殊寄存器之外。 答:如果是RESET脚的复位按钮:一般MCU的RESET复位,其特殊寄存器会被重新初始化,而通用寄存器的值保持不变。 如果复位按钮是电源复位:那就是MCU的上电复位,其特殊寄存器会被初始化,而通用寄存器的值是随机数。 35. 将P2.7用来驱动一个NPN三极管,中间串接了一个1K的电阻。问题是:当我尝试向P2.7写’1’时,发现管脚只能输出大约0.5V的一个电平。这个电路的使用得妥当么?如何正确的使用IO功能? 答:是在仿真时遇到的问题,还是烧录芯片后遇到的问题? 可以先将P2.7的外部电路断开,测量输出电压是否正常。如果断开后输出电压正常,那就说明P2.7的驱动能力不够,不能驱动NPN三极管,应该改用PNP三极管(一般在MCU应用中,都采用PNP方式驱动)。如果断开后输出电压还不正常,那有可能是仿真器(或芯片)已经损坏。 36. 在做充电管理的时候,提高pwm的频率往往以牺牲精度为代价,如果用的AT90S4433(avr)、78P458(elan)频率分别做到16kHz(8bit)和32kHz(8bit),而希望做到的是100kHz(8bit以上),诸如atiny15那样。怎么办? 答:你所说的PWM是通过定时/计数器来控制其频率和占空比的,所以要提高频率,必然会降低精度。如果要提高PWM的频率,只能通过提高系统振荡频率来解决。 37. 汽车电子用的单片机是8位多,还是32位?如何看待单片机在汽车ic37中的前景? 答:现今汽车制造也是一个进步很快的工业,特别是电子应用于汽车上,令多种新功能得以实现。 总的来说,汽车电子应用分三部份。  汽车发动机控制:限速控制,涡轮增压,燃料喷注控制等。  汽车舒适装置:遥控防盗系统,自动空调系统,影音播放系统,卫星导航系统等。  汽车操控和制动:刹车防抱死系统(ABS),循迹系统(TCS),防滑系统(ASR),电子稳定系统(ESP)等。 汽车上的各系统繁多,且日新月异,故利用何种单片机是依各系统规格,要求不一,但有一样可肯定是该单片机要符工业规格,才能忍受汽车应用的恶劣环境,高温,电源干扰,可靠度要求。不同档次的汽车其功能配置相对亦有差别,故8位单片机在较低阶的系统如机械控制,遥控防盗等应该还有空间,但高阶的系统如影音、导航及将来的无人驾驶,就非一般单片机能实现。 因汽车工业现阶段由欧美日数个大集团所把持,相关的汽车电子配件各集团会挑选单片机大厂合作, 故汽车内置的电子系统亦由单片机大厂把持,市场只剩外置系统如遥控防盗,影音导航供小厂开发。 38. 在使用三星的s3c72n4时,觉得它的time/counter不够用。现在要同时用到3个counter,该怎么办? 答:您是需要三个外部counter还是需要三个定时器?如果是三个定时器标志的话,可以取这三个定时最基本的时基作为timer的基础计数,然后以这个时基来计算这三个需要的计数标志的flag,在程序中只需要查询flag是否到,再采取动作。 如果要3个外部脉冲计数的话,这个有一定的难度,如果外部脉冲不是很频繁,可以考虑通过外部中断进行,但是这个方法必须是外部脉冲的频率与MCU执行速度有一定的数量级差,否则mcu可能无法处理其它程序,一直在处理外部中断。 39. 在芯片集成技术日益进步的今天,单片机的集成技术发展也很迅速,在传统的40引脚的基础上,飞利浦公司推出20引脚的单片机系列,使很多的引脚可以复用,这种复用技术的使用在实际应用中会不会影响其功能的执行? 答:现在有很多品牌的单片机都有引脚复用功能,不止飞利浦一家,应该说这个方式前几年就已经有了。在实际应用中不会影响其功能的执行,但是要注意的是,有的MCU如果采用复用引脚的话,该引脚会有一些应用上的限制,这在相应的datasheet里面都会有描述,所以在系统规划的时候都要予以注意。 40. Delta-Sigma软件测量方式,是什么概念? 答:Delta-Sigma原理一般应用在ADC应用中。具体来说,Delta-Sigma ADC的工作原理是由差动器、积分器和比较器构成调制器,它们一起构成一个反馈环路。调制器以大大高于模拟输入信号带宽的速率运行,以便提供过采样。模拟输入与反馈信号(误差信号)进行差动 (delta)比较。该比较产生的差动输出馈送到积分器(sigma)中。然后将积分器的输出馈送到比较器中。比较器的输出同时将反馈信号(误差信号)传送到差动器,而自身被馈送到数字滤波器中。这种反馈环路的目的是使反馈信号(误差信号)趋于零。比较器输出的结果就是1/0 流。该流如果1密度较高,则意味着模拟输入电压较高;反之,0密度较高,则意味着模拟输入电压较低。接着将1/0流馈送到数字滤波器中,该滤波器通过过采样与抽样,将1/0流从高速率、低精度位流转换成低速率、高精度数字输出。 简而言之,Delta就是差动,Sigma就是积分的意思。Delta-Sigma软件测试,我的理解应该是通过软件模拟差动积分的过程。具体来说,就是侦测外部输入的电压(或者电流)信号变化,然后通过软件积分运算,得出外部信号随时间变化的基本状况。 41. 通常采用什么方法来测试单片机系统的可靠性? 答:单片机系统可以分为软件和硬件两个方面,我们要保证单片机系统可靠性就必须从这两方面入手。 首先在设计单片机系统时,就应该充分考虑到外部的各种各样可能干扰,尽量利用单片机提供的一切手段去割断或者解决不良外部干扰造成的影响。我们以HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK最基本的I/O单片机HT48R05A-1为例,它内部提供了看门狗定时器WDT防止单片机内部程序乱跑出错;提供了低电压复位系统LVR,当电压低于某个允许值时,单片机会自动RESET防止芯片被锁死;HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK也提供了最佳的外围电路连接方案,最大可能的避免外部干扰对芯片的影响。 当一个单片机系统设计完成,对于不同的单片机系统产品会有不同的测试项目和方法,但是有一些是必须测试的:  测试单片机软件功能的完善性。 这是针对所有单片机系统功能的测试,测试软件是否写的正确完整。  上电掉电测试。在使用中用户必然会遇到上电和掉电的情况,可以进行多次开关电源,测试单片机系统的可靠性。  老化测试。测试长时间工作情况下,单片机系统的可靠性。必要的话可以放置在高温,高压以及强电磁干扰的环境下测试。  ESD和EFT等测试。可以使用各种干扰模拟器来测试单片机系统的可靠性。例如使用静电模拟器测试单片机系统的抗静电ESD能力;使用突波杂讯模拟器进行快速脉冲抗干扰EFT测试等等。 当然如果没有此类条件,可以模拟人为使用中,可能发生的破坏情况。例如用人体或者衣服织物故意摩擦单片机系统的接触端口,由此测试抗静电的能力。用大功率电钻靠近单片机系统工作,由此测试抗电磁干扰能力等。 42. 在开发单片机的系统时,具体有那些是衡量系统的稳定性的标准? 答:从工业的角度来看,衡量系统稳定性的标准有很多,也针对不同的产品标准不同。下面我们大概介绍单片机系统最常用的标准。  电试验(ESD) 参考标准: IEC 61000-4-2 本试验目的为测试试件承受直接来自操作者及相对对象所产生之静电放电效应的程度。  空间辐射耐受试验(RS) 参考标准:IEC 61000-4-3 本试验为验证试件对射频产生器透过空间散射之噪声耐受程度。 测试频率:80 MHz~1000 MHz  快速脉冲抗扰测试(EFT/B) 参考标准:IEC 61000-4-4 本试验目的为验证试件之电源线,信号线(控制线)遭受重复出现之快速瞬时丛讯时之耐受程度。  雷击试验(Surge) 参考标准 : IEC 61000-4-5 本试验为针对试件在操作状态下,承受对于开关或雷击瞬时之过电压/电流产生突波之耐受程度。  传导抗扰耐受性(CS) 参考标准:IEC 61000-4-6 本试验为验证试件对射频产生器透过电源线传导之噪声耐受程度。 测试频率范围:150 kHz~80 MHz  Impulse 脉冲经由耦合注入电源线或控制线所作的杂抗扰性试验。 43. 在设计软体时,大多单片机都设有看门狗,需要在软体适当的位置去喂狗,以防止软体复位和软体进入死循环,如何适当的喂狗,即如何精确判定软体的运行时间? 答:大多数单片机都有看门狗定时器功能(WDT,Watch Dog Timer)以避免程序跑错。HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK有一款基本I/O型单片机--HT48R05A-1,我们就以它为例做个说明吧。 首先了解一下WDT的基本结构,它其实是一个定时器,所谓的喂狗是指将此定时器清零。喂狗分为软件和硬件两种方法。软件喂狗就是用指令来清除WDT,即CLR WDT;硬件喂狗就是硬件复位RESET。当定时器溢出时,会造成WDT复位,也就是我们常说的看门狗起作用了。在程序正常执行时,我们并不希望WDT复位,所以要在看门狗溢出之前使用软件指令喂狗,也就是要计算WDT相隔多久时间会溢出一次。HT48R05A-1的WDT溢出时间计算公式是:256*Div*Tclock。其中Div是指wdt预分频数1~128,Tclock是指时钟来源周期。如果使用内部RC振荡作为WDT的时钟来源(RC时钟周期为65us/5V),最大的WDT溢出时间为2.1秒。 当我们得到了WDT溢出时间Twdt后,一般选择在Twdt/2左右的时间进行喂狗,以保证看门狗不会溢出,同时喂狗次数不会过多。 软件运行时间是根据不同的运行路线来决定的,如果可以预见软件运行的路线,那么可以根据T=n*T1来计算软件的运行时间。n是指运行的机器周期数,T1是指机器周期。HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK单片机是RISC结构,大部分指令由一个机器周期组成,只需要知道软件运行了多少条指令,就可以算出运行时间了。HOLTEK-p.htm" target="_blank" title="HOLTEK货源和PDF资料">HOLTEK的编译软件HT-IDE3000中,就有计算运行时间的工具。但是对于CISC结构的单片机,一条指令可以由若干个机器周期组成,那么就需要根据具体执行的指令来计算了。 44. 我们是一家开发数控系统的专业厂,利用各种单片机和CPU开发了很多产品,在软件开发上也采用了很多通用的抗干扰技术,如:软件陷阱、指令允余、看门狗和数字滤波等等,但实际运用中还是很不可靠,如:经常莫名其妙地死机、程序跳段、I/O数据错误等,并且故障的重复性很不确定,也不是周期性地重复。往往用户使用中出现故障,但又无法重现,很让人头痛。反复检查硬件也设查出原因,所以对软件的可靠性很是怀疑。怎么办? 答:防止干扰最有效的方法是去除干扰源、隔断干扰路径,但往往很难做到,所以只能看单片机抗干扰能力够不够强了。单片机干扰最常见的现象就是复位;至于程序跑飞,其实也可以用软件陷阱和看门狗将程序拉回到复位状态;所以单片机软件抗干扰最重要的是处理好复位状态。 一般单片机都会有一些标志寄存器,可以用来判断复位原因;另外也可以自己在RAM中埋一些标志。在每次程序复位时,通过判断这些标志,可以判断出不同的复位原因;还可以根据不同的标志直接跳到相应的程序。这样可以使程序运行有连续性,用户在使用时也不会察觉到程序被重新复位过。 可以在定时中断里面设置一些暂存器累加,然后加到预先设定的值(一个比较长的时间),SET标志位,这些动作都在中断程序里面。而主程序只需要查询标志位就好了,但是注意标志位使用后,记得清除,还有中断里面的时基累加器使用以后也要记得清除。
内容简介 《你必须知道的495个C语言问题》以问答的形式组织内容,讨论了学习或使用C语言的过程中经常遇到的一些问题。书中列出了C用户经常问的400多个经典问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预处理器等各个方面的主题,并分别给出了解答,而且结合代码示例阐明要点。 《你必须知道的495个C语言问题》结构清晰,讲解透彻,是各高校相关专业C语言课程很好的教学参考书,也是各层次C程序员的优秀实践指南。 -------------------------------------------------------------------------------- C是一门简洁精妙的语言,掌握基本语法容易,真正能够自如运用,就不那么简单了。你难免会遇到各种各样的问题,有些可能让你百思不得其解,甚至翻遍图书馆,也找不到问题的答案。 《你必须知道的495个C语言问题》的出版填补了这一空白。许多知识点的阐述都是其他资料中所没有的,弥足珍贵。 涵盖C99标准 目录 ~第1章 声明和初始化 1 基本类型 1 1.1 我该如何决定使用哪种整数类型? 1  1.2 为什么不精确定义标准类型的大小? 2 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 2  1.4 新的64位机上的64位类型是什么样的? 3 指针声明 3 1.5 这样的声明有什么问题?char *p1, p2; 我在使用p2的时候报错了。 3 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char *p; *p=malloc(10); 4 声明风格 4 1.7 怎样声明和定义全局变量和函数最好? 4 1.8 如何在C中实现不透明(抽象)数据类型? 5 1.9 如何生成“半全局变量”,就是那种只能被部分源文件中的部分函数访问的变量? 5 存储类型 6 1.10 同一个静态(static)函数或变量的所有声明都必须包含static存储类型吗? 6 1.11 extern在函数声明中是什么意思? 6 1.12 关键字auto到底有什么用途? 7 类型定义(typedef) 7 1.13 对于用户定义类型,typedef 和#define有什么区别? 7 1.14 我似乎不能成功定义一个链表。我试过typedef struct{char *item; NODEPTR next;}* NODEPTR; 但是编译器报了错误信息。难道在C语言中结构不能包含指向自己的指针吗? 7  1.15 如何定义一对相互引用的结构? 9 1.16 Struct{ } x1;和typedef struct{ } x2; 这两个声明有什么区别? 10 1.17 “typedef int(*funcptr)();”是什么意思? 10 const 限定词 10 1.18 我有这样一组声明:typedef char *charp; const charp p; 为什么是p而不是它指向的字符为const? 10 1.19 为什么不能像下面这样在初始式和数组维度值中使用const值?const int n=5; int a[n]; 10 1.20 const char *p、char const *p和char *const p有什么区别? 10 复杂的声明  11 1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 11  1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 12  数组大小 13 1.23 能否声明和传入数组大小一致的局部数组,或者由其他参数指定大小的参数数组? 13 1.24 我在一个文件中定义了一个extern数组,然后在另一个文件中使用,为什么sizeof取不到数组的大小? 13 声明问题 14 1.25 函数只定义了一次,调用了一次,但编译器提示非法重声明了。 14 *1.26 main的正确定义是什么?void main正确吗? 15 1.27 我的编译器总在报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 15 1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 15 1.29 为什么我的编译器不允许我定义大数组,如double array[256][256]? 15 命名空间 15 1.30 如何判断哪些标识符可以使用,哪些被保留了? 15 初始化 18 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 18  1.32 下面的代码为什么不能编译? intf(){char a[]="Hello, world!";} 18 *1.33 下面的初始化有什么问题?编译器提示“invalid initializers ”或其他信息。char *p=malloc(10); 19 1.34 char a[]= "string literal";和char *p="string literal"; 初始化有什么区别?当我向p[i] 赋值的时候,我的程序崩溃了。 19  1.35 char a{[3]}= "abc"; 是否合法? 20 1.36 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢? 20 1.37 能够初始化联合吗? 20 第2章 结构、联合和枚举 21 结构声明 21 2.1 struct x1{ };和typedef struct{ }x2; 有什么不同? 21 2.2 这样的代码为什么不对?struct x{ }; x thestruct; 22 2.3 结构可以包含指向自己的指针吗? 22 2.4 在C语言中用什么方法实现抽象数据类型最好? 22 *2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 22 2.6 为什么声明extern f(struct x *p); 给我报了一个晦涩难懂的警告信息? 23 2.7 我遇到这样声明结构的代码:struct name {int namelen; char namestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素,namelen记录了元素个数。它是怎样工作的?这样是合法的和可移植的吗? 23  2.8 我听说结构可以赋给变量也可以对函数传入和传出。为什么K&R1却明确说明不能这样做? 25 2.9 为什么不能用内建的==和!=操作符比较结构?  26 2.10 结构传递和返回是如何实现的? 26 2.11 如何向接受结构参数的函数传入常量值?怎样创建无名的中间的常量结构值? 26 2.12 怎样从/向数据文件读/写结构? 27 结构填充 27 2.13 为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式? 27  2.14 为什么sizeof返回的值大于结构大小的期望值,是不是尾部有填充? 28 2.15 如何确定域在结构中的字节偏移量? 28 2.16 怎样在运行时用名字访问结构中的域? 29 2.17 C语言中有和Pascal的with等价的语句吗?  29 2.18 既然数组名可以用作数组的基地址,为什么对结构不能这样? 29 2.19 程序运行正确,但退出时却“core dump ”(核心转储)了,怎么回事? 29 联合 30 2.20 结构和联合有什么区别? 30 2.21 有办法初始化联合吗? 30 2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 30 枚举 31 2.23 枚举和一组预处理的#define有什么不同?  31 2.24 枚举可移植吗? 31 2.25 有什么显示枚举值符号的容易方法吗? 31 位域 31 2.26 一些结构声明中的这些冒号和数字是什么意思? 31 2.27 为什么人们那么喜欢用显式的掩码和位操作而不直接声明位域? 32 第3章 表达式  33 求值顺序 33 3.1 为什么这样的代码不行?a[i]= i++; 33 3.2 使用我的编译器,下面的代码int i= 7; printf("%d\n", i++ * i++); 打印出49。不管按什么顺序计算,难道不该是56吗? 33  3.3 对于代码int i=3; i=i++; 不同编译器给出不同的i值,有的为3,有的为4,哪个是正确的? 34  *3.4 有这样一个巧妙的表达式:a^= b^= a^= b; 它不需要临时变量就可以交换a和b的值。 34 3.5 可否用显式括号来强制执行我所需要的计算顺序并控制相关的副作用?就算括号不行,操作符优先级是否能够控制计算顺序呢? 35  3.6 可是&&和||操作符呢?我看到过类似while((c = getchar()) != EOF && c != '\n')的代码…… 35 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式的结果,则右边的表达式不会被求值? 36  3.8 为什么表达式printf("%d %d", f1(), f2()); 先调用了f2?我觉得逗号表达式应该确保从左到右的求值顺序。 36  3.9 怎样才能理解复杂表达式并避免写出未定义的表达式?“序列点”是什么? 36 3.10 在a[i] = i++;中,如果不关心a[]的哪一个分量会被写入,这段代码就没有问题,i也的确会增加1,对吗? 38  3.11 人们总是说i=i++的行为是未定义的。可我刚刚在一个ANSI编译器上尝试过,其结果正如我所期望的。 38  3.12 我不想学习那些复杂的规则,怎样才能避免这些未定义的求值顺序问题呢? 38 其他的表达式问题 39 *3.13 ++i和i++有什么区别? 39 3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 39 3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 40 3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * b; 40 3.17 为什么下面的代码总是给出0?double degC, degF; degC= 5.0 / 9 * (degF - 32); 40 3.18 需要根据条件把一个复杂的表达式赋给两个变量中的一个。可以用下面这样的代码吗?((condition) ? a : b)= complicated_expression; 41  3.19 我有些代码包含这样的表达式。a ? b=c : d 有些编译器可以接受,有些却不能。为什么? 41 保护规则 42 3.20 “semantics of‘’change in ANSI C”的警告是什么意思? 42 3.21 “无符号保护”和“值保护”规则的区别在哪里? 42 第4章 指针 45 基本的指针应用 45 4.1 指针到底有什么好处? 45 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char *p; *p =malloc(10); 45  4.3 *p++自增p还是p所指向的变量? 46 指针操作 46 4.4 我用指针操作int数组的时候遇到了麻烦。 46 4.5 我有一个char *型指针碰巧指向一些int型变量,我想跳过它们。为什么((int *)p)++; 这样的代码不行? 47 4.6 为什么不能对void *指针进行算术操作? 47 4.7 我有些解析外部结构的代码,但是它却崩溃了,显示出了“unaligned access”(未对齐的访问)的信息。这是什么意思? 47 作为函数参数的指针 47 4.8 我有个函数,它应该接受并初始化一个指针:void f(int *ip){ static int dummy = 5; ip = &dummy;}但是当我如下调用时:int *ip; f(ip); 调用者的指针没有任何变化。 47  4.9 能否用void ** 通用指针作为参数,使函数模拟按引用传递参数?  48 4.10 我有一个函数extern intf(int *); ,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。 49  4.11 C语言可以“按引用传参”吗? 50 其他指针问题 50 4.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 50 4.13 通用指针类型是什么?当我把函数指针赋向void *类型的时候,编译通不过。 51 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,或者相反? 51 *4.15 我怎样把一个int变量转换为char *型?我试了类型转换,但是不行。 52 第5章 空指针  53 空指针和空指针常量 53 5.1 臭名昭著的空指针到底是什么? 53 5.2 怎样在程序里获得一个空指针? 54 5.3 用缩写的指针比较“if(p)”检查空指针是否有效?如果空指针的内部表达不是0会怎样? 55 NULL 宏 56 5.4 NULL是什么,它是怎么定义的? 56 5.5 在使用非零位模式作为空指针的内部表示的机器上,NULL 是如何定义的? 56 5.6 如果NULL定义成#define NULL((char *)0) ,不就可以向函数传入不加转换的NULL 了吗? 57 5.7 我的编译器提供的头文件中定义的NULL为0L。为什么? 57 5.8 NULL可以合法地用作函数指针吗? 57 5.9 如果NULL和0作为空指针常量是等价的,那我到底该用哪一个呢? 58 5.10 但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL(而不是0) 不是更好吗? 58  5.11 我曾经使用过一个编译器,不使用NULL就不能编译。 58 5.12 我用预处理宏#define Nullptr(type)(type *)0帮助创建正确类型的空指针。 59 回顾 59 5.13 这有点奇怪:NULL可以确保是0,但空(null)指针却不一定? 59 5.14 为什么有那么多关于空指针的疑惑?为什么这些问题如此频繁地出现? 60 5.15 有没有什么简单点儿的办法理解所有这些与空指针有关的东西呢? 60 5.16 考虑到有关空指针的所有这些困惑,要求它们的内部表示都必须为0不是更简单吗? 60 5.17 说真的,真有机器用非零空指针吗,或者不同类型用不同的表示? 61 地址0 上到底有什么? 61 5.18 运行时的整数值0转换为指针以后一定是空指针吗? 61 5.19 如何访问位于机器地址0处的中断向量?如果我将指针值设为0,编译器可能会自动将它转换为非零的空指针内部表示。 62  5.20 运行时的“null pointer assignment”错误是什么意思?应该怎样捕捉它? 62 第6章 数组和指针 63 数组和指针的基本关系 63 6.1 我在一个源文件中定义了char a[6],在另一个源文件中声明了extern char *a。为什么不行? 63 6.2 可是我听说char a[]和char *a是等价的。是这样的吗? 63 6.3 那么,在C语言中“指针和数组等价”到底是什么意思? 64 6.4 既然它们这么不同,那为什么作为函数形参的数组和指针声明可以互换呢? 65 数组不能被赋值 66 6.5 为什么不能这样向数组赋值?extern char *getpass(); char str[10]; str=getpass("Enter password:"); 66  6.6 既然不能向数组赋值,那这段代码为什么可以呢?int f(char str[]){ if(str[0] == '\0') str="none";…} 66  6.7 如果你不能给它赋值,那么数组如何能成为左值呢? 66 回顾 67 6.8 现实地讲,数组和指针的区别是什么? 67 6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 67 6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 67 6.11 我看到一些“搞笑”的代码,包含5["abcdef"]这样的“表达式”。这为什么是合法的C语言表达式呢? 68 数组的指针  68 6.12 既然数组引用会退化为指针,如果array是数组,那么array和&array又有什么区别呢? 68 6.13 如何声明一个数组的指针? 69 动态数组分配 70 6.14 如何在运行时设定数组的大小?怎样才能避免固定大小的数组? 70 6.15 我如何声明大小和传入的数组一样的局部数组? 70 6.16 如何动态分配多维数组? 71 6.17 有个很好的窍门,如果我这样写:int realarray[10]; int *array = &realarray[-1]; 我就可以把“array”当作下标从1 开始的数组。 72 函数和多维数组 73 6.18 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。 73 6.19 我怎样编写接受编译时宽度未知的二维数组的函数? 74 6.20 我怎样在函数参数传递时混用静态和动态多维数组? 74 数组的大小  75 6.21 当数组是函数的参数时,为什么sizeof不能正确报告数组的大小? 76 6.22 如何在一个文件中判断声明为extern的数组的大小(例如,数组定义和大小在另一个文件中)?sizeof操作符似乎不行。 76  6.23 sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素呢? 76 第7 章 内存分配 77 基本的内存分配问题 77 7.1 为什么这段代码不行?char *answer; printf("Type something:\n"); gets(answer); printf("You typed \"%s\"\n", answer); 77 7.2 我的strcat() 不行。我试了下面的代码:char *s1= "Hello,"; char *s2= "world!"; char *s3= strcat(s1, s2);但是我得到了奇怪的结果。 78  7.3 但是strcat的文档说它接受两个char *型参数。我怎么知道(空间)分配的事情呢? 78 *7.4 我刚才试了这样的代码:char *p; strcpy(p, "abc");它运行正常。怎么回事?为什么它没有出错? 79  *7.5 一个指针变量分配多少内存? 79 7.6 我使用fgets将文件的所有行读入一个数组,为什么读入的每一行都是最后一行的内容呢? 79 7.7 我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回的字符串却是垃圾信息。 为什么?  80 *7.8 那么返回字符串或其他聚集的正确方法是什么呢? 81 调用malloc 81 7.9 为什么在调用malloc()时报出了“waring: assignment of pointer from integer lacks a cast”? 81 7.10 为什么有些代码小心翼翼地把malloc返回的值转换为分配的指针类型? 81 *7.11 在调用malloc()的时候,错误“不能把void * 转换为int * ”是什么意思? 82 7.12 我看到下面这样的代码:char *p = malloc(strlen(s) + 1); strcpy(p,s); 难道不应该是malloc ((strlen(s) + 1) * sizeof(char)) 吗? 82  7.13 我为malloc写了一个小小的封装函数。它为什么不行? 82 7.14 我想声明一个指针并向它分配一些内存,但是不行。这样的代码有什么问题?char *p; *p = malloc(10); 82  7.15 我如何动态分配数组? 83 7.16 怎样判断还有多少内存? 83 7.17 malloc(0)是返回空指针还是指向0个字节的指针? 83 7.18 我听说有的操作系统在程序使用的时候才真正分配malloc申请的内存。这合法吗? 83 有关malloc 的问题 83 7.19 为什么malloc返回了离谱的指针值?我的确读过问题7.9,而且也在调用之前包含了extern void *malloc();声明。  83  7.20 我用一行这样的代码分配一个巨大的数组,用于数值运算:double *array = malloc (256 *256 *sizeof(double));malloc()并没有返回空指针,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存。为什么? 84  7.21 我的PC机有8兆内存。为什么我只能分配640K左右的内存? 84 7.22 我的应用程序非常依赖数据结构的节点的动态分配,而malloc/free的代价成了瓶颈。我该怎么做? 84 7.23 我的程序总是崩溃,显然发生在malloc内部的某个地方。但是我看不出哪里有问题。是malloc有bug吗? 84 释放内存 85 7.24 动态分配的内存一旦释放之后就不能再使用,是吧? 85 7.25 为什么在调用free()之后指针没有变空?使用(赋值、比较)释放之后的指针有多么不安全? 86 7.26 当我调用malloc()为一个函数的局部指针分配内存时,我还需要用free()显式地释放吗? 86 7.27 我在分配一些结构,它们包含指向其他动态分配的对象的指针。我在释放结构的时候,还需要释放每一个下级指针吗? 86  7.28 我必须在程序退出之前释放分配的所有内存吗? 86 7.29 我有个程序分配了大量的内存,然后又释放了。但是从操作系统看,内存占用率却并没有变回去。 87  分配内存块的大小 87  7.30 free()怎么知道有多少字节需要释放? 87 7.31 那么我能否查询malloc包,以查明可分配的最大块是多大? 87 7.32 为什么sizeof不能告诉我它所指的内存块的大小? 87 其他分配函数 88 7.33 (像问题6.14中那样)动态分配数组之后,还能改变它的大小吗? 88 7.34 向realloc()的第一个参数传入空指针合法吗?你为什么要这样做? 89 7.35 calloc()和malloc()有什么区别?应该用哪一个?利用calloc 的零填充功能安全吗?free()可以释放calloc()分配的内存吗,还是需要一个cfree()?  90 7.36 alloca是什么?为什么不提倡使用它? 91 第8章 字符和字符串 92 8.1 为什么strcat(string, '!'); 不行? 92 8.2 我想检查一个字符串是否跟某个值匹配。为什么这样不行?if(string == "value") 92 8.3 如果我可以写char a[] = "Hello, world!"; 那为什么不能写char a[14]; a = "Hello, world!"; 93  8.4 为什么我的strcat 不行?我试了char *s1="Hello,"; char *s2="world!"; char *s3 =strcat(s1, s2);可得到的结果很奇怪。 93  8.5 char a[]= "string literal"; 和char *p= "string literal"; 初始化有什么区别?当我对p[i]赋值的时候,程序崩溃了。 93  8.6 我怎么得到与字符相对应的数字(即ASCII 或其他字符集下的)值?反过来又该怎么做? 94 8.7 C语言有类似其他语言的"substr"(提取子串)这样的函数吗? 94 8.8 我将用户键入的字符串读入数组,然后再显示出来。当用户键入\n这样的序列时,为什么不能正确处理呢? 94  8.9 我注意到sizeof('a')是2而不是1(即不是sizeof(char)),是不是我的编译器有问题? 94 8.10 我正开始考虑多语言字符集的问题。是否有必要担心sizeof(char)会被定义为2,以便表达16位的字符集呢? 95  第9章 布尔表达式和变量 96 9.1 C语言中布尔值该用什么类型?为什么它不是一个标准类型?我应该用#define或enum定义真值和假值吗? 96  9.2 既然在C 语言中所有的非零值都被看作“真”,那是不是把TRUE 定义为1很危险?如果某个内建的函数或关系操作符“返回”不是1的其他值怎么办?  97  9.3 当p是指针时,if(p)是合法的条件表达式吗? 98 9.4 我该使用像TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 98 9.5 我准备使用的一个第三方头文件定义了自己的TRUE和FALSE,它们跟我已经开发的部分不兼容。我该怎么办? 98  第10章 C预处理器 99 宏定义 99 10.1 我想定义一些函数式的宏,例如:#define square(x)x * x但它们并不总是正确的。为什么? 99 10.2 这里有一些的预处理宏,使用它们,我可以写出更像Pascal的C代码。你觉得怎么样? 100 10.3 怎么写一个交换两个值的通用宏?  101 10.4 书写多语句宏的最好方法是什么?  101 10.5 用typdef和预处理宏生成用户定义类型有什么区别? 102 头文件 102 10.6 我第一次把一个程序分成多个源文件,我不知道该把什么放到.c文件,把什么放到.h文件。(“.h”到底是什么意思?) 102  10.7 可以在一个头文件中包含另一头文件吗? 103 10.8 完整的头文件搜索规则是怎样的?  104 10.9 我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题。 104 10.10 我使用了来自两个不同的第三方库的头文件,它们都定义了相同的宏,如TRUE、FALSE、Min()和Max()等,但是它们的定义相互冲突,而且跟我在自己的头文件中的定义也有冲突。我该怎么办? 104  10.11 我在编译一个程序,看起来我好像缺少需要的一个或多个头文件。谁能发给我一份? 105 条件编译  105 10.12 怎样构造比较字符串的#if预处理表达式? 105 10.13 sizeof操作符可以用在#if预处理指令中吗? 106 10.14 我可以像这样在#define行里使用#ifdef来定义两个不同的东西吗? 106 10.15 对typedef的类型定义有没有类似#ifdef的东西? 106 10.16 我如何用#if表达式来判断机器是高字节在前还是低字节在前? 107 10.17 为什么在我用#ifdef关掉的代码行中报出了奇怪的语法错误? 107 10.18 我拿到了一些代码,里边有太多的#ifdef。我不想使用预处理器把所有的#include 和#ifdef都扩展开,有什么办法只保留一种条件的代码呢? 107  10.19 如何列出所有的预定义宏? 107 奇异的处理 108 10.20 我有些旧代码,试图用这样的宏来构造标识符:#define Paste(a, b) a/**/b 但是现在不行了。为什么? 108  10.21 我有一个旧宏:#define CTRL(c) ('c' & 037)现在不能用了。为什么? 108 10.22 为什么宏#define TRACE(n) printf("TRACE: \%d\n", n) 报出警告“macro replacement within a string literal ”?它似乎把TRACE(count);扩展成了printf("TRACE: \%d\count", count); 109  10.23 如何在宏扩展的字符串字面量中使用宏参数? 109 10.24 我想用ANSI 的“字符串化”预处理操作符#将符号常量的值放入消息中,但它总是对宏名称而不是它的值进行字符串化。这是什么原因? 109  10.25 我想用预处理器做某件事情,但却不知道如何下手。 110 可变参数列表的宏 110 10.26 怎样写可变参数宏?如何用预处理器“关掉”具有可变参数的函数调用? 110 10.27 如何在通用的调试宏中包含__FILE__和__LINE__宏? 111 第11章 ANSI/ISO标准C 113 标准 113 11.1 什么是“ANSI C标准”? 113 11.2 如何得到一份标准的副本? 114 *11.3 我在哪里可以找到标准的更新? 115 函数原型  115 11.4 为什么我的ANSI编译器对用float声明的参数会警告类型不匹配? 115 11.5 能否混用旧式的和新型的函数语法? 116 *11.6 为什么下述声明报出了一个奇怪的警告信息“Struct X declared inside parameter list”? extern int f(struct x *p); 116  11.7 有个问题一直困扰着我,它是由这一行printf ("%d", n); 导致的,因为n是个long int型。难道 ANSI 的函数原型不能检查这种函数的参数不匹配问题吗? 116  11.8 我听说必须在调用printf之前包含stdio.h。为什么? 117 const 限定词 117 11.9 为什么不能在初始化和数组维度中使用const值?例如const int n = 5; int a[n]; 117 11.10 “const char *p”、“char const *p ”和“char * const p ”有何区别? 117 11.11 为什么不能向接受const char ** 的函数传入char **? 118 11.12 我这样声明:typedef char * charp; const charp p; 为什么是p而不是它所指向的字符为const?  118  main()函数的使用 119  11.13 能否通过将main声明为void来关掉“main没有返回值”的警告? 119 11.14 main()的第3个参数envp是怎么回事?  120 11.15 我觉得把main()声明为void也不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态。 120 *11.16 那么到底会出什么问题?真的有什么系统不支持void main()吗? 120 11.17 为什么以前流行的那些C 语言书总是使用void main()?  120 11.18 在main()中调用exit(status)和返回同样的status真的等价吗? 121 预处理功能 121 11.19 我试图用ANSI“字符串化”预处理操作符'#'向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值。为什么? 121  11.20 警告信息“warning: macro replacement within a string literal”是什么意思? 121 11.21 为什么在我用#ifdef去掉的代码里出现了奇怪的语法错误? 122 11.22 #pragma是什么,有什么用? 122 11.23 “#pragma once”什么意思?我在一些头文件中看到了它。 122 其他的ANSI C 问题 123 11.24 char a[3] = "abc";合法吗?它是什么意思? 123 11.25 既然对数组的引用会退化为指针,那么,如果array是数组,array和&array之间有什么区别呢? 123 11.26 为什么我不能对void *指针进行算术运算? 123 11.27 memcpy()和memmove() 有什么区别? 124 11.28 malloc(0)有什么用?返回一个空指针还是指向0字节的指针? 124 11.29 为什么ANSI 标准规定了外部标识符的长度和大小写限制? 125 11.30 noalias是怎么回事?在它身上发生了什么? 125 老的或非标准的编译器 125 11.31 为什么我的编译器对最简单的测试程序都报出了一大堆的语法错误?对这段代码的第一行就报错了:main(int argc. char **argv) { return0; } 125  11.32 为什么有些 ASNI/ISO 标准库函数未定义?我明明使用的就是ANSI 编译器。 126 11.33 谁有可以在旧的C 程序和ANSI C 之间相互转换的工具,或者自动生成原型的工具? 127 11.34 为什么声称兼容ANSI 的编译器不能编译这些代码?我知道这些代码是 ANSI 的,因为gcc 可以编译。 127  兼容性 127  11.35 人们好像有些在意实现定义的(implementation-defined)、不确定的(unspecified)和未定义的(undefined) 行为的区别。它们的区别到底在哪里? 128  *11.36 一个程序“合法(legal)”、“有效(valid)”或“符合标准的”(conforming )到底是什么意思? 128  11.37 我很吃惊,ANSI 标准竟然有那么多未定义的东西。标准的唯一任务不就是让这些东西标准化吗? 129 11.38 有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI 的编译器上测试,得到了我希望的结果。它真的是未定义的吗? 129  第12章 标准输入输出库 130 基本输入输出 130 12.1 这样的代码有什么问题?char c; while((c = getchar()) != EOF) 130 12.2 我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个“\EOF”呢?我看stdio.h 中定义的EOF 是-1,是不是说我该输入-1?  131  12.3 为什么这些代码把最后一行复制了两遍?while(!feof(infp)){fgets(buf, MAXLINE, infp); fputs(buf, outfp);} 131  12.4 我用fgets将文件的每行内容读入指针数组。为什么结果所有的行都是最后一行的内容呢? 132 12.5 我的程序的屏幕提示和中间输出有时没有在屏幕上显示,尤其是当我用管道通过另一个程序输出的时候。为什么? 132  12.6 我怎样才能不等待回车键而一次输入一个字符? 132 printf格式 132 12.7 如何在printf 的格式串中输出一个'%'字符?我试过\%,但是不行。 132 12.8 为什么这么写不对?long int n = 123456; printf("%d\n", n); 133 12.9 有人告诉我不能在printf 中使用%lf。为什么printf() 用%f输出double 型,而scanf 却用%lf 呢? 133  *12.10 对于size_t 那样的类型定义,当我不知道它到底是long 还是其他类型的时候,我应该使用什么样的printf格式呢? 134  12.11 如何用printf 实现可变的域宽度?就是说,我想在运行时确定宽度而不是使用%8d? 134 12.12 如何输出在千位上用逗号隔开的数字?货币格式的数字呢? 135 12.13 为什么scanf("%d", i) 调用不行? 136 *12.14 为什么char s[30]; scamf("%s", s); 不用&也可以?我原以为传给scanf的每个变量都要带&。 136 12.15 为什么这些代码不行?double d; scanf("%f", &d); 136 12.16 为什么这段代码不行?short int s; scanf("%d", &s); 136 12.17 怎样在scanf 格式串中指定可变的宽度?  136 12.18 怎样从特定格式的数据文件中读取数据?怎样读入10个float 而不用使用包含10次%f的奇怪格式?如何将一行的任意多个域读入一个数组中? 137 scanf问题 138 12.19 我像这样用"%d\n"调用scanf 从键盘读取数字:int n; scanf("%d\n",&n); printf("you typed %d\ n", n);好像要多输入一行才返回。为什么? 138  12.20 我用scanf 和%d读取一个数字,然后再用gets() 读取字符串,但是编译器好像跳过了gets() 调用!  139 12.21 我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf 的使用会安全很多。但有的时候好像会陷入无限循环。为什么? 139  12.22 为什么大家都说不要使用scanf?那我该用什么来代替呢? 140 其他stdio 函数 141 12.23 我怎样才知道对于任意的sprintf 调用需要多大的目标缓冲区?怎样才能避免sprintf 目标缓冲区溢出? 141  12.24 sprintf的返回值是什么?是int 还是char *? 142 12.25 为什么大家都说不要使用gets?  142 12.26 我觉得我应该在一长串的printf 调用之后检查errno ,以确定是否有失败的调用。为什么当我将输出重定向到文件的时候会输出奇怪的“printf failed: Not a typewriter ”信息? 142  12.27 fgetops/fsetops和ftell/fseek之间有什么区别?fgetops和fsetops 到底有什么用处? 143 12.28 如何清除用户的多余输入,以防止在下一个提示符下读入?用fflush(stdin) 可以吗? 143  打开和操作文件 144 12.29 我写了一个函数用来打开文件:myfopen(char *filename, FILE *fp){fp = fopen(filename, "r");}可我这样调用的时候:FILE *infp; myfopen("filename.dat", infp);,infp 指针并 没有正确设置。为什么? 144  12.30 连一个最简单的fopen调用都不成功!这个调用有什么问题?FILE *fp = fopen(filename, 'r'); 145  12.31 为什么我不能用完整路径名打开一个文件?这个调用总是失败:fopen("c:\newdir\ file. dat", "r"); 145  12.32 我想用fopen模式"r+"打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件。可是这样不行。为什么? 145  12.33 如何在文件中间插入或删除一行(一条记录)? 145 12.34 怎样从打开的流中恢复文件名? 145 重定向stdin 和stdout  146 12.35 怎样在程序里把stdin或stdout重定向到文件? 146 12.36 一旦使用freopen之后,怎样才能恢复原来的stdout (或stdin)? 146 12.37 如何判断标准输入或输出是否经过了重定向,即是否在命令行上使用了“”或“”? 147 12.38 我想写个像"more"那样的程序。怎样才能在stdin 被重定向之后再回到交互键盘? 147 *12.39 怎样同时向两个地方输出,如同时输出到屏幕和文件? 147 “二进制”输入输出 148 12.40 我希望按字节在内存和文件之间直接读写数字,而不像fprintf和fscanf进行格式化。我该怎么办? 148 12.41 怎样正确地读取二进制文件?有时看到0x0a和0x0d容易混淆,而且如果数据中包含0x1a的话,我好像会提前遇到EOF。 148  12.42 我在写一个二进制文件的“过滤器”,但是stdin和stdout却被作为文本流打开了。怎样才能把它们的模式改为二进制? 148  12.43 文本和二进制输入输出有什么区别? 149 12.44 如何在数据文件中读写结构? 149 12.45 怎样编写符合旧的二进制数据格式的代码? 149 第13章 库函数 151 字符串函数 151 13.1 怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 151 13.2 为什么strncpy不能总在目标串放上终止符'\0'? 152 13.3 C 语言有类似于其他语言中的“substr ”(取出子串)的例程吗? 152 13.4 怎样把一个字符串中所有字符转换成大写或小写? 153 13.5 为什么有些版本的toupper对大写字符会有奇怪的反应?为什么有的代码在调用toupper 前先调用islower? 153 13.6 怎样将字符串分割成用空白分隔的字段?怎样实现类似main 处理argc和argv的过程? 153 13.7 哪里可以找到处理正则表达式或通配符匹配的代码? 155 排序 156 13.8 我想用strcmp作为比较函数,调用qsort对一个字符串数组排序,但是不行。为什么? 156 13.9 我想用qsort()对一个结构数组排序。我的比较函数接受结构指针,但是编译器认为这个函数不是qsort需要的类型。我要怎样转换这个函数指针才能避免这样的警告? 156  13.10 怎样对一个链表排序? 158 13.11 怎样对大于内存容量的数据排序? 158 日期和时间 159 13.12 怎样在C 程序中取得当前日期或时间? 159 13.13 我知道库函数localtime可以把time_t转换成结构struct tm,而ctime可以把time_t转换成为可打印的字符串。怎样才能进行反向操作,把struct tm或一个字符串转换成time_t?  159  13.14 怎样在日期上加n天?怎样取得两个日期的时间间隔? 160 随机数 162 13.15 怎么生成一个随机数? 162 13.16 怎样获得某一范围内的随机整数? 163 13.17 每次执行程序,rand都返回相同的数字序列。为什么? 164 13.18 我需要随机的真/假值,所以我就直接用rand()%2,可是我得到交替的0, 1, 0, 1, 0 …。为什么? 164 13.19 如何获取根本不重复的随机数? 165 13.20 怎样产生正态分布或高斯分布的随机数?  165 13.21 我在移植一个程序,里边调用了一个函数drand48 ,而我的库又没有这个。这是个什么函数? 167 其他库函数 168 13.22 exit(status)是否真的跟从main 函数返回status 等价? 168 13.23 memcpy和memmove 有什么区别? 168 13.24 我想移植这个旧程序。为什么报出这些“undefined external”错误:index? 、rindex?、bcopy?、bcmp?、bzero??  168  13.25 我不断得到库函数未定义错误,但是我已经包含了所有用到的头文件了。 168 13.26 虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误。 168 13.27 一个最简单的程序,不过在一个窗口里打印出“Hello,World”,为什么会编译出巨大的可执行代码(数百K)?我该少包含一些头文件吗? 169  13.28 连接器报告_end未定义代表什么意思? 169 *13.29 我的编译器提示printf未定义!这怎么可能? 169 第14章 浮点运算 170 14.1 一个float变量赋值为3.1时,为什么printf输出的值为3.0999999? 170 14.2 我想计算一些平方根,我把程序简化成这样:main(){printf ("%f\h", sqrt(144.)); 可得到的结果却是疯狂的数字。为什么? 170 14.3 我想做一些简单的三角函数运算,也包含了math.h ,但连接器总是提示sin、cos这样的函数未定义。为什么? 171  14.4 我的浮点数计算程序表现得很奇怪,在不同的机器上给出了不同的结果。为什么? 171 14.5 有什么好的方法来检查浮点数在“足够接近”情况下的相等? 171 14.6 怎样取整? 172 14.7 为什么C语言不提供乘幂的操作符? 173 14.8 为什么我机器上的math.h没有预定义常量M_PI? 173 14.9 怎样将变量置为IEEE NaN(“Not a Number”)或检测变量是否为NaN及其他特殊值? 173 14.10 如何简洁地处理浮点异常? 174 14.11 在C语言中如何很好地实现复数? 174 14.12 我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、求逆等函数)、复数算术。 175  14.13 Turbo C的程序崩溃,显示错误为“floating point formats not linked”(浮点格式未连接)。我还缺点儿什么呢? 175  第15章 可变参数列表 176 调用变参函数 176 15.1 为什么调用printf前必须要包含stdio.h?  176 15.2 为什么%f可以在printf参数中同时表示float和double?它们难道不是不同类型吗? 177 15.3 我遇到了一个令人十分受挫的问题,后来发现是这行代码造成的:printf("%d", n);原来n 是longint型。难道ANSI的函数原型不就是用来防止这类的参数类型不匹配吗? 177  15.4 怎样写一个接受可变参数的函数?  177 15.5 怎样写一个函数,像printf那样接受一个格式串和可变参数,然后再把参数传给printf去完成大部分工作? 180 15.6 怎样写类似scanf的函数,再把参数传给scanf去完成大部分工作? 180 15.7 我用的是ANSI前的编译器,没有stdarg.h文件。我该怎么办? 181 提取可变参数 182 15.8 怎样知道实际上有多少个参数传入函数? 182 15.9 为什么编译器不允许我定义一个没有固定参数项的可变参数函数? 182 15.10 我有个接受float型的变参函数,为什么va_arg(argp, float)却不行? 183 15.11 为什么va_arg不能得到类型为函数指针的参数? 183 困难的问题 184 15.12 怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数? 184 15.13 怎样调用一个在运行时才构建参数列表的函数? 186 第16 章 奇怪的问题 187 16.1 为什么这个循环只执行了一次?for(i=start;i end ; i ++);{printf("%d\n",i);} 187 *16.2 遇到不可理解的不合理语法错误,似乎大段的程序没有编译。 187 *16.3 为什么过程调用不起作用?编译器似乎直接跳过去了。 187 16.4 程序在执行之前就崩溃了!(用调试器单步跟踪,在main函数的第一个语句之前就死了。)为什么? 188  16.5 程序执行正确,但退出时在main函数的最后一个语句之后崩溃了。为什么会这样? 188 16.6 程序在一台机器上运行完美,但在另一台上却得到怪异的结果。更奇怪的是,增加或去除调试的打印语句,就改变了症状…… 188  16.7 为什么下面的代码会崩溃?char *p = "hello, world!"; p[0] = 'H'; 189 16.8 我有些代码是用来解析外部结构的,但它却崩溃了,报了“unaligned access ”(未对齐的访问)错误。这是什么意思? 190 16.9 “Segmentation violation”、“Bus error”和“General protection fault”是什么意思? 191 第17章 风格  192 17.1 什么是C最好的代码布局风格? 192 17.2 如何在源文件中合理分配函数? 193 17.3 用if(!strcmp(s1, s2))比较两个字符串是否相等是个好风格吗? 193 17.4 为什么有的人用if(0== x)而不是if(x== 0)? 193 17.5 为什么有些代码在每次调用printf 前增加了类型转换(void)? 194 17.6 既然NULL和0都是空指针常量,我到底该用哪一个? 194 17.7 是该用TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 194 17.8 什么是“匈牙利表示法”(Hungarian Notation )?是否值得一试? 194 17.9 哪里可以找到“Indian Hill Style Guide ”及其他编码标准? 194 17.10 有人说goto是邪恶的,永远都不该用它。这是否太极端了? 195 17.11 人们总是说良好的风格很重要,但当他们使用良好的风格写出清晰易读的程序后,又发现程序的效率似乎降低了。既然效率那么重要,是否可以为了效率牺牲一些风格和可读性呢? 196 第18章 工具和资源 197 18.1 能否列一个常用工具列表? 197 18.2 怎样捕获棘手的malloc问题? 198 18.3 有什么免费或便宜的编译器可以使用? 198 lint 198 18.4 刚刚输入完一个程序,但它表现得很奇怪。你能发现有什么错误的地方吗? 199 18.5 如何关掉lint对每个malloc调用报出的“warning: possible pointer alignment problem”警告消息? 199  18.6 哪里可以找到兼容ANSI的lint? 199 18.7 难道ANSI函数原型说明没有使lint过时吗? 199 资源 200 18.8 网上有哪些C语言的教程或其他资源? 200 *18.9 哪里可以找到好的源代码实例,以供研究和学习? 201 18.10 有什么好的学习C语言的书?有哪些高级的书和参考? 201 18.11 哪里能找到K&R的练习答案? 201 18.12 哪里能找到Numerical Recipes in C 、Plauger的The Standard C Library或Kernighan和Pike的The UNIX Programming Enviroment等书里的源码? 201  18.13 哪里可以找到标准C函数库的源代码? 202 18.14 是否有一个在线的C参考指南? 202 18.15 我需要分析和评估表达式的代码。从哪里可以找到? 202 18.16 哪里可以找到C的BNF或YACC语法?  202 *18.17 谁有C编译器的测试套件? 203 *18.18 哪里有一些有用的源代码片段和例子的收集? 203 *18.19 我需要执行多精度算术的代码。 203 18.20 在哪里和怎样取得这些可自由发布的程序? 203 第19章 系统依赖 205 键盘和屏幕I/O 205 19.1 怎样从键盘直接读入字符而不用等回车键?怎样防止字符输入时的回显? 205 19.2 怎样知道有未读的字符(如果有,有多少)?另外,如何在没有字符的时候不阻塞读入? 209 19.3 怎样显示一个在原地更新自己的百分比或“旋转棒”的进度指示器? 209 19.4 怎样清屏?怎样反色输出?怎样把光标移动到指定的x, y位置? 210 19.5 怎样读入方向键、功能键? 210 其他I/O 211 19.6 怎样读入鼠标输入? 211 19.7 怎样做串口(“comm”)的输入输出? 211 19.8 怎样直接输出到打印机? 211 19.9 怎样发送转义字符序列控制终端或其他设备? 211 19.10 怎样做图形? 212 *19.11 怎样显示GIF和JPEG图像? 212 文件和目录 212 19.12 怎样检验一个文件是否存在?如果请求的输入文件不存在,我希望向用户提出警告。 212 19.13 怎样在读入文件前,知道文件大小? 213 *19.14 怎样得到文件的修改日期和时间? 213 19.15 怎样原地缩短一个文件而不用清除或重写? 213 19.16 怎样在文件中插入或删除一行(或一条记录)? 214 19.17 怎样从一个打开的流或文件描述符得到文件名? 214 19.18 怎样删除一个文件? 214 *19.19 怎样复制文件? 215 19.20 为什么用了详尽的路径还不能打开文件?下面的代码会返回错误。Fopen("c:\newdir\file.dat", "r") 215  *19.21 fopen不让我打开文件"$HOME/.profile"和"~~/.myrcfile"。 215 *19.22 怎样制止MS-DOS下令人恐怖的“Abort,Retry,Ignore? ”信息? 215 19.23 遇到“Too many open files(打开文件太多)”的错误,怎样增加同时打开文件的允许数目? 215 19.24 如何得到磁盘的可用空间大小? 216 19.25 怎样在C语言中读入目录? 216 19.26 如何创建目录?如何删除目录(及其内容)? 217 访问原始内存 217 19.27 怎样找出系统还有多少内存可用? 217 19.28 怎样分配大于64K的数组或结构? 217 19.29 错误信息“DGROUP data allocation exceeds 64K(DGROUP 数据分配内存超过64K)”什么意思?我应该怎么做?我以为使用了大内存模型,就可以使用大于64K的数据! 217  19.30 怎样访问位于某特定地址的内存(内存映射的设备或图形显示内存)? 218 19.31 如何访问机器地址0处的中断向量?如果将指针设为0,编译器可能把它转成一个非零的内部空指针值。 218 “系统”命令 219 19.32 怎样在一个C程序中调用另一个程序(独立可执行的程序或系统命令)? 219 19.33 如果运行时才知道要执行的命令的参数(文件名等),应该如何调用system? 219 19.34 在MS-DOS上如何得到system返回的准确错误状态? 220 19.35 怎样调用另一个程序或命令,然后获取它的输出? 220 进程环境  220 19.36 怎样才能发现程序自己的执行文件的全路径? 220 19.37 怎样找出和执行文件在同一目录的配置文件? 221 19.38 进程如何改变它的调用者的环境变量? 221 19.39 如何打开命令行给出的文件并解析选项?  221 19.40 exit(status)是否真的和从main函数返回同样的status等价? 221 19.41 怎样读入一个对象文件并跳跃到其中的函数? 221 其他系统相关的操作 222 19.42 怎样以小于1秒的精度延时或计算用户响应时间? 222 19.43 怎样捕获或忽略control-C这样的键盘中断? 222 19.44 怎样简洁地处理浮点异常? 223 19.45 怎样使用socket?如何联网?如何写客户/服务器程序? 223 *19.46 怎样调用BIOS函数?如何写ISR?如何创建TSR?  224 *19.47 什么是“near”和“far”指针? 224 回顾 224 19.48 我不能使用这些非标准、依赖系统的函数,程序需要兼容ANSI! 224 19.49 为什么这些内容没有在C语言中进行标准化?任何现实程序都会用到这些东西。 224 第20章 杂项 226 20.1 怎样从函数返回多个值? 226 20.2 用什么数据结构存储文本行最好?我开始用固定大小的char型数组的数组,但是有很多局限。 227 20.3 怎样打开命令行提到的文件并处理参数? 229 20.4 如何正确地使用errno? 231 20.5 怎样写数据文件,使之可以在不同字大小、字节顺序或浮点格式的机器上读入? 232 20.6 怎样用char *指针指向的函数名调用函数? 232 位和字节  233 20.7 如何操作各个位? 233  20.8 怎样实现位数组或集合? 234  20.9 怎样判断机器的字节顺序是高字节在前还是低字节在前? 235  *20.10 怎样调换字节? 236  20.11 怎样将整数转换到二进制或十六进制? 237  20.12 可以使用二进制常数(类似0b101010这样的东西)吗?printf有二进制的格式说明符吗? 237  效率 238 20.13 用什么方法计算整数中为1的位的个数最高效? 238 20.14 怎样提高程序的效率? 238  20.15 指针真的比数组快吗?函数调用会拖慢程序多少?++i比i=i+1快吗? 240 20.16 用移位操作符替换乘法和除法是否有价值? 240 *20.17 人们说编译器优化得很好,我们不再需要为速度而写汇编了,但我的编译器连用移位代替i/=2都做不到。 240 *20.18 怎样不用临时变量而交换两个值? 241 switch 语句 241 20.19 switch语句和if/else链哪个更高效? 241 20.20 是否有根据字符串进行条件切换的方法? 241 20.21 是否有使用非常量case行标的方法(如范围或任意的表达式)? 242 各种语言功能 243 20.22 return语句外层的括号是否真的可选择?  243 20.23 为什么C语言的注释不能嵌套?怎样注释掉含有注释的代码?引号包含的字符串内的注释是否合法? 243  20.24 为什么C语言的操作符不设计得更全面一些?好像还缺了一些^^、&&=和-=这样的操作符。 244 *20.25 C语言有循环移位操作符吗? 244 *20.26 C是个伟大的语言还是别的什么东西?哪个其他语言可以写出像a+++++b这样的代码? 244 20.27 如果赋值操作符是:=,是不是就不容易意外地写出if(a=b)了? 245 20.28 C语言有和Pascal 的with等价的语句吗? 245 20.29 为什么C语言没有嵌套函数? 245 *20.30 assert是什么?如何使用? 246 其他语言  246 20.31 怎样从C中调用FORTRAN(C++、BASIC、Pascal、Ada、LISP)的函数?反之如何? 246 20.32 有什么程序可以将Pascal或FORTRAN(或LISP、Ada、awk、“老”C)程序转化为C程序? 246 20.33 C++是C的超集吗?可以用C++编译器来编译C代码吗? 247 20.34 我需要用到“近似”的strcmp例程,比较两个字符串的近似度,并不需要完全一样。有什么好办法? 247 20.35 什么是散列法? 248 20.36 如何生成正态或高斯分布的随机数? 248 20.37 如何知道某个日期是星期几? 249 20.38 (year % 4== 0)是否足以判断闰年?2000年是闰年吗? 250 20.39 为什么tm结构中的tm_sec的范围是0到61,暗示一分钟有62秒? 250 琐事 250 20.40 一个难题:怎样写一个输出自己源代码的程序? 250 20.41 什么是“达夫设备”(Duff’s Device)?  251 20.42 下届国际C语言混乱代码竞赛(International Obfuscated C Code Contest,IOCCC)什么时候进行?哪里可以找到当前和以前的获胜代码? 251  20.43 K&R1提到的关键字entry是什么? 252 20.44 C的名字从何而来? 252 20.45 “char”如何发音? 252 *20.46 “lvalue”和“rvalue”代表什么意思? 252 20.47 哪里可以获得本书的在线版? 252 术语表 253 参考文献 261~ ……
第1章 声明和初始化 基本类型 1.1 我该如何决定使用哪种整数类型? 1.2 为什么不精确定义标准类型的大小? 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 1.4 新的64位机上的64位类型是什么样的? 指针声明 1.5 这样的声明有什么问题?char*p1,p2;我在使用p2的时候报错了。 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char*p;*p=malloc(10); 声明风格 1.7 怎样声明和定义全局变量和函数最好? 1.8 如何在C中实现不透明(抽象)数据类型? 1.9 如何生成“半全局变量”,就是那种只能被部分源文件中的部分函数访问的变量? 存储类型 1.10 同一个静态(static)函数或变量的所有声明都必需包含static存储类型吗? 1.11 extern在函数声明中是什么意思? 1.12 关键字auto到底有什么用途? 类型定义(typedef) 1.13 对于用户定义类型,typedef和#define有什么区别? 1.14 我似乎不能成功定义一个链表。我试过typedefstruct{char*item;NODEPTRnext;}*NODEPTR;但是编译器报了错误信息。难道在C语言中结构不能包含指向自己的指针吗? 1.15 如何定义一对相互引用的结构? 1.16 Struct{ }x1;和typedefstruct{ }x2;这两个声明有什么区别? 1.17 “typedefint(*funcptr)();”是什么意思? const限定词 1.18 我有这样一组声明:typedefchar*charp;constcharpp;为什么是p而不是它指向的字符为const? 1.19 为什么不能像下面这样在初始式和数组维度值中使用const值?constintn=5;inta[n]; 1.20 constchar*p、charconst*p和char*constp有什么区别? 复杂的声明 1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 数组大小 1.23 能否声明和传入数组大小一致的局部数组,或者由其他参数指定大小的参数数组? 1.24 我在一个文件中定义了一个extern数组,然后在另一个文件中使用,为什么sizeof取不到数组的大小? 声明问题 1.25 函数只定义了一次,调用了一次,但编译器提示非法重声明了。 *1.26 main的正确定义是什么?voidmain正确吗? 1.27 我的编译器总在报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 1.29 为什么我的编译器不允许我定义大数组,如doublearray[256][256]? 命名空间 1.30如何判断哪些标识符可以使用,哪些被保留了? 初始化 1.31 对于没有显式初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,它可否作为空指针或浮点零? 1.32 下面的代码为什么不能编译?intf(){chara[]="Hello,world!";} *1.33 下面的初始化有什么问题?编译器提示“invalidinitializers”或其他信息。char*p=malloc(10); 1.34 chara[]="stringliteral";和char*p="stringliteral";初始化有什么区别?当我向p[i]赋值的时候,我的程序崩溃了。 1.35 chara{[3]}="abc";是否合法? 1.36 我总算弄清楚函数指针的声明方法了,但怎样才能初始化呢? 1.37 能够初始化联合吗? 第2章 结构、联合和枚举 结构声明 2.1 structx1{ };和typedefstruct{ }x2;有什么不同? 2.2 这样的代码为什么不对?structx{ };xthestruct; 2.3 结构可以包含指向自己的指针吗? 2.4 在C语言中用什么方法实现抽象数据类型最好? *2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 2.6 为什么声明externf(structx*p);给我报了一个晦涩难懂的警告信息? 2.7 我遇到这样声明结构的代码:structname{intnamelen;charnamestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素,namelen记录了元素个数。它是怎样工作的?这样是合法的和可移植的吗? 2.8 我听说结构可以赋给变量也可以对函数传入和传出。为什么K&R1却明确说明不能这样做? 2.9 为什么不能用内建的==和!=操作符比较结构? 2.10结构传递和返回是如何实现的? 2.11 如何向接受结构参数的函数传入常量值?怎样创建无名的中间的常量结构值? 2.12 怎样从/向数据文件读/写结构? 结构填充 2.13 为什么我的编译器在结构中留下了空洞?这导致空间浪费而且无法与外部数据文件进行“二进制”读写。能否关掉填充,或者控制结构域的对齐方式? 2.14 为什么sizeof返回的值大于结构大小的期望值,是不是尾部有填充? 2.15 如何确定域在结构中的字节偏移量? 2.16 怎样在运行时用名字访问结构中的域? 2.17 C语言中有和Pascal的with等价的语句吗? 2.18 既然数组名可以用作数组的基地址,为什么对结构不能这样? 2.19 程序运行正确,但退出时却“coredump”(核心转储)了,怎么回事? 联合 2.20 结构和联合有什么区别? 2.21 有办法初始化联合吗? 2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 枚举 2.23 枚举和一组预处理的#define有什么不同? 2.24 枚举可移植吗? 2.25 有什么显示枚举值符号的容易方法吗? 位域 2.26 一些结构声明中的这些冒号和数字是什么意思? 2.27 为什么人们那么喜欢用显式的掩码和位操作而不直接声明位域? 第3章 表达式 求值顺序 3.1 为什么这样的代码不行?a[i]=i++; 3.2 使用我的编译器,下面的代码inti=7;printf("%d\n",i++*i++);打印出49。不管按什么顺序计算,难道不该是56吗? 3.3 对于代码inti=3;i=i++;不同编译器给出不同的i值,有的为3,有的为4,哪个是正确的? *3.4 有这样一个巧妙的表达式:a^=b^=a^=b;它不需要临时变量就可以交换a和b的值。 3.5 可否用显式括号来强制执行我所需要的计算顺序并控制相关的副作用?就算括号不行,操作符优先级是否能够控制计算顺序呢? 3.6 可是&&和||操作符呢?我看到过类似while((c=getchar())!=EOF&&c!='\n')的代码…… 3.7 是否可以安全地认为,一旦&&和||左边的表达式已经决定了整个表达式的结果,则右边的表达式不会被求值? 3.8 为什么表达式printf("%d%d",f1(),f2());先调用了f2?我觉得逗号表达式应该确保从左到右的求值顺序。 3.9 怎样才能理解复杂表达式并避免写出未定义的表达式?“序列点”是什么? 3.10在a[i]=i++;中,如果不关心a[]的哪一个分量会被写入,这段代码就没有问题,i也的确会增加1,对吗? 3.11 人们总是说i=i++的行为是未定义的。可我刚刚在一个ANSI编译器上尝试过,其结果正如我所期望的。 3.12 我不想学习那些复杂的规则,怎样才能避免这些未定义的求值顺序问题呢? 其他的表达式问题 *3.13 ++i和i++有什么区别? 3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 3.15 我要检查一个数是不是在另外两个数之间,为什么if(abc)不行? 3.16 为什么如下的代码不对?inta=1000,b=1000;longintc=a*b; 3.17 为什么下面的代码总是给出0?doubledegC,degF;degC=5.0/9*(degF-32); 3.18 需要根据条件把一个复杂的表达式赋给两个变量中的一个。可以用下面这样的代码吗?((condition)?a:b)=complicated_expression; 3.19 我有些代码包含这样的表达式。a?b=c:d有些编译器可以接受,有些却不能。为什么? 保护规则 3.20 “semanticsof‘’changeinANSIC”的警告是什么意思? 3.21 “无符号保护”和“值保护”规则的区别在哪里? 第4章 指针 基本的指针应用 4.1 指针到底有什么好处? 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char*p;*p=malloc(10); 4.3 *p++自增p还是p所指向的变量? 指针操作 4.4 我用指针操作int数组的时候遇到了麻烦。 4.5 我有一个char*型指针碰巧指向一些int型变量,我想跳过它们。为什么((int*)p)++;这样的代码不行? 4.6 为什么不能对void*指针进行算术操作? 4.7 我有些解析外部结构的代码,但是它却崩溃了,显示出了“unalignedaccess”(未对齐的访问)的信息。这是什么意思? 作为函数参数的指针 4.8 我有个函数,它应该接受并初始化一个指针:voidf(int*ip){staticintdummy=5;ip=&dummy;}但是当我如下调用时:int*ip;f(ip);调用者的指针没有任何变化。 4.9 能否用void**通用指针作为参数,使函数模拟按引用传递参数? 4.10 我有一个函数externintf(int*);,它接受指向int型的指针。我怎样用引用方式传入一个常数?调用f(&5);似乎不行。 4.11 C语言可以“按引用传参”吗? 其他指针问题 4.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 4.13 通用指针类型是什么?当我把函数指针赋向void*类型的时候,编译通不过。 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,或者相反? *4.15 我怎样把一个int变量转换为char*型?我试了类型转换,但是不行。 第5章 空指针 空指针和空指针常量 5.1 臭名昭著的空指针到底是什么? 5.2 怎样在程序里获得一个空指针? 5.3 用缩写的指针比较“if(p)”检查空指针是否有效?如果空指针的内部表达不是0会怎样? NULL宏 5.4 NULL是什么,它是怎么定义的? 5.5 在使用非零位模式作为空指针的内部表示的机器上,NULL是如何定义的? 5.6 如果NULL定义成#defineNULL((char*)0),不就可以向函数传入不加转换的NULL了吗? 5.7 我的编译器提供的头文件中定义的NULL为0L。为什么? 5.8 NULL可以合法地用作函数指针吗? 5.9 如果NULL和0作为空指针常量是等价的,那我到底该用哪一个呢? 5.10但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL(而不是0) 不是更好吗? 5.11 我曾经使用过一个编译器,不使用NULL就不能编译。 5.12 我用预处理宏#defineNullptr(type)(type*)0帮助创建正确类型的空指针。 回顾 59 5.13 这有点奇怪:NULL可以确保是0,但空(null)指针却不一定? 5.14 为什么有那么多关于空指针的疑惑?为什么这些问题如此频繁地出现? 5.15 有没有什么简单点儿的办法理解所有这些与空指针有关的东西呢? 5.16 考虑到有关空指针的所有这些困惑,要求它们的内部表示都必须为0不是更简单吗? 5.17 说真的,真有机器用非零空指针吗,或者不同类型用不同的表示? 地址0上到底有什么? 5.18 运行时的整数值0转换为指针以后一定是空指针吗? 5.19 如何访问位于机器地址0处的中断向量?如果我将指针值设为0,编译器可能会自动将它转换为非零的空指针内部表示。 5.20运行时的“nullpointerassignment”错误是什么意思?应该怎样捕捉它? 第6章 数组和指针 数组和指针的基本关系 6.1 我在一个源文件中定义了chara[6],在另一个源文件中声明了externchar*a。为什么不行? 6.2 可是我听说chara[]和char*a是等价的。是这样的吗? 6.3 那么,在C语言中“指针和数组等价”到底是什么意思? 6.4 既然它们这么不同,那为什么作为函数形参的数组和指针声明可以互换呢? 数组不能被赋值 6.5 为什么不能这样向数组赋值?externchar*getpass();charstr[10];str=getpass("Enterpassword:"); 6.6 既然不能向数组赋值,那这段代码为什么可以呢?intf(charstr[]){if(str[0]=='\0')str="none";…} 6.7 如果你不能给它赋值,那么数组如何能成为左值呢? 回顾 6.8 现实地讲,数组和指针的区别是什么? 6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 6.11 我看到一些“搞笑”的代码,包含5["abcdef"]这样的“表达式”。这为什么是合法的C语言表达式呢? 数组的指针 6.12 既然数组引用会退化为指针,如果array是数组,那么array和&array又有什么区别呢? 6.13 如何声明一个数组的指针? 动态数组分配 6.14 如何在运行时设定数组的大小?怎样才能避免固定大小的数组? 6.15 我如何声明大小和传入的数组一样的局部数组? 6.16 如何动态分配多维数组? 6.17 有个很好的窍门,如果我这样写:intrealarray[10];int*array=&realarray[-1];我就可以把“array”当作下标从1 开始的数组。 函数和多维数组 6.18 当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了。 6.19 我怎样编写接受编译时宽度未知的二维数组的函数? 6.20 我怎样在函数参数传递时混用静态和动态多维数组? 数组的大小 6.21 当数组是函数的参数时,为什么sizeof不能正确报告数组的大小? 6.22 如何在一个文件中判断声明为extern的数组的大小(例如,数组定义和大小在另一个文件中)?sizeof操作符似乎不行。 6.23 sizeof返回的大小是以字节计算的,怎样才能判断数组中有多少个元素呢? 第7章 内存分配 基本的内存分配问题 7.1 为什么这段代码不行?char*answer;printf("Typesomething:\n");gets(answer);printf("Youtyped\"%s\"\n",answer); 7.2 我的strcat()不行。我试了下面的代码:char*s1="Hello,";char*s2="world!";char*s3=strcat(s1,s2);但是我得到了奇怪的结果。 7.3 但是strcat的文档说它接受两个char*型参数。我怎么知道(空间)分配的事情呢? *7.4 我刚才试了这样的代码:char*p;strcpy(p,"abc");它运行正常。怎么回事?为什么它没有出错? *7.5 一个指针变量分配多少内存? 7.6 我使用fgets将文件的所有行读入一个数组,为什么读入的每一行都是最后一行的内容呢? 7.7 我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回的字符串却是垃圾信息。 为什么? *7.8 那么返回字符串或其他聚集的正确方法是什么呢? 调用malloc 7.9 为什么在调用malloc()时报出了“waring:assignmentofpointerfromintegerlacksacast”? 7.10为什么有些代码小心翼翼地把malloc返回的值转换为分配的指针类型? *7.11 在调用malloc()的时候,错误“不能把void*转换为int*”是什么意思? 7.12 我看到下面这样的代码:char*p=malloc(strlen(s)+1);strcpy(p,s);难道不应该是malloc((strlen(s)+1)*sizeof(char))吗? 7.13 我为malloc写了一个小小的封装函数。它为什么不行? 7.14 我想声明一个指针并向它分配一些内存,但是不行。这样的代码有什么问题?char*p;*p=malloc(10); 7.15 我如何动态分配数组? 7.16 怎样判断还有多少内存? 7.17 malloc(0)是返回空指针还是指向0个字节的指针? 7.18 我听说有的操作系统在程序使用的时候才真正分配malloc申请的内存。这合法吗? 有关malloc的问题 7.19 为什么malloc返回了离谱的指针值?我的确读过问题7.9,而且也在调用之前包含了externvoid*malloc();声明。 7.20 我用一行这样的代码分配一个巨大的数组,用于数值运算:double*array=malloc(256 *256 *sizeof(double));malloc()并没有返回空指针,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存。为什么? 7.21 我的PC机有8兆内存。为什么我只能分配640K左右的内存? 7.22 我的应用程序非常依赖数据结构的节点的动态分配,而malloc/free的代价成了瓶颈。我该怎么做? 7.23 我的程序总是崩溃,显然发生在malloc内部的某个地方。但是我看不出哪里有问题。是malloc有bug吗? 释放内存 7.24 动态分配的内存一旦释放之后就不能再使用,是吧? 7.25 为什么在调用free()之后指针没有变空?使用(赋值、比较)释放之后的指针有多么不安全? 7.26 当我调用malloc()为一个函数的局部指针分配内存时,我还需要用free()显式地释放吗? 7.27 我在分配一些结构,它们包含指向其他动态分配的对象的指针。我在释放结构的时候,还需要释放每一个下级指针吗? 7.28 我必须在程序退出之前释放分配的所有内存吗? 7.29 我有个程序分配了大量的内存,然后又释放了。但是从操作系统看,内存占用率却并没有变回去。 分配内存块的大小 7.30 free()怎么知道有多少字节需要释放? 7.31 那么我能否查询malloc包,以查明可分配的最大块是多大? 7.32 为什么sizeof不能告诉我它所指的内存块的大小? 其他分配函数 7.33 (像问题6.14中那样)动态分配数组之后,还能改变它的大小吗? 7.34 向realloc()的第一个参数传入空指针合法吗?你为什么要这样做? 7.35 calloc()和malloc()有什么区别?应该用哪一个?利用calloc的零填充功能安全吗?free()可以释放calloc()分配的内存吗,还是需要一个cfree()? 7.36 alloca是什么?为什么不提倡使用它? 第8章 字符和字符串 8.1 为什么strcat(string,'!');不行? 8.2 我想检查一个字符串是否跟某个值匹配。为什么这样不行?if(string=="value") 8.3 如果我可以写chara[]="Hello,world!";那为什么不能写chara[14];a="Hello,world!"; 8.4 为什么我的strcat不行?我试了char*s1="Hello,";char*s2="world!";char*s3 =strcat(s1,s2);可得到的结果很奇怪。 8.5 chara[]="stringliteral";和char*p="stringliteral";初始化有什么区别?当我对p[i]赋值的时候,程序崩溃了。 8.6 我怎么得到与字符相对应的数字(即ASCII或其他字符集下的)值?反过来又该怎么做? 8.7 C语言有类似其他语言的"substr"(提取子串)这样的函数吗? 8.8 我将用户键入的字符串读入数组,然后再显示出来。当用户键入\n这样的序列时,为什么不能正确处理呢? 8.9 我注意到sizeof('a')是2而不是1(即不是sizeof(char)),是不是我的编译器有问题? 8.10 我正开始考虑多语言字符集的问题。是否有必要担心sizeof(char)会被定义为2,以便表达16位的字符集呢? 第9章 布尔表达式和变量 9.1 C语言中布尔值该用什么类型?为什么它不是一个标准类型?我应该用#define或enum定义真值和假值吗? 9.2 既然在C语言中所有的非零值都被看作“真”,那是不是把TRUE定义为1很危险?如果某个内建的函数或关系操作符“返回”不是1的其他值怎么办? 9.3 当p是指针时,if(p)是合法的条件表达式吗? 9.4 我该使用像TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 9.5 我准备使用的一个第三方头文件定义了自己的TRUE和FALSE,它们跟我已经开发的部分不兼容。我该怎么办? 第10章 C预处理器 宏定义 10.1 我想定义一些函数式的宏,例如:#definesquare(x)x*x但它们并不总是正确的。为什么? 10.2 这里有一些的预处理宏,使用它们,我可以写出更像Pascal的C代码。你觉得怎么样? 10.3 怎么写一个交换两个值的通用宏? 10.4 书写多语句宏的最好方法是什么? 10.5 用typdef和预处理宏生成用户定义类型有什么区别? 头文件 10.6 我第一次把一个程序分成多个源文件,我不知道该把什么放到.c文件,把什么放到.h文件。(“.h”到底是什么意思?) 10.7 可以在一个头文件中包含另一头文件吗? 10.8 完整的头文件搜索规则是怎样的? 10.9 我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题。 10.10 我使用了来自两个不同的第三方库的头文件,它们都定义了相同的宏,如TRUE、FALSE、Min()和Max()等,但是它们的定义相互冲突,而且跟我在自己的头文件中的定义也有冲突。我该怎么办? 10.11 我在编译一个程序,看起来我好像缺少需要的一个或多个头文件。谁能发给我一份? 条件编译 10.12 怎样构造比较字符串的#if预处理表达式? 10.13 sizeof操作符可以用在#if预处理指令中吗? 10.14 我可以像这样在#define行里使用#ifdef来定义两个不同的东西吗? 10.15 对typedef的类型定义有没有类似#ifdef的东西? 10.16 我如何用#if表达式来判断机器是高字节在前还是低字节在前? 10.17 为什么在我用#ifdef关掉的代码行中报出了奇怪的语法错误? 10.18 我拿到了一些代码,里边有太多的#ifdef。我不想使用预处理器把所有的#include和#ifdef都扩展开,有什么办法只保留一种条件的代码呢? 10.19 如何列出所有的预定义宏? 奇异的处理 10.20 我有些旧代码,试图用这样的宏来构造标识符:#definePaste(a,b)a/**/b但是不行了。为什么? 10.21 我有一个旧宏:#defineCTRL(c)('c'&037)不能用了。为什么? 10.22 为什么宏#defineTRACE(n)printf("TRACE:\%d\n",n)报出警告“macroreplacementwithinastringliteral”?它似乎把TRACE(count);扩展成了printf("TRACE:\%d\count",count); 10.23 如何在宏扩展的字符串字面量中使用宏参数? 10.24 我想用ANSI的“字符串化”预处理操作符#将符号常量的值放入消息中,但它总是对宏名称而不是它的值进行字符串化。这是什么原因? 10.25 我想用预处理器做某件事情,但却不知道如何下手。 可变参数列表的宏 10.26 怎样写可变参数宏?如何用预处理器“关掉”具有可变参数的函数调用? 10.27 如何在通用的调试宏中包含__FILE__和__LINE__宏? 第11章 ANSI/ISO标准C 标准 11.1 什么是“ANSIC标准”? 11.2 如何得到一份标准的副本? *11.3 我在哪里可以找到标准的更新? 函数原型 11.4 为什么我的ANSI编译器对用float声明的参数会警告类型不匹配? 11.5 能否混用旧式的和新型的函数语法? *11.6 为什么下述声明报出了一个奇怪的警告信息“StructXdeclaredinsideparameterlist”?externintf(structx*p); 11.7 有个问题一直困扰着我,它是由这一行printf("%d",n);导致的,因为n是个longint型。难道ANSI的函数原型不能检查这种函数的参数不匹配问题吗? 11.8 我听说必须在调用printf之前包含stdio.h。为什么? const限定词 11.9 为什么不能在初始化和数组维度中使用const值?例如constintn=5;inta[n]; 11.10“constchar*p”、“charconst*p”和“char*constp”有何区别? 11.11 为什么不能向接受constchar**的函数传入char**? 11.12 我这样声明:typedefchar*charp;constcharpp;为什么是p而不是它所指向的字符为const? main()函数的使用 11.13 能否通过将main声明为void来关掉“main没有返回值”的警告? 11.14 main()的第3个参数envp是怎么回事? 11.15 我觉得把main()声明为void也不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态。 *11.16 那么到底会出什么问题?真的有什么系统不支持voidmain()吗? 11.17 为什么以前流行的那些C语言书总是使用voidmain()? 11.18 在main()中调用exit(status)和返回同样的status真的等价吗? 预处理功能 11.19 我试图用ANSI“字符串化”预处理操作符'#'向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值。为什么? 11.20 警告信息“warning:macroreplacementwithinastringliteral”是什么意思? 11.21 为什么在我用#ifdef去掉的代码里出现了奇怪的语法错误? 11.22 #pragma是什么,有什么用? 11.23 “#pragmaonce”什么意思?我在一些头文件中看到了它。 其他的ANSIC问题 11.24 chara[3]="abc";合法吗?它是什么意思? 11.25 既然对数组的引用会退化为指针,那么,如果array是数组,array和&array之间有什么区别呢? 11.26 为什么我不能对void*指针进行算术运算? 11.27 memcpy()和memmove()有什么区别? 11.28 malloc(0)有什么用?返回一个空指针还是指向0字节的指针? 11.29 为什么ANSI标准规定了外部标识符的长度和大小写限制? 11.30 noalias是怎么回事?在它身上发生了什么? 老的或非标准的编译器 11.31 为什么我的编译器对最简单的测试程序都报出了一大堆的语法错误?对这段代码的第一行就报错了:main(intargc.char**argv){return0;} 11.32 为什么有些ASNI/ISO标准库函数未定义?我明明使用的就是ANSI编译器。 11.33 谁有可以在旧的C程序和ANSIC之间相互转换的工具,或者自动生成原型的工具? 11.34 为什么声称兼容ANSI的编译器不能编译这些代码?我知道这些代码是ANSI的,因为gcc可以编译。 兼容性 11.35 人们好像有些在意实现定义的(implementation-defined)、不确定的(unspecified)和未定义的(undefined)行为的区别。它们的区别到底在哪里? *11.36 一个程序“合法(legal)”、“有效(valid)”或“符合标准的”(conforming)到底是什么意思? 11.37 我很吃惊,ANSI标准竟然有那么多未定义的东西。标准的唯一任务不就是让这些东西标准化吗? 11.38 有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI的编译器上测试,得到了我希望的结果。它真的是未定义的吗? 第12章 标准输入输出库 基本输入输出 12.1 这样的代码有什么问题?charc;while((c=getchar())!=EOF) 12.2 我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个“\EOF”呢?我看stdio.h中定义的EOF是-1,是不是说我该输入-1? 12.3 为什么这些代码把最后一行复制了两遍?while(!feof(infp)){fgets(buf,MAXLINE,infp);fputs(buf,outfp);} 12.4 我用fgets将文件的每行内容读入指针数组。为什么结果所有的行都是最后一行的内容呢? 12.5 我的程序的屏幕提示和中间输出有时没有在屏幕上显示,尤其是当我用管道通过另一个程序输出的时候。为什么? 12.6 我怎样才能不等待回车键而一次输入一个字符? printf格式 12.7 如何在printf的格式串中输出一个'%'字符?我试过\%,但是不行。 12.8 为什么这么写不对?longintn=123456;printf("%d\n",n); 12.9 有人告诉我不能在printf中使用%lf。为什么printf()用%f输出double型,而scanf却用%lf呢? *12.10 对于size_t那样的类型定义,当我不知道它到底是long还是其他类型的时候,我应该使用什么样的printf格式呢? 12.11 如何用printf实现可变的域宽度?就是说,我想在运行时确定宽度而不是使用%8d? 12.12 如何输出在千位上用逗号隔开的数字?货币格式的数字呢? 12.13 为什么scanf("%d",i)调用不行? *12.14 为什么chars[30];scamf("%s",s);不用&也可以?我原以为传给scanf的每个变量都要带&。 12.15 为什么这些代码不行?doubled;scanf("%f",&d); 12.16 为什么这段代码不行?shortints;scanf("%d",&s); 12.17 怎样在scanf格式串中指定可变的宽度? 12.18 怎样从特定格式的数据文件中读取数据?怎样读入10个float而不用使用包含10次%f的奇怪格式?如何将一行的任意多个域读入一个数组中? scanf问题 12.19 我像这样用"%d\n"调用scanf从键盘读取数字:intn;scanf("%d\n",&n);printf("youtyped%d\n",n);好像要多输入一行才返回。为什么? 12.20 我用scanf和%d读取一个数字,然后再用gets()读取字符串,但是编译器好像跳过了gets()调用! 12.21 我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf的使用会安全很多。但有的时候好像会陷入无限循环。为什么? 12.22 为什么大家都说不要使用scanf?那我该用什么来代替呢? 其他stdio函数 12.23 我怎样才知道对于任意的sprintf调用需要多大的目标缓冲区?怎样才能避免sprintf目标缓冲区溢出? 12.24 sprintf的返回值是什么?是int还是char*? 12.25 为什么大家都说不要使用gets? 12.26 我觉得我应该在一长串的printf调用之后检查errno,以确定是否有失败的调用。为什么当我将输出重定向到文件的时候会输出奇怪的“printffailed:Notatypewriter”信息? 12.27 fgetops/fsetops和ftell/fseek之间有什么区别?fgetops和fsetops到底有什么用处? 12.28 如何清除用户的多余输入,以防止在下一个提示符下读入?用fflush(stdin)可以吗? 打开和操作文件 12.29 我写了一个函数用来打开文件:myfopen(char*filename,FILE*fp){fp=fopen(filename,"r");}可我这样调用的时候:FILE*infp;myfopen("filename.dat",infp);,infp指针并没有正确设置。为什么? 12.30 连一个最简单的fopen调用都不成功!这个调用有什么问题?FILE*fp=fopen(filename,'r'); 12.31 为什么我不能用完整路径名打开一个文件?这个调用总是失败:fopen("c:\newdir\file.dat","r"); 12.32 我想用fopen模式"r+"打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件。可是这样不行。为什么? 12.33 如何在文件中间插入或删除一行(一条记录)? 12.34 怎样从打开的流中恢复文件名? 重定向stdin和stdout 12.35 怎样在程序里把stdin或stdout重定向到文件? 12.36 一旦使用freopen之后,怎样才能恢复原来的stdout(或stdin)? 12.37 如何判断标准输入或输出是否经过了重定向,即是否在命令行上使用了“”或“”? 12.38 我想写个像"more"那样的程序。怎样才能在stdin被重定向之后再回到交互键盘? *12.39 怎样同时向两个地方输出,如同时输出到屏幕和文件? “二进制”输入输出 12.40 我希望按字节在内存和文件之间直接读写数字,而不像fprintf和fscanf进行格式化。我该怎么办? 12.41 怎样正确地读取二进制文件?有时看到0x0a和0x0d容易混淆,而且如果数据中包含0x1a的话,我好像会提前遇到EOF。 12.42 我在写一个二进制文件的“过滤器”,但是stdin和stdout却被作为文本流打开了。怎样才能把它们的模式改为二进制? 12.43 文本和二进制输入输出有什么区别? 12.44 如何在数据文件中读写结构? 12.45 怎样编写符合旧的二进制数据格式的代码? 第13章 库函数 字符串函数 13.1 怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 13.2 为什么strncpy不能总在目标串放上终止符'\0'? 13.3 C语言有类似于其他语言中的“substr”(取出子串)的例程吗? 13.4 怎样把一个字符串中所有字符转换成大写或小写? 13.5 为什么有些版本的toupper对大写字符会有奇怪的反应?为什么有的代码在调用toupper前先调用islower? 13.6 怎样将字符串分割成用空白分隔的字段?怎样实现类似main处理argc和argv的过程? 13.7 哪里可以找到处理正则表达式或通配符匹配的代码? 排序 13.8 我想用strcmp作为比较函数,调用qsort对一个字符串数组排序,但是不行。为什么? 13.9 我想用qsort()对一个结构数组排序。我的比较函数接受结构指针,但是编译器认为这个函数不是qsort需要的类型。我要怎样转换这个函数指针才能避免这样的警告? 13.10 怎样对一个链表排序? 13.11 怎样对大于内存容量的数据排序? 日期和时间 13.12 怎样在C程序中取得当前日期或时间? 13.13 我知道库函数localtime可以把time_t转换成结构structtm,而ctime可以把time_t转换成为可打印的字符串。怎样才能进行反向操作,把structtm或一个字符串转换成time_t? 13.14 怎样在日期上加n天?怎样取得两个日期的时间间隔? 随机数 13.15 怎么生成一个随机数? 13.16 怎样获得某一范围内的随机整数? 13.17 每次执行程序,rand都返回相同的数字序列。为什么? 13.18 我需要随机的真/假值,所以我就直接用rand()%2,可是我得到交替的0,1,0,1,0…。为什么? 164 13.19 如何获取根本不重复的随机数? 13.20 怎样产生正态分布或高斯分布的随机数? 13.21 我在移植一个程序,里边调用了一个函数drand48 ,而我的库又没有这个。这是个什么函数? 其他库函数 13.22 exit(status)是否真的跟从main函数返回status等价? 13.23 memcpy和memmove有什么区别? 13.24 我想移植这个旧程序。为什么报出这些“undefinedexternal”错误:index?、rindex?、bcopy?、bcmp?、bzero?? 13.25 我不断得到库函数未定义错误,但是我已经包含了所有用到的头文件了。 13.26 虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误。 13.27 一个最简单的程序,不过在一个窗口里打印出“Hello,World”,为什么会编译出巨大的可执行代码(数百K)?我该少包含一些头文件吗? 13.28 连接器报告_end未定义代表什么意思? *13.29 我的编译器提示printf未定义!这怎么可能? 第14章 浮点运算 14.1 一个float变量赋值为3.1时,为什么printf输出的值为3.0999999? 14.2 我想计算一些平方根,我把程序简化成这样:main(){printf("%f\h",sqrt(144.));可得到的结果却是疯狂的数字。为什么? 14.3 我想做一些简单的三角函数运算,也包含了math.h,但连接器总是提示sin、cos这样的函数未定义。为什么? 14.4 我的浮点数计算程序表现得很奇怪,在不同的机器上给出了不同的结果。为什么? 14.5 有什么好的方法来检查浮点数在“足够接近”情况下的相等? 14.6 怎样取整? 14.7 为什么C语言不提供乘幂的操作符? 14.8 为什么我机器上的math.h没有预定义常量M_PI? 14.9 怎样将变量置为IEEENaN(“NotaNumber”)或检测变量是否为NaN及其他特殊值? 14.10 如何简洁地处理浮点异常? 14.11 在C语言中如何很好地实现复数? 14.12 我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、求逆等函数)、复数算术。 14.13 TurboC的程序崩溃,显示错误为“floatingpointformatsnotlinked”(浮点格式未连接)。我还缺点儿什么呢? 第15章 可变参数列表 调用变参函数 15.1 为什么调用printf前必须要包含stdio.h? 15.2 为什么%f可以在printf参数中同时表示float和double?它们难道不是不同类型吗? 15.3 我遇到了一个令人十分受挫的问题,后来发现是这行代码造成的:printf("%d",n);原来n是longint型。难道ANSI的函数原型不就是用来防止这类的参数类型不匹配吗? 15.4 怎样写一个接受可变参数的函数? 15.5 怎样写一个函数,像printf那样接受一个格式串和可变参数,然后再把参数传给printf去完成大部分工作? 15.6 怎样写类似scanf的函数,再把参数传给scanf去完成大部分工作? 15.7 我用的是ANSI前的编译器,没有stdarg.h文件。我该怎么办? 提取可变参数 15.8 怎样知道实际上有多少个参数传入函数? 15.9 为什么编译器不允许我定义一个没有固定参数项的可变参数函数? 15.10 我有个接受float型的变参函数,为什么va_arg(argp,float)却不行? 15.11 为什么va_arg不能得到类型为函数指针的参数? 困难的问题 15.12 怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数? 15.13 怎样调用一个在运行时才构建参数列表的函数? 第16 章奇怪的问题 16.1 为什么这个循环只执行了一次?for(i=start;iend;i++);{printf("%d\n",i);} *16.2 遇到不可理解的不合理语法错误,似乎大段的程序没有编译。 *16.3 为什么过程调用不起作用?编译器似乎直接跳过去了。 16.4 程序在执行之前就崩溃了!(用调试器单步跟踪,在main函数的第一个语句之前就死了。)为什么? 16.5 程序执行正确,但退出时在main函数的最后一个语句之后崩溃了。为什么会这样? 16.6 程序在一台机器上运行完美,但在另一台上却得到怪异的结果。更奇怪的是,增加或去除调试的打印语句,就改变了症状…… 16.7 为什么下面的代码会崩溃?char*p="hello,world!";p[0]='H'; 16.8 我有些代码是用来解析外部结构的,但它却崩溃了,报了“unalignedaccess”(未对齐的访问)错误。这是什么意思? 16.9 “Segmentationviolation”、“Buserror”和“Generalprotectionfault”是什么意思? 第17章 风格 17.1 什么是C最好的代码布局风格? 17.2 如何在源文件中合理分配函数? 17.3 用if(!strcmp(s1,s2))比较两个字符串是否相等是个好风格吗? 17.4 为什么有的人用if(0==x)而不是if(x==0)? 17.5 为什么有些代码在每次调用printf前增加了类型转换(void)? 17.6 既然NULL和0都是空指针常量,我到底该用哪一个? 17.7 是该用TRUE和FALSE这样的符号名称还是直接用1和0来作布尔常量? 17.8 什么是“匈牙利表示法”(HungarianNotation)?是否值得一试? 17.9 哪里可以找到“IndianHillStyleGuide”及其他编码标准? 17.10 有人说goto是邪恶的,永远都不该用它。这是否太极端了? 17.11 人们总是说良好的风格很重要,但当他们使用良好的风格写出清晰易读的程序后,又发现程序的效率似乎降低了。既然效率那么重要,是否可以为了效率牺牲一些风格和可读性呢? 第18章 工具和资源 18.1 能否列一个常用工具列表? 18.2 怎样捕获棘手的malloc问题? 18.3 有什么免费或便宜的编译器可以使用? lint 18.4 刚刚输入完一个程序,但它表现得很奇怪。你能发现有什么错误的地方吗? 18.5 如何关掉lint对每个malloc调用报出的“warning:possiblepointeralignmentproblem”警告消息? 18.6 哪里可以找到兼容ANSI的lint? 18.7 难道ANSI函数原型说明没有使lint过时吗? 资源 18.8 网上有哪些C语言的教程或其他资源? *18.9 哪里可以找到好的源代码实例,以供研究和学习? 18.10 有什么好的学习C语言的书?有哪些高级的书和参考? 18.11 哪里能找到K&R的练习答案? 18.12 哪里能找到NumericalRecipesinC、Plauger的TheStandardCLibrary或Kernighan和Pike的TheUNIXProgrammingEnviroment等书里的源码? 18.13 哪里可以找到标准C函数库的源代码? 18.14 是否有一个在线的C参考指南? 18.15 我需要分析和评估表达式的代码。从哪里可以找到? 18.16 哪里可以找到C的BNF或YACC语法? *18.17 谁有C编译器的测试套件? *18.18 哪里有一些有用的源代码片段和例子的收集? *18.19 我需要执行多精度算术的代码。 18.20 在哪里和怎样取得这些可自由发布的程序? 第19章 系统依赖 键盘和屏幕I/O 19.1 怎样从键盘直接读入字符而不用等回车键?怎样防止字符输入时的回显? 19.2 怎样知道有未读的字符(如果有,有多少)?另外,如何在没有字符的时候不阻塞读入? 19.3 怎样显示一个在原地更新自己的百分比或“旋转棒”的进度指示器? 19.4 怎样清屏?怎样反色输出?怎样把光标移动到指定的x,y位置? 19.5 怎样读入方向键、功能键? 其他I/O 19.6 怎样读入鼠标输入? 19.7 怎样做串口(“comm”)的输入输出? 19.8 怎样直接输出到打印机? 19.9 怎样发送转义字符序列控制终端或其他设备? 19.10 怎样做图形? *19.11 怎样显示GIF和JPEG图像? 文件和目录 19.12 怎样检验一个文件是否存在?如果请求的输入文件不存在,我希望向用户提出警告。 19.13 怎样在读入文件前,知道文件大小? *19.14 怎样得到文件的修改日期和时间? 19.15 怎样原地缩短一个文件而不用清除或重写? 19.16 怎样在文件中插入或删除一行(或一条记录)? 19.17 怎样从一个打开的流或文件描述符得到文件名? 19.18 怎样删除一个文件? *19.19 怎样复制文件? 19.20 为什么用了详尽的路径还不能打开文件?下面的代码会返回错误。Fopen("c:\newdir\file.dat","r") *19.21 fopen不让我打开文件"$HOME/.profile"和"~~/.myrcfile"。 *19.22 怎样制止MS-DOS下令人恐怖的“Abort,Retry,Ignore?”信息? 19.23 遇到“Toomanyopenfiles(打开文件太多)”的错误,怎样增加同时打开文件的允许数目? 19.24 如何得到磁盘的可用空间大小? 19.25 怎样在C语言中读入目录? 19.26 如何创建目录?如何删除目录(及其内容)? 访问原始内存 19.27 怎样找出系统还有多少内存可用? 19.28 怎样分配大于64K的数组或结构? 19.29 错误信息“DGROUPdataallocationexceeds64K(DGROUP数据分配内存超过64K)”什么意思?我应该怎么做?我以为使用了大内存模型,就可以使用大于64K的数据! 19.30 怎样访问位于某特定地址的内存(内存映射的设备或图形显示内存)? 19.31 如何访问机器地址0处的中断向量?如果将指针设为0,编译器可能把它转成一个非零的内部空指针值。 “系统”命令 19.32 怎样在一个C程序中调用另一个程序(独立可执行的程序或系统命令)? 19.33 如果运行时才知道要执行的命令的参数(文件名等),应该如何调用system? 19.34 在MS-DOS上如何得到system返回的准确错误状态? 19.35 怎样调用另一个程序或命令,然后获取它的输出? 进程环境 19.36 怎样才能发现程序自己的执行文件的全路径? 19.37 怎样找出和执行文件在同一目录的配置文件? 19.38 进程如何改变它的调用者的环境变量? 19.39 如何打开命令行给出的文件并解析选项? 19.40 exit(status)是否真的和从main函数返回同样的status等价? 19.41 怎样读入一个对象文件并跳跃到其中的函数? 其他系统相关的操作 19.42 怎样以小于1秒的精度延时或计算用户响应时间? 19.43 怎样捕获或忽略control-C这样的键盘中断? 19.44 怎样简洁地处理浮点异常? 19.45 怎样使用socket?如何联网?如何写客户/服务器程序? *19.46 怎样调用BIOS函数?如何写ISR?如何创建TSR? *19.47 什么是“near”和“far”指针? 回顾 19.48 我不能使用这些非标准、依赖系统的函数,程序需要兼容ANSI! 19.49 为什么这些内容没有在C语言中进行标准化?任何现实程序都会用到这些东西。 第20章 杂项 20.1 怎样从函数返回多个值? 20.2 用什么数据结构存储文本行最好?我开始用固定大小的char型数组的数组,但是有很多局限。 20.3 怎样打开命令行提到的文件并处理参数? 20.4 如何正确地使用errno? 20.5 怎样写数据文件,使之可以在不同字大小、字节顺序或浮点格式的机器上读入? 20.6 怎样用char*指针指向的函数名调用函数? 位和字节 20.7 如何操作各个位? 20.8 怎样实现位数组或集合? 234 20.9 怎样判断机器的字节顺序是高字节在前还是低字节在前? *20.10 怎样调换字节? 20.11 怎样将整数转换到二进制或十六进制? 20.12 可以使用二进制常数(类似0b101010这样的东西)吗?printf有二进制的格式说明符吗? 效率 20.13 用什么方法计算整数中为1的位的个数最高效? 20.14 怎样提高程序的效率? 20.15 指针真的比数组快吗?函数调用会拖慢程序多少?++i比i=i+1快吗? 20.16 用移位操作符替换乘法和除法是否有价值? *20.17 人们说编译器优化得很好,我们不再需要为速度而写汇编了,但我的编译器连用移位代替i/=2都做不到。 *20.18 怎样不用临时变量而交换两个值? switch语句 20.19 switch语句和if/else链哪个更高效? 20.20 是否有根据字符串进行条件切换的方法? 20.21 是否有使用非常量case行标的方法(如范围或任意的表达式)? 各种语言功能 20.22 return语句外层的括号是否真的可选择? 20.23 为什么C语言的注释不能嵌套?怎样注释掉含有注释的代码?引号包含的字符串内的注释是否合法? 20.24 为什么C语言的操作符不设计得更全面一些?好像还缺了一些^^、&&=和-=这样的操作符。 *20.25 C语言有循环移位操作符吗? *20.26 C是个伟大的语言还是别的什么东西?哪个其他语言可以写出像a+++++b这样的代码? 20.27 如果赋值操作符是:=,是不是就不容易意外地写出if(a=b)了? 20.28 C语言有和Pascal的with等价的语句吗? 20.29 为什么C语言没有嵌套函数? *20.30 assert是什么?如何使用? 其他语言 20.31 怎样从C中调用FORTRAN(C++、BASIC、Pascal、Ada、LISP)的函数?反之如何? 20.32 有什么程序可以将Pascal或FORTRAN(或LISP、Ada、awk、“老”C)程序转化为C程序? 20.33 C++是C的超集吗?可以用C++编译器来编译C代码吗? 20.34 我需要用到“近似”的strcmp例程,比较两个字符串的近似度,并不需要完全一样。有什么好办法? 20.35 什么是散列法? 20.36 如何生成正态或高斯分布的随机数? 20.37 如何知道某个日期是星期几? 20.38 (year%4==0)是否足以判断闰年?2000年是闰年吗? 20.39 为什么tm结构中的tm_sec的范围是0到61,暗示一分钟有62秒? 琐事 20.40 一个难题:怎样写一个输出自己源代码的程序? 20.41 什么是“达夫设备”(Duff’sDevice)? 20.42 下届国际C语言混乱代码竞赛(InternationalObfuscatedCCodeContest,IOCCC)什么时候进行?哪里可以找到当前和以前的获胜代码? 20.43 K&R1提到的关键字entry是什么? 20.44 C的名字从何而来? 20.45 “char”如何发音? *20.46 “lvalue”和“rvalue”代表什么意思? 20.47 哪里可以获得本书的在线版? 术语表 参考文献
适于初学者 第五章:函数 概述   在第一章中已经介绍过,C源程序是由函数组成的。 虽然在前面各章的程序中都只有一个主函数main(), 但实用程序往往由多个函数组成。函数是C源程序的基本模块, 通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。 C语言不仅提供了极为丰富的库函数(如Turbo C,MS C 都提供了三百多个库函数),还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。   可以说C程序的全部工作都是由各式各样的函数完成的, 所以也把C语言称为函数式语言。 由于采用了函数模块式的结构, C语言易于实现结构化程序设计。使程序的层次结构清晰,便于程序的编写、阅读、调试。   在C语言中可从不同的角度对函数分类。 1. 从函数定义的角度看,函数可分为库函数和用户定义函数两种。 (1)库函数   由C系统提供,用户无须定义, 也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf 、 scanf 、 getchar 、putchar、gets、puts、strcat等函数均属此类。 (2)用户定义函数   由用户按需要写的函数。对于用户自定义函数, 不仅要在程序中定义函数本身, 而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。 2. C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。 (1)有返回值函数   此类函数被调用执行完后将向调用者返回一个执行结果, 称为函数返回值。如数学函数即属于此类函数。 由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型。 (2)无返回值函数   此类函数用于完成某项特定的处理任务, 执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。 由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”, 空类型的说明符为“void”。 3. 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。 (1)无参函数   函数定义、函数说明及函数调用中均不带参数。 主调函数和被调函数之间不进行参数传送。 此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。 (2)有参函数   也称为带参函数。在函数定义及函数说明时都有参数, 称为形式参数(简称为形参)。在函数调用时也必须给出参数, 称为实际参数(简称为实参)。 进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。 4. C语言提供了极为丰富的库函数, 这些库函数又可从功能角度作以下分类。 (1)字符类型分类函数   用于对字符按ASCII码分类:字母,数字,控制字符,分隔符,大小写字母等。 (2)转换函数   用于字符或字符串的转换;在字符量和各类数字量 (整型, 实型等)之间进行转换;在大、小写之间进行转换。 (3)目录路径函数   用于文件目录和路径操作。 (4)诊断函数   用于内部错误检测。 (5)图形函数   用于屏幕管理和各种图形功能。 (6)输入输出函数   用于完成输入输出功能。 (7)接口函数   用于与DOS,BIOS和硬件的接口。 (8)字符串函数   用于字符串操作和处理。 (9)内存管理函数   用于内存管理。 (10)数学函数   用于数学函数计算。 (11)日期和时间函数   用于日期,时间转换操作。 (12)进程控制函数   用于进程管理和控制。 (13)其它函数   用于其它各种功能。      以上各类函数不仅数量多,而且有的还需要硬件知识才会使用,因此要想全部掌握则需要一个较长的学习过程。 应首先掌握一些最基本、 最常用的函数,再逐步深入。由于篇幅关系,本书只介绍了很少一部分库函数, 其余部分读者可根据需要查阅有关手册。   还应该指出的是,在C语言中,所有的函数定义,包括主函数main在内,都是平行的。也就是说,在一个函数的函数体内, 不能再定义另一个函数, 即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。 函数还可以自己调用自己,称为递归调用。main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。 因此,C程序的执行总是从main函数开始, 完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个C源程序必须有,也只能有一个主函数main。    函数定义的一般形式 1.无参函数的一般形式 类型说明符 函数名() { 类型说明 语句 }   其中类型说明符和函数名称为函数头。 类型说明符指明了本函数的类型,函数的类型实际上是函数返回值的类型。 该类型说明符与第二章介绍的各种说明符相同。 函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少。{} 中的内容称为函数体。在函数体中也有类型说明, 这是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值, 此时函数类型符可以写为void。 我们可以改为一个函数定义: void Hello() { printf ("Hello,world \n"); }  这里,只把main改为Hello作为函数名,其余不变。Hello 函数是一个无参函数,当被其它函数调用时,输出Hello world字符串。 2.有参函数的一般形式 类型说明符 函数名(形式参数表) 型式参数类型说明 { 类型说明 语句 }   有参函数比无参函数多了两个内容,其一是形式参数表, 其二是形式参数类型说明。在形参表中给出的参数称为形式参数, 它们可以是各种类型的变量, 各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 形参既然是变量,当然必须给以类型说明。例如,定义一个函数, 用于求两个数中的大数,可写为: int max(a,b) int a,b; { if (a>b) return a; else return b; }   第一行说明max函数是一个整型函数,其返回的函数值是一个整数。形参为a,b。第二行说明a,b均为整型量。 a,b 的具体值是由主调函数在调用时传送过来的。在{}中的函数体内, 除形参外没有使用其它变量,因此只有语句而没有变量类型说明。 上边这种定义方法称为“传统格式”。 这种格式不易于编译系统检查,从而会引起一些非常细微而且难于跟踪的错误。ANSI C 的新标准中把对形参的类型说明合并到形参表中,称为“现代格式”。   例如max函数用现代格式可定义为: int max(int a,int b) { if(a>b) return a; else return b; }   现代格式在函数定义和函数说明(后面将要介绍)时, 给出了形式参数及其类型,在编译时易于对它们进行查错, 从而保证了函数说明和定义的一致性。例1.3即采用了这种现代格式。 在max函数体中的return语句是把a(或b)的值作为函数的值返回给主调函数。有返回值函数中至少应有一个return语句。 在C程序中,一个函数的定义可以放在任意位置, 既可放在主函数main之前,也可放在main之后。例如例1.3中定义了一个max 函数,其位置在main之后, 也可以把它放在main之前。 修改后的程序如下所示。 int max(int a,int b) { if(a>b)return a; else return b; } void main() { int max(int a,int b); int x,y,z; printf("input two numbers:\n"); scanf("%d%d",&x,&y); z=max(x,y); printf("maxmum=%d",z); }   现在我们可以从函数定义、 函数说明及函数调用的角度来分析整个程序,从中进一步了解函数的各种特点。程序的第1行至第5行为max函数定义。进入主函数后,因为准备调用max函数,故先对max函数进行说明(程序第8行)。函数定义和函数说明并不是一回事,在后面还要专门讨论。 可以看出函数说明与函数定义中的函数头部分相同,但是末尾要加分号。程序第12 行为调用max函数,并把x,y中的值传送给max的形参a,b。max函数执行的 结果 (a或b)将返回给变量z。最后由主函数输出z的值。   函数调用的一般形式前面已经说过,在程序中是通过对函数的调用来执行函数体的,其过程与其它语言的子程序调用相似。C语言中, 函数调用的一般形式为:   函数名(实际参数表) 对无参函数调用时则无实际参数表。 实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式。 各实参之间用逗号分隔。'Next of Page在C语言中,可以用以下几种方式调用函数: 1.函数表达式   函数作表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如: z=max(x,y)是一个赋值表达式,把max的返回值赋予变量z。'Next of Page 2.函数语句   函数调用的一般形式加上分号即构成函数语句。例如: printf ("%D",a);scanf ("%d",&b);都是以函数语句的方式调用函数。 3.函数实参   函数作为另一个函数调用的实际参数出现。 这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。例如: printf("%d",max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。 所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。 对此, 各系统的规定不一定相同。在3.1.3节介绍printf 函数时已提 到过,这里从函数调用的角度再强调一下。 看例5.2程序。 void main() { int i=8; printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--); } 如按照从右至左的顺序求值。例5.2的运行结果应为: 8 7 7 8 如对printf语句中的++i,--i,i++,i--从左至右求值,结果应为: 9 8 8 9   应特别注意的是,无论是从左至右求值, 还是自右至左求值,其输出顺序都是不变的, 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值,所以结果为8,7,7,8。上述问题如还不理解,上机一试就明白了。函数的参数和函数的值 一、函数的参数   前面已经介绍过,函数的参数分为形参和实参两种。 在本小节中,进一步介绍形参、实参的特点和两者的关系。 形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。 形参和实参的功能是作数据传送。发生函数调用时, 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。   函数的形参和实参具有以下特点: 1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。 2.实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。 3.实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。 4.函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。例5.3可以说明这个问题。 void main() { int n; printf("input number\n"); scanf("%d",&n); s(n); printf("n=%d\n",n); } int s(int n) { int i; for(i=n-1;i>=1;i--) n=n+i; printf("n=%d\n",n); } 本程序中定义了一个函数s,该函数的功能是求∑ni=1i 的值。在主函数中输入n值,并作为实参,在调用时传送给s 函数的形参量n( 注意,本例的形参变量和实参变量的标识符都为n, 但这是两个不同的量,各自的作用域不同)。 在主函数中用printf 语句输出一次n值,这个n值是实参n的值。在函数s中也用printf 语句输出了一次n值,这个n值是形参最后取得的n值0。从运行情况看,输入n值为100。即实参n的值为100。把此值传给函数s时,形参 n 的初值也为100,在执行函数过程中,形参n的值变为5050。 返回主函数之后,输出实参n的值仍为100。可见实参的值不随形参的变化而变化。 二、函数的值   函数的值是指函数被调用之后, 执行函数体中的程序段所取得的并返回给主调函数的值。如调用正弦函数取得正弦值,调用例5.1的max函数取得的最大数等。对函数的值(或称函数返回值)有以下一些说明: 1. 函数的值只能通过return语句返回主调函数。return 语句的一般形式为: return 表达式; 或者为: return (表达式); 该语句的功能是计算表达式的值,并返回给主调函数。 在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行, 因此只能返回一个函数值。 2. 函数值的类型和函数定义中函数的类型应保持一致。 如果两者不一致,则以函数类型为准,自动进行类型转换。 3. 如函数值为整型,在函数定义时可以省去类型说明。 4. 不返回函数值的函数,可以明确定义为“空类型”, 类型说明符为“void”。如例5.3中函数s并不向主函数返函数值,因此可定义为: void s(int n) { …… }   一旦函数被定义为空类型后, 就不能在主调函数中使用被调函数的函数值了。例如,在定义s为空类型后,在主函数中写下述语句 sum=s(n); 就是错误的。为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为空类型。函数说明在主调函数中调用某函数之前应对该被调函数进行说明, 这与使用变量之前要先进行变量说明是一样的。 在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型, 以便在主调函数中按此种类型对返回值作相应的处理。 对被调函数的说明也有两种格式,一种为传统格式,其一般格式为: 类型说明符 被调函数名(); 这种格式只给出函数返回值的类型,被调函数名及一个空括号。   这种格式由于在括号中没有任何参数信息, 因此不便于编译系统进行错误检查,易于发生错误。另一种为现代格式,其一般形式为: 类型说明符 被调函数名(类型 形参,类型 形参…); 或为: 类型说明符 被调函数名(类型,类型…);   现代格式的括号内给出了形参的类型和形参名, 或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。例5.1 main函数中对max函数的说明若 用传统格式可写为: int max(); 用现代格式可写为: int max(int a,int b); 或写为: int max(int,int);   C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。 1. 如果被调函数的返回值是整型或字符型时, 可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。例5.3的主函数中未对函数s作说明而直接调用即属此种情形。 2. 当被调函数的函数定义出现在主调函数之前时, 在主调函数中也可以不对被调函数再作说明而直接调用。例如例5.1中, 函数max的定义放在main 函数之前,因此可在main函数中省去对 max函数的函数说明int max(int a,int b)。 3. 如在所有函数定义之前, 在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。例如: char str(int a); float f(float b); main() { …… } char str(int a) { …… } float f(float b) { …… } 其中第一,二行对str函数和f函数预先作了说明。 因此在以后各函数中无须对str和f函数再作说明就可直接调用。 4. 对库函数的调用不需要再作说明, 但必须把该函数的头文件用include命令包含在源文件前部。数组作为函数参数数组可以作为函数的参数使用,进行数据传送。 数组用作函数参数有两种形式,一种是把数组元素(下标变量)作为实参使用; 另一种是把数组名作为函数的形参和实参使用。一、数组元素作函数实参数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时, 把作为实参的数组元素的值传送给形参,实现单向的值传送。例5.4说明了这种情况。[例5.4]判别一个整数数组中各元素的值,若大于0 则输出该值,若小于等于0则输出0值。编程如下: void nzp(int v) { if(v>0) printf("%d ",v); else printf("%d ",0); } main() { int a[5],i; printf("input 5 numbers\n"); for(i=0;i<5;i++) { scanf("%d",&a[i]); nzp(a[i]); } }void nzp(int v) { …… } main() { int a[5],i; printf("input 5 numbers\n"); for(i=0;i<5;i++) { scanf("%d",&a[i]); nzp(a[i]); } }   本程序中首先定义一个无返回值函数nzp,并说明其形参v 为整型变量。在函数体中根据v值输出相应的结果。在main函数中用一个for 语句输入数组各元素, 每输入一个就以该元素作实参调用一次nzp函数,即把a[i]的值传送给形参v,供nzp函数使用。 二、数组名作为函数参数   用数组名作函数参数与用数组元素作实参有几点不同: 1. 用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此, 并不要求函数的形参也是下标变量。 换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时, 则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。 2. 在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢? 在第四章中我们曾介绍过,数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送, 也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。图5.1说明了这种情形。图中设a为实参数组,类型为整型。a占有以2000 为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送, 把实参数 组a的首地址传送给形参数组名b,于是b也取得该地址2000。 于是a,b两数组共同占有以2000 为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占相同的两个内 存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a[i]等于b[i]。 [例5.5]数组a中存放了一个学生5门课程的成绩,求平均成绩。 float aver(float a[5]) { int i; float av,s=a[0]; for(i=1;i<5;i++) s=s+a[i]; av=s/5; return av; } void main() { float sco[5],av; int i; printf("\ninput 5 scores:\n"); for(i=0;i<5;i++) scanf("%f",&sco[i]); av=aver(sco); printf("average score is %5.2f",av); } float aver(float a[5]) { …… } void main() { …… for(i=0;i<5;i++) scanf("%f",&sco[i]); av=aver(sco); …… }   本程序首先定义了一个实型函数aver,有一个形参为实型数组a,长度为5。在函数aver中,把各元素值相加求出平均值,返回给主函数。主函数main 中首先完成数组sco的输入,然后以sco作为实参调用aver函数,函数返回值送av,最后输出av值。 从运行情况可以看出,程序实现了所要求的功能 3. 前面已经讨论过,在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同, 而形参的值发生改变后,实参并不变化, 两者的终值是不同的。例5.3证实了这个结论。 而当用数组名作函数参数时,情况则不同。 由于实际上形参和实参为同一数组, 因此当形参数组发生变化时,实参数组也随之变化。 当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。为了说明这种情况,把例5.4改为例5.6的形式。[例5.6]题目同5.4例。改用数组名作函数参数。 void nzp(int a[5]) { int i; printf("\nvalues of array a are:\n"); for(i=0;i<5;i++) { if(a[i]<0) a[i]=0; printf("%d ",a[i]); } } main() { int b[5],i; printf("\ninput 5 numbers:\n"); for(i=0;i<5;i++) scanf("%d",&b[i]); printf("initial values of array b are:\n"); for(i=0;i<5;i++) printf("%d ",b[i]); nzp(b); printf("\nlast values of array b are:\n"); for(i=0;i<5;i++) printf("%d ",b[i]); } void nzp(int a[5]) { …… } main() { int b[5],i; …… nzp(b); …… }   本程序中函数nzp的形参为整数组a,长度为 5。 主函数中实参数组b也为整型,长度也为5。在主函数中首先输入数组b的值,然后输出数组b的初始值。 然后以数组名b为实参调用nzp函数。在nzp中,按要求把负值单元清0,并输出形参数组a的值。 返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b 的初值和终值是不同的,数组b 的终值和数组a是相同的。这说明实参形参为同一数组,它们的值同时得以改变。 用数组名作为函数参数时还应注意以下几点: a. 形参数组和实参数组的类型必须一致,否则将引起错误。 b. 形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。如把例5.6修改如下: void nzp(int a[8]) { int i; printf("\nvalues of array aare:\n"); for(i=0;i<8;i++) { if(a[i]<0)a[i]=0; printf("%d",a[i]); } } main() { int b[5],i; printf("\ninput 5 numbers:\n"); for(i=0;i<5;i++) scanf("%d",&b[i]); printf("initial values of array b are:\n"); for(i=0;i<5;i++) printf("%d",b[i]); nzp(b); printf("\nlast values of array b are:\n"); for(i=0;i<5;i++) printf("%d",b[i]); }   本程序与例5.6程序比,nzp函数的形参数组长度改为8,函数体中,for语句的循环条件也改为i<8。因此,形参数组 a和实参数组b的长度不一致。编译能够通过,但从结果看,数组a的元素a[5],a[6],a[7]显然是无意义的。c. 在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。 例如:可以写为: void nzp(int a[]) 或写为 void nzp(int a[],int n)   其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。 由此,例5.6又可改为例5.7的形式。 [例5.7] void nzp(int a[],int n) { int i; printf("\nvalues of array a are:\n"); for(i=0;i<n;i++) { if(a[i]<0) a[i]=0; printf("%d ",a[i]); } } main() { int b[5],i; printf("\ninput 5 numbers:\n"); for(i=0;i<5;i++) scanf("%d",&b[i]); printf("initial values of array b are:\n"); for(i=0;i<5;i++) printf("%d ",b[i]); nzp(b,5); printf("\nlast values of array b are:\n"); for(i=0;i<5;i++) printf("%d ",b[i]); } void nzp(int a[],int n) { …… } main() { …… nzp(b,5); …… }   本程序nzp函数形参数组a没有给出长度,由n 动态确定该长度。在main函数中,函数调用语句为nzp(b,5),其中实参5将赋予形参n作为形参数组的长度。 d. 多维数组也可以作为函数的参数。 在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的。 int MA(int a[3][10]) 或 int MA(int a[][10]) 函数的嵌套调用   C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。 但是C语言允许在一个函数的定义中出现对另一个函数的调用。 这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。 这与其它语言的子程序嵌套的情形是类似的。其关系可表示如图5.2。   图5.2表示了两层嵌套的情形。其执行过程是:执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b 函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a 函数执行完毕返回main函数的断点继续执行。 [例5.8]计算s=22!+32! 本题可编写两个函数,一个是用来计算平方值的函数f1, 另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值, 再在f1中以平方值为实参,调用 f2计算其阶乘值,然后返回f1,再返回主函数,在循环程序中计算累加和。 long f1(int p) { int k; long r; long f2(int); k=p*p; r=f2(k); return r; } long f2(int q) { long c=1; int i; for(i=1;i<=q;i++) c=c*i; return c; } main() { int i; long s=0; for (i=2;i<=3;i++) s=s+f1(i); printf("\ns=%ld\n",s); } long f1(int p) { …… long f2(int); r=f2(k); …… } long f2(int q) { …… } main() { …… s=s+f1(i); …… }   在程序中,函数f1和f2均为长整型,都在主函数之前定义, 故不必再在主函数中对f1和f2加以说明。在主程序中, 执行循环程序依次把i值作为实参调用函数f1求i2值。在f1中又发生对函数f2的调用,这时是把i2的值作为实参去调f2,在f2 中完成求i2! 的计算。f2执行完毕把C值(即i2!)返回给f1,再由f1 返回主函数实现累加。至此,由函数的嵌套调用实现了题目的要求。 由于数值很大, 所以函数和一些变量的类型都说明为长整型,否则会造成计算错误。 函数的递归调用   一个函数在它的函数体内调用它自身称为递归调用。 这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中, 主调函数又是被调函数。执行递归函数将反复调用其自身。 每调用一次就进入新的一层。例如有函数f如下: int f (int x) { int y; z=f(y); return z; }   这个函数是一个递归函数。 但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行, 必须在函数内有终止递归调用的手段。常用的办法是加条件判断, 满足某种条件后就不再作递归调用,然后逐层返回。 下面举例说明递归调用的执行过程。 [例5.9]用递归法计算n!用递归法计算n!可用下述公式表示: n!=1 (n=0,1) n×(n-1)! (n>1) 按公式可编程如下: long ff(int n) { long f; if(n<0) printf("n<0,input error"); else if(n==0||n==1) f=1; else f=ff(n-1)*n; return(f); } main() { int n; long y; printf("\ninput a inteager number:\n"); scanf("%d",&n); y=ff(n); printf("%d!=%ld",n,y); } long ff(int n) { …… else f=ff(n-1)*n; …… } main() { …… y=ff(n); …… }   程序中给出的函数ff是一个递归函数。主函数调用ff 后即进入函数ff执行,如果n<0,n==0或n=1时都将结束函数的执行,否则就递归调用ff函数自身。由于每次递归调用的实参为n-1,即把n-1 的值赋予形参n,最后当n-1的值为1时再作递归调用,形参n的值也为1,将使递归终止。然后可逐层退回。下面我们再举例说明该过程。 设执行本程序时输入为5, 即求 5!。在主函数中的调用语句即为y=ff(5),进入ff函数后,由于n=5,不等于0或1,故应执行f=ff(n-1)*n,即f=ff(5-1)*5。该语句对ff作递归调用即ff(4)。 逐次递归展开如图5.3所示。进行四次递归调用后,ff函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。ff(1)的函数返回值为1,ff(2)的返回值为1*2=2,ff(3)的返回值为2*3=6,ff(4) 的返 回值为6*4=24,最后返回值ff(5)为24*5=120。   例5. 9也可以不用递归的方法来完成。如可以用递推法,即从1开始乘以2,再乘以3…直到n。递推法比递归法更容易理解和实现。但是有些问题则只能用递归算法才能实现。典型的问题是Hanoi塔问题。      [例5.10]Hanoi塔问题 一块板上有三根针,A,B,C。A针上套有64个大小不等的圆盘, 大的在下,小的在上。如图5.4所示。要把这64个圆盘从A针移动C针上,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。 本题算法分析如下,设A上有n个盘子。 如果n=1,则将圆盘从A直接移动到C。 如果n=2,则: 1.将A上的n-1(等于1)个圆盘移到B上; 2.再将A上的一个圆盘移到C上; 3.最后将B上的n-1(等于1)个圆盘移到C上。 如果n=3,则: A. 将A上的n-1(等于2,令其为n`)个圆盘移到B(借助于C), 步骤如下: (1)将A上的n`-1(等于1)个圆盘移到C上,见图5.5(b)。 (2)将A上的一个圆盘移到B,见图5.5(c) (3)将C上的n`-1(等于1)个圆盘移到B,见图5.5(d) B. 将A上的一个圆盘移到C,见图5.5(e) C. 将B上的n-1(等于2,令其为n`)个圆盘移到C(借助A), 步骤如下: (1)将B上的n`-1(等于1)个圆盘移到A,见图5.5(f) (2)将B上的一个盘子移到C,见图5.5(g) (3)将A上的n`-1(等于1)个圆盘移到C,见图5.5(h)。 到此,完成了三个圆盘的移动过程。 从上面分析可以看出,当n大于等于2时, 移动的过程可分解为 三个步骤: 第一步 把A上的n-1个圆盘移到B上; 第二步 把A上的一个圆盘移到C上; 第三步 把B上的n-1个圆盘移到C上;其中第一步和第三步是类同的。 当n=3时,第一步和第三步又分解为类同的三步,即把n`-1个圆盘从一个针移到另一个针上,这里的n`=n-1。 显然这是一个递归过 程,据此算法可编程如下: move(int n,int x,int y,int z) { if(n==1) printf("%c-->%c\n",x,z); else { move(n-1,x,z,y); printf("%c-->%c\n",x,z); move(n-1,y,x,z); } } main() { int h; printf("\ninput number:\n"); scanf("%d",&h); printf("the step to moving %2d diskes:\n",h); move(h,'a','b','c'); } move(int n,int x,int y,int z) { if(n==1) printf("%-->%c\n",x,z); else { move(n-1,x,z,y); printf("%c-->%c\n",x,z); move(n-1,y,x,z); } } main() { …… move(h,'a','b','c'); }   从程序中可以看出,move函数是一个递归函数,它有四个形参n,x,y,z。n表示圆盘数,x,y,z分别表示三根针。move 函数的功能是把x上的n个圆盘移动到z 上。当n==1时,直接把x上的圆盘移至z上,输出x→z。如n!=1则分为三步:递归调用move函数,把n-1个圆盘从x移到y;输出x→z;递归调用move函数,把n-1个圆盘从y移到z。在递归调用过程中n=n-1,故n的值逐次递减,最后n=1时,终止递归,逐层返回。当n=4 时程序运行的结果为 input number: 4 the step to moving 4 diskes: a→b a→c b→c a→b c→a c→b a→b a→c b→c b→a c→a b→c a→b a→c b→c 变量的作用域   在讨论函数的形参变量时曾经提到, 形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的, 离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量, C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。 C语言中的变量,按作用域范围可分为两种, 即局部变量和全局变量。 一、局部变量   局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。 例如: int f1(int a) /*函数f1*/ { int b,c; …… }a,b,c作用域 int f2(int x) /*函数f2*/ { int y,z; }x,y,z作用域 main() { int m,n; } m,n作用域 在函数f1内定义了三个变量,a为形参,b,c为一般变量。在 f1的范围内a,b,c有效,或者说a,b,c变量的作用域限于f1内。同理,x,y,z的作用域限于f2内。 m,n的作用域限于main函数内。关于局部变量的作用域还要说明以下几点: 1. 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。 2. 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。 3. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如在例5.3 中,形参和实参的变量名都为n,是完全允许的。4. 在复合语句中也可定义变量,其作用域只在复合语句范围内。例如: main() { int s,a; …… { int b; s=a+b; ……b作用域 } ……s,a作用域 }[例5.11]main() { int i=2,j=3,k; k=i+j; { int k=8; if(i==3) printf("%d\n",k); } printf("%d\n%d\n",i,k); } main() { int i=2,j=3,k; k=i+j; { int k=8; if(i=3) printf("%d\n",k); } printf("%d\n%d\n",i,k); }   本程序在main中定义了i,j,k三个变量,其中k未赋初值。 而在复合语句内又定义了一个变量k,并赋初值为8。应该注意这两个k不是同一个变量。在复合语句外由main定义的k起作用,而在复合语句内则由在复合语句内定义的k起作用。因此程序第4行的k为main所定义,其值应为5。第7行输出k值,该行在复合语句内,由复合语句内定义的k起作用,其初值为8,故输出值为8,第9行输出i,k值。i是在整个程序中有效的,第7行对i赋值为3,故以输出也为3。而第9行已在复合语句之外,输出的k应为main所定义的k,此k值由第4 行已获得为5,故输出也为5。 二、全局变量 全局变量也称为外部变量,它是在函数外部定义的变量。 它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。 例如: int a,b; /*外部变量*/ void f1() /*函数f1*/ { …… } float x,y; /*外部变量*/ int fz() /*函数fz*/ { …… } main() /*主函数*/ { …… }/*全局变量x,y作用域 全局变量a,b作用域*/   从上例可以看出a、b、x、y 都是在函数外部定义的外部变量,都是全局变量。但x,y 定义在函数f1之后,而在f1内又无对x,y的说明,所以它们在f1内无效。 a,b定义在源程序最前面,因此在f1,f2及main内不加说明也可使用。 [例5.12]输入正方体的长宽高l,w,h。求体积及三个面x*y,x*z,y*z的面积。 int s1,s2,s3; int vs( int a,int b,int c) { int v; v=a*b*c; s1=a*b; s2=b*c; s3=a*c; return v; } main() { int v,l,w,h; printf("\ninput length,width and height\n"); scanf("%d%d%d",&l,&w,&h); v=vs(l,w,h); printf("v=%d s1=%d s2=%d s3=%d\n",v,s1,s2,s3); }   本程序中定义了三个外部变量s1,s2,s3, 用来存放三个面积,其作用域为整个程序。函数vs用来求正方体体积和三个面积, 函数的返回值为体积v。由主函数完成长宽高的输入及结果输出。由于C语言规定函数返回值只有一个, 当需要增加函数的返回数据时,用外部变量是一种很好的方式。本例中,如不使用外部变量, 在主函数中就不可能取得v,s1,s2,s3四个值。而采用了外部变量, 在函数vs中求得的s1,s2,s3值在main 中仍然有效。因此外部变量是实现函数之间数据通讯的有效手段。对于全局变量还有以下几点说明: 1. 对于局部变量的定义和说明,可以不加区分。而对于外部变量则不然,外部变量的定义和外部变量的说明并不是一回事。外部变量定义必须在所有的函数之外,且只能定义一次。其一般形式为: [extern] 类型说明符 变量名,变量名… 其中方括号内的extern可以省去不写。 例如: int a,b; 等效于: extern int a,b;   而外部变量说明出现在要使用该外部变量的各个函数内, 在整个程序内,可能出现多次,外部变量说明的一般形式为: extern 类型说明符 变量名,变量名,…; 外部变量在定义时就已分配了内存单元, 外部变量定义可作初始赋值,外部变量说明不能再赋初始值, 只是表明在函数内要使用某外部变量。 2. 外部变量可加强函数模块之间的数据联系, 但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的, 因此在不必要时尽量不要使用全局变量。 3. 在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。 [例5.13]int vs(int l,int w) { extern int h; int v; v=l*w*h; return v; } main() { extern int w,h; int l=5; printf("v=%d",vs(l,w)); } int l=3,w=4,h=5;   本例程序中,外部变量在最后定义, 因此在前面函数中对要用的外部变量必须进行说明。外部变量l,w和vs函数的形参l,w同名。外部变量都作了初始赋值,mian函数中也对l作了初始化赋值。执行程序时,在printf语句中调用vs函数,实参l的值应为main中定义的l值,等于5,外部变量l在main内不起作用;实参w的值为外部变量w的值为4,进入vs后这两个值传送给形参l,wvs函数中使用的h 为外部变量,其值为5,因此v的计算结果为100,返回主函数后输出。变量的存储类型各种变量的作用域不同, 就其本质来说是因变量的存储类型相同。所谓存储类型是指变量占用内存空间的方式, 也称为存储方式。 变量的存储方式可分为“静态存储”和“动态存储”两种。   静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。5.5.1节中介绍的全局变量即属于此类存储方式。动态存储变量是在程序执行过程中,使用它时才分配存储单元, 使用完毕立即释放。 典型的例子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配, 调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、 释放形参变量的存储单元。从以上分析可知, 静态存储变量是一直存在的, 而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性称变量的生存期。 生存期表示了变量存在的时间。 生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。 一个变量究竟属于哪一种存储方式, 并不能仅从其作用域来判断,还应有明确的存储类型说明。   在C语言中,对变量的存储类型说明有以下四种: auto     自动变量 register   寄存器变量 extern    外部变量 static    静态变量   自动变量和寄存器变量属于动态存储方式, 外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后, 可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。 因此变量说明的完整形式应为: 存储类型说明符 数据类型说明符 变量名,变量名…; 例如: static int a,b;           说明a,b为静态类型变量 auto char c1,c2;          说明c1,c2为自动字符变量 static int a[5]={1,2,3,4,5};    说明a为静整型数组 extern int x,y;           说明x,y为外部整型变量 下面分别介绍以上四种存储类型: 一、自动变量的类型说明符为auto。   这种存储类型是C语言程序中使用最广泛的一种类型。C语言规定, 函数内凡未加存储类型说明的变量均视为自动变量, 也就是说自动变量可省去说明符auto。 在前面各章的程序中所定义的变量凡未加存储类型说明符的都是自动变量。例如: { int i,j,k; char c; …… }等价于: { auto int i,j,k; auto char c; …… }   自动变量具有以下特点: 1. 自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量只在该复合语句中有效。 例如: int kv(int a) { auto int x,y; { auto char c; } /*c的作用域*/ …… } /*a,x,y的作用域*/ 2. 自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。例如以下程序: main() { auto int a,s,p; printf("\ninput a number:\n"); scanf("%d",&a); if(a>0){ s=a+a; p=a*a; } printf("s=%d p=%d\n",s,p); } s,p是在复合语句内定义的自动变量,只能在该复合语句内有效。而程序的第9行却是退出复合语句之后用printf语句输出s,p的值,这显然会引起错误。 3. 由于自动变量的作用域和生存期都局限于定义它的个体内( 函数或复合语句内), 因此不同的个体中允许使用同名的变量而不会混淆。 即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名。例5.14表明了这种情况。 [例5.14] main() { auto int a,s=100,p=100; printf("\ninput a number:\n"); scanf("%d",&a); if(a>0) { auto int s,p; s=a+a; p=a*a; printf("s=%d p=%d\n",s,p); } printf("s=%d p=%d\n",s,p); }   本程序在main函数中和复合语句内两次定义了变量s,p为自动变量。按照C语言的规定,在复合语句内,应由复合语句中定义的s,p起作用,故s的值应为a+ a,p的值为a*a。退出复合语句后的s,p 应为main所定义的s,p,其值在初始化时给定,均为100。从输出结果可以分析出两个s和两个p虽变量名相同, 但却是两个不同的变量。 4. 对构造类型的自动变量如数组等,不可作初始化赋值。 二、外部变量外部变量的类型说明符为extern。 在前面介绍全局变量时已介绍过外部变量。这里再补充说明外部变量的几个特点: 1. 外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。 2. 当一个源程序由若干个源文件组成时, 在一个源文件中定义的外部变量在其它的源文件中也有效。例如有一个源程序由源文件F1.C和F2.C组成: F1.C int a,b; /*外部变量定义*/ char c; /*外部变量定义*/ main() { …… } F2.C extern int a,b; /*外部变量说明*/ extern char c; /*外部变量说明*/ func (int x,y) { …… } 在F1.C和F2.C两个文件中都要使用a,b,c三个变量。在F1.C文件中把a,b,c都定义为外部变量。在F2.C文件中用extern把三个变量说明为外部变量,表示这些变量已在其它文件中定义,并把这些变量的类型和变量名,编译系统不再为它们分配内存空间。 对构造类型的外部变量, 如数组等可以在说明时作初始化赋值,若不赋初值,则系统自动定义它们的初值为0。 三、静态变量   静态变量的类型说明符是static。 静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量, 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。 对于自动变量,前面已经介绍它属于动态存储方式。 但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。 由此看来, 一个变量可由static进行再说明,并改变其原有的存储方式。 1. 静态局部变量   在局部变量的说明前再加上static说明符就构成静态局部变量。 例如: static int a,b; static float array[5]={1,2,3,4,5};      静态局部变量属于静态存储方式,它具有以下特点: (1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。 (2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。 (3)允许对构造类静态局部量赋初值。在数组一章中,介绍数组初始化时已作过说明。若未赋以初值,则由系统自动赋以0值。 (4)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。 根据静态局部变量的特点, 可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。 [例5.15]main() { int i; void f(); /*函数说明*/ for(i=1;i<=5;i++) f(); /*函数调用*/ } void f() /*函数定义*/ { auto int j=0; ++j; printf("%d\n",j); }   程序中定义了函数f,其中的变量j 说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋初值为0,故每次输出值均为1。现在把j改为静态局部变量,程序如下: main() { int i; void f(); for (i=1;i<=5;i++) f(); } void f() { static int j=0; ++j; printf("%d\n",j); } void f() { static int j=0; ++j; printf("%d/n",j); } 由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。读者可自行分析其执行过程。 2.静态全局变量   全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它 的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。 四、寄存器变量   上述各类变量都存放在存储器内, 因此当对一个变量频繁读写时,必须要反复访问内存储器,从而花费大量的存取时间。 为此,C语言提供了另一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写, 这样可提高效率。寄存器变量的说明符是register。 对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。 [例5.16]求∑200i=1imain() { register i,s=0; for(i=1;i<=200;i++) s=s+i; printf("s=%d\n",s); } 本程序循环200次,i和s都将频繁使用,因此可定义为寄存器变量。 对寄存器变量还要说明以下几点: 1. 只有局部自动变量和形式参数才可以定义为寄存器变量。因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为寄存器变量。 2. 在Turbo C,MS C等微机上使用的C语言中, 实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高。 而在程序中允许使用寄存器变量只是为了与标准C保持一致。3. 即使能真正使用寄存器变量的机器,由于CPU 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。 内部函数和外部函数   函数一旦定义后就可被其它函数调用。 但当一个源程序由多个源文件组成时, 在一个源文件中定义的函数能否被其它源文件中的函数调用呢?为此,C语言又把函数分为两类: 一、内部函数   如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用, 这种函数称为内部函 数。定义内部函数的一般形式是: static 类型说明符 函数名(形参表) 例如: static int f(int a,int b) 内部函数也称为静态函数。但此处静态static 的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件。 因此在不同的源文件中定义同名的静态函数不会引起混淆。 二、外部函数   外部函数在整个源程序中都有效,其定义的一般形式为: extern 类型说明符 函数名(形参表) 例如: extern int f(int a,int b)如在函数定义中没有说明extern或static则隐含为extern。在一个源文件的函数中调用其它源文件中定义的外部函数时,应 用extern说明被调函数为外部函数。例如: F1.C (源文件一) main() { extern int f1(int i); /*外部函数说明,表示f1函 数在其它源文件中*/ …… } F2.C (源文件二) extern int f1(int i); /*外部函数定义*/ { …… } 本章小结 1. 函数的分类 (1)库函数:由C系统提供的函数; (2)用户定义函数:由用户自己定义的函数; (3)有返回值的函数向调用者返回函数值,应说明函数类型( 即返回值的类型 ); (4)无返回值的函数:不返回函数值,说明为空(void)类型; (5)有参函数:主调函数向被调函数传送数据; (6)无参函数:主调函数与被调函数间无数据传送; (7)内部函数:只能在本源文件中使用的函数; (8)外部函数:可在整个源程序中使用的函数。 2. 函数定义的一般形式 [extern/static] 类型说明符 函数名([形参表]) 方括号内为可选项。 3. 函数说明的一般形式 [extern] 类型说明符 函数名([形参表]); 4. 函数调用的一般形式 函数名([实参表]) 5. 函数的参数分为形参和实参两种,形参出现在函数定义中,实参出现在函数调用中,发生函数调用时,将把实参的值传送给形参。 6. 函数的值是指函数的返回值,它是在函数中由return语句返回的。 7. 数组名作为函数参数时不进行值传送而进行地址传送。形参和实参实际上为同一数组的两个名称。因此形参数组的值发生变化,实参数组的值当然也变化。 8. C语言中,允许函数的嵌套调用和函数的递归调用。 9. 可从三个方面对变量分类,即变量的数据类型,变量作用域和变量的存储类型。在第二章中主要介绍变量的数据类型,本章中介绍了变量的作用域和变量的存储类型。 10.变量的作用域是指变量在程序中的有效范围, 分为局部变量和全局变量。 11.变量的存储类型是指变量在内存中的存储方式,分为静态存储和动态存储,表示了变量的生存期。 12.变量分类特性表存储方式存储类型说明符何处定义生存期作用域赋值前的值可赋初值类型动态存储自动变量 auto 寄存器变量 register 函数或复合语句内被调用时在定义它的函数或复合语句内不定基本类型int或char外部变量extern函数之外整个源程序整个源程序静态局部变量static 函数或复合语句内静态全局变量static 函数之外整个源程序在定义它的函数或复合语句内在定义它的源文件内0任何类型 资料收集:beck Copyright 2002 www.vcok.com, All Rights Reserved

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值