您的位置:首页 > 编程语言 > C语言/C++

C++编程思想杂记(②4章 输入输出流)

2015-03-09 20:36 253 查看
为什么使用输入输出流而不使用C的文件I/O函数?

第一,用户可以直接操作FILE指针,并且需要调用close函数来关闭文件,使用输入输出流可以安全的打开文件并且不依赖用户调用close来关闭它。

第二,C中的可变参数列表函数(printf)中包含运行时解释程序,运行时解释程序是一段代码在运行时可以解析字符串。但是即使要使用解释程序的一部分功能,解释程序的所有部分都会被加载到可执行程序中,并且解释发生在运行时,这就无法免除运行时开销,也不能在编译时进行错误检查。最后,类似printf的函数不具备扩展性,只支持内置类型,如果想通过重载,重载函数的参数列表必须不同而对于printf函数来说,它的类型信息隐藏在可变参数列表和格式串中。

C++提供了如下几种类用于输入输出。用于文件输入输出的ifstream,ofsream,fstream。用于标准C++输入输出的istringstream,ostringstream,stringstream。这些流其实是模板的特化。

按行输入,三种方法流变量成员函数get(),成员函数getline(),定义在<string>中的getline()函数。

其中get()有3个重载版本

#include <iostream>

using namespace std;

int main()

{

char buf[100] = {0};

char c;



char *p = &c;

cin.get(); //读取一个输入

cin.get(c); //读取一个输入到c

cin.get(buf,2,'\n'); //读取直到遇到'\n',这里需要注意的是,第二个参数表示的是给予存储的空间大小,流成员函数get函数和getline函数都会在结果缓冲区末尾

//存储一个零,所以如果第二个参数是1,就不会读取数据到buf

cin.get(buf,2,'\n'); //get()不会从输入流中提取界定符,所以如果再次调用get()还会遇到同一个界定符

cout<<buf;

cout<<*p;

return 0;

}

针对get()不会从输入流中提取界定符,所以如果再次调用get()还会遇到同一个界定符

#include <iostream>

using namespace std;

int main()

{

char buf[100] = {0};

char c;



char *p = &c;

cin.get();

cin.get(c);

cin.get(buf,5,'c');

cin.get(buf,5,'c');

cout<<buf;

cout<<*p;

return 0;

}

假设输入是1234c12

在第一次get语句后,buf中为3,4,第二次get是没有读取的!这使得会在buf中存储一个零,replace了原先3的位置,在cout输出时就没有任何输出了

如果是getline

#include <iostream>

using namespace std;

int main()

{

char buf[100] = {0};

char c;

cin.getline(buf,5,'c');

cin.getline(buf,5,'c');

cout<<buf;

return 0;

}

输入是1234c12c

这样第一个getline之后,buf中是1234,第二次getline和get不同,是有读取的,使得buf变为12'\0'4,在cout时,输出为12

getline还有一个重载版本

#include <iostream>

using namespace std;

int main()

{

char buf[100] = {0};

char c;

cin.getline(buf,5);

cin.getline(buf,5,'c');

cout<<buf;

return 0;

}

这里第一个getline最多读取4个数据,加上一个'\0',如果输入是12345,则buf中只有1234。

如果第一个getline能读取的数据多于4个,则第二次getline不会读取任何数据,即结果变为‘\0'234,cout输出为空

如果输入是1234,回车2345,则最后buf的结果是2345。

对于定义在string中的getline

有两个重载版本

#include <iostream>

#include <string>

using namespace std;

int main()

{

string abc;

std::getline(cin,abc,'c');



cout<<abc;

return 0;

}

第二种就是多了一个结束符

有趣的地方是string类型变量创建的时候,它的capacity就被设置成了15,当字符串长度超过15的时候,就会扩大容量到31,但是之后的变动我搞不懂,先变成47,然后是70,然后是105

顺带一提,vector的初始size和capacity都是1,随后在DevC++中以1,2,4,8.....2^n方式增长,而在VS中按照1,2,3,4,6,,9,13,19......这种大约是1.5倍



类ios_base自类ios派生而来,定义了4个标志位来测试流的状态,goodbit,failbit(I/O失败,非法数据,输入结束),eofbit(输入结束,指人为Ctrl+Z),badbit(物理致命错误,流不能再使用)

流的good函数返回为true标志goodbit位设置,正常,如果failbit/badbit被设置,则流的fail函数返回true,badbit设置,流的bad函数返回true。

清理标志位使用的是clear函数(failbit&&badbit)

operator>>即提取符,返回的是它的流参数。

ifstream定义如下:typefdef basic_ifstream<char> ifstream

打开文件模式选择:

ios::in
ios::out
ios::app打开一个仅用于追加的输入文件
ios::ate打开一个已存在的文件,文件指针在末尾
ios::trunc默认打开方式,截断旧文件
输入输出流的字符的传递复制。使用了streambuf类,每一个输入输出流对象都包含streambuf类型一个指针。同时提供一个rdbuf函数来访问streambuf对象,该对象与流对象通过<<连接就可以实现两个流的字符传递复制。

