文章目录
一、IO概念
通过linux操作系统提供的接口函数和linux内核进行交互,再通过内核对硬件进行操作。
(一)IO分类
1. 文件IO:系统调用
操作系统提供的接口
可移植性不强,仅限当前系统使用
没有缓冲区,效率比较低,但是实时性高
2. 标准IO:库函数
在文件IO的基础上又做了一层封装
可移植性强
有缓冲区,可以减少系统调用次数,提高效率
(二)man手册说明
- 可执行程序或 shell 命令 // man 1 ls
- 系统调用(内核提供的函数) // man 2 read
- 库调用(程序库中的函数) // man 3 fopen
二、标准IO
(一)FILE指针
FILE类型是内核中定义的一个结构体类型,FILE指针的本质就是结构体类型指针。
FILE指针指向的空间里记录所有和该文件相关的信息。
当我们使用fopen打开一个文件的时候,就会产生一个FILE指针,后面通过fputs fread …等函数对文件进行操作时,都需要使用这个FILE指针。
每个进程都会维护自己的FILE指针,即使多个进程打开了同一个文件,FILE指针也是不一样的。
- 补充:在每一个正在运行的程序中,都有三个已经打开可以直接使用的文件指针
stdin 标准输入
stdout 标准输出
stderr 标准出错
(二)fopen/fclose函数
1. fopen函数
(1)定义
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:
以标注IO的方式打开一个文件
参数:
pathname:文件的路径和名字 不写路径时默认操作的是当前路径下的文件
mode:打开方式
r:以只读的方式打开文件 将光标定位到文件开头 "r"
r+:以读写的方式打开文件 将光标定位到文件开头 "r+"
w:以只写的方式打开文件 将光标定位到文件开头 "w"
如果文件不存在会新建 如果存在会被清空
w+:以读写的方式打开文件 将光标定位到文件开头 "w+"
如果文件不存在会新建 如果存在会被清空
a:以结尾写的方式打开文件 文件不存在会新建
将光标定位到文件结尾 "a"
a+:以读和结尾写的方式打开文件 文件不存在会新建
读操作光标在文件开头 写操作总是结尾写 "a+"
返回值:
成功 FILE 指针
失败 NULL 重置错误码
- 注意点:
-
- 两个参数均为char*类型参数,传参可以传一个char指针或者一个字符串
-
- 返回值是FILE*类型的指针
-
- mode为"r"和"r+"打开文件,光标在文件开头,且文件内容不会清空
-
- mode为"w"和"w+"打开,如果文件不存在就创建文件,光标在文件开头,但是文件内容会被清空
-
- mode为"a"和"a+"打开,如果文件不存在就会创建文件,光标在文件的结尾,文件内容不会清空
(2) 使用实例
#include <stdio.h>
int main(int argc, char const *argv[])
{
/*(一)关于打开一个不存在的文件*/
//使用r模式打开不存在的文件,会打开文件失败,同时重置错误码
//FILE *fp=fopen("test.c","r");
//使用r+模式打开不存在的文件,会打开文件失败,同时重置错误码
//FILE *fp=fopen("test.c","r+");
//使用w/w+模式打开不存在的文件会创建文件
//FILE *fp=fopen("test.c","w");
//FILE *fp=fopen("test.c","w+");
//使用a/a+模式打开不存在的文件会创建文件
FILE *fp=fopen("test.c","a");
if(fp==NULL){
perror("fopen('a') error");
return -1;
}
return 0;
}
输出结果:
以r模式打开
以r+模式打开
以w/w+模式打开
以a/a+模式打开
- 注:
- 使用"r/r+"模式打开不存在的文件,会打开失败,返回NULL,同时重置错误码;
- 使用"w/w+"和"a/a+"模式打开不存在的文件,会新建文件,同时返回新建文件的FILE指针
2. fclose函数
#include <stdio.h>
int fclose(FILE *stream);
功能:关闭一个文件
参数:stream:文件指针
返回值:
成功 0
失败 EOF 重置错误码
函数 | fopen | fclose |
---|---|---|
头文件 | stdio.h | stdio.h |
功能 | 打开一个文件 | 关闭一个文件 |
参数 | char *pathname,char *mode | FILE* stream |
返回值 | 成功 FILE *;失败 NULL | int类型 成功 0;失败EOF |
重置错误码 | 重置 | 重置 |
(三)错误码原理
1. 错误码
定义在<errno.h>
文件中,errno是一个int类型的数
错误码的宏定义在errno-base.h文件中
2. strerror
#include <string.h>
char *strerror(int errnum);
功能:
将错误码转换成错误信息描述的字符串
参数:
errnum:错误码
返回值:
返回的就是指向错误信息描述字符串的指针
- 补充:
- _ _FILE_ _ 当前文件
- _ _func_ _ 当前函数
- _ _LINE_ _ 当前行
#include <stdio.h>
int main(int argc, char const *argv[])
{
printf("file:%s; function:%s; line:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
输出结果:
3. perror
#include <stdio.h>
void perror(const char *s);
功能:
当错误码被重置后,打印错误信息(通过stderr)
参数:
s:用户附加的打印信息
返回值:无
函数 | strerror | perror |
---|---|---|
头文件 | string.h | stdio.h |
功能 | 将错误码转换为错误信息描述的字符串 | 根据错误码打印出错误信息 |
参数 | int 错误码 | char * 用户附加信息 |
返回值 | 指向错误信息描述字符串的指针 | 无返回值 |
(四)fgetc/fputc函数
1. fgetc函数
(1)定义
#include <stdio.h>
int fgetc(FILE *stream);
功能:在文件中读取一个字符
参数:stream:文件指针
返回值:
成功 读到的字符的ascii码
失败或者读到文件结束 EOF(-1)
(2) 使用示例
功能需求:使用fgetc统计文件的行数,要求命令行传参
需求分析:遍历文件每一个字符,当遇到’\n’就line++
注意点:如果是vscode编辑的文件最后一行没有换行符,统计的结果会少一行;
vi编辑器最后一行有换行符。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if(2 != argc){
printf("Usage:./a/out filename\n");
exit(-1);
}
FILE *fp = fopen(argv[1],"r");
if(NULL == fp)
perror("file open error");
int c=0;
int line=0;
while(-1 != (c = fgetc(fp))){
if('\n' == c)
line++;
}
printf("line = %d\n",line);
return 0;
}
输出结果:
2. fputc函数
(1)定义
#include <stdio.h>
int fputc(int c, FILE *stream);
功能:
将c中传入的字符写入到文件stream中
参数:
c:要操作的字符
stream:文件指针
返回值:
成功 被写入的字符的ascii码
失败 EOF(-1)
(2) 使用示例
功能需求:使用fgetc和fputc复制文件,要求从命令行传参
需求分析:
将fgetc读到的字符通过fputc写入文件
fgetc读到文件尾或者读取失败会返回EOF,可以以此作为判定循环结束的条件
代码实现:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
if(3 != argc){
printf("Usage: ./a.out src dest\n");
exit(-1);
}
FILE *src_fp=fopen(argv[1],"r");
if(NULL == src_fp){
perror("fopen src file error");
exit(-1);
}
FILE *dest_fp=fopen(argv[2],"w");
if(NULL == dest_fp){
perror("fopen dest file error");
exit(-1);
}
int c=0;
while(0< (c = fgetc(src_fp))){
//fputc写入失败会返回EOF
if(0 > fputc(c,dest_fp)){
printf("fputc %c error\n",c);
exit(-1);
}
}
printf("复制成功\n");
return 0;
}
- 补充:
- stderr没有缓冲区,错误不应该被缓存
diff fie1 file2
比较两个文件内容是否相同的命令,如果没有输出说明两个文件内容一样
函数 | fgetc | fputc |
---|---|---|
头文件 | stdio.h | stdio.h |
功能 | 在文件中读取一个字符 | 向文件中写入一个字符 |
参数 | FILE * 文件 | int c,FILE *stream |
返回值 | 成功 读取字符的ascii码;失败 EOF | 成功 写入字符的ascii码;失败 EOF |
(五)fgets/fputs函数
1. fgets函数
(1)定义
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:
从文件stream中读取最多 size-1 个字节到s指向的缓冲区中
遇到EOF或者'\n'会停止读取 '\n'也会被读到buff中 且在结尾加一个'\0'
参数:
s:保存读到的内容的内存地址
size:想读取的字节数
stream:文件指针
返回值:
成功 s的首地址
文件结束或者出错 NULL
(2) 使用示例
功能需求:使用fgets统计文件的行数,要求命令行传参
需求分析:
fgets读满size-1个字符后就会停止本次读取;并且遇到’/n’后也会停止读取;到文件结尾后也会停止读取。
定义一个数组,每次读取数组长度的字节,
当数组读满且倒数第二个字符不是换行符,说明时读取了size-1个字符;
当数组读满但倒数第二个字符是换行符,说明恰好句尾时读取了size-1个字符,此时line++;
当数组未读满且未报错,说明必有换行符,此时line++。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if(2 != argc){
printf("Usage:./a/out filename\n");
exit(-1);
}
FILE *fp = fopen(argv[1],"r");
if(NULL == fp)
perror("file open error");
char buff[10]={0};
int line=0;
while((fgets(buff,sizeof(buff),fp))){
if(strlen(buff)==sizeof(buff)-1 && '\n'!=buff[sizeof(buff)-2]){
continue;
}
line++;
}
printf("line = %d\n",line);
return 0;
}
2. fputs函数
(1)定义
#include <stdio.h>
int fputs(const char *s, FILE *stream);
功能:
将字符串s写入文件stream中
参数:
s:要写入的字符串的首地址
stream:文件指针
返回值:
成功 非负数
失败 EOF(-1)
函数 | fgets | fputs |
---|---|---|
头文件 | stdio.h | stdio.h |
功能 | 在文件中读取size-1个字符 | 将字符串写入文件中 |
参数 | char *s,int size,FILE * | char *s,FILE * |
返回值 | 成功 s的首地址; 失败 NULL | 成功 非负数;失败 EOF |
(2) 使用示例
功能需求:使用fgets和fputs复制文件,要求从命令行传参
需求分析:
将fgets读到的字符通过fputs写入文件
fgets读到文件尾或者读取失败会返回NULL,可以以此作为判定循环结束的条件
代码实现:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
if(3 != argc){
printf("Usage: ./a.out src dest\n");
exit(-1);
}
FILE *src_fp=fopen(argv[1],"r");
if(NULL == src_fp){
perror("fopen src file error");
exit(-1);
}
FILE *dest_fp=fopen(argv[2],"w");
if(NULL == dest_fp){
perror("fopen dest file error");
exit(-1);
}
char buff[10]={0};
int c=0;
//fgtes返回NULL说明已经到文件尾或者出错
while(fgets(buff,sizeof(buff),src_fp)){
//fputs写入失败会返回EOF
if(0 > fputs(buff,dest_fp)){
printf("futs %s error\n",buff);
exit(-1);
}
}
printf("复制成功\n");
return 0;
}
输出结果:
三、标准IO的缓冲区
缓冲区就是一块内存空间
(一)缓冲区的类型
行缓冲:和终端相关的缓冲区就是行缓冲
全缓冲:和文件相关的缓冲区就是全缓冲, fopen打开的文件
无缓冲:stderr
(二)缓冲区的区别
1. 大小
(1)行缓冲 1024byte=1k
连续的空间
未使用缓冲区时,其大小为0;在使用后其大小就是1k
(2) 全缓冲 4096byte=4k
未使用缓冲区时,其大小为0;在使用后其大小就是4k
2. 刷新时机
(1) 行缓冲
- 遇到换行符会刷新
- 程序结束会刷新(Ctrl+c结束,不会刷新)
- 输入输出切换时会刷新
- fclose函数在关闭文件指针时会刷新缓冲区
- fflush可以手动刷新缓冲区
- 缓冲区恰好满时不刷新,再有写入动作就会刷新缓冲区,新写入的内容才会进入缓冲区。
(2)全缓冲的刷新时机
- 遇到换行符不刷新
- 程序结束时会刷新
- fclose关闭文件指针时会刷新
- fflush可以手动刷新缓冲区
- 输入输出切换时会刷新
- 缓冲区恰好满时不刷新,再有写入动作就会刷新缓冲区,新写入的内容才会进入缓冲区。
四、获取系统时间
(一)time
#include <time.h>
time_t time(time_t *tloc);
功能:
获取 1970年1月1日 0点0分0秒 距今的秒数
参数:
如果 tloc是非NULL 指针 也可以通过地址传参获取秒数
返回值:
成功 秒数
失败 -1 重置错误码
用法1:
time_t sec = time(NULL);
用法2:
time_t sec = 0;
time(&sec);
(二)localtime
#include <time.h>
struct tm *localtime(const time_t *timep);
功能:将秒数 转化成 本地时间
参数:timep :用来存秒数的变量的地址
返回值:
成功 struct tm 类型的指针
失败 NULL 重置错误码
struct tm {
int tm_sec; /* 秒 */
int tm_min; /* 分钟 */
int tm_hour; /* 小时 */
int tm_mday; /* 一个月中的第几天 */
int tm_mon; /* 月份 +1 */
int tm_year; /* 年 + 1900 */
int tm_wday; /* 一周的第几天 */
int tm_yday; /* 一年中的第几天 */
};
函数 | time | localtime |
---|---|---|
头文件 | time.h | time.h |
功能 | 获取 1970年1月1日 0点0分0秒 距今的秒数 | 将秒数 转化成 本地时间 |
参数 | timet_t * | timet_t * |
返回值 | time_t 成功返回秒数,失败返回 -1 | 成功 返回struct tm类型的指针;失败 返回NULL |
重置错误码 | 重置 | 重置 |
(三)使用示例
功能需求:每秒获取一次时间,把他写入文件 hello.txt 中,要求实现 断点续写 。
需求分析:使用"a+"模式打开文件,可是读光标在文件头,然后写光标在文件尾;
每次写入前先统计当前文件中的内容的行数,然后再在下一行继续写入;
通过time函数获取当前时间的秒数,再通过localtime将秒数转换为当前时间,并写入hello.txt文件中
代码实现:
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
FILE *fp=fopen("hello.txt","a+");
//统计行数
int lines=0;
int c;
//fgetc读到文件尾或者出错的话返回EOF
while(0 < (c = fgetc(fp))){
if('\n' == c)
lines++;
}
time_t sec=0;
struct tm *my_time=NULL;
time_t currt_sec=0;
#if 0
//这种方式积累误差会比较大,因为每次都会sleep一秒,但是其他程序运行仍需要时间,由此误差会越来越大
//但是这种方式cpu的开销会比较小
while(1){
//获取当前秒数,time函数的第一种用法
time(&sec);
my_time=localtime(&sec);
fprintf(fp,"%d: %4d-%02d-%02d %02d:%02d:%02d\n",++lines,my_time->tm_year+1900,my_time->tm_mon+1,my_time->tm_mday,\
my_time->tm_hour,my_time->tm_min,my_time->tm_sec);
fflush(fp);
sleep(1);
}
#else
//这种方式积累误差会比较小,但是这种方式cpu的开销会比较大,因为程序一直在判断是否需要更新时间了
while(1){
sec=time(NULL);//time函数的第二种用法
if(currt_sec != sec){
my_time=localtime(&sec);
fprintf(fp,"%d: %4d-%02d-%02d %02d:%02d:%02d\n",++lines,my_time->tm_year+1900,my_time->tm_mon+1,my_time->tm_mday,\
my_time->tm_hour,my_time->tm_min,my_time->tm_sec);
fflush(fp);
currt_sec=sec;
}
}
#endif
return 0;
}