读《The C Programming Language》(9)

本文详细介绍了C语言中的输入输出库函数,包括标准输入输出、格式化输出输入、文件访问等内容,并深入探讨了printf与scanf的工作原理及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第七章主要介绍了有关输入输出的库函数。在提到库函数时作者说到,"Programs that confine their system interactions to facilities provided by the standard library can be moved from one system to another without change." 由此可见,程序中和操作系统打交道的地方应该尽可能地使用标准库函数,这样可以增加可移植性。

第一节 标准输入和输出

这节介绍了标准输入输出的模型,就是把输入输出的内容想象成字符流。这样的字符流由一行一行的字符组成,每一行以'/n'结束。另外,作者还介绍了重定向(redirection)和管道(pipe)机制,想必用过DOS和Unix/Linux的人都很熟悉。

最后,作者提到了很有意思的一件事,就是像getchar, putchar和tolower这样的库函数都是宏,这样能避免函数调用的开销。具体是怎么实现的,要等到第八章再介绍。

第二节 格式化输出-printf

本节详细介绍了printf和sprintf。如果以后写程序需要严格的格式化输出,可以参考本节。作者在最后给我们提了个醒:

printf(s); /* FAILS if s contains % */
printf("%s", s); /* SAFE */

大家一般输出一个字符串就用前者,但如果这个字符串包含%就会失败。后者才是安全的写法。

第三节 变长参数列表

本节作者介绍了像printf这种参数数量不定的函数是怎样实现的,重点讲了函数声明和怎样取到未命名的参数。首先来看这种函数是怎么声明的:

int printf(char *fmt, ...)

省略号...意味着参数的数量和类型可变,它只能用在参数列表的末尾。

要取到未命名的参数,首先要把头文件<stdarg.h>包含进来。这个头文件定义的va_list, va_start, va_arg和va_end配合使用就可以完成这件工作。具体怎么用可以参考作者举的例子:

#include <stdarg.h>
/* minprintf: minimal printf with variable argument list */
void minprintf(char *fmt, ...)
{
    va_list ap; /* points to each unnamed arg in turn */
    char *p, *sval;
    int ival;
    double dval;
    va_start(ap, fmt); /* make ap point to 1st unnamed arg */
    for (p = fmt; *p; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }
        switch (*++p) {
        case 'd':
            ival = va_arg(ap, int);
            printf("%d", ival);
            break;
        case 'f':
            dval = va_arg(ap, double);
            printf("%f", dval);
            break;
        case 's':
            for (sval = va_arg(ap, char *); *sval; sval++)
                putchar(*sval);
            break;
        default:
            putchar(*p);
            break;
        }
    }
    va_end(ap); /* clean up when done */
}

第四节 格式化输入-scanf

本节介绍了printf和sprintf的counterpart:scanf和sscanf。sscanf从一个指定的字符串中接受输入。这两对函数很多性质都很相似,只不过一个是输出函数,另一个是输入函数。另外,scanf和sscanf中接受输入的参数必须是指针。

通常我们用scanf只读入一个值,其实也可以读入多个值。比如想从输入流中读到25 Dec 1988这样的信息,就可以写这样的语句:

int day, year;
char monthname[20];
scanf("%d %s %d", &day, monthname, &year);

一次读入年月日的信息。另外,如果读取的格式不固定,可以一次读入一行,然后用sscanf从这行信息中匹配想要的格式。

第五节 文件访问

本节作者介绍了用于文件访问的库函数和相关用法,包括fopen, fclose, getc和putc。其中getc和putc与getchar和putchar相似,只不过前者用于文件读写,后者用于标准输入输出。它们的关系可从以下两式看出:

#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)

这正是getchar和putchar的实现。其中stdin和stdout是系统维护的两个文件指针,另一个类似的文件指针是stderr。通常情况下stdin和键盘相连,而stdout和stderr和显示器相连。

如果想进行格式化输入输出,作者还介绍了两个与scanf和printf类似的函数:

int fscanf(FILE *fp, char *format, ...)
int fprintf(FILE *fp, char *format, ...)

不同处只是它们对文件输入输出,第一个参数指定了文件指针。

第六节 错误处理-stderr和exit

这一节的内容很有价值,特别是当你做一个严肃的项目时。大学时代学C语言时,这方面的内容根本不考,所以一直是我的薄弱环节。这次看书搞清了之前很多糊涂的东西。

之前我知道stdout和stderr都是向显示器输出的,但不明白它们除了在概念上,还有哪些实际的区别。作者在这节就告诉我们了,区别在这里:写到stdout的输出有可能被重定向(另一个文件或者管道),即使处理过程中出现了错误,错误信息也会被重定向;而写到stderr的输出即使标准输出被重定向了,也会显示在屏幕上。

关于exit,我也不知道它和return的区别。看了作者的讲解后明白了,在程序中只要一调用exit,程序立马停止执行,而return只是函数返回。所以在main函数中exit和return是等价的,但在别的函数中完全不同。exit的优势就在于不管是谁调用,只要调用就退出程序执行。

第七节 行输入输出

本节作者介绍了两个针对文件的行输入输出库函数:fgets和fputs。它们与库函数gets和puts类似,只不过后者在stdin和stdout上读写。这一节没有太多值得一书的地方,但通过这么多标准输入输出函数都有相应的文件操纵函数可以看出,其实标准输入输出函数只是文件输入输出函数的特殊情况——它们在两个特殊的文件指针stdin和stdout上操作。

第八节 各种其它函数

这一节罗列了很多有用的库函数,可以作为很好的参考资料。下面列举几个我觉得重要的点:

  • int ungetc(int c, FILE *fp):当你不小心从文件中多读了一个字符时,没关系,用这个函数给退回去。其实大部分情况下多读都是故意的,程序的需要,不过要记住一个文件只能退回一个字符。
  • system(char *s):调用这个函数会让系统执行字符串中的命令。至于命令可以是什么,和具体的操作系统密切相关。
  • malloc和calloc:前些天还在论坛上看到有人问这两个函数的区别,我觉得唯一明显的区别在于calloc有一个把申请到的内存初始化成0的过程。至于用法就是仁者见仁了,我感觉calloc从它接受的参数上讲更适合给数组分配空间,而malloc则是通吃,适应面更广一点。
  • rand()生成一个0到RAND_MAX之间的随机整数,而srand(unsigned)给它设置种子(seed)。如果想生成一个0到1之间的随机浮点数,可以这样定义:#define frand() ((double) rand() / (RAND_MAX+1.0))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值