《The C Programming Language, 2nd Edition》K&R Chapter 7 - Input and Output 笔记

标准库

标准库将输入的回车符和换页符都转换为换行符,输出时反向转换。

<ctype.h>中的函数一般都是宏,减少了函数调用开销。

重定向

prog <infile表示从infile文件输入。<infile不会包含在main的argv参数中。
otherprog | prog将otherprog的标准输出重定向到prog的标准输入。
prog >输出文件名表示输出到指定文件中。

printf

printf格式化字符串中每个转换说明都由一个%开始一个转换字符结束,中间依次包含以下内容:

  1. 负号,表示左对齐输出。
  2. 数,最小字段宽度。空格填充。
  3. 小数点
  4. 数,精度。指字符串中最大字符数,浮点小数位数,整数最少输出的数字个数(0填充)等。
  5. 字母h或l,h表示整数作为short打印,l表示作为long。

printf的转换字母表:
d, i int 类型;十进制数
o int 类型;无符号八进制数(没有前导 0)
x, X int 类型;无符号十六进制数(没有前导 0x 或 0X),10~15 分别用 abcdef 或 ABCDEF 表示
u int 类型;无符号十进制数
c int 类型;单个字符
s char *类型;顺序打印字符串中的字符,直到遇到'\0'或已打印了由精度指定的字符数为止
f double 类型;十进制小数[-]m.dddddd,其中 d 的个数由精度指定(默认值为 6)
e, E double 类型;[-]m.dddddd e ±xx 或[-]m.dddddd E ±xx,其中 d 的个数由精度指定(默认值为 6)
g, G double 类型;如果指数小于-4 或大于等于精度,则用%e 或%E 格式输出,否则用%f 格式输出。尾部的 0 和小数点不打印
p void *类型;指针(取决于具体实现)
% 不转换参数;打印一个百分号%

转换说明中的宽度和精度可以用*表示,宽度和精度的值通过下一个实参(必须为int)来计算,比如printf("%.*s", max, s)表示打印字符串s中最多max个字段。

printf的实参不够时,打印错误的结果。

sprintf将printf的输出保存在字符串string中。前提是string足够大。

变长参数表

printf的正确声明为int printf(char *fmt, ...)

<stdarg.h>中定义了一组宏用来遍历参数表。具体实现取决于机器。

va_list声明一个变量,用于访问参数,如ap。
va_start初始化用有名参数,初始化ap,使它指向第一个无名参数。因此函数必须至少有一个有名参数。
每次调用va_arg返回一个参数,va_arg使用一个类型名决定返回对象的类型和指针移动的步长。
函数返回之前必须调用va_end完成清理工作。

scanf

scanf除格式字符串外的实参必须为指针。
scanf遇到与格式字符串不匹配的情况时终止,返回匹配的个数,并给对应的实参赋值。
scanf返回0表示下一个输入字符与格式字符串中第一个格式说明不匹配,下一次调用scanf从下一个字符开始继续。
scanf返回EOF表示遇到文件末尾。

scanf的格式字符串,可以包含以下:

  1. 空格或制表符,处理时被忽略。
  2. 普通字符,用于匹配下一个非空白字符。
  3. 转换说明,依次由一个%,一个可选的赋值禁止字符*,一个可选的数值(指定最大字段宽度),一个可选的h、l(指定目标对象的宽度)和一个转换字符组成。

scanf会跳过换行符,因为换行符是空白字符。
sscanf从字符串中读入。
scanf读取格式不固定的输入,最好每次读入一行,之后用sscanf分离合适的格式。

scanf可以与其他输入函数混合使用,输入函数从scanf没读取的第一个字符开始读取。

scanf转换字符表:
d 十进制整数;int *类型
i 整数;int *类型,可以是八进制(以 0 开头)或十六进制(以 0x 或 0X 开头)
o 八进制整数(可以以 0 开头,也可以不以 0 开头);int *类型
u 无符号十进制整数;unsigned int *类型
x 十六进制整数(可以 0x 或 0X 开头,也可以不以 0x 或 0X 开头);int *类型
c 字符;char *类型,将接下来的多个输入字符(默认为 1 个字符)存放到指定位置。该转换规范通常不跳过空白符。如果需要读入下一个非空白符,可以使用%1s
s 字符串(不加引号);char *类型,指向一个足以存放该字符串(还包括尾部的字符'\0')的字符数组。字符串的末尾将被添加一个结束符'\0'
e, f, g 浮点数,它可以包括正负号(可选)、小数点(可选)及指数部分(可选);float *类型
% 字符%;不进行任何赋值操作

转换说明d、i、o、u、x前辍h表示指向short的指针,l表示指向long的指针。
转换说明e、f、g前辍l表示指向double的指针。

文件操作

<stdio.h>中定义了结构FILE,FILE是类型名不是结构标记。

fopen的mode参数支持读("r")、写("w")、追加("a")。一些系统还区分文本文件和二进制文件,访问二进制文件需要添加"b"。

fopen打开一个不存在的文件用于写或追加,将创建这个文件(如果可能)。
fopen以写的方式打开一个已存在的文件,原来的内容会被覆盖。
fopen以追加的的方式打开一个已存在的文件,原来的内容保留。
fopen读一个不存在的文件将报错。
fopen读一个无权限的文件将报错。
fopen发生错误时fopen返回NULL。

getc从文件中读取一个字符,文件末尾或出错返回EOF。
putc向文件中写入一个字符,返回写入的字符,出错返回EOF。
getc和putc是宏不是函数。

启动C程序时,操作系统打开三个文件stdin、stdout、stderr,它们是常量,都在<stdio.h>中声明。
freopen可以重定向stdin和stdout。

fclose断开fopen的文件指针与外部名之间的连接,并释放指针。因为大多数操作系统都限制了一个程序可以同时打开的文件数。
fclose会将缓冲区中的输出,写入到文件。
当文件正常终止时,程序会自动为每个打开的文件调用fclose。

exit函数

程序使用了exit,那么任何调用这个程序的进程都可以获得exit的实参。
通常exit返回0表示正常,非0表示出错。
exit为每个打开的输出文件调用fclose,将缓冲区中的输出写到文件中。
main函数中return expr等价于exit(expr)

ferror和feof函数

当流出现错误时ferror返回非0。
当到达文件末尾时feof返回非0。

行输入/输出

fgets的声明:char *fgets(char *line, int maxline, FILE *fp)
fgets读取一行,最多读取maxline-1个字符,读取的行以'\0'结尾。
fgets正常返回line,如果到达文件末尾或发生错误返回NULL。

fputs写入一行(不需要包含换行符),返回非负值,发生错误返回EOF。

gets和puts与fgets和fputs类似,区别是它们操作stdin和stdout。

字符串操作

<stding.h>中定义了一系列字符串操作函数:
strcat(s, t) 将 t 指向的字符串连接到 s 指向的字符串的末尾
strncat(s, t, n) 将 t 指向的字符串中前 n 个字符连接到 s 指向的字符串的末尾
strcmp(s, t) 根据 s 指向的字符串小于(s<t)、等于(s==t)或大于(s>t)t
指向的字符串的不同情况,分别返回负整数、0 或正整数
strncmp(s, t, n) 同 strcmp 相同,但只在前 n 个字符中比较
strcpy(s, t) 将 t 指向的字符串复制到 s 指向的位置
strncpy(s, t, n) 将 t 指向的字符串中前 n 个字符复制到 s 指向的位置
strlen(s) 返回 s 指向的字符串的长度
strchr(s, c) 在 s 指向的字符串中查找 c,若找到,则返回指向它第一次出现的位
置的指针,否则返回 NULL
strrchr(s, c) 在 s 指向的字符串中查找 c,若找到,则返回指向它最后一次出现的
位置的指针,否则返回 NULL

字符操作

