C语言文件读写
写文件
1 #include <stdio.h>
2 #include <string.h>
3
4
5 int main()
6 {
7 //w:文件不存在会自动创建,先清空文件,再写入
8 //a:we\\文件不存在自动创建,会在文件的末尾继续写入
9 //FILE* p = fopen("log.txt","w");
10 FILE* p = fopen("log.txt","a");
11 const char* inf = "welcome to read\n";
12 fwrite(inf,strlen(inf),1,p);
13 //fprintf(p,"%s",inf);
14 fclose(p);
15 return 0;
16 }
未指定路径会在默认在当前路径,为什么?
进程属性cwd(current work directory)会有路径的信息,如果我们修改这个属性,文件就会创建到修改后的路径下:
当前路径下并没有创建文件
到我们修改的路径下面发现了我们写入的文件
读文件
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4
5
6 int main()
7 {
8 char* msage[100];
9 FILE* p = fopen("log.txt","r");
10 fread(msage,1,sizeof(msage),p);
11 printf("%s\n", msage);
12 fclose(p);
13 return 0;
14 }
系统操作文件的接口
每一种语言可以对文件进行读写,它们的库函数都是对系统调用的封装。主要有这几个接口
open
参数:
pathname是文件路径,如果只有文件名默认当前路径
flags是可以指定函数功能的参数,主要有这几个宏
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 O_APPEND: 追加写
mode指定新建文件的权限
返回值:
成功:新打开的文件描述符
失败:-1
flag的传递类似于位图,是一种比特位的标志位传参,比如:
1 #include <stdio.h>
2 #define fun1 1
3 #define fun2 1<<1
4 #define fun3 1<<2
5 #define fun4 1<<3
6
7 void fun(int n)
8 {
9 if(n&1) printf("fun1\n");
10 if(n&2) printf("fun2\n");
11 if(n&4) printf("fun3\n");
12 if(n&8) printf("fun4\n");
13
14 }
15 int main()
16 {
17 fun(fun1|fun3);
18 return 0;
19 }
write
read
close
使用样例:
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7
8 int main()
9 {
10 int fd = open("log.txt", O_RDWR|O_CREAT|O_APPEND,0666);
11 if(fd < 0)
12 {
13 return -1;
14 }
15 const char* inf = "writing success!!!\n";
16 write(fd,inf,strlen(inf));
17 char buf[1024];
18 int fd_r = open("log.txt",O_RDONLY);
19 while(1)
20 {
21 ssize_t ret = read(fd_r, buf, strlen(inf));
22 if(ret > 0)
23 printf("%s",buf);
24 else
25 break;
26 }
27 close(fd_r);
28 close(fd);
29 return 0;
30 }
原理
文件被打开时,操作系统会为文件创建结构体。
当进程要访问文件,进程的PCB有文件描述符表指针,会从该表中寻找空元素,指向文件结构体
进程启动,操作系统都会为我们打开三个文件,stdin,stdout,stderr.可以验证一下:
文件重定向
文件描述符的分配规则
stderr对应的描述符编号是2,将它关闭后再打开文件,新文件的描述符编号是2.这说明了文件描述符的分配规则是:在files_struct中寻找最小的未被使用的下标,作为新文件的描述符
重定向
将stdout关闭后,新建的文件流的文件描述符就是1,当我们再次向stdout输入时,就会将内容放到文件里。这就是输出重定向。
重定向系统调用接口——dup2
newfd是oldfd的拷贝,所以这个接口是用oldfd替换newfd,特别容易混淆
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7
8 int main()
9 {
10 int fd = open("log.txt",O_WRONLY|O_TRUNC|O_CREAT, 0666);
11 dup2(fd,2);
12 printf("%d\n", fd);
13 const char* message = "writing!!!!\n";
14 ssize_t ret = write(2,message, strlen(message));
15 if(ret < 0)
16 {
17 perror("write fail\n");
18 }
19 return 0;
20 }
重新理解,一切皆文件
硬件驱动会提供对应的操作接口,操作系统会在文件结构体创建的时候初始化。文件结构体存储在文件描述符表中,进程对硬件的使用对上层的角度来看就是对文件的操作,所以说一切皆文件。
文件缓冲区
示例
看这段代码的结果:
为什么只有系统调用接口write可以输出呢?这个答案与文件缓冲区有很大的关系。
原理
每种编程语言都有自己的文件操作接口,以c语言为例:
fprintf() printf fwrite等等c接口,在操作完成后都会将数据放到c语言自己维护的缓冲区中,只有在一定条件下(后面说)才会刷新缓冲区到操作系统的缓冲区,最后由操作系统传给硬件设备,例如磁盘,显示器等。
所以刚才的代码里最后一行,直接将stdout关闭,直接将操作系统缓冲区刷新到硬件设备,而c语言自己的缓冲区由于之前未刷新到操作系统缓冲区,关闭直接就找不到操作系统的缓冲区了,更别提将数据传输到硬件设备上了。
缓冲区什么时候刷新?
缓冲区的刷新分为三种:
无缓冲——直接刷新
行缓冲——直到碰到\n才刷新(显示器)
全缓冲——缓冲区满了才刷新(写文件)
当进程退出时也会刷新缓冲区。
为什么要有缓冲区?
提高效率,将多个数据放到缓冲区一起传输会比单个数据多次传输效率高。就好像快递都是一车一车运送的,而不是一个包裹一个包裹运送的。
配合格式化,当我们向显示器打印一个数字时,例如,printf("%d," 10),最终要显示的其实是两个字符
再次理解缓冲区
为什么重定向输出到文件,c语言文件接口输出的内容被写了两遍?
fork创建子进程,子进程会以写时拷贝的方式创建,缓冲区也不例外(每个进程都有缓冲区), 在进程结束后,每个进程都会刷新到文件中,只有系统调用write写的内容在操作系统缓冲区中,只有一份。