一、概论
(一)为什么要学习IO?
- 目的:为了将数据存储以及对于文件的操作
- 注意:
文件:普通文件或者硬件设备(Linux下一切皆文件) - Linux下的7种文件属性:
(1)普通文件 -
(2)目录文件 d
(3)符号链接文件(软链接): l
(4)字符设备文件:c
(5)块设备文件 :b
(6)管道文件:p
(7)套接字文件:s
(二)IO的分类
第一类:标准IO(高级磁盘IO)
- ANSI(美国标准协会)联合ISO(国际化标准组织)所形成的的一个C标准(C标准:属于C库,含有一些列输入输出函数),只要操作系统支持C库,就可以使用这一类标准IO提供的函数(移植性比较高)。
- 操作的文件一般是普通文件。
- 属于高级磁盘IO。存在缓冲区,减少了用户态切换至内核态,最后又返回用户态这样的频繁操作,意味着减少了系统开销。
- 通过文件流(FILE *)操作文件(打开文件时,系统会自动将该文件的信息定义结构体类型struct FILE 来进行存储,因此可以通过FILE *文件指针来操作文件)
第二类:文件IO(低级磁盘IO)
- POSIX(可移植操作系统接口)推出的对于支持POSIX标准的系统可以操作文件的一系列函数(UNIX系统一般都会支持POSIX的标准)。移植性不高(只能应用于UNIX系统)
- 操作的文件可以普通文件或者设备文件(硬件)。
- 低级磁盘IO。没有缓冲区,每一次都是系统调用,都会存在用户空间和内核空间的频繁切换工作,好处就是可以直接对于设备进行读写操作。
- 通过文件描述符来(非负的数字)操作文件。
(三)系统调用和缓冲机制
注意:
库函数和系统调用函数的关系:大多数库函数都是由系统调用函数封装起来的。
二、标准IO
(一)流
- 概念:文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为“流”。
- 分析:为啥称结构体指针为流?
因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据(每一次的放入数据:均是需要通过文件指针来进行读写指向的文件),存在三个特点:
(1)有源头:APP
(2)有目的:缓冲区
(3)持续性:不断放入数据到缓冲区
一旦具备以上3个特点,就会形成流,所以通过文件指针操作文件可以理解为是通过操作流来操作文件。
(二)流的分类
文件被打开的时候,会默认具备3个类:
- stdin(标准输入:键盘) -->0
- stdout(标准输出:终端) -->1
- stderr(标准出错:会向终端打印,不带缓冲区(意味着每一次出错就会立即刷新缓冲区)) -->2
因此:当用户以后想要操作文件时,就需要先打开文件,此时会拥有一个文件流指针,以后通过该文件指针就可以操作文件。
(三)缓冲机制
- 全缓冲
缓冲区被放满,程序结束,强制刷新:会引起缓冲区的刷新 - 行缓冲
缓冲区被放满,程序结束,强制刷新,遇到换行符:会引起缓冲区的刷新 - 不带缓冲
不存在缓冲区的概念:每一次读写都是直接输出:stderr
(1)缓冲区被放满:
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
while(1)
{
printf("hello");
//延时函数
sleep(1);
}
return 0;
}
此时处于不断向缓冲区放hello,延时时间为1秒钟1次,因此当缓冲区没有放满时,不会刷新缓冲区,也就看不到hello。
(2)程序结束:
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("hello");
return 0;
}
(3)强制刷新:
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
while(1)
{
printf("hello");
sleep(1);
//强制刷新
fflush(stdout);
}
return 0;
}
一秒钟后再强制刷新输出流
(四)操作文件
1、流程
提供给用户来操作文件的方式有3种:
不管是哪种方式来操作文件,流程为:
(1)打开文件FILE *fopen(const char *path,const char *mode);
- 参数1:需要打开文件的名字(可以包含路径,指针类型)
- 参数2:打开文件的方式(指针类型)
- 返回值:
打开文件成功之后的文件流指针
失败为NULL
打开文件代码如下:
#include <stdio.h>
int main(int argc, const char *argv[])
{
//打开一个指定文件
//char *FileName = "1.c";
//char *Mode = "r";
FILE *fr = fopen(argv[1],"r");
//FILE *fr = fopen("2.c","r");
//FILE *fr = fopen(FileName, Mode);
//判断fr的值
if(NULL == fr)
{
perror("fopen error");
return -1;
}
printf("fopen_read ok!\n");
//关闭文件
fclose(fr);
return 0;
}
(2)操作文件:读写文件
3种方法:按照字符操作、按照行操作、按照块操作
(3)关闭文件
头文件:#include <stdio.h>
函数原型:int fclose(FILE *stream);
因此:打开文件和关闭文件在标准IO下都是使用同一个接口,只是在操作文件的方式上存在选择.
2、按照字符操作
(1)函数名:fgetc()
fputc
(2)头文件:#include <stdio.h>
(3)函数原型:
int fgetc(FILE *stream);
- 功能:从指定的文件流中获取一个字符
- 参数:指定获取一个字符所处文件的文件流
- 返回值:
成功返回获取到的字符值
读取到文件末尾返回EOF(-1)
操作中失败返回负数
int fputc(int c, FILE*stream);
- 功能:向指定的文件流中输出一个字符
- 参数:
参数1:需要输出的指定字符(字符被称为单字节的整形)
参数2:指定输出字符到的文件对应的文件流 - 返回值:
成功返回刚写入的字符值
失败返回EOF(-1)
案例:
实现对于指定一个文件读取,读取方式为按照字符操作,将读取完毕之后的结果显示在终端上。(自己实现命令cat 文件名)
思路:
1、打开需要显示文件内容所在的文件
2、获取一个字符
3、输出一个字符
4、重复2、3直至文件末尾截止
5、关闭文件
代码:
#include <stdio.h>
//功能:实现命令cat的功能
int main(int argc, const char *argv[])
{
//入参检查
if(argc < 2)
{
printf("传参过少!\n");
return -1;
}
//打开需要读取的文件
FILE * fr = fopen(argv[1], "r");
if(NULL == fr)
{
perror("fopen_read error");
}
printf("fopen_read success!\n");
//借助于循环完成读取并显示
while(1)
{
//读取
char chr = fgetc(fr)