《你必须知道的495个C语言问题》读书笔记之第11-14章:ANSI C标准、库函数、浮点数
一、ANSI C标准
1. ANSI向C语言预处理器引入了几项新的功能,包括“字符串化”操作符(#)、“符号粘贴”操作符(##)、#pragma指令。
2. Q:char a[3] = "abc";合法吗?
A:它在ANSI C标准中是合法的,它声明了一个长度为3的数组,把它的3个字符初始化为'a', 'b'和'c',但没有终止符'\0',因此该数组并不是一个真正的C字符串,从而不能用在strcpy, printf %s等语句中。
3. Q:memcpy()和memmove()有什么区别?
A:如果源和目的参数有重叠,memmove()能提供有保证的行为,而memcpy()则不能提供这样的保证,因此可以实现得更加有效率。如果有疑问,最好使用memmove()。
4. Q:为什么ANSI标准竟然有那么多未定义的东西?标准的唯一任务不就是让这些东西标准化吗?
A:某些构造随编译器和硬件的实现而变化,这一直是C语言的一个特点。这种有意的不严格可以让编译器生成效率更高的代码,而不必让所有程序为了不合理的情况承担额外的负担。因此,标准只是把现存的实践整理成文。
编程语言标准可以看做是语言使用者和编译器实现者之间的协议。协议的一部分是编译器实现者同意提供、用户可以使用的功能,而其他部分则包括用户同意遵守和编译器实现者认为会被遵守的规则。只要双方都恪守自己的保证,程序就可以正确运行。如果任何一方违背它的诺言,结果肯定失败。
二、标准输入输出库
1. Q:我有个读取字符直到遇到EOF的程序,我该如何在键盘中输入“EOF”来终止读入呢?
A:根据你的操作系统,可能使用不同的按键组合来表示文件结束,通常是Ctrl-D或Ctrl-Z。(我的电脑是Ctrl-Z)
2. 使用printf输出long型整数时,格式字符串应该使用"%ld",而非"%d"。
3. Q:为什么printf()用%f输出double型,而scanf却用%lf呢?
A:printf的%f说明符确实既可以输出float型又可以输出double型,根据“默认参数提升”规则,float型会被提升为double型,因此printf()只会看到双精度数。对于scanf,情况就不同了,它接受指针,这里没有类似的类型提升。通过指针向float存储和向double存储不一样,因此scanf区别%f和%lf。
4. Q:对于size_t型变量,当我不知道它的具体类型时,如何使用printf输出它的值?
A:把那个值转换为一个已知的长度够大的类型,然后使用与之对应的printf格式。例如:
printf("%lu", (unsigned long)sizeof(sometype));
5. Q:如何用printf实现可变的域宽度?就是说,我想在运行时确定宽度而不是使用%8d?
A:使用printf("%*d", width, x)。格式说明符中的星号表示,参数列表中的一个int值用来表示域的宽度。注意在参数列表中。宽度在输出的值之前。
6. Q:如何将一行的任意多个域读入一个数组中?
A:(a) 使用带有正确格式串的fscanf和sscanf。另外,scanf格式串也可以用来处理紧凑的数据,如"1234ABC5.678"就可以用"%d%3s%f"读出。
(b) 用strtok或等价的工具将数据行分解为用空白隔开的域,然后用atoi或atol等函数单独处理各个域。这个方法尤其适用于将任意多个域的一行读入一个数组中。
#include<stdlib.h> #define MAXARGS 10 char *av[MAXARGS]; int ac, i; double array[MAXARGS]; char line[] = "1 2.3 4.5e6 789e10"; ac = makeargv(line, av, MAXARGS); for (i = 0; i < ac; i++) { array[i] = atof(av[i]); }
7. Q:我用"%d\n"调用scanf从键盘读取数字时,为什么要多输入一行才返回?
int n; scanf("%d\n", &n); printf("you type %d\n", n);
A:"\n"在scanf格式字符串中不表示等待换行符,而是读取并放弃连续的空白字符,事实上scanf格式串中的任何空白字符都表示读取并放弃空白字符。因此,"%d\n"中的"\n"会让scanf读到非空白字符为止。所以应该去掉"\n"仅仅使用"%d"即可。
8. Q:我用scanf和%d读取一个数字,然后再用gets()读取字符串,但是编译器好像跳过了gets()调用!
int n; char str[80]; printf("Enter a number:\n"); scanf("%d", &n); printf("Enter a string:\n"); gets(str); printf("you typed %d and \"%s\"\n", n, str);
A:如果你向问题中的程序输入两行,假设分别是"42"和"a string",scanf会读取42,但却不会读到紧接其后的换行符。换行符会保留在输入流中,然后被gets()读取,后者会读入一个空行,而第二行的"a string"则根本不会被读取。
作为一个一般规则,不能混用scanf和gets或任何其他的输入例程的调用,scanf对换行符的特殊处理几乎一定会带来问题。要么就用scanf处理所有的输入,要么干脆不用。
9. Q:为什么下面的程序有时候会陷入无限循环?
int n; while (1) { printf("Enter a number:\n"); if (1 == scanf("%d", &n)) // if input isn't a number, the program would calculate infinitely. break; printf("try again:\n"); }
A:在scanf转换数字时,它遇到的任何非数字字符都会终止转换并被保留在输入流中。因此,除非采用了其他步骤,否则未预料的非数字输入会不断“阻塞”scanf,因为scanf永远都不能越过错误的非数字字符而处理后面的合法数字字符。
你可能很奇怪为什么scanf会把未匹配的字符留在输入流。假设你有一行文本"123code",你希望用"%d%s"格式的scanf来解析这行文本。但是如果%d不把未匹配的字符保留在输入流中,则%s会错误地读入"ode"而不是"code"。这是词法分析中的一个标准问题:在扫描任意长度的数字常量或字母数字字符串时,只有读到越界位置,你才能知道它已结束。
10. Q:为什么大家都说不要使用scanf?那我该用什么代替?
A:scanf存在许多问题,除了上述问题,它的"%s"格式和gets()一样,都很难保证接收的缓冲区不溢出。
一般来说,scanf正如其名"scan formatted"所示,适用于相对结构化的、格式整齐的输入,如果你注意,它会告诉你成功或失败,但它只能提供失败的大概位置,对scanf做错误恢复几乎是不可能的。对缺乏格式化的输入,可以先用fgets读入整行,然后再用scanf等函数(strtol, strtok和atoi等)进行解释。
如果数据文件格式已知,使用fscanf可能很合适。如果希望匹配失败后可以很容易地恢复控制、重启扫描或放弃输入,可以使用sscanf。
11. Q:为什么连一个最简单的fopen调用都不成功!
FILE *fp = fopen(filename, 'r');
A:问题在于fopen的mode参数必须是字符串,如"r",而非字符('r')。
12. Q:为什么我不能用完整路径名打开一个文件?
fopen("C:\newdir\file.dat", "r");
A:你可能需要重复那些反斜杠。因为反斜杠字符是'\\',而不是'\'。
三、库函数
1. Q:怎样把数字转换成字符串?
A:C提供sprintf作为通用解决方法,以把int型、long型或浮点数转换成字符串。
sprintf(str, "%d", inum); sprintf(str, "%ld", lnum); sprintf(str, "%f", fnum);
2. Q:为什么strncpy不能总在目标串放上终止符'\0'?
A:strncpy最初被设计为用来处理一种现已废弃的数据结构——定长、不必以'\0'结束的字符串,其他环境下使用strncpy有些麻烦,因为必须经常在目的串末尾手工加上'\0'。另外,strncpy的另一个相关的怪癖是它会用多个'\0'填充短串,直到达到指定的长度,这样允许更有效的字符串比较。
3. Q:C语言有没有类似其他语言中的"substr"(取出子串)的例程?
A:没有。C语言没有提供自动管理的字符串类型。要从字符串的pos位置取长度为len的子串,可以用:
// method 1: char dest[LEN+1]; strncpy(dest, &src[POS], LEN); dest[LEN] = '\0'; // method 2: char dest[LEN+1] = ""; strncat(dest, &src[POS], LEN);
4. Q:怎样将字符串分割成用空白分隔的字段?
A:标准中唯一用于这种分隔的函数是strtok,虽然用起来需要些技巧。
#include<stdio.h> #include<string.h> char str[] = "today is a rainy day."; char *p; for (p = strtok(str, " \t\n"); p != NULL; p = strtok(NULL, " \t\n")) printf("\"%s\"\n", p);
5. Q:我想用strcmp作为比较函数,调用sort对一个字符串数组排序,但是不行,为什么?
A:qsort比较函数的参数是被排序对象的指针,在这里是字符指针的指针,然而strcmp只接受字符指针。因此不能直接使用strcmp,要用下边这样的间接比较函数:
/* compare strings via pointers */ int pstrcmp(const void *p1, cosnt void *p2) { return strcmp(*(char * const *)p1, *(char * const *)p2); } /* use pstrcmp() as the compare function of qsort()*/ #include<stdlib.h> char *str[LEN]; qsort(str, LEN, sizeof(char *), pstrcmp);
注意,因为qsort用了通用的方式来处理未知类型的内存块,所以它用通用指针(void *)来引用它们。当qsort调用比较函数时,它把将要比较的两块内存以两个通用指针的形式传入你的函数。因为它使用通用指针,比较函数必须接受通用指针,然后在使用前(例如比较前)将其转换回它们本来的类型。
四、浮点数
1. Q: 如何检查浮点数在“足够接近”情况下的相等?
A:浮点数的定义决定它的绝对精度会随着其量级而变化,所以比较两个浮点数的最好方法是利用一个与浮点数的量级相关的精确阈值。
double a, b; ... if (a == b) // error if (fabs(a-b) < 0.0001) // not good if (fabs(a-b) <= epsilon * fabs(a)) // good
2. Q:怎样取整?
A:C语言的浮点数到整数的转换会去掉小数部分,因此取整之前加上0.5会使>=0.5的小数部分进位(对负数是减去0.5)。
// 浮点数四舍五入取整 (int) ((x < 0)? (x - 0.5) : (x + 0.5))
浙公网安备 33010602011771号