为什么使用输入输出流而不使用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种,包含参数的算子和不包含参数的算子,
包含参数的算子在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