网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
3.fread写文件
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 int main()
5 {
6 FILE\* fp=fopen("test.txt","w+");
7 if(fp==NULL)
8 {
9 printf("文件打开失败!\n");
10 exit(1);
11 }
12 fwrite("bit person!",sizeof(char),11,fp);
13 fseek(fp,0,SEEK\_CUR);
14 char arr[100];
15 fread(arr,sizeof(char),strlen(arr),fp);
printf("%s\n",arr);
16 fclose(fp);
17 return 0;
18 }
4.fwrite写文件
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 int main()
5 {
6 FILE\* fp=fopen("test.txt","a+");
7 if(fp==NULL)
8 {
9 printf("打开文件失败!\n");
10 exit(1);
11 }
12 char \*arr="bit education!\n";
13 fwrite(arr,sizeof(char),strlen(arr),fp);
14 fclose(fp);
15 return 0;
16 }
5.fseek移动文件指针
6.ftell获取文件指针当前位置
7.rewind让文件指针回到文件起始位置
8.fcloes关闭文件
9.输出信息到显示器方法
fwrite(msg,strlen(msg),sizeof(char),fp);//往log.txt文件里面写
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 FILE \*fp=fopen("log.txt","w");
6 if(NULL==fp)
7 {
8 printf("打开文件失败!\n");
9 }
10 else
11 {
12 /\* char c='A';
13 for(;c<'Z';c++)
14 {
15 fputc(c,fp);
16 }\*/
17 const char\* msg="Hello bit!\n";
18 // fwrite(msg,strlen(msg),sizeof(char),fp);//往log.txt文件里面写
19 fwrite(msg,strlen(msg),sizeof(char),stdout);//输出,往显示屏上输出
20
21 }
22 fclose(fp);
23 return 0;
24 }
fwrite(msg,strlen(msg),sizeof(char),stdout);//输出,往显示屏上输出
10.stdin & stdout & stderr
- C默认会打开三个输入输出流,分别是stdin, stdout, stderr
- 仔细观察发现,这三个流的类型都是FILE, fopen返回值类型,文件指针
二、系统文件I/O
1.read和write的初次使用
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
写文件
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<string.h>
7 int main()
8 {
9 int fd=open("myfile",O_WRONLY|O_CREAT,0644);
10 if(fd<0)
11 {
12 printf("文件打开失败!\n");
13 exit(1);
14 }
15 char\* arr="bit education!";
16 write(fd,arr,strlen(arr)-1);
17 close(fd);
18 return 0;
19 }
读文件
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<fcntl.h>
5 #include<sys/stat.h>
6 #include<unistd.h>
7 int main()
8 {
9 int fd=open("myfile.txt",O_RDONLY);
10 if(fd<0)
11 {
12 printf("文件打开失败!\n");
13 exit(1);
14 }
15 char\* arr="hello bit!";
16 char buff[100];
17 read(fd,buff,strlen(arr)-1);
18 printf("%s\n",buff);
19 close(fd);
20 return 0;
21 }
2.接口介绍
open man open
#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。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
mode_t理解:直接 man 手册,比什么都清楚。
open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
3.open函数返回值
- 在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数
- 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
- 而open close read write lseek 都属于系统提供的接口,称之为系统调用接口。
- 回忆一下我们讲操作系统概念时,画的一张图。
系统调用接口和库函数的关系,一目了然。所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。
4.文件描述符fd(file descriptor)
- 通过对open函数的学习,我们知道了文件描述符就是一个小整数。
5.0 & 1 & 2
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。
- 0,1,2对应的物理设备一般是:键盘,显示器,显示器。
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 int main()
7 {
8 char buff[100];
9 int fd=read(0,buff,sizeof(buff));
10 write(1,buff,strlen(buff)-1);
11 write(2,buff,strlen(buff)-1);
12 return 0;
13 }
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
6.文件描述符的分配规则
输出发现fd是3:
1 #include<iostream>
2 #include<fcntl.h>
3 #include<sys/stat.h>
4 #include<sys/types.h>
5 #include<unistd.h>
6 int main()
7 {
8 int fd=open("myfile",O_RDONLY|O_CREAT);
9 if(fd<0)
10 {
11 std::cout<<"文件打开失败!"<<std::endl;
12 }
13 std::cout<<fd<<std::endl;
14 close(fd);
15 return 0;
16 }
关闭0或者2,在看:
1 #include<iostream>
2 #include<unistd.h>
3 #include<fcntl.h>
4 #include<sys/stat.h>
5 #include<sys/types.h>
6 int main()
7 {
8 close(0);
9 // close(2);
10 int fd=open("myfile",O_RDONLY|O_CREAT,0664);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 return 1;
15 }
16 std::cout<<"fd:"<<fd<<std::endl;
17 close(fd);
18 return 0;
19 }
发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
----------------------最小未分配原则----------------------
7.重定向
1.清空重定向
我们发现第一次写的hello world被第二次写的你好呀覆盖了,这就是清空重定向。
2.追加重定向
我们发现第一次写的hello没有被第二次写的你覆盖,而是追加在hello后面,这就是追加重定向。
3.重定向本质
那如果关闭1呢?看代码:
1 #include<iostream>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 close(1);
10 int fd=open("myfile",O_WRONLY|O_CREAT,0644);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 exit(1);
15 }
16 std::cout<<"fd:"<<fd<<std::endl;
17 close(fd);
18 return 0;
19 }
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <
- 那重定向的本质是什么呢?
8.FILE
- 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
- 所以C库当中的FILE结构体内部,必定封装了fd。
来段代码在研究一下:
1 #include<iostream>
2 #include<fcntl.h>
3 #include<sys/stat.h>
4 #include<sys/types.h>
5 #include<string.h>
6 #include<unistd.h>
7 #include<stdio.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 return 1;
15 }
16 const char\* str1="hello printf\n";
17 const char\* str2="hello fwrite\n";
18 const char\* str3="hello write\n";
19 write(1,str3,strlen(str3));
20 printf("%s\n",str1);
21 fprintf(stdout,"%s\n",str2);
22 fork();
23 close(fd);
24 return 0;
25
26
27 }
但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:
- 我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!
- 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
- printf、fwrite库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
- 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。
- 但是进程退出之后,会统一刷新,写入文件当中。
- 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
- write 没有变化,说明没有所谓的缓冲。
综上: printf、fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
如果有兴趣,可以看看FILE结构体:
typedef struct _IO_FILE FILE; 在/usr/include/stdio.h
在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. /
#define _IO_file_flags _flags
//缓冲区相关
/ The following pointers correspond to the C++ streambuf protocol. /
/ Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. /
char _IO_read_ptr; /* Current read pointer /
char _IO_read_end; /* End of get area. /
char _IO_read_base; /* Start of putback+get area. /
char _IO_write_base; /* Start of put area. /
char _IO_write_ptr; /* Current put pointer. /
char _IO_write_end; /* End of put area. /
char _IO_buf_base; /* Start of reserve area. /
char _IO_buf_end; /* End of reserve area. /
/ The following fields are used to support backing up and undo. */
char _IO_save_base; / Pointer to start of non-current get area. */
char _IO_backup_base; / Pointer to first valid character of backup area */
char _IO_save_end; / Pointer to end of non-current get area. /
struct _IO_marker _markers;
struct _IO_FILE _chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; / This used to be _offset but it’s too small. /
#define __HAVE_COLUMN / temporary /
/ 1+column number of pbase(); 0 is unknown. /
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/ char _save_gptr; char _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
9.使用 dup2 系统调用
函数原型如下:
#include <unistd.h>
int dup2(int oldfd, int newfd);
1 #include<iostream>
2 #include<fcntl.h>
3 #include<sys/stat.h>
4 #include<sys/types.h>
5 #include<string.h>
6 #include<unistd.h>
7 #include<stdio.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);
11 if(fd<0)
12 {
13 std::cout<<"文件打开失败!"<<std::endl;
14 return 1;
15 }
16
17 dup2(fd,1);
//close(fd);
18 const char\* str1="hello printf\n";
19 const char\* str2="hello fwrite\n";
20 const char\* str3="hello write\n";
21 write(1,str3,strlen(str3));
22 printf("%s\n",str1);
23 fprintf(stdout,"%s\n",str2);
24 fflush(stdout);
25 close(fd);
26 return 0;
27
28
29 }
10.理解文件系统
我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据。
- 每行包含7列:
- 模式
- 硬链接数
- 文件所有者
- 组
- 大小
- 最后修改时间
- 文件名
ls -l读取存储在磁盘上的文件信息,然后显示出来
其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
上面的执行结果有几个信息需要解释清楚:
inode:
为了能解释清楚inode我们先简单了解一下文件系统:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!