(
2010-7-1
~
7-2
)
要点
:
讨论
C++
中输入输出流的强大功能,阐述文件流、字符串流、标准控制台流
1. 为什么要引入输入输出流
为了使用更加安全方便
2. 改进输入输出流
l
插入符和提取符
流是一个传送和格式化固定宽度字符的对象。
用户可以获得一个输入流
(istream
类的子类
)
、一个输入流
(ostream
类的子类
)
、一个类的子类
) (iostream
类派生的对象
)
。
输入输出流类库提供了以下几种不同的类:用于文件输入输出的
ifstream
、
ofstream
、
fstream
,用于标准
C++
中
stirng
类输入输出的
istringstream
、
ostringstream
、
stringstream
。所有这些流类拥有几乎相同的接口。所以能以统一的方式使用这些流类,不论是操作对象文件、标准
I/O
、内存区、还是
string
对象。这样单一的接口也同样支持扩充和增加一些新定义的类。这些流类实际上是模板的特化。下图为输入输出流类继承体系中的基本类:
在输入输出流中重载了用于输出流的插入运算符(
>>
),用于输入流的提取运算符(
<<
)。常见
cin
或
cout
对象与
>>
或
<<
结合起来使用。为所有的内置类型都重载了这些运算符,所以它们会根据目标对象的类型解析出输入输出的信息。同样,
cerr
相当于标准错误输出。
用户也可以为自己的类重载这些运算符。当重载这些运算符时:
l
第一个参数定义成流(输入是
istream
,输出是
ostream
)的非
const
引用
l
执行向
/
从流中插入
/
提取数据(通过处理对象的组成元素)
l
返回流的引用
如:
ostream& operator<<(ostream& os,const Date& d)
{
char fillc=os.fill('0');
os<<setW(2)<<d.getMonth()<<'_' <<setW(2)<<d.getDay()<<'_' <<setW(4)<<<<setfill(fillc)<<d.getYear();
return os;
}
ostream
的成员函数
fill()
用于替换填充字符,譬如当输出域宽大于数据宽度时,可用
ostream.fill(char)
来填充,域宽由操作算子
setW()
设置。
l
通常用法
在交互程序中,通常是先把输入内容放入缓冲区,再进行扫描和转换。
一般采用如下方法:
l
如果程序需要输入,则从文件中读入数据,通过输入输出流使用文件非常容易。文件输入输出流在图形用户界面下也能很好的工作。
l
读取输入但不试对其进行转换。当输入数据被保存到某处,且在转换时不会造成错误时,才可以安全地扫描它。
l
输出:在图形用户界面,不需
cout
,必须把数据输出到文件或使用图形用户界面应用程序实现数据显示。否则,把数据输出到
cout
是很有意义的。在这两种情况下,输入输出流的输出格式化功能非常有用。
l
按行输入
有3种可选的方法实现按行输入:
1)
成员函数
get(); (
读取下一个字符保存到第一个引用参数中,无参数则不保存而是返回
)
2)
成员函数
getline()
3)
定义在头文件
<string>
中的全局函数
getlin()
,是名字空间
std
中声明的独立函数
1)
和
2)
有3个参数,分别是:指向保存结果的字符缓冲区的指针,缓冲区的大小,结束符。这两个函数的区别在于:
get(void)
遇到结束符时停止执行,不会提取结束符,这时若再调用
get()
函数,那么不会提取输入而立即返回(为解决这一问题需在下一个
get()
函数时使用不同的结束符);而
getlin()
会提取结束符但不会把它存入缓冲区中。
3)
有两个非默认参数:输入流和
string
对象。使用
getlin()
不用关心缓冲大小的问题,它遇到结束符就停止读取并丢弃这个结束符。
一般情况下,采用按行处理的输入方式处理文本文件时,需要使用其中的一个
getlin()
函数
get()
的三个重载:
l
int get(void);
返回下一个字符
l
get(char&);
从流中读取一个字符放到参数中
l
get()
把流类对象直接存储到基础缓冲区结构
get(),getlin()
都是
istream
的成员函数
补充:
get
语法
:
1) int get();
//
返回从输入流读取的字符值
(int
型
),
如果是
-1
表明已到文件尾
2) istream &get( char &ch );
//
从输入流中提取一个字符存入
ch
中,返回被应用的
istream
对象
3) istream &get( char *buffer, streamsize num );
4) istream &get( char *buffer, streamsize size, char delimiter=’/n’ );
//buffer
用来存放读取的字符
,size
表示读取的最大长度,
//delimiter
是表结束的字符
,delimiter
不会被读入
buffer
,而是保留在输入流,在下次读取时会读入
buffer
//
为避免下次读入
delimiter,
可以用成员函数
ignore()
将它滤掉
5) istream &get( streambuf &buffer );
6) istream &get( streambuf &buffer, char delim );
ignore():
默认情况下
ignore()
从输入流对象中读取一个字符并丢弃。也可以指定长度和
delimiter
,如下:
ignore( streamsize length = 1, int delim = traits::eof )
ignore()
从
istream
中读入并丢弃
length
个字符或者遇到
delimiter
之前包含
delimiter
在内的所有字符或者直到文件结尾它返回当前被应用的
istream
对象
.
get()
函数被用于输入流,和以下这些
:
1)
读入一个字符并返回它的值,
2)
读入一个字符并把它存储在
ch
,
3)
读取字符到
buffer
直到
num - 1
个字符被读入
,
或者碰到
EOF
或换行标志,
4)
读取字符到
buffer
直到已读入
num - 1
个字符,或者碰到
EOF
或
delim(delim
直到下一次不会被读取
)
,
5)
读取字符到
buffer
中,直到碰到换行或
EOF
,
6)
读取字符到
buffer
中,直到碰到换行,
EOF
或
delim
。
(
相反
, delim
直到下一个
get()
不会被读取
).
例如,下面的代码一个字符一个字符的显示文件
temp.txt
中的内容:
char ch; ifstream fin( "temp.txt" ); while( fin.get(ch) ) cout << ch; fin.close();
getline():
getline(char* buffer, streamsize size, char delimiter=’/n’)
功能同
get(char* buffer, streamsize size, char delimiter=’/n’)
,且
getline()
可以自动滤去
delimiter,
gcount()
在
cin.get()
或
cin.getlin()
之后调用
cin.gcount()
可以得到当前读取了多少个字符
.
read(
指向目标内存的指针
,
需要读入的字节数
)
,用来将字节移动到内存中的一个变量、数组或结构中。当读取原始字节时,用非格式化的
read()
函数就很有用
3. 处理流错误
l
流状态
在读文件时,如果
eofbit
被设置,则
eor()
返回真;如果
failbit(
期望的数据没被成功读取
,
可能是
I/O
问题
)
被设置,则函数
fail()
返回真;如果
badbit(
期望的数据发生严重错误可能是物理上的,流将不能再使用
)
被设置,则函数
fail()
返回真;若这三个没有被设置,则流成员函数
good()
返回真。当这些标志位被设置后,就保持不变,除非调用
clear()
函数来清空标志位。默认的
clear()
函数置
goodbit
为真
(
如:myStream.clear();
// Clears all error bits)
,如果只想设置其中某一个标志位,可以调用
clear()
的重载函数,把要置的位作参数(如:myStream.clear(ios::failbit | ios::eofbit);
)
。
l
流和异常
传统的用手工检查流的状态,现代的输入输出流用异常处理来检查流的状态。
输入输出流用异常处理来检查流的状态方法:首先,调用流的成员函数
exceptions(status)
来设置在哪个状态时要抛出异常(因为我们并不希望它所有的状态都抛出异常),那么当到了“
status
”的状态时就会抛出异常(
std::ios_base::failure
类型,它继承自
std::exception
),然后,就要捕获异常做异常处理。当然也要把流处理部分包含到含有
io::failure
处理器的
try{}
块中。
4. 文件输入输出流
实际使用的文件输入输出流也是由类型定义的模板的特化。如下是它们的继承关系:
譬如:
ifstream
用来处理
char
文件,定义:
typedef basic_ifstream<char> ifstream;
是
char
类型定义的模板的特化。
注意:在写程序时,虽然有些平台上只包含
<fstream>
就会连同
<iostream>
也包含进来,但是有些编译器未必能这样做,所以编写可移置代码时把
<fstream>
和
<iostrem>
都要包含进来。
l
一个文件处理的例子
get()
如果不带参数,将读取下一个字符并返回它;如果带参数则把读取的值保存在第一个引用参数里。在看例
strfile.cpp
时有以下问题不明白:
可以用
out(
如同用
cout
那样
)
直接把内容输出到文件
1)
我发现在第一个while(in.get(buf, SZ)){}循环里用in.get()滤去结束符时,并没把结束符滤去(难道是我理解错了?)
2)
为什么这里的in.get(buf,SZ)只能读取(SZ-1)个字符?
--
后来发现get(buf,SZ)源函数里只保存了SZ-1个字符,第SZ个字符虽然读了,但没保存到buf里,而是在它的位置加了一个结束符
3)
这样岂不是不能把内容完整的读出来?
4)
为什么不能把一个完整的strfile.cpp输出?
5)
如果在文件中有空行会不会出现空行之后的文字不能给输出的现象?
l
打开模式
Flag
|
Function
|
ios::in
|
打开一个输入文件,将这种模式用于打开ofstream(?) 可以使现存文件不被截断
|
ios::out
|
打开一个输出文件。将这种模式用于ofstream,且没有使用ios::app、ios::ate或ios::in时,意味着使用的是ios::trunc模式
|
ios::app
|
打开一个仅用于追加的文件
|
ios::ate
|
打开一个已存在的文件(输入或输出),并把文件指针指向文件末尾
|
ios::trunc
|
如果文件存在则截断文件
|
ios::binary
|
Opens a file in binary mode. The default is text mode. 当使用read()/write()或流定位指针命令时,应该使用二进制模式打开文件。 |
使用
fstream
时必须使用足够的打开模式标记
,
告诉文件系统现在想输入?输出?还是输入也输出
?
从输出切换到输入时,需要刷新流或重定位文件指针。从输入切换到输出时,而要重定位文件指针。通过调用
fstream
对象创建一个文件,在构造函数中使用
ios:trunc
打开模式标志,可以调用输入和输出文件。
5. 输入输出流缓冲
每个输入输出流包含一个指向
流缓冲区类型
(streambuf)
对象
的指针,这个指针可以用
streambuf
对象的成员函数
rdbuf()
返回。当用
rdbuf()
读出流中的内容,同时将会把流中的内容清除。
streambuf
类有可供调用的成员函数。对于
file
、
IO
有特殊的
streambuf
。如图:
当我们创建一个
ifstream
对象
in
并打开一个文件,再调
in.rdbuf();
就可以把整个完整的文件读出。多么方便而且
高效
的啊!
三种从一个文件中读取并输出的方法:
1)
用普通的方法,
in.get(buf,len);
,再
cout<<buf;
;
2)
用
streambuf
类,
用rdbuf()返回的指向streambuf对象的指针,结合输出来实现
,就是输出时用
:cout<<in.rdbuf();
;
3)
也是用
streambuf
,结合通过
rdbuf()
返回的指向
streambuf
对象的指针,
用
get()
函数直接把数据写到另一个对象的
streambuf
中。如:
streambuf& sb=*cout.rdbuf(); while(!in.get(sb).eof())
流缓冲区没有拷贝构造函数,所以它不可以被复制。
6. 在输入输出流中定位
有两种定位方法:
1)
用流指针
streampos
绝对定位。可以对
ostream
用
tellp()
或对
istream
用
tellkg()
来获得当前流指针的确切位置,然后改变这个位置值,再用
seekp(
新位置
)
或
seekg(
新位置
)
重定位;
2)
用重载的
seekp()
或
seekg()
相对定位。让流指针从当前位置前移或后移。
Seekp(int , ios::beg/ios::cur/ios::end)
用不同的两种方法定义一个既可以读又可以写的文件流对象:
(a)
用
fstream,
创建流的时候要注明它是即可读又可写的标记
;
(b)
用
ifstream
(创建流的时候要注明它是即可读又可写的标记)和文件流缓冲
7. 字符串输入输出流
字符串输入输出流
(iostirngstream)
是对内存而不是对文件和标准输出设备进行操作。可以通过一个字符串流对字符串读写操作。
要创建一个字符串流,并从其中读取字符串,就创建一个
istringstream
;要往其中写入字符串,就创建一个
ostringstream
。
对字符串流操作需要包含
<sstream>
l
输入字符串流(
istringstream
)
创建一个读字符串流对象并用字符串初始化
可以用
字符串流对象
<<var
来提取字符串中的数据,它是依赖数据类型进行摘录,而不是依空格为首选界定符界定符,这种方法来提取一个小数中的整数位和小数位很有效。
epsilon()7/21/2010 1:52 PM
。
一般来说,如果已经知道一个数据流的顺序并把它转化成除字符串之外的其它数据类型时,最好使用输入输出流提取符。如果要想一次提取一个串中剩余的部分并把它输出到另一个输出流中,可以用
rdbuf()
成员函数
,
它会连同第一个空格一起提取。对于用流提取符提取一个字符串,它只能提取当前开始下一个空格之前的字符串。
l
输出字符串流(
ostringstream
)
为了创建输出字符串流,可以构造
ostringstream
对象,这个类对象可以动态管理字符缓冲区,缓冲区里存放要插入任何字符串,可以用成员函数
str()
将输出的字符串格式化。每次调用
str()
将返回一个新的
string
对象,而不会破坏
stringbuf
里的字符串流对象。
8. 输出流的格式化
有两种方法:用成员函数和运算符
使用成员函数时用格式化标志来控制格式化
l
格式化标志
用成员函数
ios::flag()
得到格式化标志所代表的值(
fmtflags
对象)
fmtflags ios::flags(fmtflags newflags);
//
强制改变程序所需要的所有标志
fmtflags ios::setf(fmtflags ored_flag); //
设置打开标志
fmtflags ios::unsetf(fmtflags clear_flag); //
设置关闭标志
fmtflags ios::setf(fmtflags bits, fmtflags field); //
先清除
field
中的所有值,再设置
bit
标志
开关标志
|
作用
|
ios::skipws
|
Skip white space. (For input; this is the default.)
|
ios::showbase
|
Indicate the numeric base (as set, for example, by dec, oct, or hex) when printing an integral value. Input streams also recognize the base prefix when showbase is on.
|
ios::showpoint
|
Show decimal point and trailing zeros for floating-point values.
|
ios::uppercase
|
Display uppercase A-F for hexadecimal values and E for scientific values.
|
ios::showpos
|
Show plus sign (+) for positive values.
|
ios::unitbuf
|
单元缓冲区,每次插入后刷新流。
“Unit buffering.” The stream is flushed after each insertion.
|
用
ios::unitbuf
,每次插入数据后都会立即刷新流,譬如:用于跟踪调试的日志文件很方便。
ofstream out(“log.txt”);
out.setf(ios::unitbuf);
out<<”one”<<endl;
l
格式化域
ios::basefield
|
Effect
|
ios::dec
|
Format integral values in base 10 (decimal) (the default radix—no prefix is visible).
|
ios::hex
|
Format integral values in base 16 (hexadecimal).
|
ios::oct
|
Format integral values in base 8 (octal).
|
ios::floatfield
|
Effect
|
ios::scientific
|
Display floating-point numbers in scientific format. Precision field indicates number of digits after the decimal point.
|
ios::fixed
|
Display floating-point numbers in fixed format. Precision field indicates number of digits after the decimal point.
|
“automatic” (Neither bit is set.)
|
Precision field indicates the total number of significant digits.
|
ios::adjustfield
|
Effect
|
ios::left
|
Left-align values; pad on the right with the fill character.
|
ios::right
|
Right-align values. Pad on the left with the fill character. This is the default alignment.
|
ios::internal
|
Add fill characters after any leading sign or base indicator, but before the value. (In other words, the sign, if printed, is left-justified while the number is right-justified.)
|
l
宽度、填充和精度设置
Function
|
Effect
|
int ios::width( )
|
Returns the current width. Default is 0. Used for both insertion and extraction.
|
int ios::width(int n)
|
Sets the width, returns the previous width.
|
int ios::fill( )
|
Returns the current fill character. Default is space.
|
int ios::fill(int n)
|
Sets the fill character, returns the previous fill character.
|
int ios::precision( )
|
Returns current floating-point precision. Default is 6.
|
int ios::precision(int n)
|
Sets floating-point precision, returns previous precision. See ios::floatfield table for the meaning of “precision.”
|
要想得到相同的宽度,每次在插入或提取之后都要调用
width()
。
9. 操作算子
操作算子是改变流的状态而不是改变数据
在大量使用流格式化时,使用操作算子替代使用流成员函数可以简化代码。
可以在表达式中插入操作算子
l
不带参数的操作算子
针对提取操作的操作算子
ws
可以吃掉空格
(cin>>ws)
操作算子
|
作用
|
showbase
noshowbase |
Indicate the numeric base (dec, oct, or hex) when printing an integral value.
|
showpos
noshowpos |
Show plus sign (+) for positive values.
|
uppercase
nouppercase |
Display uppercase A-F for hexadecimal values, and display E for scientific values.
|
showpoint
noshowpoint |
Show decimal point and trailing zeros for floating-point values.
|
skipws
noskipws |
Skip white space on input.
|
left
right internal |
Left-align, pad on right.
Right-align, pad on left. Fill between leading sign or base indicator and value. |
scientific
fixed |
指出浮点数的优先输出格式
(
科学计数法
vs.
定点小数
)
|
l
带参数的操作算子(6个)
Manipulator
|
effect
|
setiosflags(fmtflags n)
|
Equivalent to a call to setf(n). The setting remains in effect until the next change, such as ios::setf( ).
|
resetiosflags(fmtflags n)
|
Clears only the format flags specified by n. The setting remains in effect until the next change, such as ios::unsetf( ).
|
setbase(base n)
|
Changes base to n, where n is 10, 8, or 16. (Anything else results in 0.) If n is zero, output is base 10, but input uses the C conventions: 10 is 10, 010 is 8, and 0xf is 15. You might as well use dec, oct, and hex for output.
|
setfill(char n)
|
Changes the fill character to n, such as ios::fill( ).
|
setprecision(int n)
|
Changes the precision to n, such as ios::precision( ).
|
setw(int n)
|
Changes the field width to n, such as ios::width( ).
|
l
效用算子:
属于操作算子,是独立于机器实现的用户自定义的操作算子。一个效用算子是一个独立的类,它的构造函数可以格式化一个串
10. 输入输出流程序举例
11. 国际化
补充:函数
c_str()
的用法:
string::c_str()函数返回一个char*指针,作用就是把string类型的变量转化为C字符串
语法:
const char *c_str();
c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同.
这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式。
注意:一定要使用strcpy()函数 等来操作方法c_str()返回的指针
比如:最好不要这样: char* c;
string s="1234";
c = s.c_str(); //c最后指向的内容是垃圾,因为s对象被析构,其内容被处理
应该这样用: char c[20];
string s="1234";
strcpy(c,s.c_str());
这样才不会出错,c_str()返回的是一个临时指针,不能对其进行操作
再举个例子
c_str() 以 char* 形式传回 string 内含字符串
如果一个函数要求char*参数,可以使用c_str()方法:
string s = "Hello World!";
printf("%s", s.c_str()); //输出 "Hello World!"