0%

C++复习(IO流)

C++中有两种IO操作,一种是由C语言继承来的IO函数输入输出语句,一种是面向对象的IO流类库,IO流不是C++语言的一部分,而是标准C++库的一部分,是C++类的一个集合。

1、C++中的输入输出

输入输出是数据传递的过程,数据如流水一般从一处流到另外一处。

1.1、C++中输入输出的分类

程序的输入是指将文件中的数据传递给程序(读),输出是指将程序中的结果传递给文件(写),C++系统将输入输出分为三类。

1.1.1、标准IO

内存与标准输入输出设备(键盘和显示器)之间的数据传递,即从键盘输入数据,输出到显示器屏幕,这种输入输出称为标准输入输出,简称标准IO。

1.1.2、文件IO

以磁盘(或光盘、硬盘)文件为对象进行输入和输出,例如从硬盘文件中输入数据,或数据输出到硬盘文件。这种以外存文件为对象的输入输出称为文件的输入输出,简称文件IO。

1.1.3、串IO

对内存中指定的空间进行输入输出,通常指定一个字符数组为存储空间(实际上可以利用该空间存储任何信息),这种输入输出称为字符串输入输出,简称串IO。

输入输出的数据的传递过程会形成不同的IO流,C++将这些流定义了不同的类,用类来定义流对象以实现数据的传递。

1.2、C++IO流类的安全性和可扩展性

1.2.1、C++IO流类的安全性

C++系统会对输入输出的数据类型进行严格的检查,防止出现C语言中scanf()输入整型变量i的值时没有写上&取地址符号而造成把输入的数值存储到地址编号为1的内存中,或printf()函数没有写上格式控制符等。

1.2.2、C++IO流类的可扩展性

C++可以输入输出用户自定义的数据类型,而C语言不行。

2、IO流类库简介

C++中的IO流时由stream来完成的,stream就是一条数据流,字符序列在其中“川流不息”,stream数据流主要包括输入流和输出流,可以进行输入输出操作。

输入操作是指数据流出stream,即把stream流中的数据读取出来。

输出操作时指数据流入stream,即将数据写入到流中的某个地方。

IO流类库中不同的stream类就是用来进行不同数据的读写输入输出的。

2.1、IO流类库

C++为实现数据的输入输出定义了一个庞大的流类库,它以ios为根基类,直接派生了四个类:

输入流类istream、输出流类ostream、文件流基类fstream、字符串流基类strstreambase。

这四个直接派生类又派生出其它的类,形成了标准IO流类库、文件流类库和字符串流类库。

输入输出流类iostream,它同时继承了输入流类和输出流类。

输入文件流类ifstream,它同时继承了输入流类和文件流基类。

输出文件流类ofstream,它同时是继承了输出流类和文件流类。

输入输出文件流类fstream,它同时继承了输入输出流类和文件流基类。

输入字符串流类istrstream,它同时继承了输入流类和字符串流基类。

输出字符串流类ostrstream,它同时继承了输出流类和字符串流基类。

输出输出字符串流类strstream,它同时继承了输出输出流类和字符串流基类。

其中的每一个类都称做相应的流或流类,用来完成某一方面的功能,根据一个流或流类定义出的对象也被称为流对象。以下是它们之间的派生关系:

即:C++流类库中定义的各种流可以给用户直接使用,它们分别包含在iostream、fstream、strstream三个头文件中。

其中:标准IO操作iostream、文件IO操作fstream串IO操作strstream或者sstream(新版本C++支持)

2.2、缓冲区类

在对数据进行输入输出时,为了减少输入输出的次数,节省计算机自由,常常需要使用到缓冲区,C++提供了一个缓冲区流类库,它以streambuf为父类的类层次,主要完成信息通过缓冲区的交换,因为streambuf是一个抽象类,不会直接使用,一般都是是用它的三个派生类。

2.2.1、stdiobuf类

stdiobuf类主要用于C++语言的流类层次方法和C语言的标准输入输出方法火鹤使用时系统的缓冲区管理。

