每日一结
一 文件I/O 和标准I/O
相同点:
都可以实现对文件操作
不同点:
<1>缓存
文件I/O没有缓存,直接和系统调用关联
标准I/O有缓存,它的内部调用了系统调用接口
<2>操作对象
文件I/O操作对象是文件描述符
标准I/O操作对象是流
<3>操作函数
文件I/O : open ,read ,write,lseek,close
标准I/O : fopen/freopen/fdopen , fgetc/fgets/fread ,fputc/fputs/fwrite,fseek/rewind/ftll,fclose
二 文件I/O的操作函数
1.打开文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
@pathname 文件名
@flags 打开的标志
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_CREAT 文件不存在则创建,此时需要指定文件的权限
O_TRUNC 文件存在则清空文件
O_APPEND 追加方式
注意:这些宏可以通过"|"一起来使用
"r" -> O_RDONLY
"r+" -> O_RDWR
"w" -> O_WRONLY | O_CREAT |O_TRUNC,0666
"w+" -> O_RDWR | O_CREAT |O_TRUNC,0666
"a" -> O_WRONLY | O_CREAT |O_APPEND,0666
"a+" -> O_RDWR | O_CREAT |O_APPEND,0666
@mode 指定权限(新建一个文件时,默认的权限)
文件实际的权限 mode & ~(umask)
返回值:
成功返回文件描述符号, 失败返回-1
-----------------------------------------------------------
文件描述符号分配规则: 最小未使用的文件描述符
0
1
2
若close(1),关闭的是标准输出
则open 获得是1
-----------------------------------------------------------
练习:
(1).用文件IO以只写的方式打开文件,文件不存在则创建,文件存在则清空
(2).观察新建文件的权限
2.读写操作
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中尝试读取count个字节到buf中
参数:
@fd 文件
@buf 存放数据的地址
@count 期望读取的字节数
返回值:
成功返回实际读取的字节数,失败返回-1,如果读到文件尾部则返回0
参考man page如下:
-----------------------------------------------------------
ssize_t write(int fd, void *buf, size_t count);
功能:将buf中count个字节写入文件
参数:
@fd 文件
@buf 存放数据的地址
@count 写入的字节个数
返回值:
成功返回实际写入的字节数,失败返回-1
参考man page如下:
具体示例如下:
代码分析如下:
n = write(fd,write_buf,strlen(write_buf));
这条语句的作用是讲buf【】这个数组中的内容写到fd文件标示符(注意,这是系统调用函数,不是标准的I/O,所以不是流)中去,之后遇到这个语句:fd = open(argv[1],O_RDONLY);本已经打开了一次,且是同一个文件,为什么又要把同一个文件再打开一次呢?原因是这样的:因为之前写过一次之后,offset的值已经偏移了,所以若想再将文件中的内容从头开始读走,亦即让offset的值置为0,那么如何才能达到此效果呢?其中一个办法是把文件重新打开,所以也就出现了上述fd那条语句。之后又出现了 n = read(fd,read_buf,sizeof(read_buf));这条语句。其作用是将fd中的内容读到read_buf【】这个数组中,之后出现read_buf[n] = '\0';的原因是因为你若想把它当做字符串输出的话,则需要将其先变成字符串(因为文件中的内容不一定是字符串),亦即在其后加上‘\0’,这样就能达到字符串输出的效果。在这里我有一个疑问就是read_buf[n]中,它为什么不用sizeof(),因为虽然你是读sizeof()个字节,但是,它不一定就会读sizeof()个字节,这只是它的一个期望值而已。若想要知道read()函数成功读取的个数,只要看它的返回值即可。在read_buf[n]中,使用n的原因是数组下标是从0开始的,所以不能是n+1。再一个需要注意的是:这个是文件I/O,不是标准I/O,因为不带缓存,所以不需要刷新。
练习:通过文件I/O实现文件拷贝
3.文件定位
off_t lseek(int fd, off_t offset, int whence);
功能:实现文件定位
参数:
@fd 文件
@offset > 0 向后偏移 , < 0 向前偏移
@whence SEEK_SET,SEEK_CUR,SEEK_END
返回值:
成功返回新的文件偏移值,失败返回-1
练习:
struct student
{
char name[15];
int age;
int id;
};
struct student stu = {"xiaoming",10,100};
struct student stu1;
将stu写入文件,然后从文件中读取内容存放在stu1,输出stu1
参考man page如下:
代码如下:
注:在读写文件时,需要注意文件中offset的值的变化,若读写同一个文件,记得将其offset的值置为你想要操作的位置。
三 文件属性的获得
int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:
@path 文件所在路径
@buf 存放文件的属性
返回值:
成功返回0,失败返回-1
查看man page 如下:
-----------------------------------------------------------
具体示例如下:
getpwuid()函数,查看man page如下:
getgrgid()函数,查看man page 如下:
localtime()函数,查看man page如下:
代码分析如下:
I. 为什么不直接使用stat()中的成员,而是调用上述函数?因为若直接使用stat()中的成员的话,则它对应输出的是数字,而达不到我们想要的像ls那样格式输出的效果。所以就调用了上述的那些函数。
II. 对于第二种方法移位的解释:
对于第一种方法,我们好理解。对于第二种方法,其解释如下:对于文件的属性,我们从man page中可以知道,是通过st_mode的16个位来标识文件的属性的,所以,通过分析我们知道,最外层的那个循环是将每个大的文件所有者/组/其他组 依次移到最低的前三位,对于里面。那层循环,我们举个例子来理解吧!
for(I=0;i<3;i++)
data&(1<<i);
从上述那条语句中,我们分析可以知道,data的可能值可能有0,1,2,4所以通过内层那个循环,我们可以得到的目的是:是将对应的某一个组的
rwx分别通过data&(1<<i)这种方法移出来。
III.从st_mode中,我们可以知道,它是用16位来标识其文件属性的。通过得到的文件的st_mode & 对应的属性的宏,即可得到这个文件对应的单个的属性。
四 晚间作业
./a.out 目录名
->显示目录下每个文件的属性信息
./a.out 普通文件名
->显示普通文件的属性信息
---------------------------------------------------
1.怎么判断用户传递的文件名是普通文件还是目录文件?
获取文件的属性信息,然后判断文件类型
2.如何对目录进行操作呢?
<1>打开目录文件
DIR *opendir(const char *name);
功能:打开一个指定的目录文件
参数:
@name 目录名
返回值:
成功返回目录流,失败返回NULL
<2>读一个目录文件
void disply_dir(const char *dirname)
--------------------------------------------
struct dirent *readdir(DIR *dirp);
struct dirent *pdirent;
while( (pdirent = readdir(pdir)) != NULL)
{
printf("%s\n",pdirent->d_name);
}
测试:
./a.out file1
./a.out .
./a.out /home/linux
代码示例如下:
关注微信公众号获取更多资讯