分享

输入输出流-Thinking in C 笔记

 gold 2008-08-12
Thinking in C++ 2 笔记 2
作者:lester 2008-01-11 18:07:12
标签:
 

标准C++(98版本) -- 预计2008年出的第二版标准,可能会使得下面一些东西发生变化,比如string[]也可以抛出异常了。

=======================================================================
                           
二、输入输出流
=======================================================================
1.
自定义的 插入符 一般采用友元函数。
2.
接受用户格式化输入的一般模式是:
先把用户的输入缓冲到一个地儿,然后扫描并检查之,确认格式无误后,再用istream对象对这块内存进行格式化输入。
不要用老方法直接用cin得到用户输入。


(
)格式化函数:
1.
格式化的操作符:
<<
>>
默认读到空格,或者不再是该类型为止。
“间隔提取”代码示例:
std::istream& operator>>(std::istream& is, const char *s) {
    if (s == NULL) return is;

    std::string str(s);
    return is.ignore(str.length());
}
2.
格式化操作算子:
算子的作用:暂时改变流的状态!!,直到你再改为其他的。
有一组函数操作等于操作算子,但用起来麻烦,我没看。
(1)#include <iostream>:
不带参数的操作算子。
例子:cout << left << ... 将流的每次输出改为左对齐。
     cout << hex << ...
16进制 (oct 八进制,dec 十进制)
(2)#include <iomanip>:
带参数的操作算子。
例子:
     cout << setw(4) << ...
将每次输出的宽度(称为域宽)设为4,意思是:可以超过4,但不足的话,将以填充字符填满4个。
     cout << setfill(‘0‘) << ...
设置和setw()配套的填充字符。
    
比如:怎样输出09-08-2007 ? 因为不确定月份和日期是1位还是2位,所以,这里的技巧是:设定域宽,并用填充字符!!

(
)非格式化函数:
1.
按行输入:
应该用到三个中的一个:
(1) std::getline(istream& i, string& s)
:这个是首选,原因是读到s中,就不用关心分配的内存不够的问题。
(2)
成员getline(char *buf, int bufsize, int 行结尾符=‘\n‘) : 这个参数意义很明确了,因为读到buf中,所以会在末尾加0
以上两个getline都会读取并抛弃行结尾符,所以下一次getline会读下一行。
(3)
成员get(char *buf, int bufsize, int 行结尾符=‘\n‘) : 这个参数和getline一样,不同的是 它不去读取并抛弃行结尾符!所以调用一次之后,无论你调用多少此get,它都还在第一行的行结尾符之前。
NOTICE:
C库的风格一样,上面(2)(3)中的bufsize,将限制最大获取长度为bufsize-1,最后一个字节总会被置0的!!

2.
读原始字节:
应该用到两个中的一个:
(1)
成员int get(): 这个是重载版本的get,它一个字节一个字节读取,包括换行符号和EOF,等效于c库的fgetc()
(2)
成员 read(char *buf, int read_howmany_bytes):等效于c库的read,好处一样,就是可以一次读取多个字节,而get只有一个。

注意:按行输出和输出原始字节,和上面类似了。


(
)判断流状态:
1.
必须判断本次流操作是否成功:———— 直接将流对象用于bool表达式
原理:大部分流的操作,都是返回*this,即流对象本身。而流出现在bool表达式里,则会被自动类型转换为其good()的值。而good()的意义如2所述。
(1)
判断“打开”及“打开模式”是否正常:
ifstream is("sth.txt");
if(!is) cout << "open error!" << endl;
(2)
判断文件尾:
while(myStream.getline(dest, 100)) {
    cout << dest;
}
(3)
判断seek的结果是否正常:
if(!seek(-5, ios::end)) cout << "seek error!" << endl;

2.
少数情况下,我们需要详细了解流的状态,那么有四个操作可以入手:
good()
:当下面三个状态都正常的时候,才为true
eof()
:流到文件尾,设置为false

fail()
eof() || bad() || 流遇到了非致命性错误(如格式化输入时遇到不合要求的类型,流可以继续使用)
bad()
:流遇到了致命性错误,无法再使用。多半是物理上的。
3. clear()

NOTICE
!!: 上诉3个状态—— eof,fail,bad,一旦设置,会导致很多I/O操作失效!!比如你会发现fstream读到EOF后,你再试图去写,但是无效,这不是因为你忘了用seekp重定位,因为你确实重定位了啊。或者你发现seekp失败后,再写怎么不行啊。这时候,你应该先手动清除: myStream.clear()。因此:必须判断本次I/O操作是否成功(除非你肯定一定成功),若不成功,一定要调用clear清除错误状态,再进行下一步操作。

(
)设置流应抛出的异常:
建议设置:myStream.exceptions(ios::badbit)
这样在badbit被设置(一般是物理错误)时抛出异常。但是ios::failbit, ios::eofbit还是不要抛出异常的好。

