一、IO基础
1> IO:(inout output) 所谓IO,就是程序与外部设备进行信息交换的过程
2> IO的分类:标准IO和文件IO
3> 标准IO:调用封装好的相关库函数,来实现数据的输入输出
4> 文件IO:调用系统(内核)提供的相关函数,来实现数据的输入输出
5> 标准IO和文件IO的区别:
1、标准IO属于库函数,文件IO属于系统调用
2、标准IO操作的是文件指针,文件IO操作的是文件描述符
3、标准IO有缓冲器,文件IO没有缓冲区
6> 阻塞IO和非阻塞IO
7> 目前所接触过的IO函数:
printf、scanf、putchar、getchar、puts、gets
8> 常用的IO接口函数
标准IO:fprint、fscanf、fputc、fgetc、fputs、fgets、fread、 fwrite、fopen、fclose、fseek、ftell、rewind。。。
文件IO:open、close、read、write、seek。。。
9> IO操作流程
二、标准IO
2.1 标准IO提供的内容
可以通过指令:man 7 stdio.h 可以查看该头文件提供的内容
1、标准的缓冲输入输出
2、提供的数据类型
FILE 文件结构体类型.
off_t 偏移量类型
size_t 大小的类型,一般是一个long int类型
3、提供的偏移量的宏值
SEEK_SET:文件起始位置
SEEK_END:文件结束位置
SEEK_CUR:文件当前位置
4、提供文件结束标志:
EOF (end of file)
5、默认提供的文件指针:
stdin: 标准输入指针
stdout: 标准输出指针
stderr: 标准出错指针
6、标准IO会提供三种缓冲区:
全缓存
行缓存
不缓存
2.2 FILE结构体
1> 如何追该结构体:可以使用 vi -t FILE
2> 结构体解析
struct _IO_FILE {
char* _IO_buf_base; /* 缓冲区开始地址 */
char* _IO_buf_end; /* 缓冲区结束地址 */
int _fileno; //用于系统调用的文件描述符
};
3> 当一个程序启动后,系统会自动打开三个文件指针:
stdin:标准输入文件指针 ---> scanf、getchar、gets
stdout:标准输出文件指针 ----> printf、putchar、puts
stderr:标准出错文件指针 ---> perror
以上三个文件指针,都是针对于终端文件操作而言的
2.3 fopen 打开文件
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:以指定的方式打开一个给定的文件,并返回该文件的文件地址
参数1:要打开的文件文件路径,可以是相对路径,也可以是绝对路径
参数2:文件打开的方式,需要以以下字符开头的字符串
r 以只读的形式打开文件,文件光标定位在开头.如果文件不存在,则报错
r+ 以读写的形式打开文件,文件光标定位在开头.如果文件不存在,则报错
w 以只写的形式打开文件,如果文件存在,则清空文件内容,如果文件不存在,则创建该文件,文件光标定位在开头.
w+ 以读写的形式打开文件,如果文件存在,则清空文件内容,如果文件不存在,则创建该文件,文件光标定位在开头.
a 以追加的形式打开文件,如果文件不存在则创建文件,文件光标定位在结尾
a+ 以读或者追加的形式打开文件,如果文件不存在,则创建文件,如果第一次是读数据,则光标定位在开头,否则定位在结尾
返回值:成功调用返回打开的文件地址,失败返回NULL,并置位错误码
2.4 fclose:关闭文件
#include <stdio.h> int fclose(FILE *stream);
功能:关闭给定的文件指针
参数1:要关闭的文件指针
返回值:成功返回0,失败返回EOF,并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件类型的结构体以便于接受打开的文件地址
FILE * fp = NULL;
//打开一个文件
//fp = fopen("./text.txt", "r"); //以只读的形式打开一个当前路径下的文件
fp = fopen("./text.txt", "w"); //以只写的形式打开一个当前路径下的文件
if(NULL == fp)
{
printf("fopen error\n");
return -1;
}
printf("fopen success");
//关闭文件指针
fclose(fp);
return 0;
}
2.5 fgetc\fputc:单字符的输入输出
#include <stdio.h> int fputc(int c, FILE *stream);
功能:将给定的字符,写入到文件指针stream指向的文件中去
参数1:要写入的字符
参数2:打开的文件指针
返回值:成功返回写入字符的ascii值,失败返回EOF,并置位错误码
#include <stdio.h> int fgetc(FILE *stream);
功能:从指定文件中,读取一个字符
参数:打开的文件指针
返回值:从文件中读取的第一个字符的ascii值,失败返回EOF并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件类型的结构体以便于接受打开的文件地址
FILE * fp = NULL;
//打开一个文件
//fp = fopen("./text.txt", "r"); //以只读的形式打开一个当前路径下的文件
fp = fopen("./text.txt", "w+"); //以只写的形式打开一个当前路径下的文件
if(NULL == fp)
{
printf("fopen error\n");
return -1;
}
printf("fopen success\n");
//向文件中写入字符
fputc('H', fp);
fputc('e', fp);
fputc('l', fp);
fputc('l', fp);
fputc('o', fp);
fputc('\n', fp);
//文件中存储的结果为 Hello
//说明每次写入数据时,光标会自动后移
//关闭文件指针
fclose(fp);
//再次以只读的形式打开文件
fp = fopen("./text.txt", "r"); //以只写的形式打开一个当前路径下的文件
if(NULL == fp)
{
printf("fopen error\n");
return -1;
}
//定义变量用于读取数据
char buf = 0;
while(1)
{
buf = fgetc(fp); //从fp中读取数据
if(EOF == buf) //表示文件读取结束
{
break;
}
printf("%c\t", buf); //将读取的数据显示到终端
}
//关闭文件指针
fclose(fp);
return 0;
}
练习:
1、使用fgetc完成,打开一个文件,统计该文件中的行数
#include <myhead.h>
int main(int argc, char const *argv[])
{
if (argc!=2)
{
printf("input file error!!!");
return-1;
}
FILE *fp = NULL;
fp = fopen("./01open.c", "r");
int n=0;
char buf =0;
while (1)
{
buf=fgetc(fp);
if (EOF==buf)
{
n++;
break;
}
if ('\n'==buf)
{
n++;
}
}
printf("%d\n",n);
fclose(fp);
return 0;
}
2、使用fgetc和fputc完成,cp指令的功能,实现两个文件的拷贝,将src文件中的内容,拷贝到dest文件中
#include <myhead.h>
int main(int argc, char const *argv[])
{
if (argc!=3)
{
printf("input file error\n");
return -1;
}
FILE *fp = NULL;
FILE *fq = NULL;
fp = fopen(argv[1], "r");
fq = fopen(argv[2], "w");
if (NULL == fp||NULL==fq)
{
printf("fopen error\n");
return -1;
}
printf("fopen success\n");
int n;
char buf = 0;
while (1)
{
buf = fgetc(fp);
if (EOF == buf)
{
break;
}
fputc(buf,fq);
}
fclose(fq);
fclose(fp);
return 0;
}
2.6 有关错误码问题
1> 错误码是调用内核提供的函数时,如果调用出错,那么内核空间会向用户空间反馈一个错误信息
2> 错误信息千奇百怪,并且是字符串内容,为了方便起见,给这些错误信息,进行编号
3> 如果,内核空间函数调用出问题时,只需要反馈错误编号即可,这个错误编号就叫做错误码
4> 常见错误码:可以通过指令 vi -t EIO 查看
5 #define EPERM 1 /* Operation not permitted */
6 #define ENOENT 2 /* No such file or directory */
7 #define ESRCH 3 /* No such process */
8 #define EINTR 4 /* Interrupted system call */
9 #define EIO 5 /* I/O error */
10 #define ENXIO 6 /* No such device or address */
11 #define E2BIG 7 /* Argument list too long */
12 #define ENOEXEC 8 /* Exec format error */
13 #define EBADF 9 /* Bad file number */
14 #define ECHILD 10 /* No child processes */
15 #define EAGAIN 11 /* Try again */
16 #define ENOMEM 12 /* Out of memory */
17 #define EACCES 13 /* Permission denied */
18 #define EFAULT 14 /* Bad address */
19 #define ENOTBLK 15 /* Block device required */
20 #define EBUSY 16 /* Device or resource busy */
21 #define EEXIST 17 /* File exists */
22 #define EXDEV 18 /* Cross-device link */
23 #define ENODEV 19 /* No such device */
24 #define ENOTDIR 20 /* Not a directory */
25 #define EISDIR 21 /* Is a directory */
26 #define EINVAL 22 /* Invalid argument */
27 #define ENFILE 23 /* File table overflow */
28 #define EMFILE 24 /* Too many open files */
29 #define ENOTTY 25 /* Not a typewriter */
30 #define ETXTBSY 26 /* Text file busy */
31 #define EFBIG 27 /* File too large */
32 #define ENOSPC 28 /* No space left on device */
33 #define ESPIPE 29 /* Illegal seek */
34 #define EROFS 30 /* Read-only file system */
35 #define EMLINK 31 /* Too many links */
36 #define EPIPE 32 /* Broken pipe */
37 #define EDOM 33 /* Math argument out of domain of func */
38 #define ERANGE 34 /* Math result not representable */
5> 有关错误码的相关函数
1、strerror函数
#include <string.h>
char *strerror(int errnum);
功能:将给定的错误码,转变成错误信息
参数1:错误码
返回值:错误码对应的错误信息的字符串
错误码如何获得? 答:需要加一个头文件:#include<errno.h>
#include <errno.h>
const char * const sys_errlist[];
int sys_nerr;
int errno; /* Not really declared this way; see errno(3) */
2、perror函数
#include <stdio.h>
void perror(const char *s);
功能:向标准出错缓冲区中,写入最新的错误码对应的信息
参数:提示字符串,会自动提供一个冒号,并且输出结束后,会自动加一个换行
返回值:无
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;
if((fp = fopen("./file.txt", "r")) == NULL)
{
//strerror:将错误码转变成错误信息
//printf("errno:%s\n", strerror(errno));
perror("fopen error");
return -1;
}
printf("open success\n");
return 0;
}
2.7 fputs\fgets:字符串输入输出
#include <stdio.h>
int fputs(const char *s, FILE *stream);
功能:将给定的字符串,写入到文件中
参数1:要写入的字符串起始地址
参数2:打开的文件指针
返回值:成功返回写入的字符个数(字符串长度),失败返回EOF
#include <stdio.h> char *fgets(char *s, int size, FILE *stream);
功能:从stream所指向的文件中,最多读取size-1的字符到s中,在读取过程中,如果遇到回车或者文件结束,会结束本次读取,并且会把回车也放入容器中。在后面自动加上'\0'
参数1:存放数据的容器,一般是一个字符数组
参数2:读取的大小 参数3:文件指针
返回值:成功返回容器的起始地址,失败返回NULL
fputs案例
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;
if((fp = fopen("./file.txt", "w")) == NULL)
{
//strerror:将错误码转变成错误信息
//printf("errno:%s\n", strerror(errno));
perror("fopen error");
return -1;
}
printf("open success\n");
//向文件中写入字符串
fputs("Hello world\n", fp);
fputs("I love China\n", fp);
fputs("Hua qing yuan jian\n", fp);
fputs("good good study day day up\n", fp);
//关闭文件
fclose(fp);
return 0;
}
fgets和fputs案例
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;
if((fp = fopen("./file.txt", "w")) == NULL)
{
//strerror:将错误码转变成错误信息
//printf("errno:%s\n", strerror(errno));
perror("fopen error");
return -1;
}
printf("open success\n");
//向文件中写入字符串
fputs("Hello world\n", fp);
fputs("I love China\n", fp);
fputs("Hua qing yuan jian\n", fp);
fputs("good good study day day up\n", fp);
//关闭文件
fclose(fp);
//以只读的形式重新打开文件
if((fp = fopen("./file.txt", "r")) == NULL)
{
perror("fopen error");
return -1;
}
//循环读取数据
char buf[128] = "";
while(1)
{
//清空容器
bzero(buf, sizeof(buf));
if( fgets(buf, sizeof(buf), fp) == NULL)
{
break; //说明文件读取结束
}
//输出读取的数据
printf("%s", buf);
}
printf("\n");
//关闭文件
fclose(fp);
return 0;
}
2.8 关于缓冲区
1> 标准IO提供了三种缓冲区:行缓存、全缓存、不缓存
2> 行缓存:有关标准输入、标准输出指针对应的缓冲区,其大小位1024字节
3> 全缓存:有关普通文件指针对应的缓冲区,其大小位4096字节
4> 不缓存:有关标准出错文件指针对应的缓冲区,其大小位 0
#include<myhead.h>
int main(int argc, const char *argv[])
{
printf("行缓存的大小为:%ld\n", stdout->_IO_buf_end - stdout->_IO_buf_base);//0
printf("行缓存的大小为:%ld\n", stdout->_IO_buf_end - stdout->_IO_buf_base);//1024
getchar(); //使用一次标准输入
printf("行缓存的大小为:%ld\n", stdin->_IO_buf_end - stdin->_IO_buf_base);//1024
perror("usage"); //使用一次标准出错
printf("不缓存的大小为:%ld\n", stderr->_IO_buf_end - stderr->_IO_buf_base);//0
//验证全缓冲
FILE *fp = NULL;
if((fp = fopen("./tt.c", "w+")) == NULL)
{
perror("fopen error");
return -1;
}
fgetc(fp); //使用一次全缓冲
printf("全缓存的大小为:%ld\n", fp->_IO_buf_end - fp->_IO_buf_base);//0
//关闭
fclose(fp);
return 0;
}
5> 行缓存的刷新时机
1、换行会刷新行缓存
2、程序结束后,会自动刷新行缓存
3、当文件指针关闭后,会刷新行缓存
4、当使用fflush函数刷新文件指针时,会刷新行缓存
5、当输入输出切换时,会刷新行缓存
6、当缓存区满了后,再放数据时,会刷新行缓存
#include<myhead.h>
int main(int argc, const char *argv[])
{
/*1、如果行缓存刷新时机未到,则不会刷新
printf("hello world"); //不会输出
while(1);
*/
/*2、遇到换行,会刷新行缓冲
printf("hello world\n"); //不会输出
while(1);
*/
/*3、当程序结束后,会刷新行缓冲
printf("hello world");
*/
/*4、当文件指针关闭后,会刷新行缓冲
printf("hello world");
fclose(stdout); //关闭标准输出文件指针
while(1);
*/
/*5、手动调用fflush刷新缓冲器时,会刷新行缓冲
printf("hello world");
fflush(stdout);
while(1);
*/
/*6、当输入输出发生切换时,也会刷新行缓冲
int num = 0;
printf("请输入num的值:");
scanf("%d", &num);
*/
//7、当行缓冲区满了后,再往缓冲器区中放数据时,会刷新行缓冲
for(int i=0; i<1999; i++)
{
printf("A");
}
while(1);
return 0;
}
6> 全缓存的刷新时机
1、程序结束后,会自动刷新全缓存
2、当文件指针关闭后,会刷新全缓存
3、当使用fflush函数刷新文件指针时,会刷新全缓存
4、当输入输出切换时,会刷新全缓存
5、当缓存区满了后,再放数据时,会刷新全缓存
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件指针
FILE *fp = NULL;
if((fp = fopen("./tt.c", "w+")) == NULL)
{
perror("fopen error");
return -1;
}
/*1、换行不会刷新全缓冲
fputs("hello a\n", fp);
while(1);
*/
/*2、程序结束后,会刷新全缓冲
fputs("hello a\n", fp);
*/
/*3、关闭文件指针时,会刷新全缓冲
fputs("hello a\n", fp);
fclose(fp);
while(1);
*/
/*4、使用fflush刷新文件指针时,会刷新全缓冲
fputs("hello a\n", fp);
fflush(fp);
while(1);
*/
/*5、输入输出发生切换时,会刷新全缓冲
fputs("hello a\n", fp);
fgetc(fp);
while(1);
*/
//6、当缓冲区满了后,会刷新全缓冲
for(int i=0; i<4097; i++)
{
fputc('A', fp);
}
while(1);
return 0;
}
2.9 标准IO函数也可以使用stdin、stdout、stderr
#include<myhead.h>
int main(int argc, const char *argv[])
{
char buf[20] = "";
printf("请输入一个字符串:");
fgets(buf, sizeof(buf), stdin); //从终端读取数据到buf中
buf[strlen(buf)-1] = '\0'; //将回车换成 '\0'
printf("buf = %s", buf);
fputs("我是一个错误", stderr); //向标准出错中写入数据
return 0;
}
作业
1> 使用fgets统计给定文件的行号
2> 使用fgets、fputs完成两个文件的拷贝
#include<myhead.h>
int main(int argc, char const *argv[])
{
if (argc!=2)
{
printf("open file error\n");
return -1;
}
FILE *p=NULL;
if ((p=fopen(argv[1],"r"))==NULL)
{
printf("fopen error\n");
return -1;
}
char buf[128]=" ";
int n;
while (1)
{
bzero(buf, sizeof(buf));
if (fgets(buf,sizeof(buf),p)==NULL)
{
break;
}
n++;
}
printf("%d\n",n);
return 0;
}
#include<myhead.h>
int main(int argc, char const *argv[])
{
if (argc!=3)
{
printf("open file error\n");
return -1;
}
FILE *P=NULL;
FILE *Q=NULL;
char *C=(char *)malloc(2);
if ((P=fopen(argv[1],"r"))==NULL)
{
printf("fopen error\n");
return -1;
}
if ((Q=fopen(argv[2],"w"))==NULL)
{
printf("fopen error\n");
return -1;
}
while (1)
{
if (fgets(C,2,P)==NULL)
{
break;
}
fputs(C,Q);
}
free(C);
C=NULL;
return 0;
}