标准IO
1. 基本概念
Linux 标准 IO 是指在 Linux 操作系统中用于处理输入和输出的三个流:标准输入流(stdin)、标准输出流(stdout)和标准错误流(stderr)。
-
标准输入流(stdin)通常从键盘读取输入,并将其发送到程序。
-
标准输出流(stdout)通常用于将程序的输出发送到屏幕。
-
标准错误流(stderr)通常用于将程序的错误信息发送到屏幕。
设备(流) | 文件描述符(int) | 文件指针(FILE *) |
---|---|---|
标准输入设备(键盘) | STDIN_FILENO:0 | stdin |
标准输出设备(屏幕) | STDOUT_FILEMO:1 | stdout |
标准出错设备(屏幕) | STDERR_FILEMO:2 | stderr |
这三个流的处理方式可以通过重定向来改变,例如,可以将标准输出重定向到一个文件中,或者将标准输入重定向到一个文件中读取数据。
总之,Linux 标准 IO 是 Linux 操作系统中用于处理输入和输出的一组标准流,它们可以通过重定向来改变处理方式。
2. 打开文件
fopen()
函数用于在 Linux 系统中打开文件。函数原型为:
FILE *fopen(const char *path, const char *mode);
该函数接受两个参数:
path
:指定要打开的文件的路径。mode
:指定文件打开模式,比如:读、写。如果打开文件成功,则该函数返回文件指针。否则,返回 NULL。
注意:在使用完文件后,需要使用 fclose()
函数关闭文件。重复打开一个已经打开的文件或者打开不存在的文件可能导致错误。
mode 模式可选的参数有:
mode | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
3. 关闭文件
fclose()
函数用于在 Linux 系统中关闭文件。函数原型为:
FILE *fclose(FILE *stream);
该函数接受一个参数:
stream
:指定要关闭的文件的文件指针。如果关闭文件成功,则该函数返回 0。否则,返回 EOF。
注意:重复关闭一个已经关闭了的文件或者尚未打开的文件是安全的。但是,在使用完文件后,应该尽量及时使用 fclose()
函数关闭文件,以释放系统资源。
4. 判断文件情况
feof()
函数用于判断文件是否已经到达末尾。函数原型为:
int feof(FILE *stream);
该函数接受一个参数:
stream
:指定要判断的文件的文件指针。该函数返回一个非零值(true),表示文件已经到达末尾;如果文件还未到达末尾,则返回零(false)。
ferror()
函数用于判断文件是否出现错误。函数原型为:
int ferror(FILE *stream);
该函数接受一个参数:
stream
:指定要判断的文件的文件指针。该函数返回一个非零值(true),表示文件出现错误;如果文件没有出现错误,则返回零(false)。
5. 获取或设置文件偏移量
5.1 设置文件偏移量
fseek()
函数用于将文件指针移动到指定的位置。函数原型为:
int fseek(FILE *stream, long offset, int whence);
该函数接受三个参数:
stream
:指定要移动指针的文件的文件指针。offset
:指定要移动的字节数。如果为正数,则文件指针向后移动;如果为负数,则文件指针向前移动。whence
:指定文件指针移动的起始位置,可以取以下值:
SEEK_SET
:文件开头。SEEK_CUR
:当前位置。SEEK_END
:文件末尾。
fseek()
函数返回 0 表示成功,返回非零值表示失败。
rewind()
函数用于将文件指针移动到文件开头。函数原型为:
void rewind(FILE *stream);
该函数接受一个参数:
stream
:指定要移动指针的文件的文件指针。该函数无返回值。
注意:rewind(fp); 相当于 fseek(fp, 0L, SEEK_SE);
5.2 获取文件偏移量
ftell()
函数用于获取文件指针当前位置相对于文件开头的偏移量。函数原型为:
long ftell(FILE *stream);
该函数接受一个参数:
stream
:指定要获取指针位置的文件的文件指针。该函数返回文件指针当前位置相对于文件开头的偏移量,如果出现错误,则返回 -1。
下面是一个使用 ftell()
函数获取文件指针当前位置的示例代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// 定义文件指针
FILE *fp;
// 使用 fopen() 函数打开文件,并获取文件指针
fp = fopen("test.txt", "w+");
if (fp == NULL) {
// 打开文件失败,输出错误信息
perror("open() file");
exit(EXIT_FAILURE);
}
// 向文件中写入字符
if (fputs("AAAA", fp) == EOF) {
// 写入失败,输出错误信息
perror("fputc() error");
}
// 获取文件指针当前位置
long pos = ftell(fp);
if (pos == -1) {
// 获取失败,输出错误信息
perror("ftell() error");
} else {
// 输出文件指针当前位置
printf("current file position: %ld\n", pos);
}
// 使用 fclose() 函数关闭文件
fclose(fp);
return 0;
}
6. 读写文件(一个字符)
标准IO的读写接口是非常丰富的。
6.1 一个字符的读取
fgetc()\getc()\getchar()
函数用于从文件中读取一个字符。函数原型为:
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
该函数接受一个参数:
stream
:指定要读取的文件的文件指针。void
:无。该函数返回读取到的字符,如果读取失败或者到达文件末尾,则返回 EOF。
6.2 一个字符的写入
fputc()\putc()\putchar()
函数用于向文件中写入一个字符。函数原型为:
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(void);
该函数接受两个参数:
c
:指定要写入文件中的字符。stream
:指定要写入的文件的文件指针。void
:无。该函数返回写入的字符,如果写入失败,则返回 EOF。
6.3 注意点
-
fgec()\getc()\getchar()
返回值是 int,而不是 char,原因是因为他们在出错或者读到文件末尾的时候需要返回一 个值为-1的EOF标记,而char型数据有可能因为系统的差异而无法表示负整数。 -
当
fgec()\getc()\getchar()
返回EOF时,有可能是发生了错误,也有可能是读到了文件末尾,可以用feof()\ferror()
函数接口来进一步加以判断。 -
getchar()
缺省从标准输入设备读取一个字符,putchar()
缺省从标准输出设备输出一个字符。 -
fgetc()\fputc()
是函数,getc( )\putc()
是宏定义。 -
两组输入输出函数一般成对地使用,
fgetc()
和fputc()
,getc()
和putc( )
,getchar()
和putchar()
。
6.4 示例代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// 定义文件指针
FILE *fp;
// 使用 fopen() 函数打开文件,并获取文件指针
fp = fopen("test.txt", "w+");
if (fp == NULL) {
// 打开文件失败,输出错误信息
perror("open() file");
exit(EXIT_FAILURE);
}
// 向文件中写入字符
if (fputc('A', fp) == EOF) {
// 写入失败,输出错误信息
perror("fputc() error");
exit(EXIT_FAILURE);
}
// 将文件指针移动到文件开头
if (fseek(fp, 0, SEEK_SET) != 0) {
// 移动文件指针失败,输出错误信息
perror("fseek() error");
exit(EXIT_FAILURE);
}
// 循环读取文件中的每一个字符
int c;
while ((!feof(fp)) && ((c = fgetc(fp)) != EOF)) {
// 判断文件是否出现错误
if (ferror(fp)) {
// 出现错误,输出错误信息
perror("fgetc() error");
break;
}
printf("%c", c);
}
// 判断是否到达文件末尾
if (feof(fp)) {
printf("\nEOF reached.\n");
}
// 使用 fclose() 函数关闭文件
fclose(fp);
return 0;
}
7. 读写文件(一行数据)
标准IO的读写接口是非常丰富的。
7.1 一行数据的读取
fgets()\gets()
函数用于从文件中读取一行字符串。函数原型为:
char *fgets(char *str, int n, FILE *stream);
char *gets(char *str);
该函数接受三个参数:
str
:指向要存储读取到的字符串的缓冲区的指针。n
:指定缓冲区的大小。stream
:指定要读 取的文件的文件指针。该函数返回读取到的字符串,如果读取失败或者到达文件末尾,则返回
NULL
。
7.2 一行数据的写入
fputs()
函数用于将一个字符串写入文件。函数原型为:
int fputs(const char *str, FILE *stream);
int puts(const char *str);
该函数接受两个参数:
str
:指定要写入文件的字符串。stream
:指定要写入的文件的文件指针。该函数返回写入的字符数,如果写入失败,则返回
EOF
。
7.3 注意点
fgets()
跟fgetc()
一样,当其返回NULL时并不能确定究竟是达到文件末尾还是碰到错误,需要用feof( )/ferror()来进一步判断。fgets()
每次读取至多不超过size个字节的一行,所谓“一行”即数据至多包含一个换行符'\n'。gets()
是一个已经过时的接口,因为他没有指定自定 义缓冲区s的大小,这样很容易造成缓冲区溢出,导致程序段访问错误。fgets()
和fputs()
,gets()
和puts()
一般成对使用,鉴于gets()
的不安全性,一般建议使用前者。
7.4 示例代码
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 256
int main(int argc, char *argv[])
{
// 打开文件
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
// 文件打开失败,退出程序
perror("fopen() error");
exit(EXIT_FAILURE);
}
// 跳过文件的前 4 个字符
if (fseek(fp, 4, SEEK_SET) != 0) {
perror("fseek() error");
exit(EXIT_FAILURE);
}
// 循环读取文件的每一行
char buffer[BUFFER_SIZE];
while (!feof(fp) && !ferror(fp)) {
// 使用 fgets() 读取文件的一行
if (fgets(buffer, BUFFER_SIZE, fp) != NULL){
// 使用 fputs() 打印读取到的一行
fputs(buffer, stdout);
}
}
// 关闭文件
fclose(fp);
return 0;
}
该程序打开了一个名为 "test.txt" 的文件,然后跳过了文件的前 4 个字符。然后,它使用 fgets()
读取文件的每一行,并使用 fputs()
函数将读取到的内容输出到标准输出。最后,它关闭了打开的文件。
8. 读写文件(若干数据)
8.1 读取若干数据
fread()
函数用于从文件中读取数据。函数原型为:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
该函数接受四个参数:
ptr
:指向要存储读取到的数据的缓冲区的指针。size
:指定每个数据项的大小,以字节为单位。nmemb
:指定要读取的数据项的个数。stream
:指定要读取的文件的文件指针。该函数返回读取的数据项的数量。如果发生错误,则返回一个小于
nmemb
的值。
8.2 写入若干数据
fwrite()
函数用于将数据写入文件。函数原型为:
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
该函数接受四个参数:
ptr
:指向要写入文件的数据的指针 。size
:每个数据项的大小(以字节为单位)。nmemb
:要写入的数据项的数量。stream
:指定要写入的文件的文件指针。该函数返回写入的数据项的数量,如果写入失败,则返回
0
。
8.3 注意点
- 如果fread()返回值小于nmemb时,则可能已达末尾,或者遇到错误,需要借助于feof( )/ferror()来加以进一步判断。
- 当发生上述第1种情况时,其返回值并不能真正反映其读取或者写入的数据块数,而只是一个所谓的“截短值”,比如正常读取5个数据块,每个数据块100个字节,在执行成功的情况下返回值是5,表示读到5个数据块总共500个字节,但是如果只读到499个数据块,那么返回值就变成4,而如果读到99个字节,那么fread()会返回0。因此当发生返回值小于nmemb 时,需要仔细确定究竟读取了几个字节,而不能直接从返回值确定。
8.4 示例程序
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 1024
int main(int argc, char *argv[]) {
// 确保程序接收到两个文件名参数
if (argc < 3) {
printf("Usage: %s <src_file> <dest_file>\n", argv[0]);
exit(EXIT_FAILURE);
}
// 以只读模式打开源文件
FILE *src_file = fopen(argv[1], "r");
if (src_file == NULL) {
printf("Failed to open file '%s'\n", argv[1]);
exit(EXIT_FAILURE);
}
// 以写入模式打开目标文件
FILE *dest_file = fopen(argv[2], "w");
if (dest_file == NULL) {
printf("Failed to open file '%s'\n", argv[2]);
fclose(src_file);
exit(EXIT_FAILURE);
}
// 定义缓冲区和读取的字节数
char buffer[BUFSIZE];
size_t bytes_read;
// 循环读取源文件中的数据,直到遇到 EOF
while ((bytes_read = fread(buffer, 1, BUFSIZE, src_file)) > 0) {
// 将读取到的数据写入目标文件
size_t bytes_written = fwrite(buffer, 1, bytes_read, dest_file);
// 如果写入的字节数不等于读取的字节数,则发生错误
if (bytes_written != bytes_read) {
printf("Error writing to file '%s'\n", argv[2]);
fclose(src_file);
fclose(dest_file);
exit(EXIT_FAILURE);
}
}
// 检查读取操作是否出错
if (ferror(src_file)) {
printf("Error reading from file '%s'\n", argv[1]);
fclose(src_file);
fclose(dest_file);
exit(EXIT_FAILURE);
}
// 检查文件结尾标志是否设置
if (feof(src_file)) {
printf("Reached end of file '%s'\n", argv[1]);
}
// 获取文件的当前位置
long cur_pos = ftell(src_file);
printf("Current position in file '%s': %ld\n", argv[1], cur_pos);
// 使用 fseek() 函数设置文件的当前位置
int res = fseek(src_file, 0, SEEK_SET);
if (res != 0) {
printf("Error setting position in file '%s'\n", argv[1]);
}
// 关闭打开的文件
fclose(src_file);
fclose(dest_file);
return 0;
}
该程序接受两个文件名参数,并以只读模式打开源文件,以写入模式打开目标文件。然后,它循环读取源文件中的数据并写入目标文件,直到遇到文件结尾。
9. 标准格式化IO
9.1 格式化写入
fprintf()\printf()\snprintf()\sprintf
函数用于将格式化的字符串写入指定的文件流。这些函数原型为:
int fprintf(FILE *restrict stream, const char *restrict format, ...);
int printf(const char *restrict format, ...);
int snprintf(char *restrict s, size_t n, const char *restrict format, ...);
int sprintf(char *restrict str, const char *restrict format, ...);
这些函数接受的参数:
stream
:指向要写入的文件流。str
:指向要写入的字符串(指向自定义缓冲区)。n
:指定要写入的字符数(指定的缓冲区大小)。format
:指定格式化的字符串。...
:表示可变的参数列表,用于格式化字符串。这些函数返回值为已写入的字符数,如果失败则返回一个负数。
9.2 格式化读取
fscanf()
函数用于从指定的文件流中读取格式化的字符串。该函数原型为:
int fscanf(FILE *restrict stream, const char *restrict format, ...);
int scanf(const char *restrict format, ...);
int sscanf(const char *restrict str, const char *restrict format, ...);
这些函数接受的参数:
stream
:指向要读取的文件流。str
:指向要读取的字符串(指向自定义缓冲区)。format
:指定格式化的字符串。...
:表示可变的参数列表,用于存储读取的数据。该函数返回已读取的正确匹配且赋值的数据格式,如果失败则返回一个负数。
9.3 注意点
格式化IO函数中最常用的莫过于printf()和 scanf()了,但从上表中可以看到,他们其实各自都有一些功能类似的兄弟函数可用,使用这些函数需要注意以下几点:
fprintf()
不仅可以像printf()
一样向标准输出设备输出信息,也可以向由 stream 指定的任何有相应权限的文件写入数据。sprintf()
和snprintf()
都是向一块自定义缓冲区写入数据,不同的是后者第二个参数提供了这块缓冲区的大小,避免缓冲区溢出,因此应尽量使用后者,放弃使用前者。fscanf()
不仅可以像scanf()
一样从标准输入设备读入信息,也可以从由 stream 指定的任何有相应权限的文件读入数据。sscanf()
从一块由 str 指定的自定义缓冲区中读入数据。
9.4 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int num = 42;
// 将整数 num 的值写入文件
FILE *fp = fopen("file.txt", "w");
if (fp == NULL) {
perror("fopen() fail");
exit(EXIT_FAILURE);
}
if (fprintf(fp, "%d", num) < 0) {
perror("fprintf() fail");
exit(EXIT_FAILURE);
}
fclose(fp);
// 从文件中读取整数
fp = fopen("file.txt", "r");
if (fp == NULL) {
perror("fopen() fail");
exit(EXIT_FAILURE);
}
if (fscanf(fp, "%d", &num) < 0) {
perror("fscanf() fail");
exit(EXIT_FAILURE);
}
printf("The value read from the file is: %d\n", num);
fclose(fp);
return 0;
}