2.2.2、filebuf类

filebuf类在streambuf类的基础上增加了文件处理功能,使用文件来保存缓冲区中的字符序列。

2.2.3、strstreambuf类

strstreambuf类在streambuf类的基础上增加了动态内存管理功能,strstreambuf类实现从内存到缓冲区的信息交换和从缓冲区到内存的信息交换,从而可以在计算机内存之间交换数据信息。

2.2.4、缓冲区类型

C++在定义一些流对象时都会自带有缓冲区,它提供的缓冲区有三种类型:全缓冲、行缓冲、无缓冲。

2.2.4.1、全缓冲

全缓冲指知道缓冲区被填满时才调用IO函数。对于读操作来说,知道读入内容的字节数等于缓冲区大小或者文件已经到达结尾,才进行实际的IO操作,将外村文件内容读入缓冲区;对于写操作来说,知道缓冲区被填满,才进行实际的IO操作,将缓冲区内容写到外存文件中。例如磁盘文件的读写。

2.2.4.2、行缓冲

档在输入和输出遇到换行符或者缓冲区被填满时,才执行真正的IO操作,这时,我们输入的字符先存放在缓冲区,等按下回车键时才进行实际的IO操作。标准的输入输出一般都是行缓冲。

2.2.4.3、无缓冲

没有缓冲区。例如标准错误信息输出对象cerr。

2.2.4.4、用户自定义缓冲区

用户可以使用上述缓冲区类来自定义缓冲区。但在使用时需要注意对缓冲的刷新。即当数据存储在缓冲区时,如果要执行IO操作,需要刷新缓冲区,将缓冲区中的数据读或者写道某一个指定地方,其中刷新缓冲区方式为:

1、缓冲区满时

2、执行flush()函数,可以执行IO操作并且清空缓冲区

3、执行endl语句,该控制符可以将光标移动到输出设备中下一行开头处,并且清空缓冲区

4、关闭文件

3、标准输出流和标准输入流

标准输入输出流的对象和操作方法都是由istream和ostream两个类提供的,这两个类虚继承至ios基类,它们预定义了标准输入输出流对象,并且提供了多种形式的输入输出功能。(头文件iostream)

3.1、提取运算符和插入运算符

C++在进行输入时需要从流中提取数据,在输出时需要向流中插入数据,提取和插入是通过在流类库中重载”>>”和”<<”运算符来实现的。

3.1.1、”>>”运算符(提取运算符)

提取运算符”>>”,又叫输入运算符,它的本义是右移操作,常用于位运算中,例如i=2,i>>1,此时i=1,即十进制下右移一位除以2,左移一位乘以2。但是C++的IO流为了实现从流中提取数据,就在istream类中重载了”>>”运算符,其中有18种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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>>(unsigned long&);
istream& operator>>(float&);
istream& operator>>(double&);
istream& operator>>(long double&);
istream& operator>>(streambuf*);
inline istream& operator>>(istream& (_cdecl*_f)(istream&));
inline istream& operator>>(ios& (_cdecl*_f)(ios&));

其中按功能可以分为三类:前十五个用于基本数据类型的输入,第16个可以直接把提取的输入流数据插入道缓冲区buf中,最后两个则建立了支持istream环境和支持ios环境的用户自定义数据类型的输入。

注意:istream类的对象cin调用>>运算符时,从流中提取数据,把数据传送个cin对象,但会跳过输入流中的空格、tab键、换行符等空白字符。

并且重载的输入运算符”>>”都是返回流类的istream的引用,这就使得”>>”运算符可以连用,例如cin>>a>>b>>c。(依次输入a、b、c)

3.1.2、”<<”运算符(插入运算符)