(
)写回文件和关闭文件:
如果你仅仅想将c++缓冲区的东西输出到内核,那么请用如 fcout << flush;
如果你不仅仅想这样做,还想关闭文件,那直接关闭就可以了 fcout.close()。(或等着对象到出了作用范围)


#include <fstream>
ifstream ofstream fstream
(
)如何“打开”多个文件 以及 如何“关闭”文件:
如果多个文件是先后打开,那么用一个流对象就可以了。用完一个,myStream.close(),然后再myStream.open("新文件")
而关闭文件,除非你要打开新文件,否则是不必要显示的close的,因为stream对象会在析构函数里面关闭。但是,如果close()函数可能抛出异常的话(不确定),那么它其实是提供一个途径然用户显示调用close来捕获可能的异常,见effective c++
(
)打开模式:
1. iso::binary
首先声明:这个选项是专为windows系统设计的,以下所有效果不存在于linux下,因为linux不区分二进制和文本文件,统一看成二进制,不做任何处理的给用户,或是写入文件。
针对windows的换行为“回车+换行“(\r\n)而设计,效果为:
- 如果以ios::binary方式打开:就是最自然的处理方式,文件的所有字节都展现在你面前,包括\r
- 如果以默认方式打开(即所谓ASCII模式):
(1)
“读”:所有的\r\n或者\r都被先转换为了\n。你可以用get, read等任何读的函数,以及seekg设定游标位置,你会很正常的使用这些函数在似乎完全没有\r的世界里翱翔。
(2)
“写”:就不像‘读’时那么正常了:
    a.
任何写的函数(put, write),只要写出的内容包含‘\n‘,则实际上会被自动转换为‘\r\n‘输出。
    b.
如果你用seekp设定一个写出的位置,那将是包含\r情况下的绝对位置。瞧,和(1)多么不一致!这点一定要小心!!!!

NOTICE:
但不管是以上哪种方式打开,tellg()tellp()返回的永远是包含\r情况下的绝对位置。因此你若用默认方式,用get()读,并用tellg()打印流的当前位置,你会发现有了一个跳跃。

***
建议 ***: 考虑到移植性,建议用ios::binary,因为linux可能操作windows的文件,windows也可能操作linux的文件。如果你用默认文本方式,则linuxwindows打开一个含\r的文件的话,则在linux上和windows上看到的是不一样的内容了,则代码很可能就不同了。
如果你用的是C库,请在模式后加个b,表示binary。如:FILE *fp = fopen("rr.txt", "r+b"); 否则C库也默认是文本方式。

2. ios::trunc
NOTICE
!!:当使用trunc的时候,不要用ios::appios::ate,不然会有不可预知的效果。

3. ios::app
ios::ate
两个都是打开文件时把指针定位到文件尾——即: EOF处!,但是不同的是,ios::app只能和ios::out连用(或者当前流是ofstream),确保永远只在文件尾添加,不管你用seek到哪儿。但若你用fstream,有读的能力的话,你用seek可以影响读的位置。

**************
标志对 fstream, ifstreamofstream 的效果 ****************
4.
对于fstream:所有的标志必须指明,否则效果相反!
NOTICE
!!: 从输出切换到输入,或者从输入切换到输出,必须用seekgseekp重定位指针到你要操作的地儿,要不然操作完全无效。对于fstream来讲,并不是有两个指针,而是同样只有一个指针,所以seekgseekp效果一样,都是对一个指针操作。
(1)
要读或要写,必须写明ios::inios::out
(2)
默认不会截断的,否则必须指明截断ios::trunc
(3)
默认不会到末尾的,否则必须ios::appios::ate

5.
对于ofstream:这个和fstream一样,啥标志都能用!当它和 ios::in 连用时表示不截断,并把指针定位在文件头,否则默认是截断的。
不要妄想那它来输入,因为ofstream没有输入函数。

6.
对于ifstream:请只选用与两个标志,in能沾边的:ios::ateios::binary
你当然可以选其他的,但可能在打开文件时就会得到一个错误流,或者在操作时得到错误流。
并且不要妄想那它来输出,因为ifstream没有输出函数。


****************
打开模式 特别: “一个互斥,一个联合” ******************
ios::trunc
请 和 ios::app/ate 互斥使用
ios::app
请一定和ios::out联合使用

(
)流定位:
tellg, seekg: seekg(offset, direction),
注意就是ios::end,是代表的EOF,所以(0, ios::end)返回的是EOF
tellp, seekp:
同上。

(
)高效完成流的复制:
如果你问:”流的复制有啥用啊?“, 那是因为你没考虑到文件内容在“文件”,“字符串”,和“终端” 这三种文件间怎样能高效转移。
传统的C做法,只能“一边提取一边写入”,但是C++提供了更方便的做法,来将文件内容从一个输入流导向一个输出流:
    ifstream in("rr.txt");
    ofstream out("rrw.txt");
    out << in.rdbuf(); //
如果out换成cout,那么可以非常方便的往屏幕上打印输出。
就行了。关键是:每个流对象都有其buffer,用rdbuf()可提取,并可作为输出流的参数。
注意的是:从rdbuf中能得到的文件内容为当前指针位置之后的,而调用一次rdbuf后,指针便到了末尾。

(
)ungetc()C++做法:
有时,我们只想看看输入流的下一个是什么,可以用get()得到后,再putback()= ungetc()
或者用peek(),它不从流中提取,只是复制,不影响下一次get()

(
)字节序:读写“字节流”时要注意的:
a.
概念:
Little-Endian
Big-Endian,是说系统和与他配套的C/C++,怎样在内存中放置一个内置类型;和怎么将一段内存看成内置类型。
首先明白,对于这样写:0x12345678, 左边都叫做MSB,右边的都叫做LSB。既然是MSB,就是说,当这样写给你看时,左边的是权值更高的位。
再看看内存:一般变量放栈里面,我们看看栈:当有一个buf指针时,在任何当今的机器上,都是这样放置的:

栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

在上诉两点的基础上,看看Little-EndianBig-Endian

Little-Endian: Intel
平台上的系统一般采用之 —— LSB放低地址
栈底 (高地址)
---------------
buf[3] (0x12) --
高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) --
低位
---------------
栈顶 (低地址)

Big-Endian: SPARK
等平台上的系统和Java这种“中间件” —— MSB放低地址
栈底 (高地址)
---------------
buf[3] (0x78) --
低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) --
高位
---------------
栈顶 (低地址)

然后再回头看看这句话,就明白概念了:
   
“怎样在内存中放置一个内置类型;和怎么将一段内存看成内置类型。”
再明确指出一点:这种放置相关性,只针对一个类型。而其他的任何只关心字节的函数,在任何平台上,比如从文件读取字节,是不会在意这点的————仍然是先读来的就放低地址!!!!
举个例子:比如上面这两种放置情况,你想把buf写入文件,你按照[0]-[3]顺序写入,那么在不同的平台上得到的文件,文件中从左到右,出现的字节序是不同的!!!!然后你再交换两个平台的文件,让他们各自读入,一定会和原先的整数字节序相反了!

b.
应用:
实际中,TCP/IP网络中为了规范,统一规定,凡是传入网络的字节流,一律按照Big-Endian。这样,一般在PC上的程序,比如,要想写出一个整数到待发送的缓冲区,就得用下面的来转换一下字节序:
htonl(), and htons()
。而PC接收时,也要用ntohl(), ntohs()转换一下。

同样的,如果你希望一个二进制文件比较通用,也可以规定,文件中统一采用Big-Endian,这样同理,PC机要做和上诉一样的转换。

另外要注意的是:JavaBig-Endian的!所以

(
)比特序:读写“比特流”时要注意的:
a.
既然,表示一个数的多个字节有字节序,那么对于一个字节内部的8个比特,也有比特序。———— 但是,我们一般从来不考虑这个。而是认为:经过网络传过来,或者得到一个别的机器上的文件,我们认为:当用任意函数,取得一个字节,则字节内部的比特序和原先的机器上看到的相同。
这点,主要是为了一些读写比特的程序。我做matlab那个程序时,就是认为:
一个字节是:0xABCDEFGH, 则写到文件中,就是说打开来看,如果也能看到比特流的话,从左到右,仍然是0xABCDEFGH. 所以,我写一个字节时,都是把先来的bit放最高位,后来的bit次高位,凑满一个字节后,写入文件。而不管从什么机器读出来,比特序和这个相同:所以,对于这一点,用这个模型吧,会舒服一点:

舒服的模型:
下面是一个字符串,左边是str[0],挨着是str[1]...
|10100011|00110101|.....
则写到文件中,“看起来”也一样!

b. windows
下的文件写入和读取:
当你打算对比特进行分析的时候,千万别用文本模式!!!
写出时:因为你不知道你的位,也许就凑了个\n,然后windows会自动在\n前加上\r
读入时:也不要用文本模式
因为windows会自动把\n前的\r去掉。

这个就是“文本模式”的全部。

(
)看了()(),这里看看64位机器和32位机器的互通性:
目前,最一般的情况是:64位机的longpointer变为了8个字节,time_t等系统定义的一些类型也变为了8位。而其他的维持4个字节。这样的话,当你试图以二进制形式(就是内存里的东西直接传,不格式化)传输时,或写入一个文件,那么在对方的机器上,如果“位数”不符合,当然会发生少读(32位机读64位机产生的数据)或多读(64位机读32位机产生的数据)现象。因此,这种东西,最好找一样大小的类型作为传送对象。比如int, short, char(不过,也不是绝对的,如int,在一些64位平台上,可能也是8个字节)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多