IO【2】(标准IO)

306 阅读9分钟

“这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

标准IO

标准IO的定义

标准IO 是由标准c库提供的API接口,是在文件IO的基础上封装出来的函数接口。

​ 📌第一个原因是为了增加可移植性。并且,标准IO在文件IO的基础上封装了一个叫缓冲区的东西。提高内核的效率。用来存放一些不是很着急的数据,等到缓冲区满,调用一次文件IO完成写入工作或者读取工作。

​ 有些数据是特别着急的数据比如说:下发的命令,实时的监控数据呀 必须使用文件IO 。

​ 伪代码: fopen(参数) { if(is win) { _open(参数); }esle if (is linux) { open(参数); } }

文件流指针

图片.png

文件流指针:是一个文件的标志,是在文件描述符上封装出来的一个结构体指针,缓冲区就在这个指针中。

流:流指针----文件流指针---这个流

追代码

安装一个索引搜索文件 tags (搜索引擎)

第一步:创建目标目录的引擎文件--tags (想搜索哪个目录就在哪个目录下创建)
        ctags -R //生成一个引擎文件叫tags 它里面就包含目录的信息
第二步:搜索
        vi -t + 目标字符串   例如:vi -t FILE    
第三步:选择准确的目录下的文件

第四步:翻阅文档 --代码跳转  
        追代码:光标点击想要跳转的字符串,ctrl + ] 进项跳转搜索
        返回上一次:ctrl +t

设置全局tags

编辑家目录的vim配置文件**.vimrc** 在末尾加上tags的路径如下:

set tags+=/user/include/tags

标准io函数接口

1.fopen

  1. 原型:FILE *fopen(const char *pathname, const char *mode);

  2. 功能:打开一个文件

  3. 参数:

    pathname:要打开的文件的路径及名称

    mode :打开文件的方式

    具体解释open() flag
    r以只读的方式打开一个文件,文件必须存在O_RDONLY
    r+以读写的方式打开这个文件,文件必须存在O_RDWR
    w如果文件存在则清空写入,如果文件不存在则创建写入O_WRONLY|O_CREAT|O_TRUNC
    w+如果文件存在则清空读写,如果文件不存在则创建读写O_RDWR|O_CREAT|O_TRUNC
    a如果文件存在则追加写入,如果文件不存在则先创建在写入O_WEONLY|O_CREAT|O_APPEND
    a+如果文件不存在则创建文件并追加写入和读取。 读取时从文件开头开始读取,写入时从文件的末尾开始写入 lseek偏移后不能进行写入操作,如果进入写入操作,则追加在末尾写入(不管写之前进行了如何偏移)O_RDWR|O_CREAT|O_APPEND

    注意:有可能以后会遇到 rb+ ,b代表执行二进制操作,linux下无区分

  4. 返回值:

    ​ 成功会返回一个文件的流指针

    ​ 失败返回NULL

  5. 代码:

