引言:
C语言是怎样进行文件操作的?
#include<stdio.h>
#include<string.h>
int main(){
FILE* fp=fopen("test.txt","w");
if(!fp){
printf("fopen error!\n");
}
const char msg="hello world\n";
int count=5;
while(count--){
fwrite(msg,strlen(msg),1,fp);
}
close(fp);
return 0;
}
对文件的读写需要用到 fopen、fread、fwrite 等系统底层函数,而用户进程每调用一次系统函数都要从用户态切换到内核态,等执行完毕后再返回用户态,这种切换要花费一定时间成本(对于高并发程序而言,这种状态的切换会影响到程序性能)。
读取文件:
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("test.txt", "r");
if (!fp)
{
printf("fopen error!\n");
}
char buf[1024];
const char* msg = "hello world!\n";
while (1)
{
size_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;
}
fread是将文件的数据读到缓冲区里
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
buffer是自己设定的缓冲区
size是要读取数据的大小,char就是1,int就是4
count是要读取的个数
stream是文件指针
返回值是实际从文件中读取的基本单元个数
feof用检测流上的文件结束符:如果遇到文件正常结束,函数返回值为非零值;如果文件异常结束,函数返回值值为0。
C程序在启动的时候会默认启动三个输入输出流,他们分别是:
extern FILE* stdin; //键盘
extern FILE* stdout; //显示器,可以打印到显示器上
extern FILE* stderr; //标准错误,收集错误,也属于显示器
他们三个在代码层面上都是文件指针的类型,也就是*FILE
很多情况下,操作系统中存在着许多被同时打开的文件。这些被打开的文件都是由磁盘打开的,操作系统需要对文件进行管理。
如何管理?
通过某种描述文件属性的数据结构,最后转换成指针之间的映射;从操作文件改为操作指针
文件=属性+内容,
以w打开文件的话,文件如果不存在,就在当前路径下新建指定文件,且默认打开文件的时候会把目标文件先清空
输出重定向伴随着文件操作
理解文件
操作文件的本质是进程在操作文件
文件一开始在磁盘上,本质上在硬件部分的存储
但是用户没有权利直接写入,操作系统是硬件的管理者,用户需要通过操作系统进行写入
操作系统为我们提供系统调用的接口,用的是C/C++对系统调用接口的封装
访问文件也是通过系统调用来访问的
系统如何访问
open:
在
open
系统调用中,flags
是一个由多个标志位组合而成的整数,常见的标志包括:
O_RDONLY
: 以只读模式打开文件。O_WRONLY
: 以只写模式打开文件。O_RDWR
: 以读写模式打开文件。O_CREAT
: 如果文件不存在,则创建文件。需要指定mode
参数。O_EXCL
: 如果文件已经存在,则返回错误(与O_CREAT
一起使用)。O_TRUNC
: 如果文件已存在,并且以写模式打开,则将文件截断为零长度。O_APPEND
: 以追加模式打开文件,将数据写入文件末尾。
系统的接口:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
int fd = open("log.txt", O_WRONLY | O_CREAT);
if (fd < 0)
{
perror("open fail");
return 1;
}
return 0;
}
执行一下:
在Linux中创建文件的时候要告诉它起始权限,否则将会是乱码
这样改:
#include<stdio.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 fail");
return 1;
}
return 0;
}
最后的权限是由我们给定的起始权限和umask掩码共同决定的
umask掩码也可以
我们自己设定:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);//修改umask
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0)
{
perror("open fail");
return 1;
}
close(fd);//记得关掉
return 0;
}
关闭文件的接口:
open函数中的第二个参数:O_WRONLY | O_CREAT是宏,代表的是打开文件以读取模式
还有别的:O_WRONLY,只写;O_RDWR,读写
open会 返回一个文件描述符(非负整数),用于后续的读写操作。打开文件后,记得使用
在操作系统设计中,系统调用接口可能会用到标志位来指示特定的功能或选项。使用比特位传递标志位可以有效地利用每个整数的多个位,从而在一个整数中存储多个独立的标志位,理解下来其实就像用二进制来表示有还是无
这种做法提高了效率,并减少了需要传递的数据量。例如,一个32位的整数可以用32个位来表示32个不同的标志位,这样只需传递一个整数,就可以传递多个开关状态。
我们也可以自己写一个具有这种传递位图标记位的函数:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
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(ONE | TWO);
printf("\n");
Print(ONE | TWO | THREE);
printf("\n");
Print(ONE | TWO | THREE | FOUR);
printf("\n");
return 0;
}
可以用标记位组合的方式向一个函数传递多个标记位(比如只传递1,就只打印1;传递1、2、3和4,就启用上面函数的对应部分)
我们把文件打开以后就涉及到写入了
写入的接口函数是:write
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0)
{
perror("open fail");
return 1;
}
const char* message = "Hello Linux file\n";
write(fd, message, strlen(message));
close(fd);
return 0;
}
执行结果:
注意:当出现这个提示的时候说明文件权限对于你来说没有“写”权限,需要自己chmod设置一下