#include <iostream>

#include <string>

#include <fstream>

using namespace std;

int main()

{

fstream fs("test2.cpp");

cout<<fs.rdbuf();

return 0;

}

这样test2.cpp的全部内容都会被复制到cout然后输出到标准输出,流缓冲区streambuf不能被复制,因为没有拷贝构造函数。

就是说:

streambuf fs = *fs.rdbuf(); NO

如果想从流中读取,还可以使用read,参数是目的地址和读取字节数目(char)。

这里提到streambuf类,这个类的引用还可以用于之前提到的get函数的重载形式

_Myt& __CLR_OR_THIS_CALL get(_Mysb& _Strbuf)和

_Myt& __CLR_OR_THIS_CALL get(_Mysb& _Strbuf, _Elem _Delim)

两者的区别是,第一种的默认结束符就是'\n'

这个_CLR_OR_THIS_CALL 是一个空的宏定义,只是作为一个标识。

这个重载形式接收一个_Mysb类型引用,返回一个_Myt类型引用。

这个_Myt类型即basic_istream<_Elem, _Traits>即isfream。_Mysb类型即basic_streambuf<_Elem, _Traits>即streambuf。

下面来看这个程序:

#include <iostream>

#include <string>

#include <fstream>

#include <vector>

using namespace std;

int main()

{

fstream fs("test2.cpp");

streambuf &buf =*cout.rdbuf();

while(!fs.get(buf).eof())

{

if(fs.fail())

fs.clear();

cout<<char(fs.get());

}



return 0;

}

这个函数可以将test2.cpp的全部内容发送到cout标准输出,注意这里的get函数就是前面提到的参数为streambuf引用的重载。

_Myt& __CLR_OR_THIS_CALL get(_Mysb& _Strbuf, _Elem _Delim)

{ // extract up to delimiter and insert into stream buffer

ios_base::iostate _State = ios_base::goodbit;

_Chcount = 0;

const sentry _Ok(*this, true);

if (_Ok)

{ // state okay, use facet to extract

_TRY_IO_BEGIN

int_type _Meta = _Myios::rdbuf()->sgetc();

for (; ; _Meta = _Myios::rdbuf()->snextc())

if (_Traits::eq_int_type(_Traits::eof(), _Meta))

{ // end of file, quit

_State |= ios_base::eofbit;

break;

}

else

{ // got a character, insert it into stream buffer

_TRY_BEGIN

_Elem _Ch = _Traits::to_char_type(_Meta);

if (_Ch == _Delim

|| _Traits::eq_int_type(_Traits::eof(),

_Strbuf.sputc(_Ch)))

break;

_CATCH_ALL

break;

_CATCH_END

++_Chcount;

}

_CATCH_IO_END

}

if (_Chcount == 0)

_State |= ios_base::failbit;

_Myios::setstate(_State);

return (*this);

}

从这个重载可以看到,没读入一个字符,计数+1,如果没有读到字符,就会置这个流的标志位为ios_base::failbit

这里的get函数,遇到'\n’会自动停止读取,然后如果不在最后调用get()读取掉结束字符,就会导致死循环,然后为了读出空白行,就需要在读到空白行get函数置标志位为fail时调用clear清空标志位

输入输出流的定位,ostream对象使用seekp().istream对象使用seekg(),注意参数为移动字符数目和方向。方向有ios::beg,ios::cur,ios::end,分别是开始,当前,结束

ofsteam继承自ostream,ifstream继承自istream

字符串输入输出流,头文件sstream,istringstream,ostringstream,istringstream用一个字符串初始化该对象,然后操作和cin类似。

输出流的格式化,操纵算子,分成2种,包含参数的算子和不包含参数的算子,

不包含参数的算子比较有用的有showbase,showpos,skipws,left,right,分别表示显示基数,即如果是16进制,会显示0x,正数前面加正号,跳过输入中的空格,左对齐右边填充字符,右对齐左边填充字符(默认是右对齐)

包含参数的算子在iomanip中定义,主要的有setiosflags,setfill,setBase,setw,setprecision

setiosflags,这个函数以fmtflags类型变量为参数,这个函数的作用类似一些无参数算子,如setiosflags(showbase)==showbase算子,都可以显示基数

fmfflags类型是一个枚举类型。代表0-0xffff中的一些常量。就好比我写一个setiosflags(showbase)实际上和setiosflags(8)一样,都可以显示基数

这里有一个隐式类型转换,setiosflags(static_cast<std::_Iosb<int>::_Fmtflags>(8)),这里这个模板类型中的int是没有意义的........因为它是Dummy,你写个啥正常类都行

setfill设置填充,字符参数,setBase设置基数显示方式,即进制,没找到实现,但是输入除了10,8,16之外都会变成十进制,setw设置输出宽度,setprecision,设置精度

注意setw在输入流控制宽度时,只针对字符串有效,比如输入123.4的字符串,结果是12,但是如果是float则结果是123.4无视了setw
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: