[转载]高手戏玩c++(2)

简单常识——关于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::adjustfieldleft|right|internal
ios_base::basefielddec|oct|hex
ios_base::floatfieldscientific|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<iostream>
#include<fstream>
usingnamespacestd;
intmain()
{
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一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值