#include <stdio.h>
int main(int argc, const char *argv[])
{
    //打开一个文件需要定义一个文件流指针来指向fopen返回的已经封装好的文件流指针
    //FILE * fp = fopen("./1.txt","r");
    FILE * fp = fopen("./1.txt","w");
    if (NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    printf("打开文件是成功的\n");
    return 0;
}

2.fclose

  1. 原型:int fclose(FILE *stream);

  2. 功能:关闭一个文件流指针//原因:文件流指针里也绑定了一个文件的描述符

  3. 参数:目标文件流指针

  4. 返回值:

    成功返回:0

    失败返回:EOF == -1

3.fgetc

  1. 原型:int fgetc(FILE *stream);

  2. 功能:从指定文件流指针中获取一个字符

  3. 参数:目标流指针

  4. 返回值:

    成功返回 获取到的字符转化成的int类型的数据

    失败返回 EOF 到达文件末尾 EOF

📢注意:获取完,指针会后移一位

int buf = fgetc(fp);         
printf("%c\n",buf);
printf("%d\n",sizeof(buf));

linux@ubuntu:~/test/IO/test$ ./a.out 
h
4

4.feof

  1. 原型:nt feof(FILE *stream);

  2. 功能:判断是否到达了文件的末尾

  3. 参数:目标文件流指针

  4. 返回值:

    ​ 如果到达了文件的末尾返回一个非0值

    ​ 其他情况返回0

5.ferror

  1. 原型:int ferror(FILE *stream);

  2. 功能:判断文件操作是否出错

  3. 参数:目标文件流指针

  4. 返回值:

    ​ 如果文件操作失败了,返回一个非0值

    ​ 其他情况返回0

  5. 代码:

#include <stdio.h>
int main(int argc, const char *argv[])
{
    //打开一个文件以r+方式
    FILE * fp = fopen("./1.txt","r");
    if (NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    
    //开始做循环的读取
    while(1)
    {
        int ret = fgetc(fp);
        if (EOF == ret)
        {
            //到达了文件的末尾或者说函数失败
            if (feof(fp))
            {
                printf("到达了文件的末尾\n");
                break;
            }else{
                printf("获取失败\n");\
                return -1;
            }
        }
        printf("获取到的数据为%d---所对应的字符为%c\n",ret,ret);

    }
    if( fclose(fp) == EOF)
    {
        printf("关闭文件失败\n");
        return -1;
    }
    return 0;
}

linux@ubuntu:~/test/IO/test$ ./a.out 
获取到的数据为104---所对应的字符为h
获取到的数据为101---所对应的字符为e
获取到的数据为108---所对应的字符为l
获取到的数据为108---所对应的字符为l
获取到的数据为111---所对应的字符为o
获取到的数据为32---所对应的字符为 
获取到的数据为119---所对应的字符为w
获取到的数据为111---所对应的字符为o
获取到的数据为114---所对应的字符为r
获取到的数据为108---所对应的字符为l
获取到的数据为100---所对应的字符为d
获取到的数据为10---所对应的字符为
       //vim的'\n'
到达了文件的末尾

6.fputc

  1. 原型:int fputc(int c, FILE *stream);

  2. 功能:向指定的一个文件中输出一个字符

  3. 参数:

    ① c:想要写入的字符,将int类型的数据转换成unsignedchar类型的数据写入

    ② stream :目标文件流指针

  4. 返回值:

    ​ 成功返回:写入的字符转换成的int类型的数据

    ​ 失败返回:EOF

  5. 代码:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //打开文件
    FILE * fp = fopen("./1.txt","w");
    if (NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    //开始像文件中写入数据
    char buf[123] = "hello world";
    int i = 0;
    while(1)
    {
        fputc(buf[i],fp);
        if ((strlen(buf) -1 ) == i)
        {
            break;
        }       
        i++;//遍历数组使用
    }
    fclose(fp);
    return 0;
}

练习:用fgetc 和fputc去完成文件的复制粘贴

7.fgets

  1. 原型:char *fgets(char *s, int size, FILE *stream);

  2. 功能:行读取

  3. 参数:

    ​ ① s:读取到的数据存放的地址

    ​ ② size:读取的字节个数

    ​ 1.当\n之前的字节个数小于size,遇到换行符结束读取,但是换行符也会被当做一个字符被读到内存之中,并且会在最后一个字符后添加'\0'.

    ​ 2.当\n之前的字节个数大于size,会读到size个大小的字节时停止读取。会在末尾加上一个'\0'。会将最后一个字节的字符给覆盖掉。机制会在读取完之后读写指针向前偏移一个字节

hello, world
size = 5 : fgets---> hello, 紧接着继续size = 5:fgets--->, wor,
//这里的覆盖就是这个意思,会把o 覆盖
​	③stream:文件流指针 

4. 返回值:

​	成功返回:读到的字符串的首地址     

​	失败返回:NULL        

​	读到文件末尾:返回NULL               

5. 代码:

#include <stdio.h>
int main(int argc, const char *argv[])
{
    FILE * fp = fopen("./1.txt","r");
    if (NULL ==fp)
    {
        perror("fopen");
        return -1;
    }
    //行读取
    while(1)
    {
    char buf[123] = {0};
    if (fgets(buf,11,fp) == NULL)
    {   
        //失败或者读到了文件的末尾
        if (feof(fp))
        {
            printf("读到了文件的末尾\n");
            break;
        }else{
            perror("fgets");
            return -1;
        }  
    }
    printf("buf = %s\n",buf);
    }
    return 0;
}

if (fgets(buf,11,fp) == NULL)
linux@ubuntu:~/test/IO/test$ ./a.out 
buf = hello worl	//hello wor'\0'd
buf = d			   //d'\n''\0'

读到了文件的末尾
    
if (fgets(buf,12,fp) == NULL)
linux@ubuntu:~/test/IO/test$ ./a.out 
buf = hello world	//hello world'\0'
buf = 			   //'\n''\0'

读到了文件的末尾    
    
if (fgets(buf,13,fp) == NULL)
linux@ubuntu:~/test/IO/test$ ./a.out 
buf = hello world  、//hello world'\n''\0'

读到了文件的末尾    

8.fputs

  1. 功能:向一个文件中进行字符串输入

  2. 原型:int fputs(const char *s, FILE *stream);

    📢注意:遇到'\0'停止

  3. 参数:

    ​ ① s:想要写入的数据的地址

    ​ ② stream:想要写入哪个文件

  4. 返回值:

    ​ 成功返回一个非负数

    ​ 失败返回EOF(-1)

  5. 代码:

#include <stdio.h>
int main(int argc, const char *argv[])
{
    FILE * fp = fopen("./1.txt","w");
    if (NULL ==fp)
    {
        perror("fopen");
        return -1;
    }
    //找到一个想要写入的数据的地址
    char buf[123] = "hello world";

    if (EOF == fputs(buf,fp))
    {
        perror("fputs");
        return -1;
    }
    return 0;
}

📝练习:请编写一个代码,用来测试文件的行数。

代码:

#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
    if (argc != 2)
    {
        printf("参数输入错误\n");
        return -1;
    }
    //打开文件,以只读的方式打开文件
    FILE * fp = fopen(argv[1],"r");
    if (NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    //开始计算
    int count = 0;//用作计数使用
    while(1)
    {
        char buf[123] = {0};
        if (NULL == fgets(buf,sizeof(buf),fp))
        {
            //出错或者到了文件的末尾
            if (feof(fp))
            {
                printf("到达了文件的末尾\n");
                break;
            }else{
                perror("fgets");
                return -1;
            }
        }
        if (buf[strlen(buf) -1] == '\n')
        {
            count++;
        }
    }
    printf("该文件行数为%d\n",count);

    return 0;
}

linux@ubuntu:~/test/IO/test$ ./a.out 1.txt 
到达了文件的末尾
该文件行数为0

linux@ubuntu:~/test/IO/test$ vi 1.txt 
//这里我进去vim编辑器输入之后再删除,文件行数会为1

linux@ubuntu:~/test/IO/test$ ./a.out 1.txt 
到达了文件的末尾
该文件行数为1

9.fread

  1. 原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  2. 功能:二进制读取文件

  3. 参数:

    ​ ① ptr :存放读到的数据的地址

    ​ ② size:对象的大小

    ​ ③ nmemb:对象的个数 📢注意:总的读取的字节个数为 对象的大小*对象的个数

    ​ ④ stream:目标文件流指针

  4. 返回值:

    ​ 成功返回读到的对象的个数

    ​ 失败返回一个 0

10.fwrite

  1. 原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);

  2. 功能:二进制写入文件

  3. 参数:

    ​ ① ptr :想要写入的数据的首地址

    ​ ② size:对象的大小

    ​ ③ nmemb :对象的个数

    ​ ④ stream :目标文件流指针

  4. 返回值:

    ​ 成功返回写入的对象的大小

    ​ 失败返回一个小的对象个数

  5. 代码:

#include <stdio.h>
#include <string.h>
//定义一个信息结构体
struct age
{
    char name[20];
    char sex;
    int age;
    char phone[12];
};
struct age myage1;//定义一个录入时使用的结构体
struct age myage2;//定义一个读取时使用的结构体
int main(int argc, const char *argv[])
{
STAT:
    //搭建界面
    printf("************************************\n");
    printf("**1.录入信息****2.读取信息*****3.退出**\n");
    printf("************************************\n");
    //用户开始输入命令
    printf("input >> ");
    int input = 0;
    scanf("%d",&input);
    if(input >3 || input <= 0)
    {
        goto STAT;
    }
    //开始写入功能
    if (1 == input)
    {
        //录入信息
        printf("请输入姓名\n");
        scanf("%s",myage1.name);
        getchar();
        printf("请输入性别\n");
        scanf("%c",&myage1.sex);
        getchar();
        printf("请输入年龄\n");
        scanf("%d",&myage1.age);
        
        printf("请输入手机号\n");
        scanf("%s",myage1.phone);
        getchar();

 //开始写入文件-------------------------------
        FILE *fp = fopen("./.1.txt","a");
            if (NULL == fp)
            {
                perror("fopen");
                return -1;
            }
        //开始写入   
        if(1 != fwrite(&myage1,sizeof(myage1),1,fp))
        {
                perror("fwrite");
                return -1;
        }
        fclose(fp);
        printf("写入信息完成,正在跳转到菜单\n");
        goto STAT;
    }else if (2 == input)
    {
        char username[23] = {0};
        //读取信息  
        printf("请输入要查询人的姓名\n");
        scanf("%s",username);
        getchar();
        //开始对文件进行读取并且匹配
         FILE *fp = fopen("./.1.txt","r");
        if (NULL == fp)
        {
            perror("fopen");
            return -1;
        }
        while(1)//循环读取
        {
                if(0 == fread(&myage2,sizeof(myage2),1,fp))
                {
                    if (feof(fp))
                    {
                        printf("读到了文件的末尾\n");
                        break;
                    }else{
                        perror("fread");
                        return -1;
                    }
                }
                //进行name匹配
                if (strcmp(username,myage2.name) == 0 )
                {
                  printf("姓名为%s\n",myage2.name);
                  printf("性别为%c\n",myage2.sex);
                  printf("年龄为%d\n",myage2.age);
                  printf("手机号为%s\n",myage2.phone);
                }             
        }
    }else {

        //退出
        return 0;
    } 
    return 0;
}


11.fseek

  1. 原型:int fseek(FILE *stream, long offset, int whence);

  2. 功能:读写指针的偏移

  3. 参数:

    ​ ① stream :目标文件流指针

    ​ ② offset

    ​ 该数为负数,向前进行偏移,如果偏移出了文件的开头,会报错返回如果

    ​ 该数为正数,向后进行偏移,如果偏移出了文件的末尾,会扩大文件,用'\0'来做填充。那么此类的文件被称为 空洞文件

    📢注意:如果偏移后没有对其进行任何写入操作,内核认为该偏移无效,不会扩大文件大小。

    ​ ③ whence:基准位置-----根据哪一个位置进行偏移

    ​ SEEK_SET(0): 根据文件开头进行偏移 ​ SEEK_CUR(1): 根据用户当前位置进行偏移 ​ SEEK_END(2): 根据文件末尾进行偏移

  4. 返回值:

    ​ 成功返回 0

    ​ 失败返回 -1

  5. 代码:

#include <stdio.h>

char ch = 0;

int main(int argc, const char *argv[])
{
    //打开一个文件
    FILE *fp = fopen("./1.txt","r+");
    if (NULL ==fp)
    {
        perror("fopen");
        return -1;
    }

    //文件中的数据为hello world

    //从末尾向后偏移--空洞文件
    fseek(fp,2000,SEEK_END);

    fputc('a',fp);
#if 0
    //开始进行指针偏移
    ch = fgetc(fp);
    printf("ch = %c \n",ch);
    fseek(fp,1,SEEK_CUR);//从当前位置向后偏移一个字节 
    ch = fgetc(fp);
    printf("ch = %c \n",ch);//--->l

    fseek(fp,-1 ,SEEK_END);

    ch = fgetc(fp);
    printf("ch = %c \n",ch);//--->d
#endif
    return 0;
}    

例如:

1.txt文件

📢注意:d不是文件的末尾,‘\n’才是

fseek(fp,1,SEEK_SET);//从当文件开头向后偏移一个字节 
ch = fgetc(fp);
printf("ch = %c \n",ch);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = e 

fseek(fp,10,SEEK_SET);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = d

fseek(fp,11,SEEK_SET);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = 
         //📢注意这里有个空行,因为vim编辑器会自动加一个'\n'

fseek(fp,12,SEEK_SET);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = � 
    
fseek(fp,13,SEEK_SET);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = � 
fseek(fp,1,SEEK_CUR);//从当前位置向后偏移一个字节 
ch = fgetc(fp);
printf("ch = %c \n",ch);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = e 
fseek(fp,0 ,SEEK_END);
ch = fgetc(fp);
printf("ch = %c \n",ch);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = � 

fseek(fp,-1 ,SEEK_END);
ch = fgetc(fp);
printf("ch = %c \n",ch);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = 

fseek(fp,-2 ,SEEK_END);
ch = fgetc(fp);
printf("ch = %c \n",ch);
linux@ubuntu:~/test/IO/fseek$ ./a.out 
ch = d 


12.ftell

  1. 原型:long ftell(FILE *stream);

  2. 功能:获取当前读写指针的偏移量

  3. 参数:目标文件流指针

  4. 返回值:

    成功返回偏移量(是根据文件开头来计算的

    失败返回-1

  5. 使用场景:通常与fseek(fp,0,SEEK_END)来配合使用测量文件当前的大小

  6. 代码:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    FILE *fp = fopen("./1.txt","r+");
    if (NULL ==fp)
    {
        perror("fopen");
        return -1;
    }
    //从末尾向后偏移--空洞文件
    fseek(fp,2000,SEEK_END);
    printf("当前的偏移量为%ld\n",ftell(fp));
    fputc('a',fp);
    printf("当前的偏移量为%ld\n",ftell(fp));
    return 0;
}
linux@ubuntu:~/test/IO/test$ ./a.out 
当前的偏移量为2006
当前的偏移量为2007
linux@ubuntu:~/test/IO/test$ ./a.out 
当前的偏移量为4007
当前的偏移量为4008
linux@ubuntu:~/test/IO/test$ ./a.out 
当前的偏移量为6008
当前的偏移量为6009

13.rewind

  1. 原型: void rewind(FILE *stream);
  2. 功能:直接将文件读写指针偏移到文件开头 类似 :fseek(stream, 0L, SEEK_SET)
  3. 参数:目标文件流指针
  4. 返回值:无
  5. 代码:
rewind(fp); 
printf("当前的偏移量为%ld\n",ftell(fp));        

14.sprintf

  1. 原型:int sprintf(char *str, const char *format, ...);

  2. 功能:向一个固定的地址中存放字符串(一般用于字符串的拼接

  3. 参数:

    ​ ① str :要存放格式化完成的字符串的地址

    ​ ② format:格式化字符串{固定的字符串和占位符}

    ​ ③ ... :可变参数(一般放置变量

  4. 返回值:成功返回输出的字节个数 失败返回负数

  5. 代码:

#include <stdio.h>
int main(int argc, const char *argv[])
{
    //name  age  sex phone 
    char name[20]="zhangsan";
    int age = 18;
    char sex = 'w';
    char phone[12] = "12345678922";
    char buf[123] = {0};
    //开始拼接
    sprintf(buf,"name:%s--age:%d--sex:%c--phone:%s",name,age,sex,phone);
    printf("buf = %s \n",buf);
    return 0;
}
linux@ubuntu:~/test/IO/test$ ./a.out 
buf = name:zhangsan--age:18--sex:w--phone:12345678922 

15.snprintf

  1. 原型:int snprintf(char *str, size_t size, const char *format, ...);

  2. 功能:按照固定的大小去格式化字符串输出到字符地址中

  3. 参数:

    ​ ① size :大小 (规定要写入str这片地址中的字节大小)

    ​ ② str :要存放格式化完成的字符串的地址

    ​ ③ format:格式化字符串{固定的字符串和占位符}

    ​ ④ ... :可变参数(一般放置变量)

  4. 返回值:

    ​ 成功返回:返回目标字符串的总大小

    ​ 失败返回 :负数

16.fprintf

  1. 原型:int fprintf(FILE *stream, const char *format, ...);

  2. 功能:格式化输出字符串到文件中(一般用于书写日志文件

  3. 参数:

    ​ ① stream:目标文件流指针

    ​ ② str :要存放格式化完成的字符串的地址

    ​ ③ format:格式化字符串{固定的字符串和占位符}

    ​ ④ ... :可变参数(一般放置变量)

  4. 返回值:

    ​ 成功返回:返回输出的字节大小

    ​ 失败返回:负数

  5. 代码:

#include <stdio.h>
int main(int argc, const char *argv[])
{
    //name  age  sex phone 
    char name[20]="zhangsan";
    int age = 18;
    char sex = 'w';
    char phone[12] = "12345678922";
    //打开文件
    FILE * fp = fopen("./1.txt","w");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
fprintf(fp,"name:%s--age:%d--sex:%c--phone:%s",name,age,sex,phone);
    return 0;
}

写简单的日志文件

#include <stdio.h>
#include <time.h>
int main(int argc, const char *argv[])
{
    //打开一个文件
    FILE *fp = fopen("./1.txt","a");
    if(NULL == fp)
    {
        perror("fopen");
        
        return -1;
    }
    while(1)
    {
        //获取当前的距离1970年的秒数
        time_t mytime = time(NULL);
        //开始转换时间
        struct tm * mytm = localtime(&mytime);
        fprintf(fp,"当前的时间为:%d年-%d月-%d日--%d:%d:%d\n",
        mytm->tm_year+1900,mytm->tm_mon+1,mytm->tm_mday,
        mytm->tm_hour,mytm->tm_min,mytm->tm_sec);
        fflush(fp);
        sleep(1);
    }
    return 0;
}

tail -f 1.txt

linux@ubuntu:~/test/test$ tail -f 1.txt 
当前的时间为:2021年-8月-25日--15:47:5
当前的时间为:2021年-8月-25日--15:47:6
当前的时间为:2021年-8月-25日--15:47:7
当前的时间为:2021年-8月-25日--15:47:8
当前的时间为:2021年-8月-25日--15:47:9