C-study(十三)

文件输入输出

文件:磁盘上的一段已命名的存储区
文件重定向:从文件读取信息、信息写入文件

C把文件看作一系列连续的字节,每个字节都能被单独读取

输入:字节流,源于文件和输入设备
输出:字节流,目的是文件和视频显示

输入函数
1、不做改动读取或存储字节
2、把字节解释为字符解释为普通文本表示数字
输出函数
1、原样转移二进制
2、转换为文本、文本表示数字

数据以二进制形式存储、数据内容分为
文本数据:文件数据->字符->字符码的二进制
二进制数据:文件数据->二进制

文件格式:
二进制格式:在二进制文件中存储二进制数据、直接存取、适用不损失精度保存或恢复数据、eg fwrite、fread
文本格式:打开文本文件中的文本数据、转换为字符存取、适用保存文本信息,文本编辑器查看文本、eg getc、fprintf、fscanf

另外支持:
二进制数据或文本数据形式存储或读取信息
二进制模式可以打开文本格式的文件
文本可以存储在二进制形式的文件中
getc拷贝包含二进制数据的文件

文件模式

文件:二进制形式(0、1)存储

文本文件:文件用二进制编码的字符表示文本,包含文本内容
二进制文件:文件中二进制值表示机器语言代码、数值数据、图片、音乐,包含二进制内容

访问文件的途径:
文本模式:把本地环境表示的行末尾或文件结尾映射为C模式,eg MS-DOS系统中换行符为\r\n ,C读取文件时将\r\n转换为\n,写入文件时把\n转换为\r\n
二进制模式:访问文件的每个字节,遇到\r\n时不会发生映射

在这里插入图片描述

标准文件

stdio.h 把3个文件指针和3个标准文件关联
标准输入:stdin
普通输入设备,eg:键盘
为程序提供输入,是getchar()和scanf()使用的文件

标准输出:stdout
普通输出设备,eg:屏幕
输出到标准输出,是putchar()、puts()和printf()使用的文件

标准错误输出:stderr
普通输出设备,eg:屏幕
提供逻辑上不同的地方发送错误消息
在这里插入图片描述

标准 I/O

printf scanf gets puts getchar putchar…

IO级别

底层I/O:使用操作系统提供的基本I/O服务

标准高级I/O:
1、使用C库的标准包和stdio.h头文件定义,可移植
2、有许多函数简化处理不同I/O的问题,eg:printf把不同形式的数据转化为和终端适应的字符串输出
3、输入输出都是缓冲,一次转移至少512字节信息,提高数据传输速率

IO流程

使用标准输入:
1、调用fopen:打开一个流
打开文件、创建1个缓冲区(读写模式2个缓冲区)、一个包含文件和缓冲区信息的结构
返回一个指向该结构的指针

缓冲区大小:512字节或倍数

结构包括:
一个指定流中当前位置的文件位置指示器
错误和文件结尾的指示器
一个指向缓冲区开始处的指针
一个文件标识符
一个计数(统计拷贝进缓冲区的字节数)

文本模式打开获得文本流、二进制模式打开获得二进制流

2、调用输入函数:fgets\fscanf\getc
拷贝文件中的数据块到缓冲区

设置fp指向的结构中的值:流的当前位置(从字节0开始)、拷贝进缓冲区的字节数)

按要求从缓冲区中读取数据、文件位置指示器设置为刚读取字符的下一个字符

读完缓冲区的所有字符之后,请求拷贝下一个数据块到缓冲区

循环读取文件所有内容

读取到缓冲区的文件结尾之后,结尾指示器设置为真,之后调用输入函数返回EOF

使用标准输出:数据写入缓冲区,缓冲区填满之后拷贝至文件

fopen

在只有一种文件类型的系统,带b的模式和不带b的模式相同

w模式:打开现有文件,会删除文件内容

C11新增 x:写模式,
打开现有内容,fopen操作失败,源文件的内容不会被删除
独占特性:其他程序无法访问已经被打开的文件

在这里插入图片描述

