什么是尘土?从大地之肺发出的一声叹息。 - 《阿多尼斯诗集》(阿多尼斯)
2024.8.23
目录
在C语言编程中,文件IO操作是基础而重要的部分。通过文件IO,程序能够读取外部数据或将结果持久化存储。
1、C语言IO接口
C语言提供了丰富的文件操作API,基于标准库中的 <stdio.h>
头文件。最常用的文件操作包括打开、读取、写入、关闭文件等。
示例代码:使用 fopen
和 fclose
读写文件
示例1:通过write写文件
#include<stdio.h>
#include<string.h>
// 通过write写文件
int main()
{
FILE* fp = fopen("test.txt", "a");
if(!fp)
{
printf("file open fail\n");
return -1;
}
const char* msg = "hello world\n";
int len = strlen(msg);
int count = 10;
while(count--)
{
fwrite(msg, 1, len, fp);
}
fclose(fp);
return 0;
}
示例2:通过read写文件
#include<stdio.h>
#include<string.h>
#define MAX 1024
// 通过read写文件
int main()
{
FILE* fp = fopen("test.txt", "r");
if(!fp)
{
printf("[error] file open fail\n");
return -1;
}
int cnt = 0;
char buf[MAX];
const char* msg = "hello world\n";
while(1)
{
cnt++;
if(feof(fp))
{
break;
}
printf("cnt -> %d\n",cnt);
size_t size = fread(buf, 1, strlen(msg), fp);
printf("%s\n", buf);
}
fclose(fp);
return 0;
}
C语言的标准流:stdin、stdout和stderr
stdin (标准输入流)
- 类型:
FILE*
- 作用:用于从键盘或另一个输入设备读取数据。
- 文件指针:通常指向文件描述符
0
。
stdout (标准输出流)
- 类型:
FILE*
- 作用:用于向控制台或终端输出数据。
- 文件指针:通常指向文件描述符
1
。
stderr (标准错误流)
- 类型:
FILE*
- 作用:用于向控制台或终端输出错误信息。与
stdout
不同,stderr
通常用于输出错误或诊断信息,即使程序的输出被重定向,错误信息仍然会显示在终端上。- 文件指针:通常指向文件描述符
2
。
标准流总结:
- 全局性:在任何C程序中,无需打开即可使用。
- 缓冲:
stdout
和stderr
通常具有不同的缓冲行为。stdout
可能是行缓冲或全缓冲,而stderr
通常是无缓冲的。也就是说,stdout
可能不会立即输出内容,但stderr
是为了快速错误报告而设计的,通常会立即输出。- 重定向:可以通过简单的重定向操作改变它们的输出目标,例如在 shell 中使用
>
或2>
将输出重定向到文件。- 可以通过
fileno
函数获取FILE*
指针对应的文件描述符,例如fileno(stdin)
将返回0
。- 在多线程程序中,
stdout
和stderr
不是线程安全的,但stderr
在写入时通常会被锁定以避免与其他线程冲突。
示例代码:使用标准流与外部设备进行设备交互
#include <stdio.h>
int main() {
char buffer[256];
// 使用 stdin 读取键盘数据
printf("Enter some text: ");
fgets(buffer, sizeof(buffer), stdin);
// 使用 stdout 向显示器输出数据
fprintf(stdout, "You entered: %s\n", buffer);
// 使用 stderr 向显示器输出错误信息
fprintf(stderr, "This is an error message.\n");
return 0;
}
2、Linux系统IO接口与库函数
系统调用是操作系统提供给用户程序的接口,而库函数则是对这些系统调用的封装,使得程序员可以使用更高级、更易用的API。
系统IO接口介绍:open
在Linux系统中,open
系统调用是一个用于打开文件或创建新文件的标准函数。它提供了一种底层的方法来操作文件,与C标准库中的 fopen
函数相比,open
函数更接近操作系统层面,允许更细致地控制文件的打开方式和属性。
#include <sys/types.h> // 包含系统类型定义
#include <sys/stat.h> // 包含文件状态结构定义
#include <fcntl.h> // 包含文件控制选项定义
int open(const char *pathname, int flags, ... /* mode_t mode */);
参数说明
pathname
:这是一个指向以null结尾的字符串的指针,表示要打开或创建的文件的路径。flags
:这是一个整数,用于指定文件打开的各种选项和模式。可以是以下宏的组合:
O_RDONLY
:以只读方式打开文件。O_WRONLY
:以只写方式打开文件。O_RDWR
:以读写方式打开文件。O_CREAT
:如果文件不存在,创建新文件。O_EXCL
:与O_CREAT
一起使用时,如果文件已存在,则open
调用失败。O_TRUNC
:如果文件存在,将其截断为零长度。O_APPEND
:所有写操作都追加到文件末尾,忽略lseek
或llseek
设置的文件偏移量。- 等等,还有其他标志位用于更特殊的用途。
mode
(可选):当使用O_CREAT
标志时,这个参数指定新创建文件的权限模式。如果不提供,则默认权限由进程的umask
值决定(就近原则)。
返回值
- 成功时,
open
函数返回一个非负的文件描述符(fd),用于在后续操作中标识打开的文件。- 失败时,返回
-1
,并设置全局变量errno
以指示错误类型。
注意事项
open
函数返回的文件描述符(fd)可以用于后续的read
、write
、lseek
等系统调用。- 使用
open
打开的文件应该在不再需要时使用close
函数关闭,以释放系统资源。 open
函数支持的flags
标志位非常灵活,可以根据需要组合使用不同的标志来控制文件的打开方式。- 与
fopen
不同,open
没有提供缓冲机制。如果需要缓冲,可以使用标准IO库中的缓冲函数,或者手动实现缓冲逻辑。
write、read、close和C语言接口类似 不多介绍啦~
示例代码:使用系统调用进行文件操作
示例1:通过系统调用接口write写文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
// 设置文件的默认权限掩码,创建文件时忽略进程的umask设置
umask(0);
// 打开文件,O_WRONLY 只写模式,O_CREAT 如果文件不存在则创建,0644 是文件的默认权限
int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open");
return 1;
}
// 定义要写入的消息和次数
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
// 循环写入消息
while (count--) {
// write 系统调用,将 msg 缓冲区中 len 大小的数据写入到文件描述符 fd 指向的文件
// 返回值是实际写入的字节数
ssize_t bytes_written = write(fd, msg, len);
if (bytes_written < 0) {
perror("write");
close(fd);
return 1;
}
}
// 关闭文件描述符
close(fd);
return 0;
}
示例2:用过系统调用接口read读文件
#include <stdio.h> // 包含标准输入输出库
#include <sys/types.h> // 包含系统类型定义
#include <sys/stat.h> // 包含文件状态结构定义
#include <fcntl.h> // 包含文件控制选项定义
#include <unistd.h> // 包含Unix标准函数定义,如read和close
#include <string.h> // 包含字符串函数定义
int main() {
// 尝试以只读方式打开文件"myfile"
int fd = open("myfile", O_RDONLY);
if (fd < 0) {
// 如果打开失败,打印错误信息并退出程序
perror("open");
return 1;
}
// 定义要读取的字符串长度,这里假设与写入时相同
const char *msg = "hello bit!\n";
size_t msg_len = strlen(msg);
char buf[1024]; // 定义一个缓冲区用于存储读取的数据
// 使用循环来读取文件,直到文件结束
while (1) {
// 调用read系统调用尝试从文件描述符fd中读取msg_len长度的数据到缓冲区buf
ssize_t s = read(fd, buf, msg_len);
if (s > 0) {
// 如果读取成功,打印缓冲区的内容
printf("%s", buf);
} else {
// 如果读取到文件末尾或发生错误,退出循环
break;
}
}
// 关闭文件描述符
close(fd);
return 0; // 正常退出程序
}
3、C语言IO接口和Linux系统IO接口的区别与联系
C语言IO接口和Linux系统IO接口是两个层面的抽象,它们之间存在明显的区别和紧密的联系:
区别
抽象级别:
- C语言IO接口(如
fopen
、fclose
、fread
、fwrite
等)是C标准库提供的功能,它们是对底层系统IO操作的高层次抽象。- Linux系统IO接口(如
open
、close
、read
、write
等)是直接由操作系统内核提供的系统调用,提供了对硬件的直接操作能力。可移植性:
- C语言IO接口具有良好的可移植性,因为它们由编译器和标准库实现,可以在不同的操作系统上运行。
- Linux系统IO接口是特定于操作系统的,只能在支持这些系统调用的系统上使用。
功能丰富性:
- C语言IO接口提供了更多格式化和方便的函数,如
fprintf
、fgets
等,简化了编程。- Linux系统IO接口提供了基础的读写操作,但通常需要程序员进行更多的底层管理。
联系
封装关系:
- C语言IO接口实际上是对Linux系统IO接口的封装。例如,当你使用
fopen
打开一个文件时,C库内部会使用open
系统调用来获取文件描述符。共同目标:
- 两者都旨在提供对文件和输入输出流的操作能力,尽管抽象级别不同。
互操作性:
- 程序员可以在使用C语言IO接口的同时,通过特定的函数(如
fileno
)获取到底层的文件描述符,并使用Linux系统IO接口进行操作。文件描述符:
- 无论是使用C语言IO接口还是Linux系统IO接口,最终都可能与文件描述符(fd)打交道。例如,
fopen
返回的FILE*
类型内部包含了一个文件描述符。底层支持:
- C语言IO接口的实现依赖于操作系统提供的底层支持,如Linux的系统调用来完成实际的文件操作。