<ctype.h>中定义了一系列字符操作函数:
isalpha(c) 若 c 是字母,则返回一个非 0 值,否则返回 0
isupper(c) 若 c 是大写字母,则返回一个非 0 值,否则返回 0
islower(c) 若 c 是小写字母,则返回一个非 0 值,否则返回 0
isdigit(c) 若 c 是数字,则返回一个非 0 值,否则返回 0
isalnum(c) 若 isalpha(c)或 isdigit(c),则返回一个非 0 值,否则返回 0
isspace(c) 若 c 是空格、横向制表符、换行符、回车符,换页符或纵向制表符,
则返回一个非 0 值
toupper(c) 返回 c 的大写形式
tolower(c) 返回 c 的小写形式

ungetc函数

ungetc支持向每个文件写回一个字符,最多一个。写回成功返回写回的字符,失败返回EOF。

system函数

system函数的参数,是操作系统中执行的命令,返回值来自所执行的命令,取决于具体的操作系统。UNIX中,返回值是exit的返回值。

内存操作

malloc和calloc都返回的指针都满足对齐要求,发生错误返回NULL。区别是calloc初始化内存空间为0,malloc不初始化。

free释放malloc和calloc分配的内存。但如果释放一个不是malloc和calloc分配的内存是严重的错误。访问已释放的内存也是错误。

数学函数

<math.h>中声明了20多个数学函数:
sin(x) x 的正弦函数,其中 x 用弧度表示
cos(x) x 的余弦函数,其中 x 用弧度表示
atan2(y, x) y/x 的反正切函数,其中,x 和 y 用弧度表示
exp(x) 指数函数 exe^x
log(x) x 的自然对数(以 e 为底),其中,x>0
log10(x) x 的常用对数(以 10 为底),其中,x>0
pow(x, y) 计算 xyx^y 的值
sqrt(x) x 的平方根(x≥0)
fabs(x) x 的绝对值

随机数

rand()生成介于0与RAND_MAX之间的伪随机整数。RAND_MAX在<stdlib.h>中定义。

生成大于等于0小于1的随机浮点数的方法:#define frand() ((double) rand() / (RAND_MAX+1.0))

srand(unsigned)设置rand的种子。

例程

简化版printf实现(只支持%d、%f和%s)

#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 */
}

cat函数(连接多个文件)

/* filecopy: copy file ifp to file ofp */
void filecopy(FILE *ifp, FILE *ofp)
{
    int c;
    
    while ((c = getc(ifp)) != EOF)
        putc(c, ofp);
}
#include <stdio.h>

/* cat: concatenate files, version 2 */
main(int argc, char *argv[])
{
    FILE *fp;
    void filecopy(FILE *, FILE *);
    char *prog = argv[0]; /* program name for errors */

    if (argc == 1 ) /* no args; copy standard input */
        filecopy(stdin, stdout);
    else
        while (--argc > 0)
            if ((fp = fopen(*++argv, "r")) == NULL) {
                fprintf(stderr, "%s: can't open %s\n",
                    prog, *argv);
                exit(1);
            } else {
                filecopy(fp, stdout);
                fclose(fp);
            }
    if (ferror(stdout)) {
        fprintf(stderr, "%s: error writing stdout\n", prog);
        exit(2);
    }
    exit(0);
}

fgets和fputs函数

/* fgets: get at most n - 1 chars from iop */
char *fgets(char *s, int n, FILE *iop)
{
    register int c;
    register char *cs;

    cs = s;
    while (--n > 0 && (c = getc(iop)) != EOF)
        if ((*cs++ = c) == '\n')
            break;
    *cs = '\0';
    return (c == EOF && cs == s) ? NULL : s;
}

/* fputs: put string s on file iop */
int fputs(char *s, FILE *iop)
{
    int c;

    while (c = *s++)
        putc(c, iop);
    return ferror(iop) ? EOF : 0;
}

读入一行返回长度

/* getline: read a line, return length */
int getline(char *line, int max)
{
    if (fgets(line, max, stdin) == NULL)
        return 0;
    else
        return strlen(line);
}