#include <stdio.h> //FILE类型
#include <stdlib.h> /*提供exit()的原型,关闭所有打开的文件并结束程序
正常结束返回0,异常结束返回非零值
stdlib.h中定义EXIT_SUCCESS和EXIT_FAILURE */
int main(int argc,char *argv[])
{
	/* count.c --使用标准I/O
    使用命令行参数 程序名argv[0] 待读文件argv[1] argc=2
    待处理文件要和可执行文件在同一目录 */

    int ch; // 读取文件时,储存每个字符的地方
    FILE *fp;
    /*"文件指针",指向FILE的指针,
    指向一个包含文件信息的数据对象,包含缓冲区信息(缓冲区的位置,缓冲区被填充的程度)*/
    unsigned long count = 0;
    if (argc < 2)
    { // 检查命令行参数,如果没有参数则报错退出程序
        printf("Usage : %s filename \n", argv[0]);
        exit(EXIT_FAILURE);//结束程序,正常结束0,异常结束非0
    }
    if ((fp = fopen(argv[1], "r")) == NULL)
    { /* 第一个参数:待打开文件的名称,包含文件名的字符串地址
        第二个参数:待打开文件的模式,r:读模式
        返回文件指针,其他I/O函数通过fp访问该文件 */
        printf("Can't open %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    while ((ch = getc(fp)) != EOF)
    { /*从标准输入中获取一个字符存入ch
     getc读取到文件结尾返回EOF
     避免读到空文件,使用入口循环 */
        putc(ch, stdout);
        /* putc(待写入的字符,文件指针);
        putc(ch,stdout); 与putchar(ch);相同 把ch标准输出
        putc(ch,fp); 把ch放入FILE指针fp指向的文件中
        putchar()/getchar()一般通过putc()/getc()定义 */
        count++;
    }
    fclose(fp); // 关文件流,if(fclose(fp)!=0)关闭失败
    printf("File %s has %lu characters\n", argv[1], count);

#if 0
    /*判断文件结尾*/
    FILE *fp;
    int ch;
    fp = fopen("wacky.txt", "r");
    // 设计范例#1
    // 用int类型的变量储存EOF
    ch = getc(fp); // 获取初始输入
    while (ch != EOF)
    {
        putchar(ch);   // 处理输入
        ch = getc(fp); // 获取下一个输入
    }
    // 设计范例#2
    while ((ch = getc(fp)) != EOF)
        putchar(ch); // 处理输入

    // 糟糕的设计(存在两个问题)
    while (ch != EOF) // 首次使用ch时,它的值尚未确定,error
    {
        ch = getc(fp); // 获取输入,此时获取到EOF,程序会继续进行,error
        putchar(ch);   // 处理输入
    }
#endif
	return 0;//相当于exit(0);具体区别待分析
}

文件 I/O

用FILE指针指定待处理的文件

fprintf() fscanf()

文件输入数据
数据输出到文件
相同类型转换


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 41
	/*addaword.c --使用fprintf()、fscanf()和rewind()*/
    FILE *fp;
    char words[MAX];
    if ((fp = fopen("wordy", "a+")) == NULL)
    { /*a+ 读写操作,没有文件创建,有文件追加*/
        fprintf(stdout, "Can't open \"wordy\" file.\n");
        /*和printf类似,第一个参数为FILE*/
        exit(EXIT_FAILURE);
    }
    puts("Enter words to add to the file; press the #");
    puts("key at the beginning of a line to terminate.");
    while ((fscanf(stdin, "%40s", words) == 1) && (words[0] != '#')) // 和scanf类似,第一个参数为FILE
        fprintf(fp, "%s \n", words);
    puts("File contents : ");
    rewind(fp); /*返回到文件开始处*/
    while (fscanf(fp, "%s", words) == 1)
        puts(words);
    puts("Done ! ");
    if (fclose(fp) != 0)
        fprintf(stderr, "Error closing filein");

fgets 和 fputs

文件输入字符串
字符串输出文件

//示例
fgets(buf, STLEN, fp);
/*buf:char类型数组的名称,获取的字符串的存放地址
STLEN:字符串大小
fp:指向FILE指针,待读取的文件
读取输入,直到第一个换行符之后(包括换行符)、文件结尾、STLEN-1个字符
在末尾加一个空字符使其称为字符串,长度为STLEN
遇到EOF返回NULL
把fp的STLEN-1内容读取到buf */
fputs(buf, fp); // buf 字符串地址,fp指定目标文件,不会添加换行符,把buf的内容输出到fp

简单的文件压缩程序

//reducto.c -把文件压缩成原来的1/3!
#include <stdio.h>
#include <stdlib.h>//提供exit()的原型
#include <string.h>//提供 strcpy()、 strcat()的原型
#define LEN 40
int main(int argc,char *argv[])
{
	 FILE *in, *out;
    /*同时打开两个文件时,声明两个指向FILE的指针,单独打开或关闭
    同时打开的文件数量有限,
    不需要同时打开的文件可以使用同一个文件指针处理*/
    int ch;
    char name[LEN]; // 储存输出文件名
    int count = 0;
    if (argc < 2)
    { // 检查命令行参数
        fprintf(stderr, "Usage : %s filename\n", argv[0]);
        /*fprintf(文件指针,格式化字符串,参数)
        stderr把错误消息发送到标准错误*/
        exit(EXIT_FAILURE);
    }
    if ((in = fopen(argv[1], "r")) == NULL)
    { // 设置输入
        fprintf(stderr, "I couldn't open the file \" %s \"\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    // 设置输出
    strncpy(name, argv[1], LEN - 5); // 拷贝文件名,LEN-5为.red预留空间
    name[LEN - 5] = '\0';            // 避免argv[1]>len-5,未拷贝空字符
    strcat(name, ".red");            // 在文件名后添加.red,从name的第一个空字符开始覆盖
    if ((out = fopen(name, "w")) == NULL)
    { // 以写模式打开文件,==NULL检查是否成功打开
        fprintf(stderr, "Can't create output file.\n");
        exit(3);
    }
    // 拷贝数据
    while ((ch = getc(in)) != EOF)
        if (count++ % 3 == 0)
            putc(ch, out); // 打印3个字符中的第1个字符,输出到.red文件中
    // 收尾工作
    if (fclose(in) != 0 || fclose(out) != 0)
        fprintf(stderr, "Error in closing files\n");
    return 0; // 相当于exit(0);具体区别待分析
}

fseek ftell

fseek 把fp移动到任意字节处

ftell:返回距离开始地址字节数
在文本模式,返回的值可以作为fseek的第二个参数
在MSDOS,返回值把\r\n作为一个字节计数

文件大小均限制在long类型能表示的范围内
ANSI标准,在stdio.h定义
在这里插入图片描述

#define CNTL_Z '\032' /*Dos文本文件中的文件结尾标记*/
#define SLEN 81
    /*reverse.c --倒序显示文件的内容
    二进制模式 支持UNIX(一种文件格式,不需要格式转换)和MS-DOS(ctrl+z标记文本文件的结尾,\r\n换行)
    文本模式时可以识别ctrl+z结尾,二进制模式时ctrl+z被看作一个字符,需要特殊处理
    文本模式可以自动将\r\n换为\n,二进制模式需要特殊处理
    待处理文件要和可执行文件在同一目录*/
    char file[SLEN];
    char ch;
    FILE *fp;
    long count, last;
    puts("Enter the name of the file to be processed: ");
    scanf("%80s", file);
    if ((fp = fopen(file, "rb")) == NULL)
    { /*只读模式*/
        printf("reverse can't open %s\n", file);
        exit(EXIT_FAILURE);
    }
    fseek(fp, 0L, SEEK_END);
    /*定位到文件末尾
    fp:FILE指针 指向待查找的文件,文件必须已打开
    0L:偏移量,从起点开始要移动的距离,long类型,正(前移)负(后移)0(不动)
    SEEK_END 模式,确定起始点

    fseek(fp, 0L, SEEK_SET);   // 定位至文件开始处
    fseek(fp, 10L, SEEK_SET);  // 定位至文件中的第10个字节
    fseek(fp, 2L, SEEK_CUR);   // 从文件当前位置前移2个字节
    fseek(fp, 0L, SEEK_END);   // 定位至文件结尾
    fseek(fp, -10L, SEEK_END); // 从文件结尾处回退10个字节
    正常返回0,错误(超出文件范围)返回-1
    */

    last = ftell(fp);
    /*传入FILE指针,返回距离文件开始处的字节数,long类型
    第一个字节到开始处的距离是0
    适用于二进制模式打开的文件
    此时fp指向文件末尾,last=文件所有字节数-1
    */
    for (count = 1L; count <= last; count++)
    {
        fseek(fp, -count, SEEK_END); /*回退*/
        ch = getc(fp);
        if (ch != CNTL_Z &&ch != '\r')
            /* MS-DOS 文件 以ctrl+z标记文件结尾 \r处理*/
            putchar(ch);
    }
    putchar('\n');
    fclose(fp);

可移植性

fseek和ftell符合UNIX
系统有差异,ANSI C限制:
二进制模式中,不必支持SEEK_END模式,移植性更高的方法:逐字节读取整个文件到文件末尾
文本模式中保证以下调用
在这里插入图片描述

fgetpos fsetpos

大文件的定位函数
文件定位类型 fpos_t 、fpos_t 类型的变量或数据对象可以在文件指定一个位置,不能是数组类型

int fgetpos(FILE *restrict stream, fpos_t *restrict pos);
/*把fpos_t 类型的值放在pos指向的位置上,描述文件中的一个位置
成功返回0,失败返回非0*/
int fsetpos(FILE *stream, const fpos_t *pos);
/*通过pos指向的fpos_t设置文件指针指向指定的位置
成功返回0,失败返回非0
fpos_t的值应通过fgetpos获取*/

其他标准I/O

ungetc

int ungetc(int c, FILE *fp);
/*把c指定的字符放回到输入流,下次标准输入时会读取
eg:要读取下一个冒号之前的所有字符,不包括冒号:getchar/getc读取到冒号,ungetc把:放回输入流中
保证每次只放回一个字符
多个字符时需要注意顺序,读入和放回顺序相反*/

在这里插入图片描述

fflush

int fflush(FILE *fp);
/*刷新缓冲区:输出缓冲区中所有未写入数据发送到fp,
fp为空时,刷新所有输出缓冲区
输入流中fflush效果未定义
更新输出操作的流*/

setvbuf

int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
/*创建一个供标准I/O函数替换使用的缓冲区
打开文件且未对流进行其他操作之前调用
fp:识别待处理的流
buf:指向待使用的存储区,buf值不为NULL时创建一个缓冲区
size:数组大小
mode:
_IOFBF表示完全缓冲(在缓冲区满时刷新);
_IOLBF表示行缓冲(在缓冲区满时或写入一个换行符时);
_IONBF 表示无缓冲。
操作成功,返回0,否则返回非零值。*/

二进制I/O: fread ()和fwrite ()

标准IO:面向文本,处理字符和字符串

二进制形式处理数据:以程序所用的表示法把数据存储在文件中,不存在从数值形式到字符串的转换过程

fwrite fread 使用二进制形式处理数据

//声明
size_t fwrite(const void *restrict ptr, size_t size, size_t nmemb, FILE *restrict fp);
/*二进制数据写入文件
返回值类型:size_t 标准C类型定义sizeof运算符返回的类型,通常是unsigned int
返回:成功写入项的数量,成功是nmemb,写入错误时<nmemb
ptr 待写入数据块的地址、参数不是固定的类型,都被转换为void的指针类型
size 待写入数据块的大小(字节为单位)
nmemb 待写入数据块的数量
fp 待写入的文件

nmemb块size字节的数据从ptr写入fp
*/

size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE *restrict fp);
/*读取fp的数据存到ptr
返回:成功读取项的数量,成功是nmemb,写入错误时<nmemb
*/
  	//示例
    double num = 1./3;
    fprintf(fp, "%f", num);
    /*把num存储为8个字符 0.333333
    fprintf(fp,"%.2f",num);存储为4个字符 0.33 读取时无法恢复为更高的精度
    fprintf(fp,"%.12f",num);14个字符0.333333333333
    改变转换说明改变存储该值所需的空间数量,导致存储不同的值
    为保证数值存储前后一致,使用与计算机相同的位组合来存储,double存在double大小的单元中
    */

    char buffer[256];
    fwrite(buffer, 256, 1, fp); /*1块256字节的数据从buffer写入文件*/
    double earnings[10];
    fwrite(earnings, sizeof(double), 10, fp); /*保存一个10个double类型值的数组*/

 
    double earnings[10];
    fread(earnings, sizeof(double), 10, fp); // 10个double大小的值拷贝进earnings 数组中。

在这里插入图片描述

feof 和ferror

标准输入函数返回EOF、文件结尾
读取错误时,函数返回EOF

int feof(FILE *fp); // 当上一次输入调用检测到文件结尾时,返回一个非零值,否则返回0。
int ferror(FILE *fp);//当读或写出现错误,返回非零值,否则返回0。

程序示例

文件传递信息:交互、命令行参数

#define BUFSIZE 4096
#define ARSIZE 1000
void append(FILE *source, FILE *dest);
char *s_gets(char *st, int n); //不跳过空白,不要换行符,读n个字符

	/*append.c --把文件附加到另一个文件末尾*/
       FILE *fa, *fs;       // fa指向目标文件,fs指向源文件,fs添加到fa
    int files = 0;       // 附加的文件数量
    char file_app[SLEN]; // 目标文件名
    char file_src[SLEN]; // 源文件名
    int ch;
    puts("Enter name of destination file: ");
    s_gets(file_app, SLEN); // 获取目标文件名
    if ((fa = fopen(file_app, "a+")) == NULL)
    { // 附加模式打开文件
        fprintf(stderr, "Can't open %s\n", file_app);
        exit(EXIT_FAILURE); // 打开失败退出程序
    }
    if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)
    { // 为文件创建一个缓冲区,创建失败返回非0值
        fputs("can't create output buffer\n", stderr);
        exit(EXIT_FAILURE); // 创建失败退出程序
    }
    puts("Enter name of first source file (empty line to quit): ");
    while (s_gets(file_src, SLEN) && file_src[0] != '\0')
    {
        if (strcmp(file_src, file_app) == 0) // 两个文件相同
            fputs("can't append file to itself\n", stderr);
        else if ((fs = fopen(file_src, "r")) == NULL) // 读模式无法打开源文件
            fprintf(stderr, "Can't open %s\n", file_src);
        else
        {
            if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)
            { // 源文件的缓冲区打开失败
                fputs("Can't create input buffer\n", stderr);
                continue;
            }
            append(fs, fa); // 添加文件
            if (ferror(fs) != 0)
                fprintf(stderr, "Error in reading file %s. \n", file_src);
            if (ferror(fa) != 0)
                fprintf(stderr, "Error in writing file %s.\n", file_app);
            fclose(fs);
            files++;
            printf("File %s appended. \n", file_src);
            puts("Next file (empty line to quit): ");
        }
    }
    printf("Done appending. %d files appended.\n", files);
    rewind(fa); // 回到文件开始处
    printf("%s contents : \n", file_app);
    while ((ch = getc(fa)) != EOF)
        putchar(ch);
    puts("Done displaying. ");
    fclose(fa);

	//readbin.c 用二进制I/O进行随机访问
        double numbers[ARSIZE];
    double value;
    const char *file = "numbers.dat";
    int i;
    long pos;
    FILE *iofile;
    for (i = 0; i < ARSIZE; i++) // 创建一组 double类型的值
        numbers[i] = 100.0 * i + 1.0 / (i + 1);
   
    if ((iofile = fopen(file, "wb")) == NULL)
    { // 尝试二进制模式创建或打开文件
        fprintf(stderr, "Could not open %s for output . \n", file);
        exit(EXIT_FAILURE);
    }
    /*以二进制格式把数组写入文件
    存储在文件中的每个值都和存储在内存中的值完全相同,没有损失精度
    每个值在文件中同样占64为空间
    不能用文本编辑器读取,无法转换为字符串*/
    fwrite(numbers, sizeof(double), ARSIZE, iofile);
    fclose(iofile);
    if ((iofile = fopen(file, "rb")) == NULL)
    { // 二进制模式读文件
        fprintf(stderr, "Could not open %s for random access.\n", file);
        exit(EXIT_FAILURE);
    }
    // 从文件中读取选定的内容
    printf("Enter an index in the range 0-%d.\n", ARSIZE - 1);
    while (scanf("%d", &i) == 1 && i >= 0 && i < ARSIZE)
    {                                             // 输入正确索引,第i个double处
        pos = (long)i * sizeof(double);           // 计算编移量
        fseek(iofile, pos, SEEK_SET);             // 定位到索引处
        fread(&value, sizeof(double), 1, iofile); // 从iofile索引处开始读1个double到value
        printf("The value there is %f.\n", value);
        printf("Next index (out of range to quit) : \n");
    }
    // 完成
    fclose(iofile);
    puts("Bye ! ");
    
void append(FILE *source, FILE *dest)
{
    size_t bytes;
    static char temp[BUFSIZE]; // 只分配一次,静态存储期和块作用域
    while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0)
        // 读BUFSIZE大小的source到temp
        fwrite(temp, sizeof(char), bytes, dest);
    // 写BUFSIZE大小的temp到dest,a+追加模式
}
char *s_gets(char *st, int n)
{
    char *ret_val;
    char *find;
    ret_val = fgets(st, n, stdin); // 标准输入中读取最多n个字节放入st中
    if (ret_val)
    {
        find = strchr(st, '\n'); // 查找换行符
        if (find)                // 如果地址不是NULL,
            *find = '\0';        // 在此处放置一个空字符
        else
            while (getchar() != '\n')
                continue; // 丢弃剩余字符,避免留在缓冲区中,导致下一次读取错误
    }
    return ret_val;
}
  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值