c语言的文本文件和二进制文件(C按语义或字节)
c语言的文本文件和二进制文件(C按语义或字节)对于字符信息,在计算机内部是以ASCII编码形式存放;计算机内部是二进制的世界,文件中的数据也是以字节为单位的二进制表示形式。然而,对于各种不同类型的数据,其二进制表示形式是不同的。文件I/O流在默认标准I/O流的基础上,实现了面向外部存储器、基于操作系统文件管理机制的数据输入和输出方法。考虑到文件操作不是每个程序都必需的,况且,由于情况各异,每个用户程序需要操作的文件也不同,无法事先给予统一定义。因此,文件I/O流机制仅仅定义了相应的各种文件I/O流类型,即ifstream、ofstream和fstream,并没有预先为用户定义它们的对象实例。相对于默认标准I/O流的使用,文件I/O流的使用除了要引用不同的头文件外,还必须增加两个步骤:1)使用前显式创建相应流类型的流对象实例;2)使用后撤销相应的流对象实例。一旦流对象实例被创建完后,流的具体使用方法与默认标准I/O流的使用方法一致。I/
输入和输出用于实现程序与外部的交互,是程序设计的一个重要机制,程序设计语言及其衍生的开发环境都必须提供相应的支持机制。输入和输出是一个相对比较复杂的过程,它涉及外部设备的控制和管理,具有平台依赖性。因此,输入和输出机制的构建核心在于如何弱化平台依赖性。基于面向对象方法,C 语言通过流概念拓展了其对输入和输出的支持机制,建立了面向标准输入设备(键盘)和输出设备(显示器)的标准I/O流机制和面向磁盘等外部存储设备输入和输出的文件I/O流机制。
任何程序都会遇到数据的输入和输出问题(特殊情况下,允许程序可以没有输入),传统的输入和输出机制一般有两种实现方法,一种是在语言中直接实现输入和输出语句及命令,另一种是将输入和输出委托给语言的支持环境,语言本身不直接支持。C语言采用第二种方法,从而使得C语言具有较强的可移植性。也就是说,只要支持C语言的开发环境提供相应的输入和输出标准库,C语言程序就能在此环境中编译和运行。C 语言保留了C语言的这种设计思想,通过C 标准库,实现对数据的输入和输出功能的支持。
依据现代计算机系统的基本工作原理,设备的控制和管理一般都是由系统软件操作系统负责,因此,无论哪种输入和输出实现机制,最终都是要调用操作系统的基本输入和输出功能。如果是语言本身直接支持,则其相应的语句或命令的内部实现就封装了对操作系统基本输入和输出功能的调用逻辑。如果委托给语言的支持环境,则是由标准库中的函数或类来封装对操作系统基本输入和输出功能的调用逻辑,而语言本身仅仅是通过引入标准库支持对相应预定函数或类的使用。
面向过程和面向对象对IO标准库的使用:
文件I/O流在默认标准I/O流的基础上,实现了面向外部存储器、基于操作系统文件管理机制的数据输入和输出方法。考虑到文件操作不是每个程序都必需的,况且,由于情况各异,每个用户程序需要操作的文件也不同,无法事先给予统一定义。因此,文件I/O流机制仅仅定义了相应的各种文件I/O流类型,即ifstream、ofstream和fstream,并没有预先为用户定义它们的对象实例。相对于默认标准I/O流的使用,文件I/O流的使用除了要引用不同的头文件外,还必须增加两个步骤:1)使用前显式创建相应流类型的流对象实例;2)使用后撤销相应的流对象实例。一旦流对象实例被创建完后,流的具体使用方法与默认标准I/O流的使用方法一致。
1 如何访问文件文件I/O流创建后(即文件打开后),在其关闭之前(即文件关闭前),可以对文件进行读和写操作。文件访问一般涉及对文件数据的处理方式、文件访问模式及文件读写位置三个方面。
1.1 对文件数据的处理方式
计算机内部是二进制的世界,文件中的数据也是以字节为单位的二进制表示形式。然而,对于各种不同类型的数据,其二进制表示形式是不同的。
对于字符信息,在计算机内部是以ASCII编码形式存放;
对于数值型数据,在计算机内部则是以补码或浮点形式等存放;
对于多媒体数据,在计算机内部则是以各种编码数据存放。
因此,针对不同含义的以字节为单位的二进制数据,如何将其输出也是不同的。所有计算机系统的信息输出都是基于ASCII编码标准(通过ASCII编码可以找到相应符号的字模信息并输出),因此,对于字符信息,可以直接读取并(最终通过操作系统的BIOS)输出;对于数值型数据,需要将内部以补码或浮点形式等存放的数据转换成相应的数字符号的ASCII编码并(最终通过操作系统的BIOS)输出;对于多媒体数据,则通过专门的处理软件和硬件将各种编码数据还原成原始信息。同样,针对不同含义的以字节为单位的二进制数据文件的创建与内容写入方法也是不同的,同样按照上述原理进行反向转换。
因此,文件读写对文件中数据的处理一般分为两种方法:按字节直接读写和按语义读写。
按字节直接读写就是直接以字节为单位按字节顺序读写二进制数据,对二进制数据不做任何语义解释。因此,这种文件访问方式被称为二进制文件访问方式,相应地,被读写的文件称为二进制文件或流式文件;
按语义读写是按顺序将一个或多个连续的字节解释为一个有意义的数据并转换为与其相应的ASCII编码进行输入或输出。例如:将一个字节解释为一个ASCII码字符、将连续两个字节解释为一个Unicode码字符或一个整型数据、将连续四个字节解释为一个整型数据等等。因此,这种文件访问方式被称为ASCII码文件访问方式,相应地,被读写的文件称为ASCII码文件或文本文件。
C 语言中,将对二进制文件的输入输出称为低级I/O或无格式化I/O,将文本文件的输入输出称为高级I/O或格式化I/O。
考虑到不同计算机系统在存储一个有意义的数据时,字节顺序不一定完全一致,例如:对于4个字节的整型数据,字节顺序可以是由低到高排列,也可以是由高到低排列。因此,文件在不同系统之间交换时就会产生错误。所以,二进制文件不适合异构系统的交换。因为ASCII码是所有计算机系统的编码标准,所以,ASCII文件适合在异构系统之间交换。
另外,二进制文件访问及传输的速度要比ASCII码文件访问及传输的速度快、效率高,因为后者需要将字节的二进制数据进一步按语义单位转换和解释。
数据输入输出是站在内存的角度考虑其方向性,文件读写自然是对文件的操作,文件读入,对于内存来说,是数据输出,数据输入,对于文件来说,是文件写出。对于数据输入输出的源与目的地,其方向无疑是相反的。
1.2 文件读写位置
为了对文件进行读写,每一个文件都有一个指示当前读写位置的标志,称为文件读写位置指针。每当打开一个文件时,操作系统在内存创建的相应于该文件的控制结构中就初始化该位置指针,使其指向文件的开始位置。随着对文件的操作,该指针位置不断自动调整。如果手动操作文件读写位置指针,在C 文件I/O流中,提供了一些与读写位置指针相关的成员常量和函数:
1.3 访问模式
访问模式是指按什么规则读写文件,一般有顺序访问和随机访问两种基本模式。
顺序访问是指从当前位置开始,按顺序读写文件中的数据,这是文件读写的默认访问方式。
随机访问是指可以使用与读写位置指针相关的成员常量和函数来手动调整文件读写位置指针(需要考虑各数据类型的颗粒度大小),然后从调整后的位置读写文件中的部分数据。
顺序访问和随机访问可以从顺序存储和链式存储来理解,我们知道,链表是链式存储的,不能随机访问,只能顺序访问,是说其指针只能指向其邻接位置,只能顺着挼,而不能跳着访问,实质就是指针不能与一个整型常量做算术运算。数组是顺序存储的,可以随机访问,所谓的随机访问是指其指针可以偏移一个常量(不只是1),这样,数据即可以随机访问(可以跳到任一元素位置),当然也可以顺序访问,文件(文本文件和二进制文件)是顺序存储的,可以随机访问,也可以顺序访问。
针对ASCII码文件,一般用顺序访问模式,此时,可以依据数据类型依次读写各个数据。也就是说,此时的读写是按语义读写,位置指针的移动是按照数据类型所占的字节数来计算距离!例如:读写一个整型数据后,位置指针自动调节并指向下一个整型数据。
针对二进制文件,一般用随机访问模式,此时,位置指针的移动是按照字节数来计算距离!
本质上,任何文件都是以字节为单位的字节流,只是字节的内容及语义不同。因此,针对ASCII码文件,也可以用随机访问模式,此时,必须按照各种数据类型所占用的字节数精确计算出所要随机访问的目标数据的位置,然后才能由此位置读写目标数据。显然,针对二进制文件,也可用顺序访问模式,只不过此时是按字节顺序访问,而不是按数据类型顺序访问。
对ASCII文件的读写操作(或按语义的读写方式),一般用以下两种方法:
1) 用流提取运算符“>>”和流插入运算符“<<”输入输出标准数据类型的数据。
2) 用文件流的成员函数put、get、getline等进行字符的输入输出。
对二进制文件的读写操作(或按字节的读写方式),主要用文件流的成员函数read和write实现。这两个成员函数的原型为:
istream& read( char *buffer, int len );
ostream& write( const char *buffer, int len);
其中,字符指针buffer指向内存中的一块存储空间,len是读写的字节数。
2 应用实例2.1 按语义顺序读写文本文件
从键盘输入一批整型数据并将它们写入一个文本文件;然后,读出该文件中的数据到数组中,并输出其中的最大值和最小值。
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
int a[10] t;
ofstream fout("d:\\f1.dat" ios::out); // 默认是非二进制方式,也就是文本方式
if(! fout) //对输出文件流进行验证
{
cerr << "file open error!"<< endl;
exit(1);
}
cout << "Please input 6 integer numbers:" << endl;
for( int i = 0; i < 6; i ) //从键盘输入一批整型数据并写入文件
{
cin>>t;
fout << t << " "; //整数之间用一个空格符分开,
}
fout.close(); //以便后续读取
ifstream fin( "d:\\f1.dat" ios::in );
if(! fin ) //对输入文件流进行验证
{
cerr << "file open error!"<< endl;
exit(1);
}
for(i = 0;i < 6; i ) //输入文件流读入整数到数组
fin >> a[i];
int max = a[0]; //求最大值和最小值
int min = a[0];
for(i = 1; i < 6; i ) {
if( a[i] > max ) max = a[i];
if( a[i] < min ) min = a[i];
}
cout << "Max:"<< max << " "<< "Min:" << min << endl;
getchar();
getchar();
return 0;
}
/*
Please input 6 integer numbers:
12 45 6 8 32 2
Max:45 Min:2
*/
数据的各种形态解析:
2.2 按字节顺序访问二进制文件
用直接字节读写方式,从键盘输入一批整数存放在数组中并将该数组写入一个二进制文件;然后,读出该文件中的数据到数组中,并输出其中的最大值和最小值。相应程序及解析如下图所示(含程序运行结果):
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
int a[6] b[6];
ofstream fout( "d:\\f2.dat" ios::out | ios::binary );
if(! fout) // 出文件流进行验证
{
cerr << "file open error!" << endl;
exit(1);
}
cout << "please input 6 integer numbers:" << endl;
for( int i = 0; i < 6; i ) //从键盘输入一批整数到数组a
cin >> a[i];
fout.write( ( char *) a sizeof( a )); // 按字节将数组a直接写入文件
fout.close();
ifstream fin( "d:\\f2.dat" ios::in | ios:: binary );
if(!fin) // 对输入文件流进行验证
{
cerr << "file open error!" << endl;
exit(1);
}
fin.read(( char *)b sizeof( b )); //从输入文件流按字节读入整数到数组
int max = b[0]; // 求最大值和最小值
int min = b[0];
for(i = 1; i < 6; i )
{
if( b[i] > max) max = b[i];
if( b[i] < min ) min = b[i];
}
cout << "Max:" << max <<" "<< "Min:" << min << endl;
getchar();getchar();
return 0;
/*
please input 6 integer numbers:
12 45 6 8 32 2
Max:45 Min:2
*/
}
下图所示是数据的各种形态解析:
2.3 随机访问文本文件
用随机访问模式,将第1例中建立的文本文件中的第2个数据改为18,然后,读出该文件中的数据到数组中,并输出数组内容。相应程序及解析如下图所示(含程序运行结果):
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
char a[2];
int b[6];
ifstream fin( "d:\\f1.dat" ios::in | ios::binary );
if(!fin ) //对输入文件流进行验证
{
cerr << "file open error222 !" << endl;
exit(1);
}
fin.seekg( 3 ); //随机定位到第3个字节(手动调整指针位置)
fin.read( a 2); //读取第2个整数对应的两个数字字符的ASCII码
fin.close();
a[0] = '1'; a[1] = '8'; //将第2个整数改为18
ofstream fout( "d:\\f1.dat" ios::in | ios::out | ios::binary );
if(! fout) //对输出文件流进行验证
{ cerr << "file open error!" << endl; exit(1);}
fout.seekp( 3 ); //随机定位到第3个字节(手动调整指针位置)
fout.write( a 2); //写入第2个整数对应的两个数字字符的ASCII码 .
fout.close();
ifstream fin2("d:\\f1.dat" ios::in | ios::binary );
if( !fin2 ) //对输入文件流进行验证
{ cerr << "file open error!333" << endl; exit(1); }
for( int i = 0; i < 6; i )
{
if(i == 2 || i == 3 || i == 5 )
{
fin2.read( a 1 );
a[1] = '\0';
} //随机读一位整数
else
fin2.read( a 2 ); //随机读两位整数
fin2.seekg( 1 ios::cur ); //将读写指针移到下一个数据的开始字节
b[i] = atoi( a ); //将数字符号串转换为对应的整数
}
fin2.close();
for(i = 0; i < 6; i )
cout << b[i]<< " ";
cout<< endl;
getchar();
return 0;
}
/*output:
12 18 6 8 32 2
*/
下图所示是数据的各种形态解析:
2.4 按语义方式顺序读取文本文件、按直接字节方式顺序读取文本文件
用顺序访问模式,分别从例1和例2中建立的文件中逐个读取数据到数组中,并输出数组内容。相应程序及解析如下图所示(含程序运行结果):
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
int a[6] b[6];
ifstream fin( "d:\\f1.dat" ios::in );
if(! fin )//对输入文件流进行验证
{ cerr << "file open error!"<< endl; exit(1);}
for( int i = 0; i < 6; i )
fin >> a[ i ]; //按语义方式顺序读取文本文件
for(i = 0; i < 6; i )
cout << a[ i ] << " ";
cout<< endl;
ifstream fin2( "d:\\f2.dat" ios::in | ios::binary );
if(!fin2) //对输入文件流进行验证
{ cerr << "file open error!" << endl; exit(1); }
fin2.read( ( char *)b sizeof( b )); //按直接字节方式顺序读取字节文件
fin2.close();
fin.close();
for(i = 0; i < 6; i )
cout<< b[ i ]<< " ";
cout<< endl;
getchar();
return 0;
}
/*output:
12 18 6 8 32 2
12 45 6 8 32 2
*/
下图所示是数据的各种形态解析:
ref
沈军《计算思维之程序设计》
-End-