IO基础
1、IO:(inout output) 程序与外部设备进行信息交换的过程
2、IO的分类:标准IO和文件IO
1)标准IO:调用封装好的相关库函数,来实现数据的输入输出
2)文件IO:
3、文件IO和标准IO的区别:
1)标准IO属于库函数,文件IO属于系统调用
2)标准IO操作的是文件指针,文件IO操作的是文件描述符
4、阻塞IO和非阻塞IO
5、目前所接触过的IO函数:
printf、scanf、putchar、getchar、puts、gets
6、常用的IO接口函数
标准IO:fprint、fscanf、fputc、fgetc、fputs、fgets、fread、fwrite、fclose、fseek、ftell、 rewind......
文件IO:open、close、read、write、seek......
7、标准IO操作流程
标准IO
标准IO提供的内容
可以通过指令:man 7 stdio.h 可以查看改头文件提供的内容
1)标准的缓存输入输出
2)提供的数据类型
FILE 文件结构体类型
off_t 偏移量类型
size_t 大小的类型
3)提供了偏移量的宏值
SEEK_SEET:文件起始地址
SEEK_END: 文件结束位置
SEEK_CUR: 文件当前位置
4)提供文件结束的标志:EOF(end of file)
5)默认提供的文件指针
stdin: 标准输入指针
stdout:标准输出指针
stderr: 标准出错指针
6)标准IO会提供三种缓冲区:
全缓冲
行缓冲
不缓冲
FILE结构体
1、追踪该结构体:vi-t FilE
2、结构体解析(部分)
245 struct _IO_FILE {
257 char* _IO_buf_base; /* 缓冲区开始地址 */
258 char* _IO_buf_end; /* 缓冲区结束地址 */
267
268 int _fileno; //用于系统调用的文件描述符
286 };
3、当一个程序启动后,系统会自动打开三个文件指针:
stdin: 标准输入指针 ----》 scanf、getchar、gets
stdout:标准输出指针 ----》 printf、putchar、puts
stderr: 标准出错指针 ----》 perror
fopen 打开文件
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:以指定的方式打开一个给定的文件,并返回该文件的文件地址
参数1:要打开的文件文件路径,可以是相对路径,也可以是绝对路径
参数2:文件打开的方式,需要以以下字符开头的字符串
r 以只读的形式打开文件,文件光标定位在开头.如果文件不存在,则报错r+ 以读写的形式打开文件,文件光标定位在开头.如果文件不存在,则报错
w 以只写的形式打开文件,如果文件存在,则清空文件内容,如果文件不存在,则创 建该文件,文件光标定位在开头.
w+ 以读写的形式打开文件,如果文件存在,则清空文件内容,如果文件不存在,则 创建该文件,文件光标定位在开头.
a 以追加的形式打开文件,如果文件不存在则创建文件,文件光标定位在结尾
a+ 以读或者追加的形式打开文件,如果文件不存在,则创建文件,如果第一次是读 数据,则光标定位在开头,否则定位在结尾
返回值:成功调用返回打开的文件地址,失败返回NULL,并置位错误码
fclose 关闭
#include <stdio.h>
int fclose(FILE *stream);
功能:关闭给定的文件指针
参数1:要关闭的文件指针
返回值:成功返回0,失败返回EOF,并置位错误码
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, char const *argv[])
{
FILE * fp = NULL;
FILE * fq = 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); //读取文件中的数据
if (EOF == buf)
{
break;
}
printf("%c\t",buf);//将读取的数据显示到终端
}
//关闭文件
fclose(fp);
return 0;
}
使用fputc、fgetc完成cp功能
#include <myhead.h>
int main(int argc, char const *argv[])
{
if (argc !=3)
{
printf("终端输入有误\n");
return -1;
}
FILE * fp = NULL;
FILE * fq = NULL;
//拷贝
//以只读的形式打开源文件
if (NULL == (fp = fopen(argv[1],"r")))
{
printf("fopen error\n");
return -1;
}
//以只写的形式打开目标文件
if (NULL == (fq = fopen(argv[2],"w")))
{
printf("fopen error\n");
return -1;
}
char temp = 0;
while (1)
{
temp = fgetc(fp); //读取文件中的数据
if (EOF == temp)
{
break;
}
fputc(temp,fq);
}
printf("拷贝结束\n");
//关闭文件
fclose(fp);
fclose(fq);
return 0;
}
终端输入输出
拷贝结果
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
案例
#include <myhead.h>
int main(int argc, char const *argv[])
{
FILE *fp = NULL;
if (NULL == (fp = fopen("./02fputs.txt","w")))
{
perror("fopen error");
return -1;
}
fputs("Hello world\n",fp);
fputs("good good study\n",fp);
fputs("day day up\n",fp);
printf("fputs success\n");
fclose(fp);
if (NULL == (fp = fopen("./02fputs.txt","r")))
{
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);
}
putchar(10);
fclose(fp);
return 0;
}
有关错误码的问题
1、错误码是调用内核提供的函数时,如果调用出错,那么内核空间会向用户空间反馈一个错误信 息
2、错误信息干奇百怪,并且是字符串内容,为了方便起见,给这些错误信息,进行编号
3、如果,内核空间函数调用出问题时,只需要反馈错误编号即可,这个错误编号就叫做错误码
常见错误码:可通过 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 */
有关错误码的相关函数
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;
}
关于缓存区的问题
标准IO提供了三种缓存区:全缓存、行缓存、不缓存
1、行缓存:有关标准输入、标准输出指针对应的缓冲区,其大小为1024字节
2、全缓存:有关普通文件指针对应的缓冲区,其大小为4096字节
3、不缓存:有关标准出错文件指针对应的缓冲区,其大小位 0
4、行缓存的刷新时机
1)换行会刷新时机
2)程序结束会刷新行缓存
3)当文件指针关闭后,会刷新行缓存
4)手动调用 fflush 刷新缓冲器时,会刷新缓冲区
5)当输入输出发生切换时,会刷新一次行缓存
6)当行缓存区满了后,再向行缓存区放入数据时,会刷新行缓存
#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;
}
5、全缓存的刷新时机
1)换行不会刷新全缓存
2)程序结束后会刷新全缓存
3)关闭文件指针时,会刷新全缓存
4)手动调用 fflush 刷新缓冲器时,会刷新缓冲区
5)当输入输出发生切换时,会刷新一次全缓存
6)当行缓存区满了后,再向行缓存区放入数据时,会刷新全缓存
#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;
}
标准IO函数也可以使用stdin、stdout、stderro
#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统计给定文件的行号
#include <myhead.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("终端输入有误\n");
return -1;
}
FILE *fp = NULL;
if (NULL == (fp = fopen(argv[1], "r")))
{
perror("打开失败\n");
return -1;
}
char buf[20] = "";
int n = 0;
while (1)
{
if ((fgets(buf, sizeof(buf), fp)) != NULL)
{
n++;
}
else
{
break;
}
}
printf("该文件有%d行\n", n);
fclose(fp);
return 0;
}
测试文件:tt.txt
2、使用fgets、fputs完成两个文件的拷贝
#include <myhead.h>
int main(int argc, char const *argv[])
{
if (argc !=3)
{
printf("终端输入有误\n");
return -1;
}
FILE * fp = NULL;
FILE * fq = NULL;
//拷贝
//以只读的形式打开源文件
if (NULL == (fp = fopen(argv[1],"r")))
{
perror("fopen error");
return -1;
}
//以只写的形式打开目标文件
if (NULL == (fq = fopen(argv[2],"w")))
{
printf("fopen error\n");
return -1;
}
char temp[20] = "";
while (1)
{
bzero(temp,sizeof(temp));
if ((fgets(temp,sizeof(temp),fp)) == NULL)
{
break;
}
fputs(temp,fq);
}
printf("拷贝结束\n");
//关闭文件
fclose(fp);
fclose(fq);
return 0;
}
拷贝结果
#include <myhead.h>
int main(int argc, char const *argv[])
{
if (argc !=3)
{
printf("终端输入有误\n");
return -1;
}
FILE * fp = NULL;
FILE * fq = NULL;
//拷贝
//以只读的形式打开源文件
if (NULL == (fp = fopen(argv[1],"r")))
{
perror("fopen error");
return -1;
}
//以只写的形式打开目标文件
if (NULL == (fq = fopen(argv[2],"w")))
{
printf("fopen error\n");
return -1;
}
char temp[20] = "";
while (1)
{
bzero(temp,sizeof(temp));
if ((fgets(temp,sizeof(temp),fp)) == NULL)
{
break;
}
fputs(temp,fq);
}
printf("拷贝结束\n");
//关闭文件
fclose(fp);
fclose(fq);
return 0;
}