-
xzsdn_08



- 注册时间 01-04-2009
- 江苏徐州
- 发帖总数 641
|
- 14.1 怎样把数字转为字符串 (与 atoi 相反)?有 itoa() 函数吗?
- 14.2 为什么 strncpy() 不能总在目标串放上终止符 '\0'?
- 14.3 为什么有些版本的 toupper() 对大写字符会有奇怪的反应?为什么有的代码在调用 toupper() 前先调用 tolower()?
- 14.4 怎样把字符串分隔成用空白作间隔符的段?怎样实现类似传递给 main() 的 argc 和 argv?
- 14.5 我需要一些处理正则表达式或通配符匹配的代码。
- 14.6 我想用 strcmp() 作为比较函数, 调用 qsort() 对一个字符串数组排序, 但是不行。
- 14.7 我想用 qsort() 对一个结构数组排序。我的比较函数接受结构指针, 但是编译器认为这个函数对于 qsort() 是错误类型。我要怎样转换这个函数指针才能避免这样的警告?
- 14.8 怎样对一个链表排序?
- 14.9 怎样对多于内存的数据排序?
- 14.10 怎样在 C 程序中取得当前日期或时间?
- 14.11 我知道库函数 localtime() 可以把 time_t 转换成结构 struct tm, 而 ctime() 可以把 time_t 转换成为可打印的字符串。怎样才能进行反向操作, 把 struct tm 或一个字符串转换成 time_t?
- 14.12 怎样在日期上加 N 天?怎样取得两个日期的时间间隔?
- 14.13 我需要一个随机数生成器。
- 14.14 怎样获得在一定范围内的随机数?
- 14.15 每次执行程序, rand() 都返回相同顺序的数字。
- 14.16 我需要随机的真/假值, 所以我用直接用 rand() % 2, 可是我得到交替的 0, 1, 0, 1, 0 ……
- 14.17 怎样产生标准分布或高斯分布的随机数?
- 14.18 我不断得到库函数未定义错误, 但是我已经 #inlude 了所有用到的头文件了。
- 14.19 虽然我在连接时明确地指定了正确的函数库, 我还是得到库函数未定义错误。
- 14.20 连接器说 _end 未定义代表什么意思?
- 14.21 我的编译器提示 printf 未定义!这怎么可能?
14.1 怎样把数字转为字符串 (与 atoi 相反)?有 itoa() 函数吗? 用 sprintf() 就可以了。不需担心用 sprintf() 会小题大作, 也不必担心会浪费运行时间或代码空间; 实践中它工作得挺好。参见问题 7.6 答案中的实例; 以及问题 8.4 和 12.19。
你也可以用 sprintf() 把长整形或浮点数转换成字符串 (使用 %ld 或 %f)。
参考资料: [K&R1, Sec. 3.6 p. 60]; [K&R2, Sec. 3.6 p. 64]。
14.2 为什么 strncpy() 不能总在目标串放上终止符 '\0'? strncpy() 最初被设计为用来处理一种现在已经废弃的数据结构---定长, 不必 '\0' 结束的 ``字符串"。strncpy 的另一个怪癖是它会用多个 '\0' 填充短串, 直到达到指定的长度。在其它环境中使用 strncpy() 有些麻烦, 因为你必须经常在目的串末尾手工加 '\0'。
你可以用 strncat 代替 strncpy 来绕开这个问题: 如果目的串开始时为空 (就是说, 如果你先用 *dest = '\0'), strncat() 就可以完成你希望 strncpy() 完成的事情。另外一个方法是用 sprintf(dest, "%.*s", n, source)。
如果需要复制任意字节 (而不是字符串), memcpy() 是个比 strncpy() 更好的选择。
14.3 为什么有些版本的 toupper() 对大写字符会有奇怪的反应?为什么有的代码在调用 toupper() 前先调用 tolower()? 老版的 toupper() 和 tolower() 不一定能够正常处理不需要转换的字符参数, 例如数字、标点或已经符合请求的字符。在 ANSI/ISO 标准 C 中, 这些函数保证对所有的字符参数正常处理。
参考资料: [ISO, Sec. 7.3.2]; [H&S, Sec. 12.9 pp. 320-1]; [PCS, p. 182]。
14.4 怎样把字符串分隔成用空白作间隔符的段?怎样实现类似传递给 main() 的 argc 和 argv? 标准中唯一用于这种分隔的函数是 strtok(), 虽然用起来需要些技巧, 而且不一定能做到你所要求的所有事。例如, 它不能处理引用。
参考资料: [K&R2, Sec. B3 p. 250]; [ISO, Sec. 7.11.5.8]; [H&S, Sec. 13.7 pp. 333-4]; [PCS, p. 178]。
14.5 我需要一些处理正则表达式或通配符匹配的代码。 确保你知道经典的正则表达式和文件名通配符的不同。前者的变体在 Unix 工具 ed 和 grep 等中使用, 后者的变体在多数操作系统中使用。
有许多匹配正则表达式的包可以利用。很多包都是用成对的函数, 一个 ``编译" 正则表达式, 另一个 ``执行" 它, 即用它比较字符串。查查头文件 <regex.h> 或 <regexp.h> 和函数 regcmp/regex, regcomp/regexec, 或 re_comp/re_exec。这些函数可能在一个单独的 regexp 库中。在 ftp://ftp.cs.toronto.edu/pub/regexp.shar.Z 或其它地方可以找到一个 Henry Spencer 开发的广受欢迎的 regexp 包, 这个包也可自由再发布。 GNU 工程有一个叫做 rx 的包。参见问题 18.18。
文件名通配符匹配 (有时称之为 ``globbing") 在不同的系统上有不同的实现。在 Unix 上, shell 会在进程调用之前自动扩展通配符, 因此, 程序几乎从不需要专门考虑它们。在 MS-DOS 下的编译器中, 通常都可以在建立 argv 的时候连接一个用来扩展通配符的特殊目标文件。有些系统 (包括 MS-DOS 和 VMS) 会提供通配符指定文件的列表和打开的系统服务。参见问题 19.25 和 20.2。
14.6 我想用 strcmp() 作为比较函数, 调用 qsort() 对一个字符串数组排序, 但是不行。 你说的 ``字符串数组" 实际上是 ``字符指针数组"。qsort 比较函数的参数是被排序对象的指针, 在这里, 也就是字符指针的指针。然而 strcmp() 只接受字符指针。因此, 不能直接使用 strcmp()。写一个下边这样的间接比较函数:
/* 通过指针比较字符串 */
int pstrcmp(const void *p1, const void *p2)
{
return strcmp(*(char * const *)p1, *(char * const *)p2);
}
比较函数的参数表示为 ``一般指针" const void *。然后, 它们被转换回本来表示的类型 (指向字符指针的指针), 再复引用, 生成可以传入 strcmp() 的 char*。
不要被 [K&R2] 5.11 节 119-20页的讨论所误导, 那里讨论的不是标准库中的 qsort。
参考资料: [ISO, Sec. 7.10.5.2]; [H&S, Sec. 20.5 p. 419]。
14.7 我想用 qsort() 对一个结构数组排序。我的比较函数接受结构指针, 但是编译器认为这个函数对于 qsort() 是错误类型。我要怎样转换这个函数指针才能避免这样的警告? 这个转换必须在比较函数中进行, 而函数必须定义为接受 ``一般指针" (const void*) 的类型, 就象上文问题 13.6 中所讨论的。比较函数可能像这样:
int mystructcmp(const void *p1, const void *p2)
{
const struct mystruct *sp1 = p1;
const struct mystruct *sp2 = p2;
/* 现在比较 sp1->whatever 和 sp2-> ... */
从一般指针到结构 mystruct 指针的转换过程发生在 sp1 = p1 和 sp2 = p2 的初始化中; 由于 p1 和 p2 都是 void 指针, 编译器隐式的进行了类型转换。
另一方面, 如果你对结构的指针进行排序, 则如问题 13.6 所示, 你需要间接使用: sp1 = *(struct mystruct * const *)p1。
一般而言, 为了让编译器 ``闭嘴" 而进行类型转换是一个坏主意。编译器的警告信息通常希望告诉你某些事情, 忽略或轻易去掉会让你陷入危险, 除非你明确知道你在做什么。参见问题 4.5。
参考资料: [ISO, Sec. 7.10.5.2]; [H&S, Sec. 20.5 p. 419]。
14.8 怎样对一个链表排序? 有时侯, 有时侯, 在建立链表时就一直保持链表的顺序要简单些 (或者用树代替)。插入排序和归并排序算法用在链表最合适了。
如果你希望用标准库函数, 你可以分配一个暂时的指针数组, 填入链表中所有节点的地址, 再调用 qsort(), 最后依据排序后的数组重新建立链表。
参考资料: [Knuth, Sec. 5.2.1 pp. 80-102, Sec. 5.2.4 pp. 159-168]; [Sedgewick, Sec. 8 pp. 98-100, Sec. 12 pp. 163-175]。
14.9 怎样对多于内存的数据排序? 你可以用 ``外部排序"法, [Knuth] 第三卷中有详情。基本的思想是对数据分段进行排序, 每次的大小尽可能多的填入内存中, 把排好序的数据段存入暂时文件中, 再归并它们。如果你的操作系统提供一个通用排序工具, 你可以从程序中调用: 参见问题 19.30 和 19.31。
参考资料: [Knuth, Sec. 5.4 pp. 247-378]; [Sedgewick, Sec. 13 pp. 177-187]。
14.10 怎样在 C 程序中取得当前日期或时间? 只要使用函数 time(), ctime(), localtime() 和/或 strftime() 就可以了。下面是个简单的例子:
#include <stdio.h>
#include <time.h>
int main()
{
time_t now;
time(&now);
printf("It's %s", ctime(&now));
return 0;
}
用函数 strftime() 可以控制输出的格式。如果你需要小于秒的解析度, 参见问题 19.36。
参考资料: [K&R2, Sec. B10 pp. 255-7]; [ISO, Sec. 7.12]; [H&S, Sec. 18]。
14.11 我知道库函数 localtime() 可以把 time_t 转换成结构 struct tm, 而 ctime() 可以把 time_t 转换成为可打印的字符串。怎样才能进行反向操作, 把 struct tm 或一个字符串转换成 time_t? ANSI C 提供了库函数 mktime(), 它把 struct tm 转换成 time_t。
把一个字符串转换成 time_t 比较难些, 这是由于可能遇到各种各样的日期和时间格式。某些系统提供函数 strptime(), 基本上是 strftime() 的反向函数。其它常用的函数有 partime() (与 RCS 包一起被广泛的发布) 和 getdate() (还有少数其它函数, 发布在 C 的新闻组)。参见问题 18.18。
参考资料: [K&R2, Sec. B10 p. 256]; [ISO, Sec. 7.12.2.3]; [H&S, Sec. 18.4 pp. 401-2]。
14.12 怎样在日期上加 N 天?怎样取得两个日期的时间间隔? ANSI/ISO 标准 C 函数 mktime() 和 difftime() 对这两个问题提供了一些有限的支持。 mktime() 接受没有规格化的日期, 所以可以用一个日期的 struct tm 结构, 直接在 tm_mday 域进行加或减, 然后调用 mktime() 对年、月、日域进行规格化, 同时也转换成了 time_t 值。可以用 mktime() 来计算两个日期的 time_t 值, 然后用 difftime() 计算两个 time_t 值的秒数差分。
但是, 这些方法只有日期在 time_t 表达范围内才保证工作正常。对于保守的 time_t, 通常范围是从 1970 年到大约 2037 年; 注意有些 time_t 的表达不是按照 Unix 和 Posix 标准的。tm_mday 域是个 int, 所以日偏移量超出 32,736 就会上溢。还要注意, 在夏令时转换的时候, 一天并不是 24 小时, 所以不要假设可以用 86400 整除。
另一个解决的方法是用 ``Julian 日期", 这可以支持更宽的时间范围。处理 Julian 日期的代码可以在以下地方找到: Snippets 收集 (参见问题 18.16); Simtel/Oakland 站点 (文件 JULCAL10.ZIP, 参见问题 18.18) 和 文献中提到的文章 ``Date conversionsciteburki。
参见问题 13.11, 20.27 和 20.28。
参考资料: [K&R2, Sec. B10 p. 256]; [ISO, Secs. 7.12.2.2,7.12.2.3]; [H&S, Secs. 18.4,18.5 pp. 401-2]; [Burki]。
14.13 我需要一个随机数生成器。 标准 C 库函数就有一个: rand()。你系统上的实现可能并不完美, 但写一个更好的并不是一件容易的事。
如果你需要实现自己的随机数生成器, 有许多这方面的文章可供参考; 象下面的文献或 sci.math.num-analysis 的 FAQ。网上也有许多这方面的包: 老的可靠的包有 r250, RANLIB 和 FSULTRA (参见问题 18.18), 还有由 Marsaglia, Matumoto 和 Nishimura 新近的成果 ``Mersenne Twister", 另外就是 Don Knuth 个人网页上收集的代码。
参考资料: [K&R2, Sec. 2.7 p. 46, Sec. 7.8.7 p. 168]; [ISO, Sec. 7.10.2.1]; [H&S, Sec. 17.7 p. 393]; [PCS, Sec. 11 p. 172]; [Knuth, Vol. 2 Chap. 3 pp. 1-177]; [P&M]。
14.14 怎样获得在一定范围内的随机数? 直接的方法是
rand() % N /* 不好 */
试图返回从 0 到 N - 1 的数字。但这个方法不好, 因为许多随机数发生器的低位比特并不随机, 参见问题 13.16。一个较好的方法是:
(int)((double)rand() / ((double)RAND_MAX + 1) * N)
如果你不希望使用浮点, 另一个方法是:
rand() / (RAND_MAX / N + 1)
两种方法都需要知道 RAND_MAX, 而且假设 N 要远远小于 RAND_MAX。 RAND_MAX 在 ANSI 里 #define 在 <stdlib.h>。
顺便提一下, RAND_MAX 是个常数, 它告诉你 C 库函数 rand() 的固定范围。你不可以设 RAND_MAX 为其它的值, 也没有办法要求 rand() 返回其它范围的值。
如果你用的随机数发生器返回的是 0 到 1 的浮点值, 要取得范围在 0 到 N - 1 内的整数, 只要将随机数乘以 N 就可以了。
参考资料: [K&R2, Sec. 7.8.7 p. 168]; [PCS, PCS Sec. 11 p. 172]。
14.15 每次执行程序, rand() 都返回相同顺序的数字。 你可以调用 srand() 来初始化模拟随机数发生器的种子, 用的值可以是真正随机数或至少是个变量, 例如当前时间。这儿有个例子:
#include <stdlib.h>
#include <time.h>
srand((unsigned int)time((time_t *)NULL));
不幸的是, 这个代码并不完美---其中, time() 返回的 time_t 可能是浮点值, 转换到无符号整数时有可能上溢, 这造成不可移植。参见问题: 19.36。
还要注意到, 在一个程序执行中多次调用 srand() 并不见得有帮助; 特别是不要为了试图取得 ``真随机数" 而在每次调用 rand() 前都调用 srand()。
参考资料: [K&R2, Sec. 7.8.7 p. 168]; [ISO, Sec. 7.10.2.2]; [H&S, Sec. 17.7 p. 393]。
14.16 我需要随机的真/假值, 所以我用直接用 rand() % 2, 可是我得到交替的 0, 1, 0, 1, 0 …… 这是个低劣的伪随机数生成器, 在低位比特中不随机。 很不幸, 某些系统就提供这样的伪随机数生成器。尝试用高位比特; 参见问题 13.14。
参考资料: [Knuth, Sec. 3.2.1.1 pp. 12-14]。
14.17 怎样产生标准分布或高斯分布的随机数? 这里有一个由 Marsaglia 首创 Knuth 推荐的方法:
#include <stdlib.h>
#include <math.h>
double gaussrand()
{
static double V1, V2, S;
static int phase = 0;
double X;
if(phase == 0) {
do {
double U1 = (double)rand() / RAND_MAX;
double U2 = (double)rand() / RAND_MAX;
V1 = 2 * U1 - 1;
V2 = 2 * U2 - 1;
S = V1 * V1 + V2 * V2;
} while(S >= 1 || S == 0);
X = V1 * sqrt(-2 * log(S) / S);
} else
X = V2 * sqrt(-2 * log(S) / S);
phase = 1 - phase;
return X;
}
其它的方法参见本文的扩展版本, 参见问题 20.36。
参考资料: [Knuth, Sec. 3.4.1 p. 117]; [Marsaglia&Bray]; [Press et al., Sec. 7.2 pp. 288-290]。
14.18 我不断得到库函数未定义错误, 但是我已经 #inlude 了所有用到的头文件了。 通常, 头文件只包含外部说明。某些情况下, 特别是如果是非标准函数, 当你连接程序时, 需要指定正确的函数库以得到函数的定义。#include 头文件并不能给出定义。参见问题 10.10, 11.29, 13.19, 14.3 和 19.39。
14.19 虽然我在连接时明确地指定了正确的函数库, 我还是得到库函数未定义错误。 许多连接器只对对象文件和函数库进行一次扫描, 同时从函数库中提取适合当前未定义函数的模块。所以函数库和对象文件 (以及对象文件之间) 的连接顺序很重要; 通常, 你希望最后搜索函数库。例如, 在 Unix 系统中, 把 -l 参数放在命令行的后部。参见问题 13.20。
14.20 连接器说 _end 未定义代表什么意思? 这是个老 Unix 系统中的连接器所用的俏皮话。当有其它符号未定义时, 你才会得到 _end 未定义的信息, 解决了其它的问题, 有关 _end 的错误信息就会消失。参见问题 13.18 和 13.19。
14.21 我的编译器提示 printf 未定义!这怎么可能? 据传闻, 某些用于微软视窗系统的 C 编译器不支持 printf()。你也许可以让这样的编译器认为你写的是 ``控制台程序", 这样编译器会打开 ``控制台窗口" 从而支持 printf()。
|
|