391 lines
11 KiB
Markdown
391 lines
11 KiB
Markdown
|
# stdio.h
|
|||
|
|
|||
|
`stdio.h`是 C 语言的标准 I/O 库,用于读取和写入文件,也用于控制台的输入和输出。
|
|||
|
|
|||
|
## 标准 I/O 函数
|
|||
|
|
|||
|
以下函数用于控制台的输入和输出。
|
|||
|
|
|||
|
- printf():输出到控制台,详见《基本语法》一章。
|
|||
|
- scanf():从控制台读取输入,详见《I/O 函数》一章。
|
|||
|
- getchar():从控制台读取一个字符,详见《I/O 函数》一章。
|
|||
|
- putchar():向控制台写入一个字符,详见《I/O 函数》一章。
|
|||
|
- gets():从控制台读取整行输入(已废除),详见《I/O 函数》一章。
|
|||
|
- puts():向控制台写入一个字符串,详见《I/O 函数》一章。
|
|||
|
|
|||
|
## 文件操作函数
|
|||
|
|
|||
|
以下函数用于文件操作,详见《文件操作》一章。
|
|||
|
|
|||
|
- fopen():打开文件。
|
|||
|
- fclose():关闭文件。
|
|||
|
- freopen():打开一个新文件,关联一个已经打开的文件指针。
|
|||
|
- fprintf():输出到文件。
|
|||
|
- fscanf():从文件读取数据。
|
|||
|
- getc():从文件读取一个字符。
|
|||
|
- fgetc():从文件读取一个字符。
|
|||
|
- putc():向文件写入一个字符。
|
|||
|
- fputc():向文件写入一个字符。
|
|||
|
- fgets():从文件读取整行。
|
|||
|
- fputs():向文件写入字符串。
|
|||
|
- fread():从文件读取二进制数据。
|
|||
|
- fwrite():向文件写入二进制数据。
|
|||
|
- fseek():将文件内部指针移到指定位置。
|
|||
|
- ftell():获取文件内部指针的当前位置。
|
|||
|
- rewind():将文件内部指针重置到文件开始处。
|
|||
|
- fgetpos():获取文件内部指针的当前位置。
|
|||
|
- fsetpos():设置文件内部指针的当前位置。
|
|||
|
- feof():判断文件内部指针是否指向文件结尾。
|
|||
|
- ferror():返回文件错误指示器的状态。
|
|||
|
- clearerr():重置文件错误指示器。
|
|||
|
- remove():删除文件。
|
|||
|
- rename():文件改名,以及移动文件。
|
|||
|
|
|||
|
## 字符串操作函数
|
|||
|
|
|||
|
以下函数用于操作字符串,详见《字符串操作》一章。
|
|||
|
|
|||
|
- sscanf():从字符串读取数据,详见《I/O 函数》一章。
|
|||
|
- sprintf():输出到字符串。
|
|||
|
- snprintf():输出到字符串的更安全版本,指定了输出字符串的数量。
|
|||
|
|
|||
|
## tmpfile()
|
|||
|
|
|||
|
`tmpfile()`函数创建一个临时文件,该文件只在程序运行期间存在,除非手动关闭它。它的原型如下。
|
|||
|
|
|||
|
```c
|
|||
|
FILE* tmpfile(void);
|
|||
|
```
|
|||
|
|
|||
|
`tmpfile()`返回一个文件指针,可以用于访问该函数创建的临时文件。如果创建失败,返回一个空指针 NULL。
|
|||
|
|
|||
|
```c
|
|||
|
FILE* tempptr;
|
|||
|
tempptr = tmpfile();
|
|||
|
```
|
|||
|
|
|||
|
调用`close()`方法关闭临时文件后,该文件将被自动删除。
|
|||
|
|
|||
|
`tmpfile()`有两个缺点。一是无法知道临时文件的文件名,二是无法让该文件成为永久文件。
|
|||
|
|
|||
|
## tmpnam()
|
|||
|
|
|||
|
`tmpname()`函数为临时文件生成一个名字,确保不会与其他文件重名。它的原型如下。
|
|||
|
|
|||
|
```c
|
|||
|
char* tmpname(char* s);
|
|||
|
```
|
|||
|
|
|||
|
它的参数是一个字符串变量,`tmpnam()`会把临时文件的文件名复制到这个变量里面,并返回指向该字符串变量的指针。如果生成文件名失败,`tmpnam()`返回空指针 NULL。
|
|||
|
|
|||
|
```c
|
|||
|
char filename[L_tmpname];
|
|||
|
|
|||
|
if (tmpnam(filename) != NULL)
|
|||
|
// 输出诸如 /tmp/filew9PMuZ 的文件名
|
|||
|
printf("%s\n", filename);
|
|||
|
else
|
|||
|
printf("Something wrong!\n");
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`L_tmpname`是`stdio.h`定义的一个宏,指定了临时文件的文件名长度。
|
|||
|
|
|||
|
`tmpname()`的参数也可以是一个空指针 NULL,同样返回指向文件名字符串的指针。
|
|||
|
|
|||
|
```c
|
|||
|
char* filename;
|
|||
|
filename = tmpnam(NULL);
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,变量`filename`就是`tmpnam()`生成的文件名。
|
|||
|
|
|||
|
该函数只是生成一个文件名,稍后可以使用`fopen()`打开该文件并使用它。
|
|||
|
|
|||
|
## fflush()
|
|||
|
|
|||
|
`fflush()`用于清空缓存区。它接受一个文件指针作为参数,将缓存区内容写入该文件。
|
|||
|
|
|||
|
```c
|
|||
|
fflush(fp);
|
|||
|
```
|
|||
|
|
|||
|
如果不需要保存缓存区内容,则可以传入空指针 NULL。
|
|||
|
|
|||
|
```c
|
|||
|
fflush(NULL);
|
|||
|
```
|
|||
|
|
|||
|
如果清空成功,`fflush()`返回0,否则返回 EOF。
|
|||
|
|
|||
|
注意,`fflush()`一般只用来清空输出缓存区(比如写文件)。如果使用它来清空输入缓存区(比如读文件),属于未定义行为。
|
|||
|
|
|||
|
`fflush()`的一个用途是不等回车键,就强迫输出缓存区。大多数系统都是行缓存,这意味着只有遇到回车键(或者缓存区满了,或者文件读到结尾),缓存区的内容才会输出,`fflush()`可以不等回车键,立即输出。
|
|||
|
|
|||
|
```c
|
|||
|
for (int i = 9; i >= 0; i--) {
|
|||
|
printf("\r%d", i);
|
|||
|
fflush(stdout);
|
|||
|
sleep(1);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
上面示例是一个倒计时效果,`\r`是回车键,表示每轮循环都会回到当前行的行首,等于删除上一轮循环的输出。`fflush(stdout)`表示立即将缓存输出到显示器,这一行是必需的,否则由于上一行的输出没有回车键,不会触发缓存输出,屏幕上不会显示任何内容,只会等到程序运行结束再一次性输出。
|
|||
|
|
|||
|
## setvbuf()
|
|||
|
|
|||
|
`setvbuf()`函数用于定义某个字节流应该如何缓存。它可以接受四个参数。
|
|||
|
|
|||
|
```c
|
|||
|
int setvbuf(FILE* stream, char* buffer, int mode, size_t size)
|
|||
|
```
|
|||
|
|
|||
|
第一个参数`stream`是文件流。
|
|||
|
|
|||
|
第二个参数`buffer`是缓存区的地址。
|
|||
|
|
|||
|
第三个参数`mode`指定缓存的行为模式,它是下面三个宏之一,这些宏都定义在`stdio.h`。
|
|||
|
|
|||
|
- `_IOFBF`:满缓存。当缓存为空时,才从流读入数据;当缓存满了,才向流写入数据。一般情况下,这是默认设置。
|
|||
|
- `_IOLBF`:行缓存。每次从流读入一行数据,或向流写入一行数据,即以行为单位读写缓存。
|
|||
|
- `_IONBF`:无缓存。不使用缓存区,直接读写设备。
|
|||
|
|
|||
|
第四个参数`size`指定缓存区的大小。较大的缓存区提供更好的性能,而较小的缓存区可以节省空间。`stdio.h`提供了一个宏`BUFSIZ`,表示系统默认的缓存区大小。
|
|||
|
|
|||
|
它的意义在于,使得用户可以在打开一个文件之前,定义自己的文件缓冲区,而不必使用`fopen()`函数打开文件时设定的默认缓冲区。
|
|||
|
|
|||
|
```c
|
|||
|
char buffer[N];
|
|||
|
|
|||
|
setvbuf(stream, buffer, _IOFBF, N);
|
|||
|
```
|
|||
|
|
|||
|
上面示例设置文件流`stream`的缓存区从地址`buffer`开始,大小为`N`,模式为`_IOFBF`。
|
|||
|
|
|||
|
`setvbuf()`的第二个参数可以为空指针 NULL。这样的话,`setvbuf()`会自己创建一个缓存区。
|
|||
|
|
|||
|
注意,`setvbuf()`的调用必须在对文件流执行任何操作之前。
|
|||
|
|
|||
|
如果调用成功,`setvbuf()`的返回值为`0`,否则返回非零值。
|
|||
|
|
|||
|
下面的例子是将缓存区调整为行缓存。
|
|||
|
|
|||
|
```c
|
|||
|
FILE *fp;
|
|||
|
char lineBuf[1024];
|
|||
|
|
|||
|
fp = fopen("somefile.txt", "r");
|
|||
|
setvbuf(fp, lineBuf, _IOLBF, 1024);
|
|||
|
```
|
|||
|
|
|||
|
## setbuf()
|
|||
|
|
|||
|
`setbuf()`是`setvbuf()`的早期版本,可以视为后者的简化版本,也用来定义某个字节流的缓存区。
|
|||
|
|
|||
|
```c
|
|||
|
void setbuf(FILE* stream, char* buffer);
|
|||
|
```
|
|||
|
|
|||
|
它的第一个参数`stream`是文件流,第二个参数`buffer`是缓存区的地址。
|
|||
|
|
|||
|
它总是可以改写成`setvbuf()`。
|
|||
|
|
|||
|
```c
|
|||
|
char buffer[BUFSIZ];
|
|||
|
|
|||
|
setbuf(stream, buffer);
|
|||
|
|
|||
|
// 等同于
|
|||
|
setvbuf(stream, buffer, _IOFBF, BUFSIZ);
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`BUFSIZ`是`stdio.h`定义的宏,表示系统默认的缓存区大小。
|
|||
|
|
|||
|
`setbuf()`函数没有返回值。
|
|||
|
|
|||
|
`setbuf()`的第二个参数如果设置为 NULL,表示不进行缓存。
|
|||
|
|
|||
|
```c
|
|||
|
setbuf(stdout, NULL);
|
|||
|
|
|||
|
// 等同于
|
|||
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|||
|
```
|
|||
|
|
|||
|
## ungetc()
|
|||
|
|
|||
|
`ungetc()`将从缓存里面读取的上一个字符,重新放回缓存,下一个读取缓存的操作会从这个字符开始。有些操作需要了解下一个字符是什么,再决定应该怎么处理,这时这个函数就很有用。
|
|||
|
|
|||
|
它的原型如下。
|
|||
|
|
|||
|
```c
|
|||
|
int ungetc(int c, FILE *stream);
|
|||
|
```
|
|||
|
|
|||
|
它的第一个参数是一个字符变量,第二个参数是一个打开的文件流。它的返回值是放回缓存的那个字符,操作失败时,返回 EOF。
|
|||
|
|
|||
|
```c
|
|||
|
int ch = fgetc(fp);
|
|||
|
|
|||
|
if (isdigit(ch)) {
|
|||
|
ch = fgetc(fp);
|
|||
|
}
|
|||
|
|
|||
|
ungetc(ch, fp);
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,如果读取的字符不是数字,就将其放回缓存。
|
|||
|
|
|||
|
## perror()
|
|||
|
|
|||
|
`perror()`用于在 stderr 的错误信息之前,添加一个自定义字符串。
|
|||
|
|
|||
|
```c
|
|||
|
void perror(const char *s);
|
|||
|
```
|
|||
|
|
|||
|
该函数的参数就是在报错信息前添加的字符串。它没有返回值。
|
|||
|
|
|||
|
```c
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <math.h>
|
|||
|
#include <errno.h>
|
|||
|
|
|||
|
int main(void) {
|
|||
|
int x = -1;
|
|||
|
|
|||
|
errno = 0;
|
|||
|
float y = sqrt(x);
|
|||
|
if (errno != 0) {
|
|||
|
perror("sqrt error");
|
|||
|
exit(EXIT_FAILURE);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,求`-1`的平方根,导致报错。头文件`errno.h`提供宏`errno`,只要上一步操作出错,这个宏就会设置成非零值。`perror()`用来在报错信息前,加上`sqrt error`的自定义字符串。
|
|||
|
|
|||
|
执行上面的程序,就会得到下面的报错信息。
|
|||
|
|
|||
|
```bash
|
|||
|
$ gcc test.c -lm
|
|||
|
$ ./a.out
|
|||
|
sqrt error: Numerical argument out of domain
|
|||
|
```
|
|||
|
|
|||
|
## 可变参数操作函数
|
|||
|
|
|||
|
(1)输出函数
|
|||
|
|
|||
|
下面是`printf()`的变体函数,用于按照给定格式,输出函数的可变参数列表(va_list)。
|
|||
|
|
|||
|
- vprintf():按照给定格式,输出到控制台,默认是显示器。
|
|||
|
- vfprintf():按照给定格式,输出到文件。
|
|||
|
- vsprintf():按照给定格式,输出到字符串。
|
|||
|
- vsnprintf():按照给定格式,输出到字符串的安全版本。
|
|||
|
|
|||
|
它们的原型如下,基本与对应的`printf()`系列函数一致,除了最后一个参数是可变参数对象。
|
|||
|
|
|||
|
```c
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdarg.h>
|
|||
|
|
|||
|
int vprintf(
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
|
|||
|
int vfprintf(
|
|||
|
FILE * restrict stream,
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
|
|||
|
int vsprintf(
|
|||
|
char * restrict s,
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
|
|||
|
int vsnprintf(
|
|||
|
char * restrict s,
|
|||
|
size_t n,
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
```
|
|||
|
|
|||
|
它们的返回值都为输出的字符数,如果出错,返回负值。
|
|||
|
|
|||
|
`vsprintf()`和`vsnprintf()`的第一个参数可以为 NULL,用来查看多少个字符会被写入。
|
|||
|
|
|||
|
下面是一个例子。
|
|||
|
|
|||
|
```c
|
|||
|
int logger(char *format, ...) {
|
|||
|
va_list va;
|
|||
|
va_start(va, format);
|
|||
|
int result = vprintf(format, va);
|
|||
|
va_end(va);
|
|||
|
|
|||
|
printf("\n");
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
// 输出 x = 12 and y = 3.20
|
|||
|
logger("x = %d and y = %.2f", x, y);
|
|||
|
```
|
|||
|
|
|||
|
(2)输入函数
|
|||
|
|
|||
|
下面是`scanf()`的变体函数,用于按照给定格式,输入可变参数列表 (va_list)。
|
|||
|
|
|||
|
- vscanf():按照给定格式,从控制台读取(默认为键盘)。
|
|||
|
- vfscanf():按照给定格式,从文件读取。
|
|||
|
- vsscanf():按照给定格式,从字符串读取。
|
|||
|
|
|||
|
它们的原型如下,跟对应的`scanf()`函数基本一致,除了最后一个参数是可变参数对象。
|
|||
|
|
|||
|
```c
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdarg.h>
|
|||
|
|
|||
|
int vscanf(
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
|
|||
|
int vfscanf(
|
|||
|
FILE * restrict stream,
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
|
|||
|
int vsscanf(
|
|||
|
const char * restrict s,
|
|||
|
const char * restrict format,
|
|||
|
va_list arg
|
|||
|
);
|
|||
|
```
|
|||
|
|
|||
|
它们返回成功读取的项数,遇到文件结尾或错误,则返回 EOF。
|
|||
|
|
|||
|
下面是一个例子。
|
|||
|
|
|||
|
```c
|
|||
|
int error_check_scanf(int expected_count, char *format, ...) {
|
|||
|
va_list va;
|
|||
|
|
|||
|
va_start(va, format);
|
|||
|
int count = vscanf(format, va);
|
|||
|
va_end(va);
|
|||
|
|
|||
|
assert(count == expected_count);
|
|||
|
|
|||
|
return count;
|
|||
|
}
|
|||
|
|
|||
|
error_check_scanf(3, "%d, %d/%f", &a, &b, &c);
|
|||
|
```
|