【记录】C-文件输入输出

写在开头

  摸鱼摸得昏天黑地,是该炸一炸鱼的本愿了~ 以前总觉得笔记没什么重要的,但有时事到临头,又十分渴求简明的提纲来唤起记忆/提供重学的门路。于是就有了以下的产物,也希望能抑制一下无效摸鱼的堕落,以免不久的将来再次被现实逼下马。

正文

C把文件看作是一系列连续的字节。

文件:存储在外部介质(硬盘、U盘、DVD等)上数据的有命名集合,是操作系统数据管理的单位
文件系统概念理解

  • 正文文件:文本文件,随字节按照某种字符集(如ASCII、Unicode)进行编码,内容均看作字符
  • 二进制文件:数据按照其在内存中的存储形式原样存放

注意不同系统下不同的文件格式

C的文件操作范式
C文件操作

文件类型指针:是一种文件结构体

作业练习而有所得

1. 一些声明

    FILE *fp;
    char filename[32],s[81],line[1024];
    //文件名一般不超过32个字符,显示器上一行通常80个字符,文件最大物理行长度一般为1024
    char buf[BUFSIZE];
    //在stdio.h中定义,作用如其意,缓冲存储中间处理的字符串
    char ch;

2. 养成好习惯,凡是打开文件,便要确认是否成功打开;配上随手关文件

    if( (fp=fopen(filename,"r"))==NULL )
    {
        perror(filename);
        return -1;
    }

3. fgetc()与fputc()
int fgetc(FILE *stream)
返回所读字符,出错则EOF
int fputc(int c,FILE *stream)
返回所输出字符,出错则EOF

//从一端读取,输入到另外一端,若为stdin/stdout,可改为getchar()与putchar()
    while((ch=fgetc(fp))!=EOF)
        fputc(ch,fp) //'\n'被同样处理,刚刚好

联合使用,灵活运用返回值

    FILE *fin,*fout;
    while(!feof(fin))
        fputc(fgetc(fin),fout);

4. fopen()中filename的规则
以编译时文件所在目录为起始,格式为路径名称。(“..”表示后从当前目录回退一层)

    char filename[]="..\\testdata\\fileio_example.c";
    //当前目录回退一层,再进入testdata目录下的文件xxx 同时注意转义字符表示'\'

5. fscanf()与fprintf()
类比sscanf()与sprintf(),字符串改为文件指针
返回值:成功I/O的个数;EOF

6. 对于返回指针的函数:C不支持调用函数时返回局部变量地址
warning:function returns address of local variable
原因:函数调用完后,存储在栈空间的数据会被自动释放,那指针所指的地方就毫无意义了!
解决办法:
①加static变为局部全局变量,存储在静态区
②使用malloc()分配空间,存储在堆空间

7. 对多维数组的正确理解:以数组为元素的数组
因此,传递参数时的形参不能简单的用多维指针,不要被平时“x维数组”的称呼而轻视了本质

void function(int (*grade)[3][4])//传递一个指向 [3][4]二维数组 的指针
void function(int grade[][3][4]) //也可
void function(int ***grade)      //错误!

8. 打表手段'\t'以及认识'\r'
'\t':字符长模8后,不足8个字符的用空格凑足8个字符
'\r':回车后,内容会被覆盖的

9. fgets()与fputs()
函数原型(注意返回值):
char *fgets(char *str,int size,FILE *stream);
返回指向自身的指针,若没有则为NULL,很自然
int *fputs(char *str,FILE *stream);
返回写入的最后一个字符,出错则为EOF

//作业 fileio_schedule.c 
//向reminder.txt文档添加事项,打印已有事项于stdout

#include<stdio.h> //定义了BUFSIZ,本地是512
#include<ctype.h>
#include<stdlib.h>//定义了exit();直接推出进程,似乎正好用于void返回值的函数打开文件失败

void append_reminder()//添加事项
{
    FILE *fp;
    char buf[BUFSIZ],filename[]="reminder.txt";
    int m,d;
    if( (fp=fopen(filename,"a"))==NULL )
    {
        perror(filename);
        exit(-1);
    }
    printf("Enter to add something to your schedule:\n");
    while(1)
    {
        fgets(buf,BUFSIZ,stdin);//fgets()确保最后一位为'\0'的前提后,会把结尾的'\n'读入。即最大读取数为BUFZISIZE-1,如果读入数<=(BUFSIZE-2)就会读入结尾的'\n'
        if(buf[0]=='\n') goto A;
        if(sscanf(buf,"%d.%d",&m,&d)!=2)//利用sscanf返回读入个数来判断是否按格式输入
        {
            fputs("Input format:<month>.<day><message>\n",stderr);//区分stderr和stdout
            continue;
        }
        fputs(buf,fp);//与fgets适配
    }

    A:fclose(fp);
    return;
}

void print_schedule()//打印已有事项
{
    FILE *fp;
    char buf[BUFSIZ];
    int m,d;
    char message[BUFSIZ]={0};//可以不初始化,因为%s会自动补上'\0' 复习
    if( (fp=fopen("reminder.txt","r"))==NULL )
    {
        perror("reminder.txt");
        exit(-2);
    }
    while(fgets(buf,BUFSIZ,fp)!=NULL)
    {
        sscanf(buf,"%d.%d%[^\n]",&m,&d,message);
        printf("%02d.%02d%s\n",m,d,message);
    }

    fclose(fp);
    return;
}

