C语言下的文件
先来段代码回顾C文件接口
- 在之前的博客 浅谈与文件相关的几种函数我们讲解了一些与文件相关的函数以及他们的用法,有兴趣的读者可以进行阅读。
- 在c语言中我们向一个文件中写入数据使用的是fwrite函数:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if (!fp){
printf("fopen error!\n");
}
const char *msg = "hello world!\n";
int count = 5;
while (count--){
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
在c语言中我们从一个文件中读出数据使用的是fread函数:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "r");
if (!fp){
printf("fopen error!\n");
}
char buf[1024];
const char *msg = "hello world!\n";
while (1){
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
ssize_t s = fread(buf, 1, strlen(msg), fp);
if (s > 0){
buf[s] = 0;
printf("%s", buf);
}
if (feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
stdin & stdout & stderr
- 我们之前也提到过C程序在运行时会默认会打开三个输入输出流,分别是stdin, stdout, stderr
- 我们仔细观察可以发现,这三个流的类型都是FILE*, fopen返回值类型,属于文件指针
总结一下打开文件的几种方式
命令 | 打开方式 |
---|---|
r | 以只读方式打开该文件,要求文件必须存在 |
r+ | 以可读可写方式打开该文件,要求文件必须存在 |
w | 以可写的方式打开该文件,要求文件必须存在 |
w+ | 以可读可写的方式打开文件,若文件存在则直接进行读或写,若文件不存在则创建 |
a | 以追加的方式打开文件,若文件不存在则创建 |
a+ | 以可读可追加的方式打开文件,若文件不存在则创建 |
系统文件I/O
- 操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
- Linux下通过open接口打开文件,通过read接口读取文件中的内容:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while (1){
ssize_t s = read(fd, buf, strlen(msg));//类比write
if (s > 0){
printf("%s", buf);
}
else{
break;
}
}
close(fd);
return 0;
}
- Linux下通过write接口往文件中写数据:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
umask(0);
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(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数
//据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
下面我们简单介绍一下这几个系统接口
open
- 通过Linux系统下的man命令可以查看open接口的各种介绍:
read
write
close
lseek
文件描述符fd(灰常重要)
- 其实我们之前讲了那么多概念,就是为了引出这个概念,通过刚刚的open函数我们也可以看多其实文件描述符fd就是一个小整数,接下来我们就具体介绍一下文件描述符
0 & 1 & 2
- 我们刚刚在前面提到C程序在运行之后会默认打开三个输入输出流,分别是stdin,stdout和stderr
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
- 0,1,2对应的物理设备一般是:键盘,显示器,显示器
- 所以输入输出还可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if (s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
- 文件描述符就是从0开始的小整数。
- 之前在 进程的概念 中我们讲过,操作系统要管理进程时,是需要 先描述,再组织,于是我们有了描述进程的结构体PCB,相同的当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。
- 表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!
- 通过上图已经我们刚刚讲到的相关信息,其实我们可以看出来文件描述符fd的本质其实就是一直存放文件指针的指针数组的下标。
文件描述符的分配规则
- 我们先来看一段代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
- 输出发现是 fd: 3
- 关闭0或者2,在看:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if (fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
- 发现是结果是: fd: 0 或者 fd 2 可见
- 文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
- 很好理解,举个栗子:我们都知道Linux进程会默认打开3个缺省文件描述符0、1、2这就是为什么我们每次创建一个新的文件,文件描述符是从3开始而不是从0开始的,若是我们将0关闭,再打开一个文件,此时该文件的文件描述符肯定是0.
重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
if (fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
- 此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <
- 通过上述概念的讲解,其实我们就可以很好的介绍Linux下的重定向是怎样实现的了,我们来解释一下: