重修C语言文件知识打开文件操作fopen函数:
我们看一段代码,以写(w)的方式来打开文件:
#include
#define FILE_NAME "log.txt"
int main()
{
FILE* fp = fopen(FILE_NAME,"w");
if(NULL==fp)
{
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
一开始我们并没有创建文件,程序运行会手动创建一个log.txt的文件
复印输出到文件fprintf函数
#include
#define FILE_NAME "log.txt"
int main()
{
FILE* fp = fopen(FILE_NAME,"w");
if(NULL==fp)
{
perror("fopen");
return 1;
}
fprintf(fp,"%sn","hello world!");
fclose(fp);
return 0;
}
运行程序,fprintf会发送低格输出到流stream中
以w的方法对文件进行操作,文件的内容会被先清空,再进行操作
打开文件操作以读(r)的形式,fgets函数从指定的流stream读取一行,并把它储存在str所指向的字符串内
#include
#include
#define FILE_NAME "log.txt"
int main()
{
FILE* fp = fopen(FILE_NAME,"r");
if(NULL==fp)
{
perror("fopen");
return 1;
}
char buffer[64];
while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)
{
buffer[strlen(buffer)-1]=0;
puts(buffer);
}
fclose(fp);
return 0;
}
这个while循环可写可不写,这样写的作用也就是保证buffer最后一个字符是中止符号
下边输出的结果就是将log.txt中的数据输出下来
4.打开文件操作以追加(a)的形式
#include
#include
#define FILE_NAME "log.txt"
int main()
{
FILE* fp = fopen(FILE_NAME,"a");
if(NULL==fp)
{
perror("fopen");
return 1;
}
int count=5;
while(count)
{
fprintf(fp,"%s:%dn","hello world!!",count--);
}
fclose(fp);
return 0;
}
打开文件相关方法:
5.在linux下新建文件默认权限=0666,遭到umask的影响,实际创建的下来的文件权限是:mask&~umask
C语言有文件的操作插口,这么C++、Java、python、php、GO等语言同样也有文件操作插口,并且它们的插口都不一样。
而文件在哪?在c盘,c盘是硬件,而须要访问硬件都必需要操作系统OS来管理,使用OS给的文件级别的系统调用,操作系统只有一个,并且语言有好多个:库函数底层必须使用系统调用插口、库函数可以变化并且底层不变。
Linux文件知识
我们使用man2来了解有关文件的系统调用知识,它与C语言有哪些不同呢?
标记位传参
通过以下代码来解释哪些是标记位传参:
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
void show(int flag)
{
if(flag & ONE)printf("onen");
if(flag & TWO)printf("twon");
if(flag & THREE)printf("threen");
if(flag & FOUR)printf("fourn");
}
int main()
{
show(ONE);
printf("---------n");
show(TWO);
printf("---------n");
show(ONE | TWO);
printf("---------n");
show(ONE | TWO | THREE);
printf("---------n");
show(ONE | TWO | THREE | FOUR);
return 0;
}
标记对应比特位,每一个宏对应的数值,只有一个比特位是1,彼此不会重叠,假如想要相互结合就或(|),函数上面通过与(&)来判定,这样就相当于可以传入多个参数
文件的系统调用
1.系统调用打开文件open,
pathname:要打开或创建的目标文件
flags:打开文件时,可以传入多个参数选项,用下边的一个或则多个常量进行“或”运算,构成flags。
参数:前三个常量,必须指定一个且只能指定一个
使用系统调用来打开文件O_WRONLY:
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
int fp = open(FILE_NAME,O_WRONLY);
if(fp < 0)
{
perror("open");
return 1;
}
close(fp);
return 0;
}
在C语言中我们使用读的方式打开文件可以直接成功,并且在系统调用中我们使用只使用读的方式访问文件linux命令ls,是会失败的。
我们必需要修改代码为:
int fp = open(FILE_NAME,O_WRONLY | O_CREAT);
修改后创建下来的文件是乱码:
凭哪些觉得Linux一创建文件就根据诸如666、777去创建?我们在C语言中使用的是早已封装过的系统调用,他会手动生成权限,而系统调用没有这种东西,他须要自己去传参,所有我们最后还须要传入作为权限的参数:
int fp = open(FILE_NAME,O_WRONLY | O_CREAT, 0666);
修改umask的值
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fp = open(FILE_NAME,O_WRONLY | O_CREAT,0666);
if(fp < 0)
{
perror("open");
return 1;
}
close(fp);
return 0;
}
我们在创建访问之前将umask的值修改为0,使用我们传入的权限值去初始化,最后这个log.txt的权限值就是666
我们再访问shell中umask的值发觉它还是0002,这是为何,我们不是刚才早已更改了,并且创建下来的文件权限也是根据修改后的umask初始的?这是由于我们程序上面的umask是这个子进程在执行,而与shell这个父进程无关系,子进程只能改变自己的文件权限,所以我们修改的时侯不会影响shell。
向文件写入write
ssize_t是有符号整型linux系统调用,在32位机器上等同与int,在64位机器上等同与longint.
open函数返回值:
我们以读的方式打开(或创建)文件,并复印出open函数的返回值
int main()
{
umask(0);
int fp = open(FILE_NAME,O_WRONLY | O_CREAT,0666);
if(fp < 0)
{
perror("open");
return 1;
}
printf("fp:%dn",fp);
close(fp);
return 0;
}
为何这个复印的值是3呢?且看目录中的理解哪些是文件详尽述说
ssize_t write(int fd, const void *buf, size_t count);
读写文件有两种读写方案:文本类、二补码类(而这种文件读取的分类是语言本身提供的)
而操作系统就很简单粗鲁,直接以void*方法返回,在操作系统看来都是二补码,操作系统只会管你要写几个字节,而不会管你具体内容(不管你是图片还是字符串哪些的,只认二补码)
如果我们也预想C语言那样写入helloword!!并加上数字。一个是字符串、一个是数字,这么我们怎样使用系统调用来实现呢?
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fp = open(FILE_NAME,O_WRONLY | O_CREAT,0666);
if(fp < 0)
{
perror("open");
return 1;
}
int count = 3;
char arr[64];
while(count)
{
sprintf(arr,"%s:%dn","hello world!!",count--);
write(fp,arr,strlen(arr));
}
close(fp);
return 0;
}
我们使用sprintf将字符串写入链表中,之后使用write将链表写入文件
我们的write(fp,arr,strlen(arr));这个代码strlen不须要加1来储存linux cp,倘若加了会出现乱码,由于以作为字符串的结束符是C语言规定的,和系统调用层面的文件操作没有关系
在C语言中,以写的方法打开文件会直接删掉掉原数据,并且在系统调用的时侯则是覆盖式的例如:
修改之前代码为:
sprintf(arr,"%s:%dn","aaaaaa!!",count--);
我们在系统调用还须要传入一个O_TRUNC,就能实现出在C语言中w的疗效:
int fp = open(FILE_NAME,O_WRONLY | O_CREAT | O_TRUNC,0666);
而追加就是将O_TRUNC跟换为O_APPEND
int fp = open(FILE_NAME,O_WRONLY | O_CREAT | O_APPEND,0666);
读文件read函数
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fp = open(FILE_NAME,O_RDONLY);
if(fp < 0)
{
perror("open");
return 1;
}
char buffer[1024];
ssize_t num = read(fp,buffer,sizeof(buffer)-1);
if(num>0)buffer[num]=0;
printf("%s",buffer);
close(fp);
return 0;
}
sizeof(buffer)-1这段作用是为填写中止符留出空间
buffer[num]=0;这个句子的作用是添加结束符(0、、NULL),由于在C语言中的函数会自己写linux系统调用,而系统调用须要我们自己去写
理解哪些是文件
文件操作的本质:进程+被打开文件的关系
进程可以打开多个文件么?答案是肯定的,进程好多,同样系统一定会存在大量的被打开文件,这么那些被打开文件肯定也须要被操作系统OS管理上去,先描述再组织->OS为了管理对应的打开文件,必将会为文件创建对应的内核数据结构标示文件:structfile{}(包含了大部份属性)
我们通过下边代码来理解文件:
#include
#include
#include
#include
#define FILE_NAME(number) "log.txt"#number//把参数转化为字符串,然后合起来
int main()
{
int fd0 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd1 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd2 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd3 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd4 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);
printf("fd: %dn", fd0);
printf("fd: %dn", fd1);
printf("fd: %dn", fd2);
printf("fd: %dn", fd3);
printf("fd: %dn", fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
通过下边的输出结果我们发觉:为何open返回值从3开始?012又去了哪儿?连续的小整数,通常情况我们只在链表下标才有所对应
三个标准输入输出流:
FILE*fp=fopen();这个FILE是结构体,它上面有一个数组是文件描述符
我们降低下边代码:
printf("stdin->fd: %dn", stdin->_fileno);
printf("stdout->fd: %dn", stdout->_fileno);
printf("stderr->fd: %dn", stderr->_fileno);
从输出结果我们可以看出,012在哪是哪些了:三个标准输入输出提早占用了012
为何这种输出的数字是为字段下标?且看右图
通过前面的学习,我们晓得了文件描述符就是一个小整数:
上图可知:文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在显存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个早已打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联上去。每位进程都有一个表针*files,指向一张表files_struct,该表最重要的部份就是包涵一个表针字段,每位元素都是一个指向打开文件的表针!所以,本质上,文件描述符就是该链表的下标。所以,只要拿着文件描述符,就可以找到对应的文件
文件描述符的本质,就是链表的下标!
文件fd的分配规则
我们首先做一个结果剖析:
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
//close(0);
umask(0);
int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd:%dn",fd);
close(fd);
return 0;
}
复印的结果是3,这是我们里面解释过的
我们在main函数刚开始的地方先close(0);瞧瞧输出结果:
我们在main函数刚开始的地方先close(2);瞧瞧输出结果:
我们在main函数刚开始的地方先close(0);close(2);瞧瞧输出结果:
正常情况我们会手动打开三个标准输入输出,我们加载一个文件才会从3开始:
然而,假若我们关掉掉一个会怎样样呢?
假如我们关掉close(1);会出现哪些情况呢?
我们发觉,显示器上并没有复印的结果,这是为何呢?且看下边解释:
printf("fd:%dn",fd);
fprintf(stdout,"fd:%dn",fd);
这两个printf的结果都是一样:
前面结果可以说明printf本质上就是复印输出到stdout中,如今我们将stdout先关掉了,之后创建的新struct_file抢占了先前的stdout的位置,所以我们可以觉得现今的printf输出应当输出到新创建的文件log.txt中,而且我们catlog.txt发觉上面并没有数据,莫非我们的推论是错误的?不是的,这儿是由于缓冲区的缘故。
我们在最后刷新一下fflush(stdout);,我们这儿刷新的是stdout,且最后输出的结果就是fd:1,证明确实是分配的1号位
原本输出复印应当复印到stdout显示器上,并且我们如今关掉close(1);却将复印的结果复印到了新创建的文件中,这些特点称作重定向
重定向
假如我们也想实现跟刚刚一样的显示疗效,将原先复印到显示器上的数据,输出到新建文件中去:
我们须要将1号位中的数据替换为新建文件fd,这么最终留出来的肯定是fd,dup2调用是将newfd中的内容替换为oldfd,留出来的是oldfd,所以我们使用dup2调用须要:dup2(fd,1);
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,1);
printf("fd:%dn",fd);
fprintf(stdout,"fd:%dn",fd);
fflush(stdout);
close(fd);
return 0;
}
这样我们就完成了一个重定向功能
追加重定向O_APPEND+dup2(fd,1);:
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_APPEND,0666);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,1);
printf("fd:%dn",fd);
fprintf(stdout,"fd:%dn",fd);
const char* msg = "hello world";
write(1, msg, strlen(msg));
fflush(stdout);
close(fd);
return 0;
}
持续运行./Test,将输出内容追加到log.txt中
输入重定向dup2(fd,0);
#include
#include
#include
#include
#include
#include
#define FILE_NAME "log.txt"
int main()
{
umask(0);
int fd = open(FILE_NAME,O_RDONLY);
if(fd < 0)
{
perror("open");
return 1;
}
dup2(fd,0);
char arr[64];
while(1)
{
printf("输入> ");
if(fgets(arr, sizeof(arr), stdin) == NULL)break;
printf("%s",arr);
}
close(fd);
return 0;
}
通过dup2(fd,0);将log.txt上面的数据用做标准输入,while循环的作用是将标准输入中的数据掏出来存装入链表中,之后将其复印下来
假如我们在父进程中创建子进程,之后这个子进程做重定向操作会影响父进程吗?
程序替换,同样不会影响当初进程打开过的重定向文件,重定向的各类操作属于内核数据结果,而程序替换则是c盘与显存的代码数据替换,两者不会影响到。
常见的重定向有:>>>>追加重定向
C语言文件层面的缓冲区知识
我们先运行下边代码:
#include
#include
#include
int main()
{
// C语言接口
printf("hello printfn");
fprintf(stdout, "hello fprintfn");
fputs("hello fputsn", stdout);
// 系统调用
const char *msg = "hello writen";
write(1, msg, strlen(msg));
return 0;
}
我们将输出的结果重定向到log.txt文件中,可以发觉复印的结果是一样的
然而假如我们将代码修改一下,在结尾创建子进程:
int main()
{
// C语言接口
printf("hello printfn");
fprintf(stdout, "hello fprintfn");
fputs("hello fputsn", stdout);
// 系统调用
const char *msg = "hello writen";
write(1, msg, strlen(msg));
fork();
//do nothing -> return quit
return 0;
}
还是一样的操作,并且在我们创建子进程后的,将输出重定向到log.txt,我们发觉C语言插口复印了两遍,而系统调用只复印了一次
缓冲区刷线策略问题:
缓冲区一定会结合具体的设备,订制自己的刷新策略
里面的出现的现象,一定与缓冲区有关,且缓冲区一定不在内核中,由于假如在内核中,write也会复印两次。我们之前谈论过的缓存区,都是指的用户级语言层面给我们提供的缓冲区,我们之前的输出输出操作都要传入->stdoutstdinstderr它们都是FILE*类型的,而FILE是一个结构体,这个结构体上面封装了fd还有一个缓冲区,所以我们刷新缓冲区都是fflush(文件表针)、fclose(文件表针)
解释里面现象:
代码结束之前,进行创建子进程
1.假如我们没有进行重定向>,听到了4条消息,stdout默认使用的是行刷新,在进程fork之前,三条C函数早已将数据进行复印输出到显示器上(外设),我们的FILE内部,进程内部不存在对应的数据了
2.假如我们进行了重定向>,写入文件不再是显示器,而是普通文件,采用的刷新策略是全缓冲,之前的3条c显示函数,即使带了n,并且不足以让stdout缓冲区写满,数据并没有被刷新!!!
执行fork的时侯,stdout属于父进程,创建子进程时,紧接着就是进程退出,谁先退出,一定要进行缓冲区刷新(就是更改),更改都会发生写时拷贝,数据最终会显示两份
3.write为何没有呢?里面的过程都和wirte无关,wirte没有FILE,而用的是fd,就没有C提供的缓冲区
如有错误或则不清楚的地方欢迎私信或则评论强调