上海专业网站建站品牌,模板网站建设多少钱,给女朋友做网站 知乎,咸阳做网站的系列文章目录
17.4.5 文件模式
程序清单17.18 append.cpp
程序清单17.19 binary.cpp 文章目录 系列文章目录17.4.5 文件模式程序清单17.18 append.cpp程序清单17.19 binary.cpp17.4.5 文件模式1.追加文件来看一个在文件尾追加数据的程序。程序清单17.18 append.cpp2.二进制文…系列文章目录
17.4.5 文件模式
程序清单17.18 append.cpp
程序清单17.19 binary.cpp 文章目录 系列文章目录17.4.5 文件模式程序清单17.18 append.cpp程序清单17.19 binary.cpp17.4.5 文件模式1.追加文件来看一个在文件尾追加数据的程序。程序清单17.18 append.cpp2.二进制文件二进制文件和文本文件程序清单17.19 binary.cpp 17.4.5 文件模式
文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时(无论是使用文件名初始化文件流对象还是使用open()方法)都可以提供指定文件模式的第二个参数:
ifstream fin(banjo,model);/constructor with mode arqument
ofstream fout();
fout.open(harp,mode2);//open()with mode argumentsios base 类定义了一个 openmode 类型用于表示模式;与 fimtflags 和 iostate 类型一样它也是一种bitmask 类型(以前其类型为 int)。可以选择 ios base 类中定义的多个常量来指定模式表 17.7列出了这些常量及其含义。C文件 I/O作了一些改动以便与 ANSIC 文件 I/O 兼容。 如果 ifstream 和 ofstream 构造函数以及 open( )方法都接受两个参数为什么前面的例子只使用一个参数就可以调用它们呢?您可能猜到了这些类成员函数的原型为第二个参数(文件模式参数)提供了默认值。例如ifstream open()方法和构造函数用ios base:in(打开文件以读取)作为模式参数的默认值而ofstream open()方法和构造函数用ios base:out|ios base:trunc(打开文件以读取并截短文件)作为默认值。位运算符 OR(1)用于将两个位值合并成一个可用于设置两个位的值。fstream 类不提供默认的模式值因此在创建这种类的对象时必须显式地提供模式。 注意ios base::trunc标记意味着打开已有的文件以接收程序输出时将被截短;也就是说其以前的内容将被删除。虽然这种行为极大地降低了耗尽磁盘空间的危险但您也许能够想象到这样的情形即不希望打开文件时将其内容删除。当然C提供了其他的选择。例如如果要保留文件内容并在文件尾添加(追加)新信息则可以使用ios base::app 模式:
ofstream fout(bagels,ios base::outios | base::app);上述代码也使用|运算符来合并模式因此ios base::out|ios base::app意味着启用模式out和 app(参见图 17.6)。 老式C实现之间可能有一些差异。例如有些实现允许省略前一例子中的iosbase::out有些则不允许。如果不使用默认模式则最安全的方法是显式地提供所有的模式元素。有些编译器不支持表17.6中的所有选项有些则提供了表中没有列出的其他选项。这些差异导致的后果之一是可能必须对后面的例子作一些修改使之能够在所用的系统中运行。好在C标准提供了更高的统一性。 标准 C根据 ANSIC标准 IO 定义了部分文件 JO。实现像下面这样的 C语句时:
ifstream fin(filename, cmode);就像它使用了C的fopen()函数一样:
fopen(filename, cmode);其中cmode 是一个 openmode 值如 ios base::in;而 cmode 是相应的℃模式字符串如“r”表 17.8列出了 C模式和C模式的对应关系。注意ios base::out本身将导致文件被截短但与ios base::in起使用时不会导致文件被截短。没有列出的组合如ios base:in[vn]ios base::trunc,将禁止文件被打开。is open()方法用于检测这种故障。 注意ios_base:ate 和ios_base::app 都将文件指针指向打开的文件尾。二者的区别在于ios base::app模式只允许将数据添加到文件尾而iosbase::ate模式将指针放到文件尾。显然各种模式的组合很多我们将介绍几种有代表性的组合。
1.追加文件来看一个在文件尾追加数据的程序。
该程序维护一个存储来客清单的文件。该程序首先显示文件当前的内容(如果有话)。在尝试打开文件后它使用isopen()方法来检查该文件是否存在。接下来程序以ios_base:app 模式打开文件进行输出。然后它请求用户从键盘输入并将其添加到文件中。最后程序显示修订后的文件内容。程序清单17.18演示了如何实现这些目标。请注意程序是如何使用isopen()方法来检测文件是否被成功打开的。 注意:在早期文件 I/0 可能是 C最不标准的部分很多老式编译器都不遵守当前的标准。例如有些编译器使用诸如 nocreate 等模式而这些模式不是当前标准的组成部分。另外只有一部分编译器要求在第二次打开同一个文件进行读取之前调用fin.clear()。
程序清单17.18 append.cpp
// append.cpp -- appending information to a file
#include iostream
#include fstream
#include string
#include cstdlib // (or stdlib.h) for exit()const char * file guests.txt;
int main()
{using namespace std;char ch;// show initial contentsifstream fin;fin.open(file);if (fin.is_open()){cout Here are the current contents of the file file:\n;while (fin.get(ch))cout ch;fin.close();}// add new namesofstream fout(file, ios::out | ios::app);if (!fout.is_open()){cerr Cant open file file for output.\n;exit(EXIT_FAILURE);}cout Enter guest names (enter a blank line to quit):\n;string name;while (getline(cin,name) name.size() 0){fout name endl;}fout.close();// show revised filefin.clear(); // not necessary for some compilersfin.open(file);if (fin.is_open()){cout Here are the new contents of the file file:\n;while (fin.get(ch))cout ch;fin.close();}cout Done.\n;// cin.get();return 0;
} 此时guests.txt文件还没有创建因此程序不能预览该文件。但第二次运行该程序时guests.txt文件已经存在因此程序将预览该文件。另外新数据被追加到旧文件的后面而不是取代它们。 可以用任何文本编辑器来读取 guest.txt的内容包括用来编写源代码的编辑器。
2.二进制文件
将数据存储在文件中时可以将其存储为文本格式或二进制格式。文本格式指的是将所有内容(甚至数字)都存储为文本。例如以文本格式存储值-2.324216e07时将存储该数字包含的13个字符。这需要将浮点数的计算机内部表示转换为字符格式这正是插入运算符完成的工作。另一方面二进制格式指的是存储值的计算机内部表示。也就是说计算机不是存储字符而是存储这个值的 64位 double 表示。对于字符来说二进制表示与文本表示是一样的即字符的 ASCI 码的二进制表示。对于数字来说二进制表示与文本表示有很大的差别(参见图17.7)。 每种格式都有自己的优点。文本格式便于读取可以使用编辑器或字处理器来读取和编辑文本文件,可以很方便地将文本文件从一个计算机系统传输到另一个计算机系统。二进制格式对于数字来说比较精确因为它存储的是值的内部表示因此不会有转换误差或舍入误差。以二进制格式保存数据的速度更快因为不需要转换并可以大块地存储数据。二进制格式通常占用的空间较小这取决于数据的特征。然而如果另一个系统使用另一种内部表示则可能无法将数据传输给该系统。同一系统上不同的编译器也可能使用不同的内部结构布局表示。在这种情况下则必须编写一个将一种数据转换成另一种的程序。来看一个更具体的例子。考虑下面的结构定义和声明:
const int LIM20;
struct planet
{char name[LIM];// name of planetdouble population;// its populationdouble g;// its acceleration of qravity
}
planet pl;要将结构p的内容以文本格式保存可以这样做:
ofstream fout(planets.dat,ios base::outios |base::app) ;
fout pl.name pl.population pl.g \n必须使用成员运算符显式地提供每个结构成员还必须将相邻的数据分隔开以便区分。如果结构有30个成员则这项工作将很乏味。 要用二进制格式存储相同的信息可以这样做:
ofstream fout(planets.datios base::outios base::appios|base::binary);
fout.write((char *)pl,sizeof pl);上述代码使用计算机的内部数据表示将整个结构作为一个整体保存。不能将该文件作为文本读取但与文本相比信息的保存更为紧凑、精确。它确实更便于键入代码。这种方法做了两个修改: 使用二进制文件模式: 使用成员函数write()。 下面更详细的介绍这两项修改。 有些系统(如Windows)支持两种文件格式:文本格式和二进制格式。如果要用二进制格式保存数据应使用二进制文件格式。在C中可以将文件模式设置为ios base::binary 常量来完成。要知道为什么在Windows系统上需要完成这样的任务请参见后面的旁注“二进制文件和文本文件”。
二进制文件和文本文件
使用二进制文件模式时程序将数据从内存传输给文件(反之亦然)时将不会发生任何隐藏的转换而默认的文本模式并非如此。例如对于 Windows文本文件它们使用两个字符的组合(回车和换行)表示换行符;Macintosh 文本文件使用回车来表示换行符;而 UNIX和 Linux 文件使用换行(linefeed)来表示换行符。C是从 UNIX系统上发展而来的因此也使用换行(linefecd)来表示换行符。为增加可移植性Windows C程序在写文本模式文件时自动将C换行符转换为回车和换行;Macintosh C程序在写文件时将换行符转换为回车。在读取文本文件时这些程序将本地换行符转换为C格式。对于二进制数据文本格式会引起问题因此 double 值中间的字节可能与换行符的 ASCII 码有相同的位模式。另外在文件尾的检测方式也有区别。因此以二进制格式保存数据时应使用二进制文件模式(UNIX 系统只有一种文件模式因此对于它来说二进制模式和文本模式是一样的)。 要以二进制格式(而不是文本格式)存储数据可以使用write0)成员函数。前面说过这种方法将内存中指定数目的字节复制到文件中。本章前面用它复制过文本但它只逐字节地复制数据而不进行任何转换。例如如果将一个long 变量的地址传递给它并命令它复制4个字节它将复制 long值中的4个字节而不会将它转换为文本。唯一不方便的地方是必须将地址强制转换为指向char 的指针。也可以用同样的方式来复制整个planet结构。要获得字节数可以使用sizeof运算符:
fout.write((char *)pl,sizeof pl);这条语句导致程序前往 pl结构的地址并将开始的36个字节(sizeofpl表达式的值)复制到与 fout相关联的文件中。 要使用文件恢复信息请通过一个ifstream对象使用相应的read()方法:
ifstream fin(planets.dat,ios base::in | ios base::binary);
fin.read((char *)pl,sizeof pl);这将从文件中复制 sizeofpl个字节到p1结构中。同样的方法也适用于不使用虚函数的类。在这种情况下只有数据成员被保存而方法不会被保存。如果类有虚方法则也将复制隐藏指针(该指针指向虚函数的指针表)。由于下一次运行程序时虚函数表可能在不同的位置因此将文件中的旧指针信息复制到对象中将可能造成混乱(请参见“编程练习6”中的注意)。 提示:read()和 write()成员函数的功能是相反的。请用read()来恢复用 write()写入的数据 程序清单17.19使用这些方法来创建和读取二进制文件。从形式上看该程序与程序清单17.18相似但它使用的是 write()和read(),而不是插入运算符和 get()方法。另外,它还使用控制符来格式化屏幕输出。注意:虽然二进制文件概念是 ANSIC的组成部分但一些C和C实现并没有提供对二进制文件模式的支持。原因在于:有些系统只有一种文件类型因此可以将二进制操作(如read()和 write())用于标准文件格式。因此如果实现认为 ios base:binary 是非法常量只要删除它即可。如果实现不支持 fixed和 right 控制符则可以使用 cout.setf(ios base::fixed、ios base::foatfield)和 cout.setf(ios base:.right.ios base::adiustfield)。另外也可能必须用ios替换ios base。其他编译器(特别是老式编译器)可能还有其他特征。
程序清单17.19 binary.cpp
// binary.cpp -- binary file I/O
#include iostream // not required by most systems
#include fstream
#include iomanip
#include cstdlib // (or stdlib.h) for exit()inline void eatline() { while (std::cin.get() ! \n) continue; }
struct planet
{char name[20]; // name of planetdouble population; // its populationdouble g; // its acceleration of gravity
};const char * file planets.dat;int main()
{using namespace std;planet pl;cout fixed right;// show initial contentsifstream fin;fin.open(file, ios_base::in |ios_base::binary); // binary file//NOTE: some systems dont accept the ios_base::binary modeif (fin.is_open()){cout Here are the current contents of the file file:\n;while (fin.read((char *) pl, sizeof pl)){cout setw(20) pl.name : setprecision(0) setw(12) pl.population setprecision(2) setw(6) pl.g endl;} fin.close();}// add new dataofstream fout(file, ios_base::out | ios_base::app | ios_base::binary);//NOTE: some systems dont accept the ios::binary modeif (!fout.is_open()){cerr Cant open file file for output:\n;exit(EXIT_FAILURE);}cout Enter planet name (enter a blank line to quit):\n;cin.get(pl.name, 20);while (pl.name[0] ! \0){eatline();cout Enter planetary population: ;cin pl.population;cout Enter planets acceleration of gravity: ;cin pl.g;eatline();fout.write((char *) pl, sizeof pl);cout Enter planet name (enter a blank line to quit):\n;cin.get(pl.name, 20);}fout.close();// show revised filefin.clear(); // not required for some implementations, but wont hurtfin.open(file, ios_base::in | ios_base::binary);if (fin.is_open()){cout Here are the new contents of the file file:\n;while (fin.read((char *) pl, sizeof pl)){cout setw(20) pl.name : setprecision(0) setw(12) pl.population setprecision(2) setw(6) pl.g endl;}fin.close();}cout Done.\n;
// keeping output window open// cin.clear();// eatline();// cin.get();return 0;
} 看到该程序的主要特征后下面再次讨论前面提到的几点。程序在读取行星的g值后将使用下面的代码(以内嵌eatline()函数的形式):
while(std::cin.get()!n)continue;这将读取并丢弃输入中换行符之前的内容。考虑循环中的下一条输入语句:
cin.get(pl.name20);如果保留换行符该语句将换行符作为空行读取然后终止循环 您可能会问如果该程序是否可以使用string对象而不是字符数组来表示planet 结构的name 成员?答案是否定的至少在不对设计做重大修改的情况下是否定的。问题在于string对象本身实际上并没有包含字符串而是包含一个指向其中存储了字符串的内存单元的指针。因此将结构复制到文件中时复制的将不是字符串数据而是字符串的存储地址。当您再次运行该程序时该地址将毫无意义。