简单常识——关于stream
从文件中读入一行
简单,这样就行了:
ifstreamifs("input.txt");
charbuf[1000];
ifs.getline(buf,sizeofbuf);
stringinput(buf);
当然,这样没有错,但是包含不必要的繁琐和拷贝,况且,如果一行超过1000个字符,就必须用一个循环和更麻烦的缓冲管理。下面这样岂不是更简单?
stringinput;
input.reserve(1000);
ifstreamifs("input.txt");
getline(ifs,input);
不仅简单,而且安全,因为全局函数getline会帮你处理缓冲区用完之类的麻烦,如果你不希望空间分配发生的太频繁,只需要多reserve一点空间。
这就是“简单常识”的含义,很多东西已经在那里,只是我一直没去用。
---------------------------------------------------------------------------
一次把整个文件读入一个string
我希望你的答案不要是这样:
stringinput;
while(!ifs.eof())
{
stringline;
getline(ifs,line);
input.append(line).append(1,'/n');
}
当然了,没有错,它能工作,但是下面的办法是不是更加符合C++的精神呢?
stringinput(
istreambuf_iterator<char>(instream.rdbuf()),
istreambuf_iterator<char>()
);
同样,事先分配空间对于性能可能有潜在的好处:
stringinput;
input.reserve(10000);
input.assign(
istreambuf_iterator<char>(ifs.rdbuf()),
istreambuf_iterator<char>()
);
很简单,不是么?但是这些却是我们经常忽略的事实。
补充一下,这样干是有问题的:
stringinput;
input.assign(
istream_iterator<char>(ifs),
istream_iterator<char>()
);
因为它会忽略所有的分隔符,你会得到一个纯“字符”的字符串。最后,如果你只是想把一个文件的内容读到另一个流,那没有比这更快的了:
fstreamfs("temp.txt");
cout<<fs.rdbuf();
因此,如果你要手工copy文件,这是最好的(如果不用操作系统的API):
ifstreamifs("in.txt");
ofstreamofs("out.txt");
ofs<<in.rdbuf();
-------------------------------------------------------------------------
open一个文件的那些选项
ios::inOpenfileforreading
ios::outOpenfileforwriting
ios::ateInitialposition:endoffile
ios::appEveryoutputisappendedattheendoffile
ios::truncIfthefilealreadyexisteditiserased
ios::binaryBinarymode
-------------------------------------------------------------------------
还有ios的那些flag
flag effectifset ios_base::boolalpha input/outputboolobjectsasalphabeticnames(true,false). ios_base::dec input/outputintegerindecimalbaseformat. ios_base::fixed outputfloatingpointvaluesinfixed-pointnotation. ios_base::hex input/outputintegerinhexadecimalbaseformat. ios_base::internal theoutputisfilledataninternalpointenlargingtheoutputuptothefieldwidth. ios_base::left theoutputisfilledattheendenlargingtheoutputuptothefieldwidth. ios_base::oct input/outputintegerinoctalbaseformat. ios_base::right theoutputisfilledatthebeginningenlargingtheoutputuptothefieldwidth. ios_base::scientific outputfloating-pointvaluesinscientificnotation. ios_base::showbase outputintegervaluesprecededbythenumericbase. ios_base::showpoint outputfloating-pointvaluesincludingalwaysthedecimalpoint. ios_base::showpos outputnon-negativenumericprecededbyaplussign(+). ios_base::skipws skipleadingwhitespacesoncertaininputoperations. ios_base::unitbuf flushoutputaftereachinsertingoperation. ios_base::uppercase outputuppercaselettersreplacingcertainlowercaseletters.
Therearealsodefinedthreeotherconstantsthatcanbeusedasmasks:
constant value ios_base::adjustfield left|right|internal ios_base::basefield dec|oct|hex ios_base::floatfield scientific|fixed
--------------------------------------------------------------------------
用我想要的分隔符来解析一个字符串,以及从流中读取数据
这曾经是一个需要不少麻烦的话题,由于其常用而显得尤其麻烦,但是其实getline可以做得不错:
getline(cin,s,';');
while(s!="quit")
{
cout<<s<<endl;
getline(cin,s,';');
}
简单吧?不过注意,由于这个时候getline只把;作为分隔符,所以你需要用;quit;来结束输入,否则getline会把前后的空格和回车都读入s,当然,这个问题可以在代码里面解决。
同样,对于简单的字符串解析,我们是不大需要动用什么Tokenizer之类的东西了:
#include<iostream>
#include<sstream>
#include<string>
usingnamespacestd;
intmain()
{
strings("hello,world,thisisasentence;andaword,end.");
stringstreamss(s);
for(;;)
{
stringtoken;
getline(ss,token,',');
if(ss.fail())break;
cout<<token<<endl;
}
}
输出:
hello
world
thisisasentence;andaword
end.
很漂亮不是么?不过这么干的缺陷在于,只有一个字符可以作为分隔符。
--------------------------------------------------------------------------
把原本输出到屏幕的东西输出到文件,不用到处去把cout改成fs
#include<fstream>
{
ofstreamoutf("out.txt");
streambuf*strm_buf=cout.rdbuf();
cout.rdbuf(outf.rdbuf());
cout<<"writesomethingtofile"<<endl;
cout.rdbuf(strm_buf);//recover
cout<<"displaysomethingonscreen"<<endl;
system("PAUSE");
return0;
}
输出到屏幕的是:
displaysomethingonscreen
输出到文件的是:
writesomethingtofile
也就是说,只要改变ostream的rdbuf,就可以重定向了,但是这招对fstream和stringstream都没用。
--------------------------------------------------------------------------
关于istream_iterator和ostream_iterator
经典的ostream_iterator例子,就是用copy来输出:
#include<iostream>
#include<fstream>
#include<sstream>
#include<algorithm>
#include<vector>
#include<iterator>
usingnamespacestd;
intmain()
{
vector<int>vect;
for(inti=1;i<=9;++i)
vect.push_back(i);
copy(vect.begin(),vect.end(),
ostream_iterator<int>(cout,"")
);
cout<<endl;
ostream_iterator<double>os_iter(cout,"~");
*os_iter=1.0;
os_iter++;
*os_iter=2.0;
*os_iter=3.0;
}
输出:
123456789
1~2~3~
很明显,ostream_iterator的作用就是允许对stream做iterator的操作,从而让算法可以施加于stream之上,这也是STL的精华。与前面的“读取文件”相结合,我们得到了显示一个文件最方便的办法:
copy(istreambuf_iterator<char>(ifs.rdbuf()),
istreambuf_iterator<char>(),
ostreambuf_iterator<char>(cout)
);
同样,如果你用下面的语句,得到的会是没有分隔符的输出:
copy(istream_iterator<char>(ifs),
istream_iterator<char>(),
ostream_iterator<char>(cout)
);
那多半不是你要的结果。如果你硬是想用istream_iterator而不是istreambuf_iterator呢?还是有办法:
copy(istream_iterator<char>(ifs>>noskipws),
istream_iterator<char>(),
ostream_iterator<char>(cout)
);
但是这样不是推荐方法,它的效率比第一种低不少。
如果一个文件temp.txt的内容是下面这样,那么我的这个从文件中把数据读入vector的方法应该会让你印象深刻。
12345234567
8910
程序:
#include<iostream>
#include<fstream>
#include<algorithm>
#include<vector>
#include<iterator>
usingnamespacestd;
intmain()
{
ifstreamifs("temp.txt");
vector<int>vect;
vect.assign(istream_iterator<int>(ifs),
istream_iterator<int>()
);
copy(vect.begin(),vect.end(),ostream_iterator<int>(cout,""));
}
输出:
123452345678910
很酷不是么?判断文件结束、移动文件指针之类的苦工都有istream_iterator代劳了。
-----------------------------------------------------------------------
其它算法配合iterator
计算文件行数:
intline_count=
count(istreambuf_iterator<char>(ifs.rdbuf()),
istreambuf_iterator<char>(),
'/n');
当然确切地说,这是在计算文件中回车符的数量,同理,你也可以计算文件中任何字符的数量,或者某个token的数量:
inttoken_count=
count(istream_iterator<string>(ifs),
istream_iterator<string>(),
"#include");
注意上面计算的是“#include”作为一个token的数量,如果它和其他的字符连起来,是不算数的。
------------------------------------------------------------------------
Manipulator
Manipulator是什么?简单的说,就是一个接受一个stream作为参数,并且返回一个stream的函数,比如上面的unskipws,它的定义是这样的:
inlineios_base&
noskipws(ios_base&__base)
{
__base.unsetf(ios_base::skipws);
return__base;
}
这里它用了更通用的ios_base。知道了这一点,你大概不会对自己写一个manipulator有什么恐惧感了,下面这个无聊的manipulator会忽略stream遇到第一个分号之前所有的输入(包括那个分号):
template<classcharT,classtraits>
inlinestd::basic_istream<charT,traits>&
ignoreToSemicolon(std::basic_istream<charT,traits>&s)
{
s.ignore(std::numeric_limits<int>::max(),s.widen(';'));
returns;
}
不过注意,它不会忽略以后的分号,因为ignore只执行了一次。更通用一点,manipulator也可以接受参数的,下面这个就是 ignoreToSemicolon的通用版本,它接受一个参数,stream会忽略遇到第一个该参数之前的所有输入,写起来稍微麻烦一点:
structIgnoreTo{
charignoreTo;
IgnoreTo(charc):ignoreTo(c)
{}
};
std::istream&operator>>(std::istream&s,constIgnoreTo&manip)
{
s.ignore(std::numeric_limits<int>::max(),s.widen(manip.ignoreTo));
returns;
}
但是用法差不多:
copy(istream_iterator<char>(ifs>>noskipws>>IgnoreTo(';')),
istream_iterator<char>(),
ostream_iterator<char>(cout)
);
其效果跟IgnoreToSemicolon一样。