基于存储的C语言文件操作常规问题分析(文本文件与二进制文件)

问题描述

我们都知道计算机中的数据都是以二进制存储的,也对不同的编码解码有所了解,比如ASCII、UNICODE、UTF-8编码等。 但当使用C语言对文件进行读写,会遇到了各种各样的问题,比如从文件中读取到的数据不是我们想要的数据,向文件中写入数据变成乱码,对C语言读写文件的各种参数不甚了解,读写位置与各种符号不好判断等问题。在程序编写的过程中只能不断查询各种读写函数的用法,不同数据类型的读写方式等。其主要原因是我们对文件的存储、编码以及读写文件的函数没有进行过系统性的了解。

文本文件与二进制文件

文本文件和二进制文件在本质上没有任何区别,都是二进制数据流。当我们使用文本工具打开一个文件时,会根据编码方式将文本数据解码,翻译成字符显示出来。所以我们使用文本工具打开文件,看到的其实都是字符。即便整个文本都是数字,那也是数字字符,所以当我们读取文件中的数据时,我们可能想读取的是该数字字符表示的值,但是使用fread和read读取到的,是文本数据本质上(物理上)存储的值,所以会发现读取到的值并不是我们想要的值。什么意思呢?
图1
如图,我们建立一个文件,写入四行数据,第一行1 2 4 a (中间用空格隔开),但是,当我们将该文件以十六进制方式打开,会发现该文件是下图这样的:

在这里插入图片描述
上图是根据ASCII编码方式将二进制数据翻译成字符,显示出来给我们看的。下图是该文件真实的数据,该文件的真面目,也就是该文件的二进制形式。
包括空格,换行符等,在文件中也都对应一个字符。

现在我们写一个程序读取该文件,程序如下:

#include<stdio.h>
#include<malloc.h>
typedef struct{
   int num1;
   int num2;
   int num3;
   char cz;
}Data,*PData;

int main(int argc,char* argv[]){
   FILE* fp=fopen("data.txt","rb");
   PData pData=(PData)malloc(sizeof(Data)*2);
   fseek(fp,0,SEEK_SET);
   fread(pData,sizeof(Data),2,fp);
   for(int i=0;i<2;i++){
     printf("%x  %x  %x  %c\n",pData[i].num1,pData[i].num2,pData[i].num3,pData[i].cz);
   }
   fclose(fp);
}

该程序我们预期得到的结果是前两行的数据1 2 4 a 和 2 2 2 b,但是执行程序之后我们得到的数据见下图,和预期不符,反而和十六进制打开该文件的数据是一致的。
在这里插入图片描述
这是因为fread从起始位置,根据要读取数据量,将文本的二进制数据读取到我们定义的buf中。注意,读取到的不是我们用文本工具打开看到的数字字符,是该数字字符对应ASCII数据。比如结构体中的num1-num3是int型数据,四字节,第一个num1的值就是原文件中前四个字节(两个空格和字符1,字符2)对应的值。(如果不明白为什么顺序是反的,请看小端存储和大端存储)

数据写入文本乱码问题

用fwrite和write将字符串写入文件,不会出现问题,但是当我们将数据写入到文件的时候,打开文件会发现乱码了,也是同样的道理。

#include<stdio.h>
#include<malloc.h>
typedef struct{
   int num1;
   int num2;
   char cz;
}Data,*PData;

int main(int argc,char* argv[]){
   FILE* fp=fopen("data.txt","a+");
   PData pData=(PData)malloc(sizeof(Data)*2);
   for(int i=0;i<3;i++){
       pData[i].num1 = 1;
       pData[i].num2 = 2;
       pData[i].cz = 'f';
   }
   fseek(fp, 0, SEEK_END);
   fwrite(pData, sizeof(Data), 3, fp);
   char* str = "aabbccdd";
   fwrite(str, sizeof(str), fp);
   fclose(fp);
}

上述代码向文件中写三次int型数据 1 ,2,字符 f ,再在末尾写入字符串aabbccdd,运行后打开文件如下图。
在这里插入图片描述
发现出现乱码,而字符f成功写入,字符串aabbccdd也没有乱码,也就是说乱码的数据是int型数据。 再查看该文件的十六进制数据,得下图
在这里插入图片描述
发现数据是按照int型数据的存储方式写入文件,之所以乱码是因为文本将该int型的数据按照ASCII编码的方式一个字节一个字节的解码显示,所以会出现乱码。
至于为什么字符f也会占四个字节,是因为代码运行的时候自动4字节对齐,所以结构体Data的大小sizeof(Data)的值是12。

综上所述,我们在写C语言读写文件的时候,会出现读取的数据不是想要的数据,写入的数据乱码,都是因为我们看到的是解码后的字符,即便是数字也是字符,读取的时候是该字符对应的ASCII码值。