int main()
{
    append_reminder();
    print_schedule();
    return 0;
}

10. stderr与stdout的理解
stdout有行缓冲,而stderr没有

//trial.c
int main()
{
    fprintf(stdout,"first ");
    fprintf(stderr,"second");
}

执行时,照理应该是先输出second的,因为stdout行缓冲,遇到'\n'才会从buf中输入到标准输出文件默认设备中。然而本地还是顺序输出的,不知道为何!

但是还是如果给stdout重定向的话,区别就很明显:在命令行中输入 trial.exe > tmp1.txt 再执行,则"first"输入到tmp1.txt中而"second"输入到屏幕上

11. fseek()和ftell()
函数原型:
int fseek(FILE *stream,long offset, int whence)

  • 位移量:offset表示以起始点为基准,向前向后移动的字节数 (e.g.1L -2L ftell(fp))
  • 起始点:whence可以是三个符号 SEEK_SET(0) fSEEK_CUR(1) SEEK_END(2)
    成功则返回非0,出错则非0

函数原型:
long ftell(FILE *stream)
成功则返回相对文件头部的偏移量,出错则-1L
void rewind(FILE *stream)
将文件指针移动到起始,等价于fseek(stream,0,SEEK_SET)

//作业 fileio_len.c 求文件的长度(字节数)-片段
    fp=fopen(filename,"rb");//用r打开似乎也一样欸
    fseek(fp,0L,SEEK_END);
    len=ftell(fp);
    printf("Lenth of File is %ld bytes\n",len);

12. 文件存储形式深入理解

//作业 fileio_reverse.c
//将文件按行倒序输出

#include<stdio.h>
#define MAXN 100
//对EOF和fgets以及返回值NULL的理解:fgets读到'\n'或EOF或指定数目时停止,返回所读字符串(也就是说若什么都没读就是NULL)
//EOF只是一个表示文件结束的常量(-1),不是特殊字符
int main()
{
    FILE *fp;
    char buf[BUFSIZ],filename[]="tmp1.txt";
    long offset[MAXN]={0};//标记每行offset的值以便跳转;offset[0]本就为0
    int i,j;
    //gets(filename);
    if((fp=fopen(filename,"r"))==NULL)
    {
        perror(filename);
        return -1;
    }
    for(i=1;fgets(buf,BUFSIZ,fp)!=NULL;i++) //!执行顺序一定要想清楚!
        offset[i]=ftell(fp);//刚好为行首偏移量

    //3行的文件,i最后为4,指针指在第5行
    
    for(j=i-2;j>=0;j--)
    {
        fseek(fp,offset[j],SEEK_SET);
        fgets(buf,BUFSIZ,fp);
        fputs(buf,stdout);
    }
    fclose(fp);
}

/*文件存储形式如下 (换行只是为了显示'\n'效果)

abcd\r\n
efghij\r\n
klmnopq\r\n
(EOF)

*/

13. 读/写操作不能连续交替进行,需要“同步文件状态”

//作业 fileio_substitute 将某字符替换成另一字符-片段
    while(!feof(fp))
    {
        if(fgetc(fp)=='*')
        {
            fseek(fp,-1L,SEEK_CUR);//回退
            putc('&',fp);
            fseek(fp,ftell(fp),SEEK_SET);//必不可少,同步文件状态
        }
    }

14. 二进制方式读写相关
应该注意,此时按数据项数计数,而不是 正文模式 中的字节数(char)
常见配套函数:fread()与fwrite()
size_t fwrite(const void *buf, size_t size, size_t count, FILE *stream)
size_t fread (void *buf, size_t size, size_t count, FILE *stream)
需要中间商buf[]作转接
返回读/写的数据项数(以size长度为单位),出错则为0

一些操作

//写入数字 3*4=12Byte
    int a[3]={23222,1132,128};
    fp=fopen("in.dat","wb");
    fwrite(a,sizeof(int),3,fp);
    fclose(fp);

//整块读取:一次性读入BUFSIZE个整数(512...)
    fp=fopen("bin_file.txt","r");
    n=fread(buf,sizeof(int),BUFSIZE,fp);
    len=ftell(fp);
    printf("%d int read,current position is:%d\n",n,len);

//复杂结构的存取
    struct d_type{
        int size,year,value,group;
        double width,height,ratio;
        char name[8];
    }d_table[MAX_N];
    int n;

    fwrite(&n,sizeof(int),1,fp_out);//直接取地址,不要忘了哟~
    fread(&n,sizeof(int),1,fp_in);
    fwrite(d_table,sizeof(struct d_type),n,fp_out);
    fread(d_table,sizeof(struct d_type),n,fp_in);

15.文件打开方式备忘录
文件打开方式

 
 
差不多就粗粗是这些了
小结

posted @ 2021-02-08 23:42  Xlucidator  阅读(108)  评论(0编辑  收藏  举报