标准IO的基础知识及相关函数的用法

发布于:2022-12-02 ⋅ 阅读:(252) ⋅ 点赞:(0)

前言

学习本文前需要新了解的相关知识Linux系统中的错误码与缓冲区

一、IO相关知识

1.1 最先接触过的IO

下面的头文件就是在我们学习时最先接触过的一个IO。

#include <stdio.h>

stdio:标准的输入输出,也就是我们本文要学习的标准IO。
printf/scanf :标准IO的接口。

1.2 标准IO与文件IO的区别

标准IO:是库函数
文件IO:是系统调用

1.3 库函数与系统调用的区别

系统调用(posix):从用户空间进入内核空间的一次过程就是一次系统调用。
系统调用没有缓冲区效率比较低。系统调用的可移植性比较差(接口不统一)。

函数库(ANSI C):函数库=缓冲区+系统调用。
库函数效率比系统调用的高,并且可以移植性比较强。
在这里插入图片描述

在这里插入图片描述

二、FILE指针

FILE是一个结构体,fopen函数返回的就是一个结构体指针,这个FILE指针记录了打开文件的一切信息,我们在操作文件的时候就通过这个FILE完成。

typedef struct _IO_FILE FILE;

struct _IO_FILE {
	char* _IO_buf_base;    //缓冲区的起始地址
	char* _IO_buf_end;     //缓冲区的结束地址
   ...
}

在一个正在运行的程序中默认已经产生了三个FILE指针

stdin:标准输入,程序从终端读取数据的时候使用。
stdout:标准输出,程序向终端输出数据的时候使用。
stderr:标准出错。

三、标准IO常用的接口

标准IO常用的接口:
printf、scanf、fopen、fclose、fgetc、fputc、fgets、fputs、fread、fwrite…(由于printf和scanf我们已经很熟悉了本文不再讲解)。

3.1 fopen/fclose函数

#include <stdio.h>

FILE *fopen(const char *pathname, const char *mode);
功能:使用标准IO接口打开文件
参数:
	@pathname:文件的路径和名字 例:"./hello.txt"
	@mode:	   文件打开的方式 例:"r","w"
		方式有以下6种:
		"r"		以只读的方式打开文件,将光标定位到开头	
		
		"r+"	以读写的方式打开文件,将光标定位到开头	
		
		"w"		以只写的方式打开文件,若文件不存在则创建文件,若文件
				存在则清空文件,将光标定位到开头
				
		"w+"	以读写的方式打开文件,若文件不存在则创建文件,若文件
				存在则清空文件,将光标定位到开头
				
		"a"		以追加的方式打开文件,若文件不存在则创建文件,若文件
				存在则将光标定位到结尾
				
		"a+"	以读和追加的方式打开文件,若文件不存在则创建文件,若
				文件存在不会清空,读的时候光标定位在开头,写的时候光
				标定位在结尾
返回值:成功返回FILE指针,失败返回NULL,置位错误码。

#include <stdio.h>

int fclose(FILE *stream);
功能:关闭文件
参数:
	@stream:文件指针
