👀樊梓慕:个人主页
🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》
🌝每一个不曾起舞的日子,都是对生命的辜负
目录
前言
1.C语言文件IO
1.1C语言文件IO接口汇总
1.2当前路径指的是什么?
1.3stdin、stdout、stderr
2.系统文件IO
2.1open
参数const char* pathname
参数int flags
*位图方式传参
参数mode_t mode
返回值int fd『 简要理解文件描述符』
2.2close
2.3write
2.4read
前言
进程周边的相关内容暂时告一段落,下面我们开始学习文件部分。
学习『 系统文件IO』之前,我会与大家先复习一下C语言部分文件IO的相关接口,为后面的学习作『 铺垫』。系统文件IO部分,本篇文章会讲解『 基本的系统调用』:open()、close()、read()、write(),有关参数传递涉及到『 位图方式传递』,这部分以前没有学习过,博主也会拿出来简单的学习一下。
欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。
=========================================================================
GITEE相关代码:🌟fanfei_c的仓库🌟
=========================================================================
1.C语言文件IO
1.1C语言文件IO接口汇总
在学习C语言期间,我们学习过一些C语言封装的文件接口:
打开文件 | |
关闭文件 | |
写入一个字符 | |
读取一个字符 | |
写入一个字符串 | |
读取一个字符串 | |
格式化写入数据 | |
格式化读取数据 | |
向二进制文件写入数据 | |
从二进制文件读取数据 | |
设置文件指针的位置 | |
计算当前文件指针相对于起始位置的偏移量 | |
设置文件指针到文件的起始位置 | |
判断文件操作过程中是否发生错误 | |
判断文件指针是否读取到文件末尾 |
本篇文章不会完全的讲解以上C语言文件接口,想要详细了解的同学可以『 点击以下内容』跳转到博主的有关C语言文件IO的博客。
『 樊梓慕』文件操作——CSDNhttp://t.csdnimg.cn/DRPJb
1.2当前路径指的是什么?
当我们利用C语言IO接口创建文件时,生成的文件默认在『 当前路径』,可当前路径具体指的是谁的路径呢?
- 『 文件是由进程创建』,所以文件的当前路径也是进程的当前路径。
在之前学习进程的部分,我们已经聊过有关话题,当前路径指的是进程在启动时,会保存当前目录的路径,保存到『 /proc/[pid]/cwd』中,该路径就是进程的当前路径。
1.3stdin、stdout、stderr
在之前学习时,我们提到过一个概念:『 Linux下一切皆文件』 ,也就是说键盘、显示器都是文件,这很好理解,我们向普通文件写入,本质上就是向磁盘写入数据,那将对象改为显示器,是不是就是打印了?
但是,向文件写入我们一般这么操作:
FILE* fp = fopen("log.txt", "w");
fputs("hello world\n", fp);
可是打印我们从未『 打开』显示器文件,也从未『 传递』过流参数:
printf("hello world\n");
这也就说明了:
进程在运行的时候都会『 默认打开』三个输入输出流,即标准输入流、标准输出流以及标准错误流,对应到C语言当中就是stdin、stdout以及stderr。
其中,标准输入流对应的设备就是键盘,标准输出流和标准错误流对应的设备都是显示器。
所以,我们想要实现打印的功能,也可以这样写:
fputs("hello world\n", stdout);
stdin、stdout以及stderr是C标准库下的标准输入输出错误流,其他语言如C++也有对应的标准输入输出错误流:cin、cout和cerr。
2.系统文件IO
C程序可以直接对硬件进行写入么?
在之前学习的时候,关于操作系统我们有过这样一张图:
什么意思?
程序不可能也不可以越过操作系统直接操作硬件,还记得『 系统调用』么?
也就是说C标准库中的文件IO接口一定『 封装了系统调用』,所以才能利用fopen()、fputs()等函数对文件进行操作。
那接下来我们就来学习文件IO的系统调用。
2.1open
open是打开文件的系统调用接口。
int open(const char *pathname, int flags, mode_t mode);
参数const char* pathname
代表要打开或创建的目标文件。
- 若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建。
- 若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建。
参数int flags
代表文件的打开方式。
参数 | 含义 |
---|---|
O_RDONLY | 以只读的方式打开文件 |
O_WRNOLY | 以只写的方式打开文件 |
O_APPEND | 以追加的方式打开文件 |
O_RDWR | 以读写的方式打开文件 |
O_CREAT | 当目标文件不存在时,创建文件 |
传递方式介绍:
例如:以只写的方式打开文件,当目标文件不存在时自动创建文件,则参数设置如下:
O_WRONLY | O_CREAT
为什么这么传递??
*位图方式传参
先写一段代码:
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)void Print(int flag)
{if(flag & ONE) printf("1\n");if(flag & TWO) printf("2\n");if(flag & THREE) printf("3\n");if(flag & FOUR) printf("4\n");if(flag & FIVE) printf("5\n");
}int main()
{Print(ONE);printf("----------------------\n");Print(TWO);printf("----------------------\n");Print(ONE|TWO);printf("----------------------\n");Print(THREE|FOUR|FIVE);printf("----------------------\n");Print(ONE|TWO|THREE|FOUR|FIVE);
}
根据代码中的宏定义,这些宏的特点就是所有位加起来只有一个1。
Print函数中五个if中的判断条件其实就是判断参数哪个位为1,如果传的是ONE,那么ONE与ONE&得到的就是1,为真就打印1,其余的都为假,不打印。
当带上|操作符后,相当于把两个参数的1位合并到一起,比如ONE|TWO得到的就是0011,所以在Print中就会满足两个条件flag & ONE 和 flag & TWO。
这就是位图方式传参的基本思想。
放到flags中呢?
#define O_RDONLY 0000
#define O_WRONLY 0001
#define O_RDWR 0010
#define O_CREAT 0100
之后通过&运算:
int open(arg1, arg2, arg3){if (arg2&O_RDONLY){//设置了O_RDONLY选项}if (arg2&O_WRONLY){//设置了O_WRONLY选项}if (arg2&O_RDWR){//设置了O_RDWR选项}if (arg2&O_CREAT){//设置了O_CREAT选项}//...
}
所以如果arg2=O_CREAT | O_WRONLY,即arg2=0101,arg2 & O_WRONLY =1 && arg2 & O_CREAT =1就达到了『 以只写的方式打开文件,当目标文件不存在时自动创建文件』的目的。
这就是『 位图方式传参』。
参数mode_t mode
代表创建文件的默认权限。
例如:将mode设置为0666,则创建出来的文件权限如下:
-rw-rw-rw-
但『 实际上』创建出来文件的权限值还会受到umask(文件默认掩码)的影响。
实际创建出来文件的权限为:mode减去对应位的umask值。
umask的默认值默认为0002,所以当我们设置mode值为0666时实际创建出来文件的权限为0664。
若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。
umask(0); //将文件默认掩码设置为0
如果不需要创建新的文件,则使用两个参数的open即可。
返回值int fd『 简要理解文件描述符』
代表打开文件的『 文件描述符』。
- fd>0:返回的是文件描述符。
- fd==-1:代表打开文件失败。
一个进程可以打开多个文件:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{umask(0);int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666);int fd2 = open("log2.txt", O_RDONLY | O_CREAT, 0666);int fd3 = open("log3.txt", O_RDONLY | O_CREAT, 0666);int fd4 = open("log4.txt", O_RDONLY | O_CREAT, 0666);int fd5 = open("log5.txt", O_RDONLY | O_CREAT, 0666);printf("fd1:%d\n", fd1);printf("fd2:%d\n", fd2);printf("fd3:%d\n", fd3);printf("fd4:%d\n", fd4);printf("fd5:%d\n", fd5);return 0;
}
为什么是从3开始呢?
我们刚才讲进程默认会打开三个输入输出流:标准输入流、标准输出流、标准错误流。
所以0、1、2分别代表了它们。
从而我们得到文件描述符的分配规则:
找到当前没有被使用的最小的下标,作为新的文件描述符。
2.2close
close是关闭文件的系统调用接口。
int close(int fd);//参数fd是文件描述符
- 关闭文件成功返回0;
- 关闭文件失败返回-1。
2.3write
write是写入文件的系统调用接口。
ssize_t write(int fd, const void *buf, size_t count);
功能:将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中。
- 写入成功,返回写入数据的字节个数。
- 写入失败,返回-1。
例如:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}const char* msg = "hello syscall\n";for (int i = 0; i < 5; i++){write(fd, msg, strlen(msg));}close(fd);return 0;
}
2.4read
read是读取文件的系统调用接口。
ssize_t read(int fd, void *buf, size_t count);
功能:从文件描述符为fd的文件读取count字节的数据到buf位置当中。
- 读取成功,返回读取数据的字节个数。
- 读取失败,返回-1。
例如:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("log.txt", O_RDONLY);if (fd < 0){perror("open");return 1;}char ch;while (1){ssize_t s = read(fd, &ch, 1);if (s <= 0){break;}write(1, &ch, 1); //向文件描述符为1的文件写入数据,即向标准输出流(显示器)写入数据}close(fd);return 0;
}
当然对于文件管理来说还有很多需要讲解的细节,博主会放到下一篇文章中详细讲解,下一篇文章会深入学习『 Linux系统是如何管理文件的』,『 文件描述符在其中又扮演了怎样的角色』,『 怎么理解Linux下一切皆文件』等等内容,本篇文章只是文件部分的简单开头,主要目的是为了『 作铺垫』,更多内容,请持续关注博主Linux系列文章。
=========================================================================
如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容
🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎
🌟~ 点赞收藏+关注 ~🌟
=========================================================================