【Linux系统编程】第二十六弹---彻底掌握文件I/O:C/C++文件接口与Linux系统调用实践
✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】
目录
1、回顾C语言文件接口
1.1、以写的方式打开文件
1.2、以追加的方式打开文件
2、初步理解文件
2.1、C++文件接口
3、进一步理解文件
3.1、系统调用实现写方式打开文件
3.2、系统调用实现追加打开文件
3.3、理解open第二个参数原理
1、回顾C语言文件接口
1.1、以写的方式打开文件
1、如果文件不存在,就在当前路径下新建指定的文件。
2、默认打开文件的时候,会先清空文件 -> 等价于 >输出重定向。
代码演示一
#include<stdio.h>
int main()
{
// 以写方式打开文件
FILE* fd = fopen("log.txt","w");
if(fd == NULL)
{
perror("fopen");
return 1;
}
// 向文件写内容
fprintf(fd,"helloworld,%d,%s,%lf\n",10,"abcd",3.14);
// 关闭文件
fclose(fd);
return 0;
}
运行结果
代码演示二
#include<stdio.h>
int main()
{
// 以写方式打开文件
FILE* fd = fopen("log.txt","w");
if(fd == NULL)
{
perror("fopen");
return 1;
}
fclose(fd);
return 0;
}
运行结果
看一段命令
[jkl@host file]$ cat log.txt
helloworld,10,abcd,3.140000
[jkl@host file]$ echo "hello linux" > log.txt
[jkl@host file]$ cat log.txt
hello linux
[jkl@host file]$ > file.txt
[jkl@host file]$ ls
file.txt log.txt makefile myfile myfile.c
[jkl@host file]$ cat log.txt
hello linux
[jkl@host file]$ >log.txt
[jkl@host file]$ cat log.txt
[jkl@host file]$
解析命令
结论:
重定向 > 可以创建新文件和清空文件内容。
1.2、以追加的方式打开文件
1、如果文件不存在,就在当前路径下新建指定的文件。
2、默认打开文件的时候,会在末尾追加内容 -> 等价于 >>输出重定向。
代码演示
#include<stdio.h>
int main()
{
// 以追加的方式打开文件
FILE* fd = fopen("log.txt","a");
if(fd == NULL)
{
perror("fopen");
return 1;
}
fprintf(fd,"helloworld,%d,%s,%lf\n",10,"abcd",3.14);
fclose(fd);
return 0;
}
运行结果
看一段命令
[jkl@host file]$ ./myfile
[jkl@host file]$ cat log.txt
helloworld,10,abcd,3.140000
helloworld,10,abcd,3.140000
helloworld,10,abcd,3.140000
[jkl@host file]$ cat myfile.c
[jkl@host file]$ echo "hello linux" >> log.txt
[jkl@host file]$ cat log.txt
helloworld,10,abcd,3.140000
helloworld,10,abcd,3.140000
helloworld,10,abcd,3.140000
hello linux
[jkl@host file]$ >> file1.txt
[jkl@host file]$ ls
file1.txt file.txt log.txt makefile myfile myfile.c
解析命令
结论:
>> 追加重定向可以创建新文件和追加文件内容。
2、初步理解文件
- 文件 = 属性 + 内容
- 打开文件:本质是进程(struct task_struct)打开文件(struct xxx)
- 文件没有打开的时候,存放在哪里?硬盘。
- 进程能打开很多文件吗?可以
- 系统能否存在很多进程?可以
- 很多情况下,OS内部存在大量被打开的文件 -> OS是否要将被打开的文件进行管理 -> 怎么管理呢?先描述在组织 -> 预言:每一个被打开的文件,在OS内部,一定要存在对应的描述文件属性的结构体,类似于PCB。
2.1、C++文件接口
以写的方式打开文件。
代码演示
#include<iostream>
#include<fstream>
#include<string>
#define FILENAME "log.txt"
int main()
{
// out写方式 in读方式 app追加 binary二进制
std::ofstream out(FILENAME,std::ios_base::out);
// is_open() 检查文件是否打开
if(!out.is_open()) return 1; //打开失败,结束程序
std::string msg = "hello C++!\n";
// ostream& write (const char* s, streamsize n);
out.write(msg.c_str(),msg.size());
// 关闭文件
out.close();
return 0;
}
运行结果
3、进一步理解文件
操作文件:本质是进程在操作文件,即进程与文件的关系。
文件 ->存储在硬盘中 -> 硬盘是一个外设 -> 外设是一个硬件 -> 向文件中写入本质是向硬件中写入 -> 用户没有权利直接写入 -> OS是硬件的管理者 -> 通过OS写入 -> OS必须给我们提供系统调用(因为OS不相信任何人) -> fopen/fwrite/fread/fclose ... -> 我们用的都是C/C++/... 对系统调用接口的封装! -> 访问文件,我们也可以直接使用系统调用!!!
为什么要对系统调用的接口进行封装?怎么封装?
后序回答。
使用系统调用接口来操作文件!!!
系统调用接口函数
打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname:
要打开或创建的目标文件。
flags:
打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
mode:
设置默认权限信息。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
O_TRUNC : 若文件存在会先清空文件
返回值:
成功:新打开的文件描述符
失败:-1
向文件中写内容
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
将缓冲区指向的 buf 内容中的count个字节数写入到文件描述符 fd 引用的文件。
关闭文件
#include <unistd.h>
int close(int fd);
关闭文件描述符 fd 引用的文件。
3.1、系统调用实现写方式打开文件
系统调用可能用到的头文件
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
代码演示一(不设置权限信息)
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC);
if(fd < 0)
{
perror("open");
return 1;
}
// ssize_t write(int fd, const void *buf, size_t count);
const char* msg = "hello linux file!\n";
write(fd,msg,strlen(msg));
close(fd);
return 0;
}
运行结果
上面的代码演示的是文件不存在,在当前路径创建指定文件,但没有设置默认权限的情况。
代码演示一(只设置权限信息)
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
// ssize_t write(int fd, const void *buf, size_t count);
const char* msg = "hello linux file!\n";
write(fd,msg,strlen(msg));
close(fd);
return 0;
}
运行结果
上面的代码演示的是文件不存在,在当前路径创建指定文件,且设置默认权限的情况。
想要第三个参数是什么权限文件就是什么权限应该怎么办呢???
将umask值设置为0即可。
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
设置umask值。
代码演示三(设置权限信息和umask)
int main()
{
umask(0);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd < 0)
{
perror("open");
return 1;
}
// ssize_t write(int fd, const void *buf, size_t count);
const char* msg = "hello linux file!\n";
write(fd,msg,strlen(msg));
close(fd);
return 0;
}
运行结果
上面的代码演示的是文件不存在,在当前路径创建指定文件,且设置默认权限和umask的情况。
代码演示四
int main()
{
umask(0);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0777);
if(fd < 0)
{
perror("open");
return 1;
}
close(fd);
return 0;
}
运行结果
上面的代码演示的是文件存在,先清空文件内容,即使修改默认权限,也不会影响该文件的权限。
3.2、系统调用实现追加打开文件
代码演示
int main()
{
umask(0);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
if(fd < 0)
{
perror("open");
return 1;
}
// ssize_t write(int fd, const void *buf, size_t count);
const char* msg = "hello linux file!\n";
write(fd,msg,strlen(msg));
close(fd);
return 0;
}
运行结果
3.3、理解open第二个参数原理
原理是位图加位运算,下面通过一个程序理解调用open第二个参数原理。
代码演示
#define ONE 1 // 0000 0001
#define TWO (1<<1) // 0000 0010
#define THREE (1<<2) // 0000 0100
#define FOUR (1<<3) // 0000 1000
void print(int flag)
{
if(flag & ONE)
{
printf("one\n");
}
if(flag & TWO)
{
printf("two\n");
}
if(flag & THREE)
{
printf("three\n");
}
if(flag & FOUR)
{
printf("four\n");
}
}
int main()
{
print(ONE);
printf("\n");
print(TWO);
printf("\n");
print(ONE | TWO);
printf("\n");
print(ONE | TWO | THREE);
printf("\n");
print(ONE | TWO | THREE | FOUR);
printf("\n");
return 0;
}
运行结果