IO进程线程(一)标准IO之fopen/fclose fputc/fgetc fputs/fgets strerror/perror time/localtime

一、IO概念

通过linux操作系统提供的接口函数和linux内核进行交互,再通过内核对硬件进行操作。

(一)IO分类

1. 文件IO:系统调用

操作系统提供的接口

可移植性不强,仅限当前系统使用
没有缓冲区,效率比较低,但是实时性高

2. 标准IO:库函数

在文件IO的基础上又做了一层封装

可移植性强
有缓冲区,可以减少系统调用次数,提高效率

(二)man手册说明

  1. 可执行程序或 shell 命令 // man 1 ls
  2. 系统调用(内核提供的函数) // man 2 read
  3. 库调用(程序库中的函数) // 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 重置错误码
  • 注意点:
    1. 两个参数均为char*类型参数,传参可以传一个char指针或者一个字符串
    1. 返回值是FILE*类型的指针
    1. mode为"r"和"r+"打开文件,光标在文件开头,且文件内容不会清空
    1. mode为"w"和"w+"打开,如果文件不存在就创建文件,光标在文件开头,但是文件内容会被清空
    1. 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 重置错误码
函数fopenfclose
头文件stdio.hstdio.h
功能打开一个文件关闭一个文件
参数char *pathname,char *modeFILE* stream
返回值成功 FILE *;失败 NULLint类型 成功 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:用户附加的打印信息
返回值:无
函数strerrorperror
头文件string.hstdio.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比较两个文件内容是否相同的命令,如果没有输出说明两个文件内容一样
函数fgetcfputc
头文件stdio.hstdio.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)
函数fgetsfputs
头文件stdio.hstdio.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) 行缓冲
  1. 遇到换行符会刷新
  2. 程序结束会刷新(Ctrl+c结束,不会刷新)
  3. 输入输出切换时会刷新
  4. fclose函数在关闭文件指针时会刷新缓冲区
  5. fflush可以手动刷新缓冲区
  6. 缓冲区恰好满时不刷新,再有写入动作就会刷新缓冲区,新写入的内容才会进入缓冲区。
(2)全缓冲的刷新时机
  1. 遇到换行符不刷新
  2. 程序结束时会刷新
  3. fclose关闭文件指针时会刷新
  4. fflush可以手动刷新缓冲区
  5. 输入输出切换时会刷新
  6. 缓冲区恰好满时不刷新,再有写入动作就会刷新缓冲区,新写入的内容才会进入缓冲区。

四、获取系统时间

(一)time

#include <time.h>
time_t time(time_t *tloc);

功能:
    获取 197011000秒 距今的秒数
    
参数:
    如果  tloc是非NULL 指针 也可以通过地址传参获取秒数
    
返回值:
    成功 秒数
    失败 -1 重置错误码
    
用法1time_t sec = time(NULL);
用法2time_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;   /* 一年中的第几天 */
};
函数timelocaltime
头文件time.htime.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;
}
  • 38
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值