所以,C语言对文件的读写是基于二进制文件的,当数据是字母字符和字符串时,读取写入不会存在太大问题,因为字符不存在二义性,当数据是数字时,就需要注意,数字字符和数字存在二义性。

fopen和open

fopen和open的区别主要在于C语言对文件的两种操作方式,流式文件操作和直接文件操作。
流式文件操作是将文件读取到缓冲区中,然后读写时都是和缓冲区进行交互。直接I/O文件操作不需要经过缓冲区,当然并不是全过程都不需要。
fopen和open区别详解.

流式文件操作常用函数

函数描述返回值
fopenFILE *fopen(const char *filename, const char *mode) ; 打开流返回指向该流的文件指针。如果文件打开失败则返回 NULL
fcloseint fclose( FILE *fp ); 关闭一个流成功关闭,fclose 返回 0,否则返回EOF(-1)
freadsize_t fread( void *buffer, size_t size, size_t count, FILE *stream );//C99前 。从流中读指定个数的字符(相当于对每个对象调用size次fgetc)返回成功读取的对象个数,若出现错误或到达文件末尾,则可能小于count。
fwritesize_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) ,向流中写指定个数的字符。该函数以二进制形式对文件进行操作,不局限于文本文件。返回实际写入的数据块数目
fgetcint fgetc(FILE *stream); 从流中读一个字符字符的ASCII值作为函数的返回值,若返回值为EOF,说明文件结束
fputcint fputc (int c, File *fp) 写一个字符到流中函数返回写入文件的字符的ASCII码值,出错时,返回EOF(-1)
fgetschar *fgets(char *str, int n, FILE *stream); 从流中读一行或指定个字符如果成功,该函数返回相同的 str 参数如果发生错误,返回一个空指针
fputsint fputs(const char *str, FILE *stream); 写字符串到流该函数返回一个非负值,如果发生错误则返回 EOF(-1)
fprintfint fprintf (FILE* stream, const char*format, [argument]) ;按格式输出到流fprintf()的返回值是输出的字符数,发生错误时返回一个负值.
fscanfint fscanf(FILE * stream, const char * format, [argument…]);从流中按格式读取整型,成功返回读入的参数的个数,失败返回EOF(-1)
feof()int feof(FILE *stream);到达文件尾时返回真值
ferrorint ferror(FILE *stream);如果ferror返回值为0(假),表示未出错。如果返回一个非零值,表示出错
fseekint fseek(FILE *stream, long offset, int fromwhere);函数设置文件指针stream的位置成功,返回0,失败返回非0值,并设置error的值,可以用perror()函数输出错误。
rewindvoid rewind(FILE *stream);将文件内部的指针重新指向一个流的开头 ,rewind函数作用等同于 (void)fseek(stream, 0L, SEEK_SET);
removeint remove(cha r*filename)删除文件 (通用)成功则返回0,失败则返回-1
tmpfileFILE *tmpfile(void); 生成一个临时的文件流指向该流的文件指针

直接I/O文件操作常用函数

函数描述返回值
openint open(constchar*pathname,intflags);打开和创建文件。open是UNIX系统(包括LINUX、Mac等)的系统调用函数成功则返回文件描述符,否则返回-1
closeint close(int fd); 关闭一个句柄成功返回0,出错返回-1
lseekoff_t lseek(int handle, off_t offset, int fromwhere); 定位到文件的指定位置当调用成功时则返回目前的读写位置,也就是距离文件开头多少个字节。若有错误则返回-1
readssize_t read (int fd, void *buf, size_t count); 块读文件成功返回读取的字节数,出错返回-1
writeint write(int handle, void *buf, int nbyte)。把参数buf所指向的内存空间,写入count个字节,任何赋给参数fd所指的文件内。当然,文件读写位置也会随之移动。如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1
eofEOF(filenumber) ;判断文件读写位置是否已经到达了文件尾返回一个数值(整数类型Integer),它包含布尔值Boolean值True,表明已经到达为Random(Open语句方法)或顺序Input (Open 语句方法)打开的文件的结尾
filelength获取文件的长度,但是最大只能获取2g的文件大小,因为返回值类型long使用4个字节大小来表示,最大为2的31次方也就是2G的大小。文件的大小,单位为字节
renameint rename(char *oldname, char *newname);重命名文件,把一个文件的完整路径的盘符改一下就实现了这个文件的移动成功则返回0,失败则返回-1
chsizeint chsize(int handle, long size);改变文件大小,为文件预分配空间等。chsize 当文件大小被成功改变时返回 0 值,当发生错误时返回 -1。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值