目录
1 标准I/O库简介
1.1 标准I/O库简介
1.2 标准 I/O 和文件 I/O 的区别
2 FILE 指针
3 标准I/O库的主要函数简介
4 标准输入、标准输出和标准错误
4.1 标准输入、标准输出和标准错误概念
4.2 示例程序
5 打开文件fopen()
5.1 fopen()函数简介
5.2 新建文件的权限
5.3 示例程序
6 fread()和fwrite()读文件和写文件
6.1 fread()和fwrite()简介
6.2 示例程序
7 fseek()定位
7.1 fseek()函数简介
7.2 示例程序
1 标准I/O库简介
1.1 标准I/O库简介
Linux系统中的标准I/O库是C语言标准库的一个关键组成部分,它提供了一套丰富的函数接口,用于执行文件的输入输出操作。这些函数定义在<stdio.h>
头文件中,包括打开和关闭文件、读取和写入数据、格式化输入输出等。由于标准I/O是C语言标准的一部分,因此使用标准I/O库编写的程序具有良好的可移植性。
1.2 标准 I/O 和文件 I/O 的区别
- 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux系统调用;
- 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的;
- 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性,通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的;而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性。
- 性能、效率: 标准 I/O 库在用户空间维护了自己的 stdio 缓冲区, 所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O。
2 FILE 指针
FILE 指针的作用相当于文件描述符,只不过 FILE 指针用于标准 I/O 库函数中、而文件描述符则用于文件I/O 系统调用中。
文件 I/O 函数都是围绕文件描述符进行的,当调用 open()函数打开一个文件时,即返回一个文件描述符 fd,然后该文件描述符就用于后续的 I/O 操作。而对于标准 I/O 库函数来说,它们的操作是围绕 FILE 指针进行的,当使用标准 I/O 库函数打开或创建一个文件时,会返回一个指向 FILE 类型对象的指针(FILE *) ,使用该 FILE 指针与被打开或创建的文件相关联,然后该 FILE 指针就用于后续的标准 I/O 操作。
FILE
指针是C语言标准库中用于处理文件输入输出的一种数据类型。它指向FILE
结构体的一个实例,这个结构体包含了执行文件操作所需的各种信息,如文件缓冲区、文件状态标志、错误标志以及指向文件当前位置的指针等。
3 标准I/O库的主要函数简介
(1) 打开文件
fopen(const char *filename, const char *mode)
filename
:要打开的文件的名称。mode
:打开文件的模式,如"r"(只读),"w"(只写,会覆盖文件),"a"(追加),"r+"(读写)等。
(2) 关闭文件
fclose(FILE *stream)
stream
:要关闭的文件的指针。
(3) 读取文件
fread(void *ptr, size_t size, size_t count, FILE *stream)
ptr
:指向用于存储读取数据的缓冲区的指针。size
:每个数据项的大小(以字节为单位)。count
:要读取的数据项数量。stream
:指向包含要读取数据的文件的指针。
(4) 写入文件
fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
ptr
:指向要写入的数据的缓冲区的指针。size
:每个数据项的大小(以字节为单位)。count
:要写入的数据项数量。stream
:指向包含目标文件的指针。
(5) 从文件中读取一个字符
fgetc(FILE *stream)
stream
:指向包含要读取字符的文件的指针。
(6) 从文件中读取一个字符
fputc(int c, FILE *stream)
c
:要写入的字符(作为int
类型传递)。stream
:指向包含目标文件的指针。
(7) 从文件中读取一行字符串
fgets(char *str, int num, FILE *stream)
str
:指向用于存储读取字符串的缓冲区的指针。num
:最大字符数,包括空字符'\0'。stream
:指向包含要读取数据的文件的指针。
(8) 向文件写入一个字符串
fputs(const char *str, FILE *stream)
str
:要写入的字符串。stream
:指向包含目标文件流的指针。
(9) 输出数据
fprintf(FILE *stream, const char *format, ...)
stream
:指向包含目标文件的指针。format
:格式字符串,定义了后续参数的输出格式。...
:要输出的数据。
(10) 检测文件是否到达文件末尾
feof(FILE *stream)
stream
:指向要检查的文件的指针。
(11) 检查文件流是否发生了错误
ferror(FILE *stream)
stream
:指向要检查的文件的指针。
4 标准输入、标准输出和标准错误
4.1 标准输入、标准输出和标准错误概念
用户通过标准输入设备与系统进行交互, 进程将从标准输入(stdin)文件中得到输入数据,将正常输出数据(譬如程序中 printf 打印输出的字符串) 输出到标准输出(stdout) 文件,而将错误信息(譬如函数调用报错打印的信息)输出到标准错误(stderr) 文件。
在Linux系统中,标准输入(stdin)、标准输出(stdout)和标准错误(stderr)是进程与其环境之间通信的三种基本方式。它们是文件描述符,分别对应文件描述符0、1和2。下面是它们各自的功能:
标准输入(stdin):
- 文件描述符:0
- 功能:允许用户将输入数据发送给正在运行的程序。
- 来源:通常是键盘输入,但也可以是重定向自文件、管道等。
标准输出(stdout):
- 文件描述符:1
- 功能:程序的输出结果默认发送到标准输出。
- 去向:通常是终端或控制台(例如计算机所连接的显示器)。
标准错误(stderr):
- 文件描述符:2
- 功能:用于发送错误消息或诊断信息。
- 去向:默认也是终端或控制台,但与标准输出分开,使得错误信息可以独立于正常输出被处理或重定向。
4.2 示例程序
下面的简单的示例程序会从标准输入读取一行文本,然后将这行文本通过标准输出打印出来,并同时将一个错误消息通过标准错误输出。
#include <stdio.h>
#include <string.h>
int main() {
char buffer[1024]; // 缓冲区,用于存储输入
// 从标准输入读取一行文本
printf("请输入一些文本:");
fgets(buffer, sizeof(buffer), stdin); // 使用fgets从stdin读取
// 将读取的文本通过标准输出打印
printf("你输入的是:%s", buffer);
// 检查输入的文本中是否包含空格
if (strchr(buffer, ' ') != NULL) {
// 如果包含空格,通过标准错误输出错误信息
fprintf(stderr, "错误:输入的文本中包含空格。\n");
}
return 0;
}
在这个程序中:
stdin
、stdout
和stderr
是stdio.h
头文件中定义的宏,分别代表标准输入、标准输出和标准错误的文件指针。fgets
函数用于从stdin
读取一行文本,直到遇到换行符或缓冲区满。它将文本存储在buffer
数组中。printf
函数将读取的文本通过stdout
打印到终端。- 使用
strchr
函数来检查输入的字符串中是否包含空格。如果strchr
返回非空指针,表示输入中包含空格,程序通过stderr
输出错误信息。
5 打开文件fopen()
5.1 fopen()函数简介
在文件 I/O 中,使用 open()系统调用打开或创建文件,而在标准 I/O 中,我们将使用库函数fopen()打开或创建文件, fopen()函数原型如下所示:
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
- path: 参数 path 指向文件路径,可以是绝对路径、也可以是相对路径。
- mode: 参数 mode 指定了对该文件的读写权限,如下所示。
mode | 说明 | 对应于 open()函数的 flags 参数取值 |
r | 以只读方式打开文件。 | O_RDONLY |
r+ | 以可读、可写方式打开文件。 | O_RDWR |
w | 以只写方式打开文件,如果参数 path 指定的文件存在,将文件长度截断为 0;如果指定文件不存在则创建该文件。 | O_WRONLY | O_CREAT | O_TRUNC |
w+ | 以可读、可写方式打开文件,如果参数 path 指定的文件存在,将文件长度截断为 0;如果指定文件不存在则创建该文件。 | O_RDWR | O_CREAT | O_TRUNC |
a | 以只写方式打开文件,打开以进行追加内容(在文件末尾写入),如果文件不存在则创建该文件。 | O_WRONLY | O_CREAT | O_APPEND |
a+ | 以可读、可写方式打开文件,以追加方式写入(在文件末尾写入),如果文件不存在则创建该文件。 | O_RDWR | O_CREAT | O_APPEND |
- 返回值: 调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联,后续的标准 I/O 操作将围绕 FILE 指针进行。 如果失败则返回 NULL,并设置 errno 以指示错误原因。
5.2 新建文件的权限
fopen()只有两个参数 path 和 mode,不同于 open()系统调用,它并没有任何一个参数来指定新建文件的权限。 当参数 mode 取值为"w"、 "w+"、 "a"、 "a+"之一时,如果参数 path 指定的文件不存在,则会创建该文件。
虽然调用 fopen()函数新建文件时无法手动指定文件的权限,但却有一个默认值:
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)
5.3 示例程序
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("Error opening file");
return 1;
}
fprintf(file, "Hello, World!\n");
fclose(file); // 记得关闭文件
return 0;
}
示例代码中,以写入模式打开example.txt
文件。如果文件打开失败,fopen()
将返回NULL
,通过perror()
函数输出错误信息。成功打开文件后,使用fprintf()
向文件写入内容,并最终使用fclose()
关闭文件。运行的结果如下:
6 fread()和fwrite()读文件和写文件
6.1 fread()和fwrite()简介
库函数中使用 fread()用于读取文件数据,函数原型如下所示:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- ptr: fread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
- size: fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节。
- nmemb: 参数 nmemb 指定了读取数据项的个数。
- stream: FILE 指针。
- 返回值: 调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数size 等于 1);如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,那么到底发生了错误还是到达了文件末尾, fread()不能区分文件结尾和错误, 究竟是哪一种情况,此时可以使用 ferror()或 feof()函数来判断。
库函数中使用 fwrite()将数据写入到文件中,函数原型如下所示:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- ptr: 将参数 ptr 指向的缓冲区中的数据写入到文件中。
- size: 参数 size 指定了每个数据项的字节大小,与 fread()函数的 size 参数意义相同。
- nmemb: 参数 nmemb 指定了写入的数据项个数,与 fread()函数的 nmemb 参数意义相同。
- stream: FILE 指针。
- 返回值: 调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数 size等于 1);如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0)。
6.2 示例程序
下面代码演示了如何在C语言中使用标准I/O函数fopen
、fwrite
、rewind
、fread
和fclose
来执行文件的基本读写操作。
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *filename = "example.txt";
char buffer[] = "Hello, World!";
char readBuffer[sizeof(buffer)];
// 打开文件用于写入和读取
FILE *fp = fopen(filename, "w+");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
// 写入数据到文件
if (fwrite(buffer, 1, sizeof(buffer), fp) != sizeof(buffer)) {
fclose(fp);
perror("Error writing to file");
return 1;
}
// 移动文件指针到文件开头,准备读取
rewind(fp);
// 从文件读取数据
if (fread(readBuffer, 1, sizeof(buffer), fp) != sizeof(buffer)) {
fclose(fp);
perror("Error reading from file");
return 1;
}
// 确保字符串以空字符结尾
readBuffer[sizeof(buffer) - 1] = '\0';
// 打印读取的数据
printf("Read from file: %s\n", readBuffer);
// 关闭文件
fclose(fp);
return 0;
}
程序首先以读写模式打开一个名为"example.txt"的文件,然后向其中写入字符串"Hello, World!"。之后,程序将文件指针重置到文件开头,从文件中读取相同数量的数据到一个缓冲区,并确保字符串以空字符结尾,最后打印读取的字符串并关闭文件。程序还包括了基本的错误检查,以确保文件操作成功完成。程序运行结果如下:
7 fseek()定位
7.1 fseek()函数简介
库函数 fseek()的作用类似于系统调用 lseek(), 用于设置文件读写位置偏移量, lseek()用于文件 I/O,而库函数 fseek()则用于标准 I/O,其函数原型如下所示:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
- stream: FILE 指针。
- offset: 与 lseek()函数的 offset 参数意义相同。
- whence: 与 lseek()函数的 whence 参数意义相同。
- 返回值: 成功返回 0;发生错误将返回-1,并且会设置 errno 以指示错误原因; 与 lseek()函数的返回值意义不同,这里要注意!
7.2 示例程序
如下程序如何使用fseek()
来设置文件读写位置偏移量。
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *filename = "input.txt";
char buffer[1024];
int i;
// 打开文件用于写入
FILE *file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file for writing");
return 1;
}
// 提示用户输入文本
printf("Enter your text (max 1024 characters): ");
fgets(buffer, sizeof(buffer), stdin); // 从标准输入读取一行文本
// 写入文本到文件
fputs(buffer, file);
// 关闭文件
fclose(file);
// 重新打开文件用于读取
file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file for reading");
return 1;
}
// 移动文件指针到文件开头
fseek(file, 4, SEEK_SET);
// 读取文件内容
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("Read from file: %s", buffer);
}
// 关闭文件
fclose(file);
return 0;
}
代码创建了一个名为"input.txt"的文件,并提示用户输入一段文本。输入的文本被写入到文件中,然后程序重新打开文件跳过前4个字节,并读取剩余的内容,将其打印到屏幕上。如果文件操作过程中出现错误,程序将显示错误信息并退出。运行结果如下: