目录
1.2 sprintf/snprintf/fprintf函数
前言
从这一周开始学习的课程是IO进程,为时八天;本周的周一是数据结构的考试,考试的总结写在了上一周的末尾;从周二到周五这四天的时间里,主要学习了:文件IO、标准IO、库和多进程。
老师说从IO进程开始,课程就从基础阶段拔高到应用层面了,经过这四天的学习下来,主要感觉就是:内容多、代码量大、要熟悉和记忆的内容多、易混淆的知识点多。比较重要的内容还是标准IO和文件IO里的海量的函数接口,因此,本次的反思将针对这几方面,进行整理归纳,难点分析和易混淆点辨析。
同样,写此文章,是想以这种碎碎念的方式回顾重点、重复盲点、加深印象,复习、总结和反思本周的学习,仅供后期自己回顾使用。知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!
周二
IO简介和标准IO
一、IO简介
1.1 什么是IO
IO(input&output)是用户空间和内核空间通过API进行的交互。
1.2 IO的分类
IO分为:标准IO和文件IO
标准IO:库函数
文件IO:系统调用
1.3 什么是系统调用和和库函数
系统调用:系统调用是用户空间进入内核空间的一次过程。
优点:操作系统不同,系统调用的函数接口也不同,因此系统调用的移植性比较差。
缺点:系统调用没有缓冲区,所以效率比较低。
库函数:库函数 = 系统调用 + 缓冲区
优点:库函数要比系统调用的效率高。
缺点:库函数的接口比较统一,移植性比较强。
区别辨析:系统调用对内核的访问是即时的,库函数的缓冲区需要刷新才能访问内核。
1.4 什么是IO接口
IO接口就是函数调用,系统已经封装好,使用时直接调用。
例如:
标准IO:fopen fread fwrite fputc fgetc fputs fgets fclose printf scanf...
文件IO:open read write close...
二、标准IO
标准IO:库函数 = 系统调用 + 缓冲区
标准IO:fopen fread fwrite fputc fgetc fputs fgets fclose printf scanf...
FILE结构体:
FILE本身是一个结构体,在调用fopen的时候产生一个结构体指针,这个FILE结构体,就代表打开文件的所有的信息(例如缓冲区的大小,光标的位置等信息),并且以后在读写文件的时候通过FILE指针完成。
在一个正在执行的程序中,默认已经有了三个FILE指针:stdin、stdout、stderr;
它们分别代表的是标准输入,标准输出,标准出错。
typedef struct _IO_FILE FILE;
struct _IO_FILE {
char* _IO_buf_base; //缓冲区的起始地址
char* _IO_buf_end; //缓冲区的结束地址
...
}
2.1 fopen/fclose函数
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:使用标准IO接口打开文件
参数:
@pathname:想要打开文件的路径及名字 "/home/linux/1.c"
@mode :打开文件的方式 "r" "r+" "w" "w+" "a" "a+"
r :以只读的方式打开文件,将光标定位到文件的开头
r+ :以读写的方式打开文件,将光标定位到文件的开头
w :以只写的方式打开文件,如果文件存在就清空文件,如果文件不存在就创建文件
w+ :以读写的方式打开文件,如果文件存在就清空文件,如果文件不存在就创建文件
a :以追加的方式打开文件,如果文件不存在就创建文件,如果文件存在光标定位到结尾进行写
a+ :以读和追加的方式打开文件,如果文件不存在就创建文件,如果进行读从开头读,如果写
在结尾写。
返回值:成功返回FILE指针,失败返回NULL,置位错误码
int fclose(FILE *stream);
功能:关闭文件
参数:
@stream:文件指针
返回值:成功返回0,失败返回EOF(-1),置位错误码
实例:
#include <stdio.h> int main(int argc,const char * argv[]) { FILE *fp; if((fp = fopen("./hello.txt","w")) == NULL){ printf("fopen file error\n"); return -1; } //有一个fopen就要对应一个fclose if(fclose(fp)){ printf("fclose file error\n"); return -1; } return 0; }
2.1 strerror函数
#include <string.h>
char *strerror(int errnum);
功能:将错误码转换为错误信息的字符串
参数:
@errnum:错误码
返回值:错误信息的字符串
实例:
#include <stdio.h> #include <errno.h> #include <string.h> int main(int argc,const char * argv[]) { FILE *fp; if((fp = fopen("./hello.txt","r")) == NULL){ printf("fopen file error\n"); printf("errno = %d,%s\n",errno,strerror(errno)); return -1; } return 0; }
2.3 perror函数
#include <stdio.h>
void perror(const char *s);
功能:将错误信息打印到终端上
参数:
@s:用户的附加信息
返回值:无
实例:
注意perror打印时会在结尾部冒号。
#include <stdio.h> int main(int argc,const char * argv[]) { FILE *fp; if((fp = fopen("./hello.txt","r")) == NULL){ perror("fopen file error"); return -1; } return 0; }
2.4 fputc/fgetc函数
put是往文件中写,get是从文件中读;
#include <stdio.h>
int fputc(int c, FILE *stream);
功能:向文件中写入字符
参数:
@c:字符的ascii
@stream:文件指针
返回值:成功返回字符ascii值,失败返回EOF(-1)
int fgetc(FILE *stream);
功能:从文件中读取字符
参数:
@stream:文件指针
返回值:成功读取到的字符的ascii,读取到文件结尾或者出错,返回EOF(-1)
fputc实例
#include <stdio.h> int main(int argc, const char* argv[]) { FILE* fp; //以只写的方式打开文件,如果文件不存在就创建文件,如果文件存在就清空文件 if ((fp = fopen("./hello.txt", "w")) == NULL) { perror("fopen file error"); return -1; } fputc('h', fp); //将h字符写入到文件中,同时光标会向后移动一个字符的位置 fputc('e', fp); //将e字符写入到文件中,同时光标会向后移动一个字符的位置 fputc('l', fp); fputc('l', fp); fputc('o', fp); fclose(fp); return 0; }
#include <stdio.h> int main(int argc, const char* argv[]) { FILE* fp; //以只写的方式打开文件,如果文件不存在就创建文件,如果文件存在就清空文件 if ((fp = fopen("./hello.txt", "w")) == NULL) { perror("fopen file error"); return -1; } //将26个英文字符写入到文件中 for(int i=0;i<26;i++){ fputc('A'+i,fp); } fclose(fp); return 0; }
fgetc实例
#include <stdio.h> int main(int argc, const char* argv[]) { FILE* fp; int ch; //以只读的方式打开文件 if ((fp = fopen("./hello.txt", "r")) == NULL) { perror("fopen file error"); return -1; } //循环读取文件中的内容,如果没有到EOF,就就一直读取。 //并把读取到的内容显示到终端上(fgetc每读取一次光标会向后移动一个字节) while ((ch = fgetc(fp)) != EOF) { printf("%c ", ch); } printf("\n"); fclose(fp); return 0;
1.使用fgetc统任意文件的行号
./a.out filename //命令行传参
#include <stdio.h> int main(int argc, const char* argv[]) { FILE* fp; int ch, line = 0; // 1.校验命令行参数的个数 if (argc != 2) { printf("input error,try again\n"); printf("usage: ./a.out filename\n"); return -1; } // 2.以只读的方式打开文件 if ((fp = fopen(argv[1], "r")) == NULL) { perror("fopen error"); return -1; } // 3.循环读取文件中的字符,判断是否等于'\n' //让line++ while ((ch = fgetc(fp)) != EOF) { if (ch == '\n') { line++; } } // 4.将行号打印到终端上 printf("line = %d\n",line); // 5.关闭文件 fclose(fp); return 0; }
2.使用fgetc/fputc实现文件拷贝
./a.out srcfile destfile
#include <stdio.h> int main(int argc, const char* argv[]) { FILE *fp1, *fp2; int ch; // 1.校验命令行参数的个数 if (argc != 3) { printf("input error,try again\n"); printf("usage: ./a.out srcfile destfile\n"); return -1; } // 2.只读方式打开源文件,以只写方式打开目标文件 if ((fp1 = fopen(argv[1], "r")) == NULL) { perror("fopen src error"); return -1; } if ((fp2 = fopen(argv[2], "w")) == NULL) { perror("fopen dest error"); return -1; } // 3.循环拷贝 while ((ch = fgetc(fp1)) != EOF) { fputc(ch,fp2); } // 4.关闭文件 fclose(fp1); fclose(fp2); return 0; }
2.5 fgets/fputs函数
char *fgets(char *s, int size, FILE *stream);
功能:从stream对应的文件中最多读取size-1个字符到s中
读停止:当遇到EOF或者换行符时候会停止,如果是换行符停止的,它会将换换行符存储到s中
s的结束:在s存储的最后一个字符之后通过添加'\0'的形式表示结束
s=0123456789'\n''\0' 读结束的原因是读到'\n','\n'也会读出,并在末尾补'\0'
s=01234'\0' 读结束的原因是读到结尾,返回EOF(-1),读结束,且在末尾补'\0'
参数:
@s:用于存储读取到的字符串的首地址
@size:读取字符串中字符的个数
@stream:文件指针
返回值:成功返回s,失败返回NULL
int fputs(const char *s, FILE *stream);
功能:将s中的内容写入到stream对应的文件中(不包含'\0')
参数:
@s:被写字符串的首地址
@stream:文件指针
返回值:成功返回大于0的值,失败返回EOF
fgets函数的实例(fgets读取文件中的内容)
#include <stdio.h> #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { FILE* fp; char buf[20] = {0}; if (argc != 2) { printf("input error,try again\n"); printf("usage: ./a.out filename\n"); return -1; } if ((fp = fopen(argv[1], "r")) == NULL) PRINT_ERR("fopen error"); //读取文件第一行的内容(不保证全部读取到) fgets(buf,sizeof(buf),fp); printf("buf = %s\n",buf); fclose(fp); return 0; }
fgets读取标准输入的内容
fgets一般用来上程序输入字符串,
因为scanf("%s")不能读取空格,gets在读的时候有越界的风险;
所以fgets是最常用来读取字符串的
#include <stdio.h> #include <string.h> #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { char buf[20] = {0}; //fgets一般用来上程序输入字符串,因为scanf("%s")不能读取空格 //gets在读的时候有越界的风险,所以fgets是最常用来读取字符串的 // 从标准输入中读取字符到buf中,最多读取sizeof(buf)-1个字符 fgets(buf,sizeof(buf),stdin); //hello'\n''\0' //将字符串中的'\n'设置为'\0' buf[strlen(buf)-1]='\0'; //将读取到的内容显示到终端上 printf("buf = %s\n",buf); return 0; }
使用fgets统计文件的行号
#include <stdio.h> #include <string.h> #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { FILE* fp; char buf[10] = { 0 }; int line = 0; // 1.校验命令行参数的个数 if (argc != 2) { printf("input error,try again\n"); printf("usage: ./a.out filename\n"); return -1; } // 2.以只读的方式打开文件 if ((fp = fopen(argv[1], "r")) == NULL) { perror("fopen error"); return -1; } // 3.循环读取文件中的字符串 while (fgets(buf, sizeof(buf), fp) != NULL) { //如果buf=sizeof(buf)-1说明读满了 if (strlen(buf) == (sizeof(buf) - 1)) { //读满之后判断倒数第二个字符,如果不是换行就执行下次循环 if (buf[sizeof(buf) - 2] != '\n') continue; } //如果没有读满line++,如果读满了倒数第二个字符是'\n'也让line++ line++; } // 4.将行号打印到终端上 printf("line = %d\n", line); // 5.关闭文件 fclose(fp); return 0; }
fputs向文件中写入字符串的实例
#include <stdio.h> #include <string.h> #define PRINT_ERR(msg) \ do { \ perror(msg); \ return -1; \ } while (0) int main(int argc, const char* argv[]) { FILE* fp; char buf[] = "i am test fputs API....."; if ((fp = fopen("hello.txt", "w")) == NULL) { perror("fopen error"); return -1; } fputs(buf,fp); fclose(fp); return 0; }
fputs向标准输出或者标准错误中写
#include <stdio.h> #include <string.h> int main(int argc, const char* argv[]) { char buf[] = "i am test fputs API.....stdout"; char buf1[] = "i am test fputs API.....stderr"; //最终看到的现象都是在终端上显示一句话 //区别stdout有缓冲区,stderr没有缓冲区 fputs(buf,stdout); fputs(buf1,stderr); while(1); return 0; }
练习:使用fgets/fputs实现文件的拷贝
#include <stdio.h> int main(int argc, const char* argv[]) { FILE *fp1, *fp2; char buf[20] = {0}; // 1.校验命令行参数的个数 if (argc != 3) { fputs("input error,try again\n",stderr); fputs("usage: ./a.out srcfile destfile\n",stderr); return -1; } // 2.只读方式打开源文件,以只写方式打开目标文件 if ((fp1 = fopen(argv[1], "r")) == NULL) { perror("fopen src error"); return -1; } if ((fp2 = fopen(argv[2], "w")) == NULL) { perror("fopen dest error"); return -1; } // 3.循环拷贝 fgets成功返回buf(非0就会进入while), //失败返回NULL(void *)0,假退出循环 while (fgets(buf,sizeof(buf),fp1)) { fputs(buf,fp2); } // 4.关闭文件 fclose(fp1); fclose(fp2); return 0; }
三、缓冲区问题
3.1 缓冲区的大小及类型
全缓存:和文件相关的缓冲区(fp)4096(4K)
行缓存:和终端相关的缓冲区 (stdin stdout) 1024(1k)
无缓存:和标准出错 (stderr) 0
#include <stdio.h>
int main(int argc, const char* argv[])
{
int num;
scanf("%d", &num);
printf("stdin size = %ld\n",
stdin->_IO_buf_end - stdin->_IO_buf_base);
printf("stdout size = %ld\n",
stdout->_IO_buf_end - stdout->_IO_buf_base);
printf("stderr size = %ld\n",
stderr->_IO_buf_end - stderr->_IO_buf_base);
FILE* fp;
if ((fp = fopen("./hello.txt", "r+")) == NULL) {
perror("fopen error");
return -1;
}
fputs("hello",fp);
printf("fp size = %ld\n",
fp->_IO_buf_end - fp->_IO_buf_base);
return 0;
}
3.2 行缓存的刷新机制
1.行缓存遇到换行符的时候会刷新缓冲区
2.当程序结束的时候会刷新行缓存
3.当输入输出发生切换的时候
4.当关闭文件的时候会刷新缓冲区
5.缓冲区满也会刷新缓冲区
6.主动刷新缓冲区fflush
#include <stdio.h>
int main(int argc, const char* argv[])
{
// 1.行缓存遇到换行符的时候会刷新缓冲区
// printf("hello\n");
// while(1);
// 2.当程序结束的时候会刷新行缓存
// printf("hello");
// 3.当输入输出发生切换的时候
// printf("hello");
// fgetc(stdin);
// while(1);
// 4.当关闭文件的时候会刷新缓冲区
// printf("hello");
// fclose(stdout);
// while (1);
// 5.缓冲区满也会刷新缓冲区
// for(int i=0;i<1025;i++){
// fputc('a',stdout);
// }
// while(1);
// 6.主动刷新缓冲区 int fflush(FILE *stream);
printf("hello");
fflush(stdout);
while (1);
return 0;
}