插入运算符”<<”,又叫输出运算符,它的本义是左移操作,常用于位运算中,例如i=2,i<<1,此时i=4,即十进制下右移一位除以2,左移一位乘以2。但是C++的IO流为了实现从流中提取数据,就在ostream类中重载了”<<”运算符,其中有19种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ostream& operator<<(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<<(stream* buf);
inline ostream& operator<<(istream& (_cdecl*_f)(ostream&));
inline ostream& operator<<(ios& (_cdecl*_f)(ios&));

其中按功能可以分为三类:前十六个用于基本数据类型的输出,第17个可以直接从缓冲区buf中提取数据插入道输出流中,最后两个则建立了支持istream环境和支持ios环境的用户自定义数据类型的输入。

注意:ostream类重载了”<<”运算符,与输入运算符”>>”相比,输出运算符在输出数据时有很多格式控制细节。

3.2、预定义流对象

为了方便用户对基本输入输出流进行操作,C++提供了四个预定义的标准流对象:cin、cout、cerr和clog。当用户在程序中包含了iostream头文件时,编译器调用相应的构造函数产生了这四个标准流对象,这样用户就可以直接使用了。

其中:

cin是istream类的对象,用于处理标准输入(即键盘输入)

cout是ostream类的对象,用于处理标准输出(即屏幕输出)

cerr和clog都是ostream类的对象,用于处理标准出错信息,并将信息显示道屏幕上。

预定义流对象表如下:

对象名 所属类 对应设备 含义 结合使用
cin istream 键盘 标准输入,有缓冲 >>
cout ostream 屏幕 标准输出,有缓冲 <<
cerr ostream 屏幕 标准错误输出,无缓冲 <<
clog ostream 屏幕 标准错误输出,有缓冲 <<

3.2.1、cin

cin是istream类的对象,它从标准输入设备(键盘)获取数据,程序中的变量通过提取运算符”>>”从流中提取数据,然后送给cin对象,由cin将对象送到指定地方。

注意:

1、cin是带缓冲区的输入流对象,只有在输入完数据并且按下回车键之后,该行数据才被送到键盘缓冲区,形成输入流,提取运算符”>>”才能从中提取数据。例如:

1
2
int a;
cin>>a;

此时从键盘输入10,则10只是存入缓冲区里面而不能为”>>”运算符所提取,而只有当按下回车键之后,相当于执行endl语句(清空缓冲区),而缓冲区内的内容才被刷新形成输入流,被”>>”运算符提取,然后传递给cin对象,由cin对象送到变量a中存储。

如果从键盘输入abc,则提取操作会失败,此时cin流被设置为出错状态,只有在正常状态下才可以此从输入流中提取数据。

2、cin对象支持多个变量的读取,因为”>>”运算符都被重载为istream类的引用。

1
2
3
string a;
float f;
cin>>a>>f;

只是此时注意,如果直接在键盘上打上abc123,则a为abc123,还会等待下一个输入为f赋值,必须要abc空格123,此时才会对应赋值,因为cin读到空格、换行、tab键等空白字符时会清空缓冲区,等待下一次输入。

3、cin对象可以对不同类型的变量进行读入,并且也需要对应类型输入正确

3.2.2、cout

cout是ostream类预定义的对象,对应的标准设备为屏幕,称作标准输出对象或屏幕输出对象,但也可以重定向输出道磁盘文件中。用户可以通过cout对象调用ostream类的插入运算符”<<”和成员函数来输出信息。

注意:

1、可以利用cout对象直接输出常量值,直接把要输出的内容放在输出运算符”<<”后即可,例如:

1
2
cout<<18238947<<endl;
cout<<"absfggdfsdfd"<<endl;

2、可以利用cout对象输出变量的值,并且在用cout输出变量值时,不用指定输出格式,输出运算符”<<”会根据变量的数据类型自动调用相匹配的重载函数。

1
2
3
4
int a=10249;
cout<<a<<endl;
string str1="asflkjdfkjhfasd";
cout<<str1<<endl;

3、可以利用cout对象输出指针、引用类型的数据,并且与printf()函数用法一致,只是不需要格式控制符号。

1
2
3
4
5
6
int a=19l24;
const int& b=a;
cout<<b<<endl;//输出a的值
const int* p=&a;
cout<<p<<endl;//输出a的地址
cout<<*p<<endl;//输出a的值

4、可以利用cout对象进行连续输出,与输入运算符”>>”相同,输出运算符”<<”重载函数返回的都是ostream类对象的引用,因此也可以进行多次输出。

5、cout也有格式化输出

3.2.3、cerr和clog

cerr与clog都是ostream类预定义的流对象,都是用于标准错误输出,默认设备都是显示器,只是cerr没有缓冲,意味着信息将被直接发送给屏幕,不会等到缓冲区填满或遇到换行符才输出错误信息。

3.3、标准输出流

ostream预定义了三个输出流对象cout、cerr、elog,而且还重载了插入运算符”<<”,使用它可以输出各种类型的数据,这是标准输出流的基本用法,而ostream还提供了put()、write()等成员函数用来输出数据。

3.3.1、put()函数

在ostream类中定义了可以输出单个字符的成员函数put(),其函数声明如下:

1
2
ostream& put(char ch);
ostream& put(const char ch);

put()函数可以将一个字符型变量的值或一个字符常量输出到屏幕上,其返回值也是ostream类对象引用,例如:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
using namespace std;
int main(void)
{
char ch='a';
cout.put(ch)<<endl;
cout.put('a').put('b').put('c')<<endl;
system("pause");
return 0;
}

3.1.2、write()函数

write()函数可以将一个字符串的部分或者全部字符送到输出流,其函数声明如下:

1
ostream& write(const char* str,int n);

该函数第一个参数是要输出的字符串首地址,第二个参数是要输出的字符个数,若要输出全部字符,则第二个参数的值可以通过函数strlen()求出,函数返回值也是ostream类对象的引用,所以它也可以多次输出。

例如:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
using namespace std;
int main(void)
{
char* p ="创制博客";
cout.write(p,8).put('\n');
cout.write(p,strlen(p))<<endl;
system("pause");
return 0;
}

3.4、标准输入流

C++中的istream类提供了多种形式的输入功能 :istream类重载了提取运算符”>>”,使用它可以输出各种类型的数据,这事标准输入流最基本的用法,另外,istream还提供了成员函数get()实现字符的输入,getline()函数实现字符串的输入,read()函数实现无格式输入等。

3.4.1、get()函数

cin是istream类的对象,它提供了从输入流中获取单个字符的成员函数get(),三种声明如下:

1
2
3
int get();
istream& get(char& ch);
istream& get(char* dst,streamsize size,char delimiter = '\0');

第一种形式表示从输入流中读取一个字符,返回该字符的ascii码值,遇到文件结束符时,返回EOF。

第二种形式表示从输入流中读取一个字符,存储在ch中。

第三种形式表示从输入流中连续读取最多size-1个字符(因为最后一个字符要留给字符’\0’),存储到首地址为dst的内存当中,delimiter时结束符,如果不传第三个参数,默认结束符是’\0’,也可以设置结束符是其它字符,遇到这个字符就结束读取,并且结束符不包含在所读取的字符串内,如果一直读取到size-1个字符也没有遇到结束符,那么会在结束读取时自动补’\0’在末尾。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace std;
int main(void)
{
cout<<"first call:"<<cin.get()<<endl;//第一种形式

char ch;
cin.get(ch);//第二种形式
cout<<"second call:"<<ch<<endl;

char str[10];
cin.get(str,8,'T');//第三种形式
cout<<"third call:"<<str<<endl;

system("pause");
return 0;
}
abcdeTfg
97
b
cde

3.4.2、getline()函数

使用get()函数可以一次读取一个字符,而getline()函数可以一次读取一个指定长度的字符串。其函数声明如下:

1
istream& getline(char* dst,streamsize size,char delimiter = '\n');

与get()函数的第三种重载方式相同,但是默认结束符为’\n’,它还可以读取空格、tab键等空白内容。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace std;
int main(void)
{
int line,max=0;
char str1[100],str2[100];
while(cin.getline(str1,100))//若想结束循环,请按ctrl+z
{
line=cin.gcount();//获取getline()函数实际读入的字符个数 \n也算
if(line>max)
{
max=line;
strcpy(str2,str1);
}
}
cout<<endl;
cout<<"the longest string is"<<str2<<endl;
cout<<"the longest string's length is"<<max<<endl;
system("pause");
return 0;
}
3.4.2.1、cin>>

用法1:输入一个数字或字符

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main(void)
{
int a,b;
cin>>a>>b;
cout<<a+b<<endl;
}

用法2:接收一个字符串,遇“空格”、“TAB”、“回车”就结束

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main(void)
{
char a[20];
cin>>a;
cout<<a<<endl;
}

输入:jkljkljkl
输出:jkljkljkl

输入:jkljkl jkljkl //遇空格结束
输出:jkljkl

3.4.2.2、cin.getline()

用法:接收一个字符串,可以接收空格并输出

1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
int main(void)
{
char m[20];
cin.getline(m,5);
cout<<m<<endl;
return 0;
}

输入:jkljkljkl
输出:jklj

接收5个字符到m中,其中最后一个为’\0’,所以只看到4个字符输出

如果把5改成20:
输入:jkljkljkl
输出:jkljkljkl

输入:jklf fjlsjf fjsdklf
输出:jklf fjlsjf fjsdklf

延伸:
1、cin.getline()实际上有三个参数,cin.getline(接收字符串的变量,接收字符个数,结束字符)
2、当第三个参数省略时,系统默认为’\0’
3、如果将例子中cin.getline()改为cin.getline(m,5,’a’);当输入jlkjkljkl时输出jklj,输入jkaljkljkl时,输出jk

3.4.2.3、getline()

用法:接收一个字符串,可以接收空格并输出,需包含“#include

1
2
3
4
5
6
7
8
9
10
#include<iostream>
#include<string>
using namespace std;
int main (void)
{
string str;
getline(cin,str);
cout<<str<<endl;
return 0;
}

输入:jkljkljkl
输出:jkljkljkl

输入:jkl jfksldfj jklsjfl
输出:jkl jfksldfj jklsjfl

3.4.2.4、注意的问题

1、cin.getline()属于istream流,而getline()属于string流,是不一样的两个函数

2、当同时使用cin>>,getline()时,需要注意的是,在cin>>输入流完成之后,getline()之前,需要通过s

tr=”\n”;

getline(cin,str);

的方式将回车符作为输入流cin以清除缓存,如果不这样做的话,在控制台上就不会出现getline()的输入提示,而直接跳过,因为程序默认地将之前的变量作为输入流。

看下面一段程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
#include<string>
#include<sstream>
using namespace std;

int main(void){
int age;
//standard input(cin)
cout<<"Please enter an integer value as your age: ";
cin>>age;
cout<<"Your ager is: "<<age<<".\n";
//cin and string
string mystr;
cout<<"What's your name? "<<endl;
mystr="\n";
getline(cin,mystr);//附加部分
getline(cin,mystr);//或getchar();
cout<<"Hello,"<<mystr<<".\n";
char sex;
cout<<"Please enter a F or M as your sex: ";
cin>>sex;
cout<<"Your sex is: "<<sex<<endl;
cout<<"What's your favorite team? ";
mystr="\n";
getline(cin,mystr);//附加部分
getline(cin,mystr);//或getchar();
cout<<"I like "<<mystr<<".\n";

system("pause");
return 0;
}

其结果为:

如果去掉附加部分:

3.4.3、read()函数

istream类还提供了一个成员函数read(),它可以从输入流中读取部分或全部数据到指定的内存空间,其函数声明如下:

1
istream& read(char* dst,streamsize size);

与get()函数、getline()函数类似,它第一个参数也是读取的size-1个字符存储在首地址为dst的内存空间中。并且没有其它限制,直接读取数据。

3.4.4、ignore()函数

跳过输入流中的n个字符或者是遇到指定的终止符号时提前结束跳过字符,其函数声明为:

1
istream& ignore(streamsize num =1,int delim = EOF);

它从输入流中读取字符,直到已经读了num个字符(默认为1)或者是直到字符delim(默认为EOF)被读入时结束。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int main(void)
{
char str[10];
cin.ignore(6,'T');//跳过前面6个字符,如果有T,则T终止跳跃
cin.getline(str,8);//开始读取输入的7个字符,并存储到str中
cout<<str<<endl;
system("pause");
return 0;
}
abcTefgThijklmn
defgThi

3.4.5、putback()函数

它把上一次通过get()或者getline()读取的字符再放回输入流中,函数声明如下:

1
istream& putback(char ch);

3.4.6、peek()函数

它用于观测下一个字符,检查输入流中的下一个字符,它不会删除该字符,字符指针也不会移动,返回值是指针指向的当前字符。其函数声明如下:

1
int peek();

3.4.7、gcount()函数

它用于计算最后一个非格式化方法(get()、getline()、read()、ignore())读取到的字符数

3.5、格式化控制

3.5.1、格式标志

3.5.1.1、格式化状态
3.5.1.2、格式化操控符
3.5.1.2.1、setf()函数
3.5.1.2.2、unsetf()函数
3.5.1.2.3、flags()函数

3.5.2、精度、域宽、填充字符的设置

3.5.2.1、浮点数精度设置
3.5.2.2、域宽设置
3.5.2.3、填充字符设置

3.5.3、操作符的格式控制

3.5.3.1、无参操作符
3.5.3.2、有参操作符

3.6、文件流

文件流是以外存文件作为输入输出对象的数据流,C++提供了三个类来支持文件的输入输出:

1、ifstream 输入文件流类 用于把文件中的数据读取到流中(读取)

2、ofstream 输出文件流类 用于把流中的数据写入文件(写)

3、fstream 输入输出文件流类,既可以读取也可以写

3.6.1、构建文件流对象

文件流并没有像标准IO流预定义了输入输出流对象,所以在使用文件流时需要调用相应的类的构造函数来构建流对象,即:

1、调用默认构造函数

1
2
3
ifstream ifs;
ofstream ofs;
fstream fs;

2、调用构造函数时指定文件名

1
2
3
ifstream ifs("filename");
ofstream ofs("filename");
fstream fs("filename");
1
2
3
4
ifstream ifs;
......指定文件
char ch;
ifs>>ch;//读取文件数据

在使用文件流对象进行输入输出时,同样可以使用插入运算符”<<”和提取运算符”>>”,因为它们是预先设计好的,用于在输入输出流对象中传递数据,而一般的提取运算符是由空白符为分隔,如果需要读取一行再对文本进行分析,可以使用非格式化成员输出函数getline()。

3.6.2、文件的打开和关闭

在看C++编程思想中,每个练习基本都是使用ofstream,ifstream,fstream。

img

这里主要是讨论fstream的内容:

1
2
3
4
5
#include <fstream>  
2.ofstream //文件写操作 内存写入存储设备
3.ifstream //文件读操作,存储设备读区到内存中
4.fstream //读写操作,对打开的文件可进行读写操作
1.打开文件

在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作

函数:open()

1
2
3
4
5
6
void open ( const char * filename,  
ios_base::openmode mode = ios_base::in | ios_base::out );

void open(const wchar_t *_Filename,
ios_base::openmode mode= ios_base::in | ios_base::out,
int prot = ios_base::_Openprot);

参数: filename 操作文件名

​ mode 打开文件的方式

​ prot 打开文件的属性 //基本很少用到,在查看资料时,发现有两种方式

打开文件的方式在ios类(所以流式I/O的基类)中定义,有如下几种方式:

ios::in 为输入(读)而打开文件
ios::out 为输出(写)而打开文件
ios::ate 初始位置:文件尾
ios::app 所有输出附加在文件末尾
ios::trunc 如果文件已存在则先删除该文件
ios::binary 二进制方式

这些方式是能够进行组合使用的,以“或”运算(“|”)的方式:例如

1
2
ofstream out;  
out.open("Hello.txt", ios::in|ios::out|ios::binary) //根据自己需要进行适当的选取

打开文件的属性同样在ios类中也有定义:

0 普通文件,打开操作
1 只读文件
2 隐含文件
4 系统文件

对于文件的属性也可以使用“或”运算和“+”进行组合使用,这里就不做说明了。

很多程序中,可能会碰到ofstream out(“Hello.txt”), ifstream in(“…”),fstream foi(“…”)这样的的使用,并没有显式的去调用open()函数就进行文件的操作,直接调用了其默认的打开方式,因为在stream类的构造函数中调用了open()函数,并拥有同样的构造函数,所以在这里可以直接使用流对象进行文件的操作,默认方式如下:

1
2
3
ofstream out("...", ios::out);  
ifstream in("...", ios::in);
fstream foi("...", ios::in|ios::out);

当使用默认方式进行对文件的操作时,你可以使用成员函数is_open()对文件是否打开进行验证

2.关闭文件

当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。成员函数close(),它负责将缓存中的数据排放出来并关闭文件。这个函数一旦被调用,原先的流对象就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程所访问了。为防止流对象被销毁时还联系着打开的文件,析构函数将会自动调用关闭函数close。

3.文本文件的读写

类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。

一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// writing on a text file  
#include <fiostream.h>
int main () {
ofstream out("out.txt");
if (out.is_open())
{
out << "This is a line.\n";
out << "This is another line.\n";
out.close();
}
return 0;
}
//结果: 在out.txt中写入:
This is a line.
This is another line

从文件中读入数据也可以用与 cin>>的使用同样的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// reading a text file  
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main () {
char buffer[256];
ifstream in("test.txt");
if (! in.is_open())
{ cout << "Error opening file"; exit (1); }
while (!in.eof() )
{
in.getline (buffer,100);
cout << buffer << endl;
}
return 0;
}
//结果 在屏幕上输出
This is a line.
This is another line

上面的例子读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。

状态标志符的验证(Verification of state flags)

除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):

  • bad()

    如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。

  • fail()

    除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。

  • eof()

    如果读文件到达文件末尾,返回true。

  • good()

    这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。

