我们通过两个代码复习一下C语言中的文件操作:
int main()
{
FILE* fp = fopen("log.txt", "w");
if(fp == nullptr) perror("fopen fail");
fputs("hello world\n", fp);
fclose(fp);
return 0;
}
int main()
{
FILE* fp = fopen("log.txt", "r");
if(fp == nullptr) perror("fopen fail");
char buffer[64];
fgets(buffer, sizeof(buffer), fp);
cout << buffer << endl;
return 0;
}
第一个是文件的写操作,用w
的格式打开一个文件进行写入。
第二个是文件的读操作,用r
的格式打开一个文件进行读取。
r
,以只读的方式打开文件,文件指针指向文件的首位置。r+
,以读写的方式打开文件,文件指针指向文件的首位置。w
,以只写的方式打开文件,清空文件内容,如果文件不存在就创建一个文件,文件指针指向文件的首位置。w+
,以读写的方式打开文件,如果文件不存在就创建一个文件,如果文件存在就清空文件内容,文件指针指向文件的首位置。a
,以追加写的方式打开文件,将内容追加在文件末尾,如果文件不存在就创建一个文件,文件指针指向文件的末尾位置。a+
,以读和写的方式打开,将内容追加到末尾位置,如果文件不存在就创建一个文件,文件指针指向文件的末尾位置。FILE* fopen(const char* path, const char* mode)
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream)
一般我们将块大小定义为1字节,返回值就是我们读了多少字节。
例如:
int main()
{
FILE *fp = fopen("log.txt", "r+");
if (fp == nullptr)
perror("fopen fail");
fputs("hello world\n", fp);
fseek(fp, 0, SEEK_SET);
char buffer[64];
memset(buffer, 0, 64);
fread(buffer, sizeof(char), sizeof buffer, fp);
printf("%s\n", buffer);
fclose(fp);
return 0;
}
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream)
int main()
{
FILE *fp = fopen("log.txt", "r+");
if (fp == nullptr)
perror("fopen fail");
const char* buffer = "xxxxxxxxxxxxxxxxxx\n";
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
return 0;
}
int fseek(FILE* stream, long offser, int whence)
SEEK_CUR,把文件流指针定义到当前文件位置。SEEK_END,把文件流指针定义到文件末尾。
long ftell(FILE* stream)
void rewind(FILE* stream)
stream:文件流指针
作用:让文件指针回到文件起始位置
int fclose(FILE* fp)
在学校系统文件操作前,有这样一个问题需要补充:为什么我们向显示器输出数据,为什么不需要进行打开显示器操作呢?为什么可以直接使用键盘向电脑输入数据呢?
有这样一句话,Linux下一切皆文件,也就是说,在Linux系统下所有东西都可以看做文件。
系统会默认帮我们打开三个流:标准输入流0、标准输出流1和标准错误流2。
它们分别对应的外设是:键盘,显示器和显示器。
它们对应到C语言中就是:stdin、stdout和stderr。
当我们的C程序跑起来时,操作系统就会默认使用C语言的相关接口将这三个输入输出流打开,之后我们才能调用类似于scanf和printf之类的函数向键盘和显示器进行相应的输入输出操作。
我们还可以查到,它们三个都是FILE*类型(和我们之前说的Linux下一切皆文件相对应)。
int open(const char *pathname, int flags, mode_t mode);
以路径给出,创建该文件时,在给出的路径下创建。
以文件名给出,创建该文件时,在当前路径下创建。
O_RDONLY:以只读的方式打开
O_WRNOLY:以只写的方式打开
O_APPEND:以追加的方式打开
O_RDWR:以读写的方式打开
O_CREAT:当文件不存在时,创建该文件
例如:设置读写权限0666(具体可以查看之前的文章Linux权限管理)
若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask
函数将文件默认掩码设置为0。
int main()
{
umask(0);
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
return 0;
}
我们也可以看看文件描述符到底是不是从3开始的。(0,1,2默认打开)
int main()
{
umask(0);
int fd1 = open("log.txt", O_RDWR | O_CREAT, 0666);
int fd2 = open("log.txt", O_RDWR | O_CREAT, 0666);
int fd3 = open("log.txt", O_RDWR | O_CREAT, 0666);
int fd4 = open("log.txt", O_RDWR | O_CREAT, 0666);
cout << fd1 << endl << fd2 << endl << fd3 << endl << fd4 << endl;
return 0;
}
int close(int fd);
作用关闭文件。
ssize_t write(int fd, const void *buf, size_t count);
作用:向文件写入信息。
fd:文件描述符
buf:需要写入内容的起始位置
count:需要写入的字节数
返回值:写入成功,返回实际写入的字节数;写入失败,返回-1。
示例:
int main()
{
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
if(fd < 0) perror("open fail");
char buffer1[64] = "hello world\n";
write(fd, buffer1, strlen(buffer1));
close(fd);
return 0;
}
ssize_t read(int fd, void *buf, size_t count);
作用:读取文件中的信息。
fd:文件描述符
buf:需要读取内容的起始地址
count:需要读取的字节数
返回值:读取成功,返回实际读取字节数;读取失败,返回-1。
示例:
int main()
{
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
if(fd < 0) perror("open fail");
char buffer2[64];
read(fd, buffer2, sizeof(buffer2));
cout << buffer2 << endl;
close(fd);
return 0;
}
我们都知道进程创建时同时会创建PCB、mm_struct和页表。其中task_struct中有一个指针,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。
我们我们运行进程打开log.txt文件的时候 我们首先会将文件从磁盘加载到内存当中 并且形成对应的struct file结构体并且连入双链表中然后在file_array中找到一个未被使用的空间 让这个空间指向我们刚刚的struct file结构体。
最后将这个空间的下标返回给进程。所以说只要我们有一个文件的fd就能够对这个文件进行各种操作。
我们之前也测试过了,文件描述符是从3开始的。
原因是:它是从file_array中找到未使用空间的最小下标开始分配,其中0、1、2已经在程序运行时自动打开了。
重定向是把原本输出到一个文件的数据输出到另一个文件。
例如:
int main()
{
close(1);
int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
if(fd < 0) perror("open fail");
fputs("hello worldxxxxxxxxxxxxxxxx\n", stdout);
cout << endl;//刷新缓冲区
close(fd);
return 0;
}
由于我们一开始关闭了stdout(1),我们把原本输出到stdout文件的输出到了log.txt文件中,所以显示器无法打印信息了,必须从log.txt中查看。
这就是一个重定向,我们也可以使用以下命令完成重定向:
[---@VM-8-4-centos day03]$ echo "xxxxxxxxxxxxxxxxx" > log.txt
[---@VM-8-4-centos day03]$ cat log.txt
[---@VM-8-4-centos day03]$ echo "yyyyyyyyyyyyyyyy" >> log.txt
[---@VM-8-4-centos day03]$ cat log.txt
在上面使用重定向时需要首先使用close关闭文件,这样我们感觉总是十分麻烦。系统给我们提供了一个便捷的系统调用:
int dup2(int oldfd, int newfd);
功能:将oldfd中的内容拷贝到newfd中。
ps:如果newfd和oldfd相同,则不做任何操作,返回newfd。
例如:
int main()
{
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0) perror("open fail");
// close(1);
dup2(fd, 1);
printf("dup2 success\n");
return 0;
}
感谢您的阅读,欢迎指正。