返回值:成功返回0,失败返回EOF(#define EOF (-1)),置位错误码

3.1.1 代码示例

fopen代码示例:

#include <stdio.h>

int main(int argc, const char* argv[])
{
    FILE* fp;

    fp = fopen("./hello.txt","w");
    if(fp == NULL){
        printf("fopen file error\n");
        return -1;
    }
    return 0;
}

fclose代码示例:

#include <stdio.h>

#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc, const char* argv[])
{
    FILE* fp;
    if ((fp = fopen("./hello.txt", "w")) == NULL)
        PRINT_ERR("fopen hello.txt error");
    if (fclose(fp))
        PRINT_ERR("close file error");
	//关闭标准输入
    if (fclose(stdin))
        PRINT_ERR("close stdin error");
    char ch;
    scanf("%c",&ch);
    printf("11111111111111111\n");
    if (fclose(stdout))//关闭标准输出
        PRINT_ERR("close stdin error");
    printf("222222222222222222\n");  //终端上不会显示2打印信息
    return 0;
}

结果展示:
在这里插入图片描述

3.1.2 总结

  1. 如果文件没有关闭,在程序结束的时候,操作系统会自动回收
  2. 对于文件指针,只能关闭一次,不能关闭多次
  3. 如果在一个服务器,打开的文件不关闭,最后就不能够打开新的文件的,因为它属于资源,所以最好自己打开的文件就自己关闭。
  4. 如果关闭了标准输入stdin就不能够在输入了,比如不能够调用scanf函数。
  5. 如果关闭了标准输出stdout,不能够使用printf函数。

3.2 fgetc/fputc函数

int fgetc(FILE *stream);
功能:从文件中获取一个字符
参数:
	@stream:文件指针
返回值:成功返回字符的ascii,失败返回EOF

int fputc(int c, FILE *stream);
功能:向文件中输入一个字符
参数:
	@c:要输入的字符
	@stream:文件指针
返回值:成功返回字符的ascii,失败返回EOF

3.2.1 代码示例

使用fgetc/fputc实现文件拷贝

#include <stdio.h>

#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc, const char* argv[])
{
    //1.判断传参是否正确./a.out srcfile destfile
    if(argc!=3)
    {
        printf("传参错误,请重新输入\n");
        printf("usage:./a.out srcfile destfile\n");
        return -1;
    }
    //2.打开源文件和目标文件
    FILE *sfp,*dfp;
    if((sfp=fopen(argv[1],"r"))==NULL)
    {
        PRINT_ERR("fopen srcfile error");
    }
    if((dfp=fopen(argv[2],"w"))==NULL)
    {
        PRINT_ERR("fopen destfile error");
    }
    //3.循环读取拷贝
    char ch;
    while((ch=fgetc(sfp))!=EOF)
    {
        fputc(ch,dfp);
    }
    //4.关闭文件
    fclose(dfp);
    fclose(sfp);
    return 0;
}

结果展示:
在这里插入图片描述
注:拷贝文件之后使用diff命令比较两个文件,如果命令没有任何输出,说明两个文件是一样的
在这里插入图片描述

3.2.2 总结

当读取到文件的结尾的时候,打印的是EOF(-1)

3.3 fgets/fputs函数

char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取字符串
/*从文件中最多读取小于size的字符,并将其储存到s所指向的缓冲区中。读取
在遇到EOF或者换行符('\n')之后停止,如果读取换行符则将其储存到缓冲区
中,并将一个终止符NULL('\0')存储在缓冲区的最后一个字符之后*/
参数:
	@s:存储读取到的字符的首地址
	@size:想读取的大小
	@stream:文件指针
返回值:成功返回s,失败返回NULL

int fputs(const char *s, FILE *stream);
功能:向文件中写入字符串
/*向文件写入字符串,但是不包括字符串结尾的终止符'\0'*/
参数:
	@s:字符串的首地址
	@stream:文件指针
返回值:成功返回大于0的值,失败返回EOF

3.3.1 代码示例

使用fgets/fputs拷贝文件

#include <stdio.h>

#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc, const char* argv[])
{
    //1.判断传参是否正确./a.out srcfile destfile
    if(argc!=3)
    {
        fputs("input error,try again\n", stderr);
        fputs("usage: ./a.out srcfile destfile\n", stderr);
        return -1;
    }
    //2.打开源文件和目标文件
    FILE *sfp,*dfp;
    if((sfp=fopen(argv[1],"r"))==NULL)
    {
        PRINT_ERR("fopen srcfile error");
    }
    if((dfp=fopen(argv[2],"w"))==NULL)
    {
        PRINT_ERR("fopen destfile error");
    }
    //3.循环读取拷贝
    char buf[128]={0};
    while(fgets(buf,sizeof(buf),sfp)!=NULL)
    {
        fputs(buf,dfp);
    }
    //4.关闭文件
    fclose(dfp);
    fclose(sfp);
    return 0;
}

结果展示:
在这里插入图片描述

3.3.2 总结

当用追加读的方式打开文件的时候,如果有fputs的操作,光标的位置会移动到结尾的位置,fgets
则会读取不到内容。
如果将错误信息写入到标准出错中,标准错误没有缓冲区会直接显示。

3.4 fread/fwrite函数

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件中读取数据到ptr中
参数:
	@prt:用来存储读取到的数据的首地址
	@size:每一项的大小
	@nmemb:项目的个数
	@stream:文件指针
返回值: 成功返回读到项目的个数,如果每一项的大小为1字节,则返回项目的
        个数就是字节的个数
        失败返回小于项目的个数或者是0
        如果失败需要通过ferror或者feof判断是哪种错误
        if(ferror(fp))
        {
        	printf("读的时候发生了错误\n");
        ]
        if(feof(fp))
        {
			printf("读取到文件的结尾了\n");
		}

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:将ptr中的数据写到文件中
参数:
	@ptr:要向文件中写入数据的首地址
	@size:每一项的大小
	@nmemb:项目的个数
	@stream:文件指针
返回值:成功返回向文件中写入项目的个数
	    失败返回小于项目的个数或者是0

3.4.1 代码示例

使用fread和fwrite完成文件的拷贝

#include <stdio.h>

#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc, const char* argv[])
{
    //1.判断传参是否正确./a.out srcfile destfile
    if(argc!=3)
    {
        fputs("input error,try again\n", stderr);
        fputs("usage: ./a.out srcfile destfile\n", stderr);
        return -1;
    }
    //2.打开源文件和目标文件
    FILE *sfp,*dfp;
    if((sfp=fopen(argv[1],"r"))==NULL)
    {
        PRINT_ERR("fopen srcfile error");
    }
    if((dfp=fopen(argv[2],"w"))==NULL)
    {
        PRINT_ERR("fopen destfile error");
    }
    //3.循环读取拷贝
    char buf[128]={0};
    int ret;
    while((feof(sfp)||ferror(sfp))==0)
    {
        ret=fread(buf,1,sizeof(buf),sfp);
        fwrite(buf,1,ret,dfp);
    }
    //4.关闭文件
    fclose(dfp);
    fclose(sfp);
    return 0;
}

结果展示:
在这里插入图片描述

3.4.2 总结

使用fread/fwrite可以读写结构体,整数,和字符串。
(feof(sfp)||ferror(sfp)等于零的时候才说明没有出错或者没有读到结尾。

3.5 sprintf/snprintf/fprintf函数

int sprintf(char *str, const char *format, ...);
功能:将字符串格式化到str中,(sprintf在越界输入的时候会发生错误)
参数:
	@str:储存格式化后的字符串
	@format,...:可变参数,和printf里的写法完全相同
返回值:成功返回格式化后的字符个数,失败或遇到错误则返回负值

int snprintf(char *str, size_t size, const char *format, ...);
功能:将字符串格式化到str中
参数:
	@str:储存格式化后的字符串
	@size:输入的字符的个数,最后一个成员包含'\0'
	@format,...:可变参数,和printf里的写法完全相同
返回值:成功返回格式化后的字符个数,失败或遇到错误则返回负值

int fprintf(FILE *stream, const char *format, ...);
功能:将字符串格式化到对应的stream文件中
参数:
	@stream:文件指针
	@format,...:可变参数,和printf里的写法完全相同
返回值:成功返回格式化后的字符个数,失败或遇到错误则返回负值

3.5.1 代码示例

sprintf/snprintf/fprintf函数举例

#include <stdio.h>
#include <string.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

struct {
    char name[20];
    int age;
} a = {
    "zhangsan",
    22
};
int main(int argc, const char* argv[])
{
    int num=123456;
    char buf[128]={0};
    //1.打开文件
    FILE *fp;
    if((fp=fopen("hello.txt","w"))==NULL)
    {
        PRINT_ERR("fopen error");
    }
    //2.将整数格式化成字符串放到buf中
    sprintf(buf,"2.num=%d,%d\n",num,num+1);
    fwrite(buf,1,strlen(buf),fp);

    //3.将结构体的成员格式化为字符串放到buf中
    memset(buf,0,sizeof(buf));
    sprintf(buf,"3.name=%s,age=%d\n",a.name,a.age);
    fwrite(buf,1,strlen(buf),fp);

    //4.即使这里越界输入,snprintf也只会格式化size-1个字符
    char buff[5]={0};
    snprintf(buf,5,"4.12345fsdfasdfasdfsadfasdfasf");
    printf("buf = %s\n",buf);

    //5.向标准错误中写字符串
    fprintf(stderr,"5.hello world\n");

    //6.向标准输出中写字符串
    fprintf(stdout,"6.1234567890\n");

    //7.向文件中写
    fprintf(fp,"7.我是夜猫徐%d\n",666);
    //8.关闭文件
    fclose(fp);
    return 0;
}

3.5.2 结果展示

在这里插入图片描述

3.6 fseek/ftell/rewind光标操作函数

int fseek(FILE *stream, long offset, int whence);
功能:用来设置光标的位置
参数:
	@stream:文件指针
	@offset:将光标设置(偏移)到哪里
		>0	向后偏移
		<0	向前偏移
		=0	不偏移
	@whence:从哪开始偏移
		SEEK_SET	开头
		SEEK_CUR	当前位置
		SEEK_END	结尾
返回值:成功返回0,失败返回-1,置位错误码

long ftell(FILE *stream);
功能:获取光标到开头的值(单位字符)
参数:
    @stream:文件指针
返回值:成功返回光标当前位置到开头的字符个数,失败返回-1,置位错误码

void rewind(FILE *stream);
功能:将光标设置到文件的开头
参数:
    @stream:文件指针
返回值:无

3.6.1 代码示例

fseek/ftell/rewind函数举例

#include <stdio.h>
#include <string.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc, const char* argv[])
{
    //1.打开文件
    FILE *fp;
    if((fp=fopen("hello.txt","r+"))==NULL)
    {
        PRINT_ERR("fopen error");
    }
    //2.从开头向后偏移10位
    fseek(fp,10,SEEK_SET);
    //3.返回光标到开头的位置
    printf("pos=%ld\n",ftell(fp));
    printf("%c\n", fgetc(fp));
    fputc('1',fp);
    //4.将光标设置到开头位置
    rewind(fp);
    fputc('2',fp);
    //.关闭文件
    fclose(fp);
    return 0;
}

结果展示:
在这里插入图片描述

3.6.2 总结

  1. rewind(fp);等价于fseek(fp,0,SEEK_SET);
  2. fseek(fp,0,SEEK_END);ftell(fp); 这两句代码就可以统计文件的大小。

注:
如果文件是以a+/a的方式打开,通过fseek修改光标的位置无效。写的内容永远在结尾。但是可以修改读的光标的位置。


网站公告

今日签到

点亮在社区的每一天
去签到