要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。

获得和设置流指针(get and put stream pointers)

所有输入/输出流对象(i/o streams objects)都有至少一个流指针:

  • ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  • fstream, 类似 iostream, 同时继承了get 和 put

我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:

  • tellg() 和 tellp()

    这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).

  • seekg() 和seekp()

    这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:

    seekg ( pos_type position ); seekp ( pos_type position );

    使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。

    seekg ( off_type offset, seekdir direction ); seekp ( off_type offset, seekdir direction );

    使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:

    ios::beg 从流开始位置计算的位移
    ios::cur 从流指针当前位置开始计算的位移
    ios::end 从流末尾处开始计算的位移

流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

以下例子使用这些函数来获得一个二进制文件的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// obtaining file size  
#include <iostream.h>
#include <fstream.h>

const char * filename = "test.txt";

int main () {
long l,m;
ifstream in(filename, ios::in|ios::binary);
l = in.tellg();
in.seekg (0, ios::end);
m = in.tellg();
in.close();
cout << "size of " << filename;
cout << " is " << (m-l) << " bytes.\n";
return 0;
}

//结果:
size of example.txt is 40 bytes.

4.二进制文件

在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。

文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:

write ( char * buffer, streamsize size ); read ( char * buffer, streamsize size );

这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// reading binary file  
#include <iostream>
#include <fstream.h>

const char * filename = "test.txt";

int main () {
char * buffer;
long size;
ifstream in (filename, ios::in|ios::binary|ios::ate);
size = in.tellg();
in.seekg (0, ios::beg);
buffer = new char [size];
in.read (buffer, size);
in.close();

cout << "the complete file is in a buffer";

delete[] buffer;
return 0;
}
//运行结果:
The complete file is in a buffer

5.缓存和同步(Buffers and Synchronization)

当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。

当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:

  • 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
  • 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
  • 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
  • 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。