承接上文:(声明:部分内容来源于MOOC农业大学网课)
基本是课堂笔记,喜欢的抱走嗷0v0~
1、应理解之前所用的cin、cout指令实际上分别是通用输入/输出流类的对象。这两个对象是C++流类库预先定义好的对象。
2、从侧面了解C++流类库的设计和编写,进一步体会前面的面向对象程序设计知识(主要是示意代码,今后理解得更好了再补)
3、重点学习如何进行文件读写操作,大部分程序都需要使用文件来保存数据。
文章目录
九、流类库与文件读写
9.1 C++中的流类库
C++语言将数据输入内存,或将某个内存变量输出到显示器的过程,视作数据流动的过程。键盘是一种提供数据的数据源,显示器则是输出数据的目的地。C++将提供输入数据的数据源称作输入数据流,将输出数据时的目的地称作输出数据流,这两者统称为输入输出流。
C语言通过输入/输出函数实现了数据的输入/输出。
C++本身没有输入输出语句,是以输入输出流类,为程序员提供输入输出的功能。C++语言提供了多个输入输出流类,可以实现不同的输入输出功能。这些流类都是从基类ios派生出来的派生类,组成了一个以ios为基类的类族,这个类族被称作C++语言的流类库。
流类库主要包含三组不同功能的输入输出流类:
流类库:以ios为基类的类族
- 通用输入/输出流类:提供通用的输入/输出(简称标准I/O)功能
- 文件输入/输出流类:提供文件输入/输出(简称文件I/O)功能
- 字符串输入/输出流类:提供字符串输入/输出(简称字符串I/O)功能
流类库不属于C++语言的主体,属于其附属组成部分。流类库需使用include指令,包含类中的头文件。我们需要掌握流类库的使用方法。另一方面也可以了解流类库中的类,是如何设计、编写的。流类库,是由全球顶尖的C++程序员设计出来的。其中运用的,就是C++的语法知识,例如类的继承与派生、构造与析构函数、静态成员与常成员、运算符重载等等。
通过流类库的学习,可以帮助我们进一步体会(上)中的语法知识。
9.1.1 流类库
首先介绍输入输出数据中的格式化及数据缓冲,流类库的组成及继承关系。
数据在输入输出过程当中,需要进行格式转换。通常,用户通过键盘,将原始数据输入到计算机,计算机再将处理结果反馈给用户。计算机以二进制格式输出数据,而人类只能阅读文本格式及字符串格式的数据。因此输入输出数据的过程中,需要进行格式转换。
格式转换需要两方面内容:
1、二进制数据与字符串之间的转换。
2、输入/输出流中的数据缓冲区:键盘输入数据先存入缓冲区,当用户按下回车键时,计算机再从缓冲区读取数据。输出数据时,cout语句先将要输出的数据放入内存缓冲区,当缓冲区满时再将数据送往显示器。
- 流类库的基类ios
流类库综合考虑不同的输入输出应用,将其共性部分抽象出来,形成基类ios。这里给出基类ios的示意代码(来源:中国农业大学MOOC):
class ios //基类ios的声明部分
{
public:
enum fmtFlags{ //枚举类型fmtFlags:定义用作格式标记的枚举常量
skipws = 0x0001, //输入时跳过空白字符(空格、制表符、回车或换行符)
left = 0x0002, //输出时左对齐,不足部分在右侧补填充字符
right = 0x0004, //输出时右对齐,不足部分在左侧补填充字符
internal = 0x0008, //输出时在正负号或数制后面补填充字符
dec = 0x0010, //输入/输出时,将整数按十进制进行转换
oct = 0x0020, //输入/输出时,将整数按八进制进行转换
hex = 0x0040, //输入/输出时,将整数按十六进制进行转换
showbase = 0x0080, //输出时包含指示数制的基字符(例如0或0x)
showpoint = 0x0100,//输出浮点数时带小数点和小数0
uppercase = 0x0200,//输出十六进制数时用大写字母
showpos = 0x0400, //输出正数时带正号
scientific = 0x0800, //输出浮点数时采用科学表示法
fixed = 0x1000, //输出浮点数时不采用科学表示法,即定点格式
};
enum open_mode{ //枚举类型open_mode: 定义用作打开模式的枚举常量
in = 0x01, out = 0x02, ate = 0x04, app = 0x08, trunc = 0x10, binary = 0x80};
enum seek_dir{ //枚举类型seek_dir: 定义用作文件指针移动基准的枚举常量
beg = 0, cur = 1, end = 2};
inline long flags(long_l); //设置输入/输出时的格式标记,内联函数
inline long flags() const; //返回当前格式标记,内联函数,常函数成员
inline int width(int_i); //设置输入/输出时的格式标记,内联函数
inline int width()const; //返回当前的输入/输出时的位数,内联函数,常函数成员
inline char fill(char_c); //设置输出时的填充字符,内联函数
inline char fill() const; //返回当前的填充字符,内联函数,常函数成员
inline int precision(int_i); //设置浮点数输出时的小数位数,内联函数
inline int precision() const; //返回浮点数输出时的小数位数,内联函数,常函数成员
inline int good() const; //返回输入/输出流状态是否正常,内联函数,常函数成员
inline int eof() const; //返回输入/输出流是否结束,内联函数,常函数成员
locale imbue(const locale &loc); //设置本地语言(例如中午、英文等)
virtual ~ios(); //虚析构函数
protected:
streambuf* bp; //流缓冲区指针
ios(); //无参构造函数
private:
ios( const ios& ); //拷贝构造函数
ios&operator = (const ios&); //重载赋值运算符
//...以下代码省略
};
我们知道C++语言可以在类声明过程中,声明一些自定义数据类型,例如枚举类型enum。enum是一个枚举类型的关键字,定义了一些在格式化过程中需要用到的格式标记,将它们定义成枚举常量。
另外ios基类声明了函数成员flags,这个函数的功能是设置输入输出时的格式标记。前面用了inline关键字,表示这是一个内联函数。声明一个虚析构函数。
设置了一个指向流缓冲区的指针bp、无参构造函数ios、一个拷贝构造函数ios,另外重载了赋值运算符,声明了赋值运算符“=”的运算符函数。
这里需要注意的是:C++可以将自定义数据类型放在类声明中,基类ios所声明的这些数据类型,在后面流类库的使用中会用到。
图1 流类库的继承关系(来源:中国农业大学MOOC)
基类ios之下有九个派生类,按照功能被分为三组。istream为标准输入流类,ostream为标准输出流类,iostream为标准输入输出流类,均公有继承基类ios基类。
这个示意图仅仅是原理性描述,不同C++编译器所提供的流类库在用户接口上是统一的,都符合C++标准,但其内部实现形式可能会有所区别。后面我们将介绍,如何使用这九个派生类来实现标准I/O,文件I/O和字符串I/O。
9.1.2 标准I/O
输入流对象cin和输出流对象cout
namespace std //命名空间std
{
istream cin; //键盘对象cin
ostream cout; //显示器对象cout
}
cin >> x; //输入数据流:数据从键盘cin流向内存变量x
cout << x; //输出数据流:数据从内存变量x流向显示器cout
使用cin,cout对象,需引用如下的头文件和命名空间:
#include <iostream>
using namespace std;
通用输入流类istream公有继承基类。这里给出通用输入流类的代码:
class istream:virtual public ios //公有继承虚基类ios,继承时使用virtual关键字,就是将继承的基类定义成虚基类
{
public:
istream& operator >> (char*);//重载了右移运算符“>>“,重载后的运算符">>"变为提取运算符
inline istream& operator>>(unsigned char *);
inline istream& operator>>(signed char*);
istream& operator>>(char&);
inline istream& operator>>(unsigned char &);
inline istream& operator>>(signed char&);
istream& operator>>(short &);
istream& operator>>(unsigned short &);
istream& operator>>(int &);
istream& operator>>(unsigned int&);
istream& operator>>(long &);
istream& operator>>(float &);
istream& operator>>(double &);
istream& operator>>(long double &);
int get(); //以下代码为新增函数成员get
inline istream& get(unsigned char &);
inline istream& get( signed char &);
inline istream& get(unsigned char *,int,char='\n');
inline istream& get( signed char *,int,char='\n');
inline istream& getline(unsigned char *,int,char='\n');
//以下代码为新增函数成员getline
inline istream& getline( signed char *,int,char='\n'};
inline istream& read(unsigned char *,int);
//以下代码为新增函数成员read
inline istream& read(signed char *,int);
int gcount() const; //新增函数成员gcount
istream& seekg(long); //以下代码为新增函数成员seekg
istream& seekg(long,ios::seek_dir);
long tellg(); //新增函数成员tellg
//...以下代码省略
通用输入流类的提取运算符,及其新增函数成员get、getline、read等分别提供了不同的数据输入方法。cin属于通用输入流类的预定义键盘对象。下面以cin为例,重点解释提取运算符和函数成员get的使用方法。getline、read、seekg、tellg等将于文件I/O中具体讲解。
//使用提取运算符“>>”
#include<iostream>
using namespace std;
int main()
{
int x=0; double y=0;
cin >> x>>y;
cout<<"x="<<x<<",y="<<y<<endl;
return 0;
}
这里输入变量类型、个数必须与输出变量一致
#include<iostream>
using namespace std;
int main()
{
char str1[5], str2[8];
cin >> str1 >> str2;
cout << "str1=" << str1 << ",str2" <<str2<<endl;
return 0;
}
上述程序如果输入过多字符,就会导致数组越界。
提升程序的鲁棒性,可以增加输入越界情况的设置:
abcdef 123456789<回车键> //这里就是越界
cin.width(5); //设置下一输入项的最大字符个数为5(含字符串结束符)
cin >> str1; //使用键盘对象cin的提取运算符输入str1,最多提取4个字符
cin.width(8); //设置下一个输入项的最大字符个数为8(含字符串结束符)
cin>>str2;
这时输入abcdef 123456789
的结果如下:
str1 = abcd, str2 = ef
因为空格、回车都是结束符。剩下的123456789将留在缓存区中,赋值给下一条cin语句。
注意:输入函数成员width仅对紧随其后的输入项有效
函数成员get可以从缓存区中直接读取键盘输入的原始字符,对所读取的字符不做任何格式转换,包括空白字符、tab键、回车键等。
函数成员get主要有两种语法,一种是每次从缓存区中读出一个字符,赋值给字符型变量。另一种是每次从缓存区中读取多个字符,赋值给字符型数组,我们主要关注第一种用法。
//使用函数成员get
#include<iostream>
using namespace std;
int main()
{
char ch;
while(true)
{
ch = cin.get(); //每次从流缓冲区中读出1个字符
if(ch == '\n')break; //遇到回车键字符‘\n'结束,跳出循环
if(ch >= 'a' && ch <= 'z')ch -= 32; //如果是小写字母,则转成大写字母
if(ch =='') ch = '*'; //如果是空格,则转成星号'*'
cout << ch; //输出该字符
}
cout << endl;
return 0;
}
//输入abc 1230<回车键>
//显示结果为:ABC*1230
下面是通用输出流类ostream以及对象cout:
cout是预定义的通用输出流类的显示器的对象
下面就以cout为例,重点讲解插入运算函数成员put的使用方法。函数成员put、write、seekp、tellp、flush之后在文件I/O中讲解。
class ostream : virtual public ios //公有继承虚基类ios,仍然指定ios为虚基类
{
public:
//以下代码为重载左移运算符"<<"
inline ostream& operator<<(ostream& (__cdecl* _f)(ostream&));
//重载后的左移运算符被称为插入运算符
inline ostream& operator<<(ios& (__cdecl* _f)(ios&));
ostream& operator<<(const char*);
inline ostream& operator<<(const unsigned char*);
inline ostream& operator<<(const signed char*);
inline ostream& operator<<(char);
ostream& operator<<(unsigned char);
inline ostream& operator<<(signed char);
ostream& operator<<(short);
ostream& operator<<(unsigned short);
ostream& operator<<(int);
ostream& operator<<(unsigned int);
ostream& operator<<(long);
ostream& operator<<(unsigned long);
inline ostream& operator<<(float);
ostream& operator<<(double);
ostream& operator<<(long double);
ostream& operator<<(const void*);
ostream& operator<<(streambuf*);
inline ostream& put(char); //以下代码为新增函数成员put
ostream& put(unsigned char);
inline ostream& put(signed char);
ostream& write(const char *,int); //以下代码为新增函数成员write
inline ostream& write(const unsigned char *,int);
inline ostream& write(const signed char*,int);
ostream& seekp(long); //以下代码为新增函数成员seekp
ostream& seekp(long,ios::seek_dir);
long tellp(); //新增函数成员tellp
ostream& flush(); //新增函数成员flush:立即输出,然后清空缓冲区
//...以下代码省略
};
格式标记(Flag)是基类ios中定义的一组枚举常量,用于表示不同的输出格式。设置输出格式,某些格式使用函数成员flags,而另外一些格式设置则通过专门的函数成员。使用函数flags设置输出格式,以十六进制输出整数。hex是基类ios定义的枚举常量
cout.flags(ios::hex);
使用专门函数设置格式:将下一输出项的输出位数设定为5位
cout.width(5);
cout<<20; //显示结果:“ 14”
也可以使用格式操作符(Manipulator)是流类库中定义的一组函数。这些函数被分散定义在不同的头文件中,示意代码如下:
//头文件:<iomanip>
inline long setiosflags(long_l); //设置某个格式标记,例如setiosflags(ios::hex)
inline long resetiosflags(long_l); //将某个格式标记恢复到其默认值
inline int setw(int_w); //设置输出位数,不足部分补空格。仅对下一个输出项有效
inline int setfill(int_m); //设置填充字符。输出位数不足部分补填充字符
inline int setprecision(int_p); //设置浮点数的输出精度(即小数位数)
例如我们用cout输出如下:
cout<<hex<<setw(5)<<20; //显示结果:“ 14”
与格式标记相比,格式操作符可以与数据一起放在cout语句中,不需要单独的函数调用语句,这样使用起来更加简单。
下面是几个实例,方便我们更好理解插入运算符、格式标记、操作符的使用细节:
🌰1:设定输出位数及填充字符
/*(a)运算符+格式标记*/
#include<iostream>
using namespace std;
int main()
{
char const *name[] = {"手电筒","电池"}; //这里如果不佳const会报错“deprecated conversion from string constant to ‘char*’”
double price[] = {75.825, 4.1 };
cout << "商品名称 单价" << endl;
cout.fill('#'); //位数不足部分补‘#’,如果不设置默认填充空格
for( int n=0;n<2;n++ )
{
cout.width(8); cout<<name[n];
cout<<" ";
cout.width(6); cout<<price[n];
cout<<endl;
}
return 0;
}
//(b)插入运算符+格式操纵符
#include<iostream>
#include<iomanip> //采用格式操纵符需要加上这个头文件
using namespace std;
int main()
{
char *name[] = {"手电筒","电池"};
double price[] = {75.825, 4.1};
cout << "商品名称 单价" << endl << setfill('#');
for(int n=0;n<2;n++)
{
cout << setw(8) << name[n];
cout << " ";
cout << setw(6) << price(n);
cout << endl;
}
return 0;
}
🌰2:设定对齐方式
/*(a)运算符+格式标记*/
#include<iostream>
using namespace std;
int main()
{
char const *name[] = {"手电筒","电池"};
double price[] = {75.825, 4.1 };
cout << "商品名称 单价\n";
cout.flags(ios::left); //左对齐
for( int n=0;n<2;n++ )
{
cout.width(8); cout<<name[n];
cout<<" ";
cout.width(6); cout<<price[n];
cout<<endl;
}
return 0;
}
/*(b)运算符+格式操纵符*/
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
char const *name[] = {"手电筒","电池"};
double price[] = {75.825, 4.1 };
cout << "商品名称 单价\n"<<setiosflags(ios::left);
cout.flags(ios::left); //左对齐
for( int n=0;n<2;n++ )
{
cout.width(8); cout<<name[n];
cout<<" ";
cout.width(6); cout<<price[n];
cout<<endl;
}
return 0;
}
🌰3:设定浮点数的输出格式
/*(a)运算符+格式标记*/
#include<iostream>
using namespace std;
int main()
{
double x = 12.345678;
cout << x << endl; //默认:12.3457
cout.flags(ios::fixed); //定点表示法
cout.precision(2); //保留2位小数
cout << x << endl; //显示结果:12.35
cout.flags(ios::scientific); //科学表示法
cout.precision(8); //保留8位小数
cout << x << endl;
//显示结果:1.23456780e+001
return 0;
}
/*(b)运算符+格式操纵符*/
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
double x = 12.345678;
cout << x << endl; //默认:12.3457
cout << setiosflags(ios::fixed); //定点表示法
cout << setprecision(2); //保留2位小数
cout << x << endl; //显示结果:12.35
cout << resetiosflags(ios::fixed); //取消定点格式
cout << setiosflags(ios::scientific);//科学表示法
cout << setprecision(8);
cout << x << endl; //显示结果1.23456780e+001
return 0;
}
- 函数成员put
显示cout的函数成员put是一种非格式化输出方法,只能输出单个字符
🌰
#include<iostream>
using namespace std;
int main()
{
char ch,str[] = "abcd";
int n=0;
while(str[n]!='\0') //循环输出字符数组str中的字符
{
ch = str[n]; //从数组str中取1个字符(小写字母)
cout << ch; //用插入运算符输出字符ch
cout.put( ch-32 ); //将字符ch转成大写字母,再用函数成员put输出
n++;
}
cout << endl;
return 0;
}
9.1.3 文件I/O
程序所处理的数据
标准I/O:键盘输入、显示器输出
文件I/O:文件输入、文件输出
//磁盘文件“temperature.txt"
日期 气温
1 30.2
2 28.7
...
30 25.9
这一部分将介绍文件的基本概念,以及如何利用流类库实现文件输入输出操作。
按存储内容分,计算机中的文件可以分为两大类,一类是程序文件,另一类是数据文件。程序文件储存的是某个程序的可执行代码,数据文件生成的是程序执行后的结果数据。 例如在一台安装了windows操作系统的计算机上,会有一个名为notepad.exe的文件,该文件就是保存记事本程序的程序文件。执行记事本程序,输入文字内容,然后保存成文件。这个由记事本程序所创建的文件就是数据文件,扩展名通常为.txt。
//程序文件与数据文件
记事本:notepad.exe, *.txt
Word:WINWORD.exe, *.docx
计算机通过文件管理存储在外存(硬盘、光盘、U盘等)上的信息。当文件数量很多时,可以为文件建立分类目录(文件夹),将文件分散在不同目录下进行管理。目录下可以再建立子目录,同一子目录下的文件不能重名。
一个完整的文件名格式:
盘符:\目录名\子目录名\...\文件名.扩展名
如:C:\Windows\notepad.exe
在C++语言中,如果用字符串常量的方式书写文件名,则应写成:
"C:\\Windows\\notepad.exe"
C++语言中的反斜杠应该采用转义字符的形式,即两个反斜杠。
文件格式:计算机中的文件按数据格式分,可以分为文本文件和二进制文件。
(1)文本文件:
- 存储字符类数据,并且主要是可见字符。例如,英文字母,阿拉伯数字,标点符号,以及其他语种的文字比如中文文字等。
文本文件具有如下特点:存储字符编码,文本文件所储存的内容是一个字符序列,每个字符所存储的是其字符编码,例如英文字母存储的是其ASCII编码(1个字节),中文字符存储的是其机内码(2个字节) - 具有换行格式。Windows系统上,文本换行时存储2个控制字符,CR(ASCII编码为13)和LF(ASCII编码为10)。
- 通用性强。文本文件存储的是纯文本内容,而且使用的是标准编码。文本文件不含任何其他附加信息(例如字体、排版格式等)。阅读文本文件不需要安装特殊的软件,使用类似于“记事本”这样的常规软件就能阅读。文本文件的阅读、修改等不依赖于某个特定的软件,换句话说,文本文件的通用性强。
- 可用于数据交换。例如一个程序的处理结果可以通过文本文件输出给人来阅读,这是程序与人之间的数据交换:一个程序的处理结果可以通过文本文件输入给另一个程序,这是程序-程序之间的数据交换;一个操作系统中程序的处理结果可以通过文本文件传送给另一个操作系统中的程序,这是操作系统-操作系统之间的数据交换。
(2) 二进制文件
- **可保存任意类型的数据。**文本文件只存储字符类型数据,任何其他类型的数据必须转换成字符串才能保存到文本文件中,而二进制文件可保存任意类型的数据。
- **存储效率高。**和文本文件相比,将内存变量中数据保存成二进制文件的效率更高,效率高具体表现在两个方面:一 是无需要格式转换,保存速度快;二是保存数值型数据所占用的储存空间少。例如,保存1个short型整数-2100时,将其转换成字符串“-2100”,保存该字符串需要5个字节。
- 通用性差。二进制文件是程序员自己定义的一种私有格式。不同程序会创建不同的二进制文件。不管什么类型的数据,保存到二进制文件后都变成了二进制的0、1序列。在不了解储存格式的情况下,任何程序都无法正确解释由其他程序创建的二进制文件。二进制文件天生就是一种加密的文件。和文本文件相比,二进制文件的通用性差。对于二进制数据文件,通常是由哪个程序创建,就由哪个程序负责阅读、修改。
- 交换数据需遵循相同的格式标准。为了能在不同程序间通过二进制文件交换数据,人们需要为二进制文件制定共同的格式标准。例如为了交换图像数据,人们专门制定了一些二进制图像文件的格式标准,常用的有JPEG、BMP、GIF、TIFF等。
9.1.3.1 文件的基本操作
(1)打开文件
程序在对文件进行输入/输出操作之前,首先需要打开文件。打开(Open)文件时需指定文件名和打开模式。打开模式指的是程序将对文件进行何种操作,例如读数据(输入)、写数据(输出),或是两者都有(输入+输出)等。
(2)读/写数据
文件打开以后,程序可以从文件中读数据(输入)、或向文件中写数据(输出),或是两者都有(输入+输出)。向文件读/写数据可以按存储顺序依次读/写,这称为顺序读/写。也可以指定从某个位置开始读/写,这称为随机读/写。
(3)关闭文件
程序在完成对文件的输入/输出操作之后,需要关闭文件。通常情况下,数据文件只能被一个程序打开,这样可以避免读/写冲突。程序在关闭数据文件之后,该文件才可以再被其他程序打开。
C++流类库中定义了3个不同的文件输入/输出流类
文件输出流类ofstream
文件输入流类ifstream
文件输入/输出流类fstream
文件输入/输出流类的对象被称为是文件对象。我们可以定义文件输入/输出流类的对象,通过调用函数成员实现文件的输入/输出功能。
使用文件输入/输出流类需包含类声明头文件:
#include<fstream>
文件输入/输出流类
文件输出流类ofstream及文件输出
//例9-4 文件输出流类ofstream的示意代码
class ofstream:public ostream
{
public:
ofstream(); //无参构造函数
ofstream(const char *, int = ios::out); //有参构造函数
~ofstream(); //析构函数
void open(const char *, int = ios::out); //打开文件
bool is_open()const; //检查文件是否正确打开
void close(); //关闭文件
//......以下代码省略
};
🌰:输出文本文件
#include<iostream>
#include<iomanip>
#include<fstream>
using namespace std;
int main()
{
char *name[]={"手电筒","电池"};
double price[] = {75.825,4.1};
int n;
//使用显示器对象cout将数据输出到显示器
cout << "商品名称 单价\n" << setiosflags(ios::left);
for(n=0;n<2;n++)
{
cout << setw(8)<<name[n]; cout<<" ";
cout << setw(6)<<price[n]; cout<<endl;
}
//使用文件输出流类ofstream的文件对象fout将数据输出到文本文件"price.txt"
ofstream fout; //文件输出流类ofstream对象fout需要程序员自己定义
fout.open("price.txt");//打开文件"price.txt",如文件不存在则创建新文件
fout<<"商品名称 单价\n"<<setiosflags(ios::left);//标题行
for(n=0;n<2;n++)
{
fout<<setw(8)<<name[n]; fout<<" ";
fout<<setw(6)<<price[n]; fout<<endl;
}
fout.close(); //关闭所打开的文件"price.txt"
return 0;
}
如上,fout的语法与cout非常相似
🌰:输出二进制文件
#include<iostream>
#include<iomanip>
#include<fstream>
#include<string.h>
using namespace std;
int main()
{
char *name[]={"手电筒","电池"};
double price[] = {75.825,4.1};
int n;
//使用显示器对象cout将数据输出到显示器
cout << "商品名称 单价\n" << setiosflags(ios::left);
for(n=0;n<2;n++)
{
cout << setw(8)<<name[n]; cout<<" ";
cout << setw(6)<<price[n]; cout<<endl;
}
//使用文件输出流类ofstream的文件对象fout将数据输出到二进制文件"price.dat"
ofstream fout; //定义1个文件输出流类ofstream的文件对象fout
fout.open("price.dat",ios::binary); //以二进制模式打开文件“Price.dat”
char str[7];
for(n=0;n<2;n++)
{
strcpy(str, name[n]);
fout.write(str, sizeof(str)); //输出商品名称
}
return 0;
}
直接打开price.dat会出现乱码。二进制文件需要专门的程序打开、阅读。
9.1.3.2 文件输入流类ifstream及文件输入
文件输入流类ifstream的示意代码
class ifstream:public istream //公有继承通用输入类istream
{
public:
ifstream(); //无参构造函数
ifstream(const char*, int = ios::in); //有参构造函数
~ifstream(); //析构函数
void open(const char*, int = ios::in);//打开文件
bool is_open()const; //检查文件是否正确打开
void close(); //关闭文件
//......以下代码省略
};
文件输入流类公有继承了通用输入流类istream的提取运算符,以及函数成员get、getline、read等,另外形成打开文件open以及关闭文件close等。程序员定义输入流ifstream的对象,调用其函数成员,就可以实现文件输入的功能。
🌰:输入文本文件
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
char name[20];
double price;
//使用文件输入流类ifstream的文件对象fin从文本文件“price.txt"中输入数据
ifstream fin; //文件输入流类ifstream对象fin需要程序员自己定义
fin.open("price.txt"); //打开文件“price.txt"
fin.getline(name, 19); //读出标题行(碰到换行标记或者达到字节数停止,下次自动换到下一行)
cout << name << endl; //显示所读出的标题行,显示结果:商品名称 单价
for(int n=0;n<2;n++)
{
fin >> name >> price; //从文件“price.txt"中读取商品名称和单价
//变量之间空格隔开,每输入一个会自动换行
cout << name << "," << price << endl; //显示商品名称和单价,验证输入结果
}
fin.close(); //关闭所打开的文件“price.txt"
return 0;
}
🌰:输入二进制文件
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
char name[20];
double price;
//使用文件对象fin从二进制文件"price.dat"中输入数据
ifstream fin; //定义1个文件输入流类ifstream的文件对象fin
fin.open("price.dat",ios::binary);//以二进制模式打开文件“price.dat"
for(int n=0;n<2;n++)
{
fin.read(name, 7); //输入商品名称
fin.read((char*)&price,8); //输入单价
cout<<name<<","<<price<<endl; //显示商品名称和单价
}
fin.close(); //关闭所打开的文件“price.dat"
return 0;
}
关于getline函数
(参考:https://blog.csdn.net/MisterLing/article/details/51697098?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-5-51697098.nonecase)
在<istream>
中的getline()函数有两种重载形式:
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
从istream中读取至多n个字符(包含结束标记符)保存在s对应的数组中。即使还没读够n个字符,如果遇到delim或字数达到限制,则读取终止,delim都不会被保存进s对应的数组中。
🌰:
int main()
{
char name[6];
std::cout << "Please input your name: ";
std::cin.getline(name, 6, '#');
std::cout << "The result is: " << name<< std::ends;
std::cout << std::endl;
return 0;
}
输入输出:
//**输入: //streamsize限定,截断输出
wonderful
//输出:
wonde
//**输入: //所设置的结束标识符,截断输出
won#derful
//输出:
won
(一些用法参考:https://blog.csdn.net/Nine_CC/article/details/107072612?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-4-107072612.nonecase)
9.1.3.3 检查输入文件状态
- eof() 检查文件是否已结束
- good() 检查文件是否已损坏
🌰
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
char ch;
ifstream fin("price.txt"); //定义并初始化输入流类ifstream对象fin
while(true)
{
fin.get(ch); //从文件“price.txt"中读取1个字符
if(fin.eof()==true|| //eof的返回值:true-文件已结束,false-文件未结束
fin.good()==false) //good的返回值:true-文件正常,false-文件已损坏
break; //结束文件输入
cout.put(ch); //显示所读出的字符ch(包括空格换行等字符)
}
fin.close(); //关闭所打开的文件“price.txt"
return 0;
}
9.1.3.4 文件输入/输出流类fstream
/*文件输入/输出流类fstream的示意代码*/
class iostream:public istream, public ostream //多继承类istream和ostream
{
public:
virtual ~iostream(); //虚析构函数
protected:
iostream(); //无参构造函数
iostream(const iostream&); //有参构造函数
//......以下代码省略
};
class fstream:public iostream //公有继承通用输入/输出类iostream,实际上间接继承了is
{
public:
fstream(); //无参构造函数
fstream(const char*,int); //有参构造函数
~fstream(); //析构函数
void open(const char*,int); //打开文件
bool is_open() const; //检查文件是否正确打开
void close(); //关闭文件
//......以下代码省略
};
-
1、文件输入/输出流类fstream
-
从类istream继承的基类成员。其中包括提取运算符">>",函数成员get、getline、read、seekp和tellp等。这些成员是从类iostream间接继承来的,它们提供了文件输入功能
-
从类ostream继承的基类成员。其中包括插入运算符“<<",函数成员put、write、seekp、tellp和flush等。这些成员也从类iostream间接继承而来,它们提供了文件输出功能
-
新增函数成员。其中包括打开文件函数open和关闭文件函数close等
-
2、打开文件函数open
void open( const char *filename, int open_mode);
第一个输入常量指定了输入文件的文件名,其对应的实参可以是字符串常量,也可以是字符数组。第二个形参open_mode
指定打开模式。流类库在基类ios中定义了若干个表示不同打开模式的枚举常量。
枚举常量 所表示的打开模式
ios::in 打开一个输入文件。若文件不存在,则打开失败
ios::out 打开一个输出文件。若文件不存在则创建文件,若文件存在则清空其内容
ios::binary 以二进制模式打开文件。后续输入/输出操作应使用real/write函数
ios::app 以追加(Append)模式打开输出文件。后续输出数据将追加在文件的末尾
ios::ate 打开文件,并将文件指针移到文件末尾
ios::trunc 打开文件,并清空其内容,若未指定ios::in、ios::app、ios::ate,则默认此模式
🌰
fstream fobj; //定义1个fstream类的文件对象fobj
//以不同模式打开文件“aaa.dat"的举例
fobj.open("aaa.dat",ios::in); //以输入/文本模式打开文件"aaa.dat",默认为文本模式
fobj.open("aaa.dat",ios::in|ios::binary); //以输入/二进制模式打开文件"aaa.dat"
fobj.open("aaa.dat",ios::out|ios::binary);//以输入/二进制模式打开文件"aaa.dat"
fobj.open("aaa.dat",ios::out|ios::app); //以输出/追加/文本模式打开文件"aaa.dat"
文件的随机读写
- 文件输入流对象包含一个读文件指针
- 文件输出流对象包含一个写文件指针
istream& seekg( long bytes,ios::seek_dir origin); //移动读文件指针
long tellg(); //返回当前读文件指针的位置
ostream& seekp( long bytes,ios::seek_dir origin); //移动写文件指针
long tellp(); //返回当前写文件指针的位置
🌰
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
char name[20];
double price;
//使用文件对象fin从二进制文件"price.dat"中输入数据
ifstream fin; //定义1个文件输入流类ifstream的文件对象fin
fin.open("price.dat",ios::binary); //以二进制模式打开文件“price.dat"
for(int n=0;n<2;n++)
{
fin.read(name,7); //输入商品名称
fin.read((char *)&price,8); //输入单价
cout<<name<<","<<price<<endl; //显示商品名称和单价
}
fin.seekg(-15,ios::end); //end表示从文件尾向前(即往回)移动1行(1行有15个字节)
fin.read( name, 7);
fin.read((char*)&price,8); //重读最后1行数据
cout<<name<<","<<price<<endl; //显示所读出的商品名称和单价
fin.close(); //关闭所打开的文件"price.dat"
return 0;
}
9.2 string类及字符串I/O
C语言采用字符数组存储字符串数据,并在系统函数中,提供了一组常用的字符串处理函数:strlen、strcpy、strcat等
C++语言提供了一个新的字符串类string,string封装了字符数组和字符串处理函数。可以定义string类对象保存字符串数据,调用其函数成员来处理字符串。string类对象可作为输入/输出数据流,即字符串I/O
//字符串类string(字符串对象的定义与初始化)
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str; //定义字符串对象str,未初始化
string str1("Hello, world"); //定义字符串对象str1,初始化方法1
string str2 = "Hello, world"; //定义字符串对象str2,初始化方法2
string str3(str2); //拷贝构造
char cArray[] = "Hello, world";
string str4(cArray); //定义字符串对象str4,初始化方法4
//......以下代码省略
}
用小括号扩起来是面向对象编程的初始化方法,用等于则是结构化初始化方法。
字符串类string(字符串对象的输入与输出)
string str;
cin >> str; //从键盘为字符串对象str输入内容
cout << str << endl; //显示字符串对象str中的内容
- 字符串类string
- string类的函数成员
string str("abcdefg");
cout << str.length() << endl; //函数length返回字符串长度,显示结果:7
cout << str.find("cd") << endl; //函数find查找子串"cd"的位置,显示结果:2
cout << str.substr(2,4) << endl;//函数substr取出1个子串,显示结果cdef
str.append("123"); //函数append在串尾追加1个字符串
cout << str << endl; //显示结果:abcdefg123
重载运算符 举例 运算说明
+ cout << str+"123"; 连接字符串,显示结果:abcd123
= string str1;str1=str;cout<<str1 对象赋值,显示结果:abcd
+= str += "123";cout<<str; 追加字符串,显示结果:abcd123
== str == "abcd" 关系运算:等于。比较2个字符串是否相等
!= str != "abcd" 关系运算:不等于
< str<"abce" 关系运算:小于。依次比较字符的ASCII码值
<= str<="abce" 关系运算:小于等于
> str>"abce" 关系运算:大于
>= str >= "abce" 关系运算:大于等于
[] cout << str[0]; 按下标访问串中的字符,显示结果:a
- 字符串I/O
- 可以把string类对象当作数据源,将其中的字符串输入给其他变量,这时string类对象就是一个输入数据流
- 也可以把string类对象当作输出目的地,将其他变量中的数据输出到string类对象,这时string类对象就是一个输出数据流。
- 在字符串对象和其他变量之间进行数据的输入/输出,这就是字符串I/O
C++流类库通过字符串输入/输出流类提供了字符串I/O功能。字符串输入/输出流类共有3个:
字符串输入流类istringstream
字符串输出流类ostringstream
字符串输入输出流类stringstream
使用这些流类需要包含类声明文件
#include<sstream>
字符串输入流类istringstream
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
int main()
{
string str("10 25.76"); //定义字符串对象str
istringstream strin(str); //定义istringstream类对象strin,用str初始化
int x; double y;
strin >> x >> y; //从串流对象strin中为变量x、y输入数据
cout << "x=" << x <<",y="<<y<<endl; //显示结果:x=10, y=25.76
return 0;
}
字符串输出流类ostringstream
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
int main()
{
int x = 10; double y = 25.76;
ostringstream strout; //定义ostringstream类对象
strout << "x=" << x <<",y=" << y<< endl; //向串流对象strout中输出数据
string str = strout.str(); //将串流对象strout中的字符串赋值给字符串对象str
cout << "x=" << x <<",y="<<y<<endl; //显示结果:x=10, y=25.76
return 0;
}
9.3 基于Unicode编码的流类库
基于Unicode编码的流类库以wios为基类
- 标准I/O。包括通用输入流类wistream、通用输出流类wostream、通用输入/输出流类wiostream
- 文件I/O。包括文件输入流类wifstream,文件输出流类wofstream、文件输入/输出流类wfstream
- 字符串I/O。包括字符串输入流类wistringstream、字符串输出流类wostringstream、字符串输入/输出流类wstringstream。
预定义的宽字符流对象
namespace std// wcin/wcout/wciog/wcerr都被定义在命名空间std中
{
wistream wcin; //宽字符的键盘对象wcin
wostream wcout; //宽字符的显示器对象wcout
}
🌰:这个程序与上几个程序相比,显示器上输出的结果没有明显区别,但其是Unicode编码的程序。
#include<iostream>
#include<iomanip>
#include<string>
#include<locale>
using namespace std;
int main()
{
wstring name[]={"手电筒","电池"}; //wstring:宽字符串类
double price[]={75.825,4.1};
wcout.imbue( locale("chs")); //将语言设置为简体中文chs
wcout<<L"商品名称 单价\n"; //前缀L表示宽字符串常量
for(int n=0;n<2;n++)
wcout << name[n] << L" " << price[n] << endl;
return 0;
}