【1】什么是IO
#include <stdio.h>
input:输入,从外部存储设备输入到内存中
output: 输出,从内存输出到外部存储设备。
> 存储设备:
>
> 外部存储设备,硬盘
>
> 内存:SDROM DDR4;
总结:数据从外部存储设备到内存,内存到外部存储设备的流动
【2】IO分类
1.文件IO
文件IO:由操作系统提供,与操作系统绑定,又称之为系统调用
| | windows | linux |
| ---- | ---------- | ----- |
| | file_read | read |
| | file_write | write |
注意:
1. 文件IO的复用性,可移植性较低
2. 文件IO涉及到cpu从用户空间切换到内核空间,C代码调用汇编语言等等操作,是一个**耗时的操作**,所以应该尽量减少文件IO的使用。
2.标准IO
标准IO:根据ANSI标准对文件IO进行二次封装,
printf();
if(OS == windows)
{
file_write;
}
else if(OS == Linux)
{
write;
}
作用:
1. 提高代码的复用性和可移植性;
2. 提高了输入输出的效率;
设置了一个缓冲区,缓冲区满或者满足一定条件后,调用文件IO,陷入内核,由内核完成对文件的输入输出,大大减少了对文件IO的使用。
2. 标准IO
【1】流和流指针
流(stream):将数据一个一个移入或者移出缓冲区的形式,称之为字节流;
流指针(FILE*):每打开一个文件,都会在内存中申请一片空间(缓冲区);
管理这片空间的变量都存储在FILE结构体中,FILE结构体由系统定义好了,我们使用即可;
1. FILE结构体成员
> vi -t指令 查找系统定义好的数据类型 和 变量 宏
>
> 1)sudo apt-get install ctags
>
> 2)cd /usr/include/ 如果只需要查看标准库中的内容
>
> 3)sudo ctags -R
>
> 4)在家目录的.vimrc中添加 set tags+=/usr/include/tags
>
> 追代码:
>
> ctrl + 鼠标左键
>
> 或者 左键选中要追的代码 ctrl + ]
>
> ```c
> $ vi -t FILE
> typedef struct _IO_FILE FILE;
> ctrl + 鼠标左键点击 _IO_FILE
> ```
```c
struct _IO_FILE {
char* _IO_buf_base; /* Start of reserve area. */ 缓冲区的起始地址
char* _IO_buf_end; /* End of reserve area. */ 缓冲区的结束地址
int _fileno; 文件描述符
}
```
2. 标准输入输出流
在main函数启动之前。系统会默认打开三个流指针;
```
stdin 标准输入流指针 从终端读取数据
stdout 标准输出流指针 将数据输出到终端上
stderr 标准错误输出流指针
```
3. man手册
$man man 查看man手册
```c
1 可执行程序或 shell 命令
2 系统调用(内核提供的函数) 文件IO
3 库调用(程序库中的函数) 标准IO
man 手册默认会从第一卷开始查找,
【2】标准IO函数
1. 常见的标准IO函数
fopen / fclose 打开/关闭一个文件
fprintf / fscanf
fputc / fgetc
fgets / fputs
fwrite / fread
fseek 修改文件偏移量
1)fopen
功能:打开一个文件;
头文件:
#include <stdio.h>
原型:
FILE *fopen(const char *pathname, const char *mode);
参数:
char *pathname:指定要打开的文件路径+文件名;
char *mode:打开方式;
r 以读的方式打开文件;
如果文件不存在,则打开失败;
r+ 以读写的方式打开文件;
如果文件不存在,则打开失败;
w 以写的方式打开文件;
如果文件不存在则创建文件;
如果文件存在则清空文件;
w+ 以读写的方式打开文件;
如果文件不存在则创建文件;
如果文件存在则清空文件;
a 以写的方式打开文件;
如果文件不存在则创建文件;
如果文件存在则以追加的方式写;
a+ 以读写的方式打开文件;
如果文件不存在则创建文件;
如果文件存在则以追加的方式写;
返回值:
成功,返回流指针;
失败,返回NULL,并设置错误码(errno);
例子:
FILE* fp = fopen("./1.txt", "r");
2)perror
功能:根据错误码errno,显示不同的错误信息;
头文件:
#include <stdio.h>
原型:
void perror(const char *s);
参数:
char *s:用于提示的字符串;
#include<errno.h>
errno:本质上是一个全局变量,对应了各种错误,perror就是根据errno打印错误信息的
cd /usr/include/asm-generic/
例子:
perror("fopen");
3)fclose
功能:关闭文件,销毁缓冲区;
头文件:
#include <stdio.h>
原型:
int fclose(FILE *stream);
参数:
FILE *stream:指定要关闭的流指针;
返回值:
成功,返回0;
失败,返回EOF(-1),更新errno;
4)fprintf
功能:将数据格式化输入到文件中;
头文件:
#include <stdio.h>
原型:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
参数:
FILE *stream:指定要输出到的流指针;
char *format:格式化;
...:不定参数;
返回值:
成功,返回被打印的字节个数; >0;
失败,返回负数;
5)fscanf
功能:将文件中的数据格式化输入到内存中;
头文件:
#include <stdio.h>
原型:
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
参数:
FILE *stream:指定要输入的流指针;
char *format:格式化;
...:不定参数;
返回值:
成功,返回获取到的数据个数;
失败或者读取到文件结尾,返回EOF;
6)fputc
功能:将单个字符输出到文件中;
头文件:
#include <stdio.h>
原型:
int putchar(int c);
int fputc(int c, FILE *stream);
参数:
int c:指定要输出的字符,可以填字符类型,也可以填对应的整型;
FILE *stream:流指针;
返回值:
成功,返回输出的字符对应的整型;
失败,返回EOF;
7)fgetc
功能:从文件中获取单个字符;
头文件:
#include <stdio.h>
原型:
int getchar(void);
int fgetc(FILE *stream);
参数:
FILE *stream:流指针;
返回值:
成功,返回获取到的字符;
失败或者读取到文件结尾,返回EOF;
8)缓冲区
标准IO中所有的数据都输存储在缓冲区中,然后输出到硬件中
i. 全缓冲
**操作对象**
对普通文件进行操作(调用fopen函数打开的文件),通过FILE* fp流指针维护该缓冲区;
**大小**
4K = 4096byte = 4*1024;
//由于系统优化,当打开文件,但是不操作的时候,不会真正的申请缓冲区
fputc('a', fp);
printf("%ld\n", fp->_IO_buf_end - fp->_IO_buf_base); //4096
**刷新缓冲区的方式**
1. 缓冲区满
2. fflush函数强制刷新缓冲区
#include <stdio.h>
int fflush(FILE *stream);
3. 关闭流指针 fclose
4. 主函数调用retrun退出
5. 调用exit函数
#include <stdlib.h>
void exit(int status);
目前只要理解该函数能退出进程;
int status:随便填一个整型即可;
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
FILE* fp = fopen("./1.txt", "w");
if(NULL == fp)
{
perror("fopen");
return -1;
}
/*
//由于系统优化,当打开文件,但是不操作的时候,不会真正的申请缓冲区
fputc('a', fp);
printf("%ld\n", fp->_IO_buf_end - fp->_IO_buf_base); //4096
*/
/*
//缓冲区满
int i = 0;
while(i<4096+1)
{
fputc('a', fp);
i++;
}
*/
/*
//fflush强制刷新缓冲区
fputc('b', fp);
fflush(fp);
*/
/*
//关闭流指针
fputc('c', fp);
fclose(fp);
*/
/*
fputc('d', fp);
return 0;
*/
fputc('e', fp);
exit(0);
while(1)
sleep(1);
fclose(fp);
return 0;
}
ii.行缓冲
**操作对象**
标准输入(stdin),标准输出(stdout)
**大小**
1K = 1024byte
```c
printf("%ld\n", stdout->_IO_buf_end - stdout->_IO_buf_base);
```
**刷新缓冲区**
1. 缓冲区满
2. fflush函数强制刷新缓冲区
```c
#include <stdio.h>
int fflush(FILE *stream);
```
3. 关闭流指针 fclose
4. 主函数调用retrun退出
5. 调用exit函数
```c
#include <stdlib.h>
void exit(int status);
目前只要理解该函数能退出进程;
int status:随便填一个整型即可;
```
6. 遇到'\n'字符
7. 遇到输入函数,例如scanf fgets fscanf fgetc
iii.无缓冲
**操作对象**
标准错误输出(stderr)
**大小**
0, perror调用的就是stderr流指针
9)fputs
功能:将字符串输出到文件中;
头文件:
#include <stdio.h>
原型:
int puts(const char *s);
int fputs(const char *s, FILE *stream);
参数:
char *s:指定要输出的字符串的首地址;
FILE *stream:指定要输出到哪个流指针对应的缓冲区中;
返回值:
成功,返回非负数;
失败,返回EOF;
例子:
//打开文件:以写的方式打开
FILE* fp = fopen("./fputs.abc", "w");
if(NULL == fp)
{
perror("fopen");
return -1;
}
fputs(str, fp);
10)fgets
功能:从指定文件中获取字符串,包括空格;
头文件:
#include <stdio.h>
原型:
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);
参数:
char *s:存储获取到的字符串;
int size:指定要获取的字符串大小;最多读取size-1个字节,因为要保留'\0'位,fgets会自动填充最后的'\0'位;
注意:fgets只能识别到'\n';
假设一行有10个字节,fgets读取20个字节,实际读取10个字节,一次不会读取到下一行;
FILE *stream:指定要从哪个流指针的缓冲区中获取;
返回值:
成功,返回s,即字符串首地址;
失败或者读取到文件结尾,返回NULL;