C++ IO
IO类
头文件 | 类 |
---|---|
iostream | istream:从流读取数据 ostream:向流写入数据 iostream:读写流 |
fstream | ifstream:从文件读取数据 ofstream:向文件写入数据 fstream:读写文件 |
sstream | istringstream:从string读取数据 ostringstrem:向string写入数据 stringstrem:读写string |
类型ifsteam和istringstrem都继承自istream,类型ofsteam和ostringstrem都继承自ostream,因此我们可以像使用cout和cin那样使用这些类,用<<和>>读写数据
IO对象无拷贝或赋值
我们不能拷贝或对IO对选购赋值:
ofstream out1,out2;
out1 = out2; //错误,不能赋值
ofstream print(ofstream); //错误,不能初始化ofstream参数
out2 = print(out2); //错误,不能拷贝流对象
由于IO对象不能拷贝,当其作为形参或返回类型时,应当按引用传递,又由于读写一个IO对象会改变其状态,故传递和返回的引用不能是const的
条件状态
一个流一旦发生错误,其后续的IO操作都会失败。确当一个流对象的最简单办法就是将它当作一个条件来使用while (cin >> word)
文件IO
头文件定义的三个支持文件IO的类型:ifstream读文件,ofstream写文件,fstream读写文件。可以使用<<、>>和getline来读写文件。
fstream fstrm | 创建一个未绑定的文件流,fstream为三种文件IO类型之一 |
---|---|
fstream fstrm(s) | 创建一个文件流并绑定文件s(string类型或者C风格字符串),使用默认mode |
**fstream fstrm(s,mode) ** | 使用mode打开文件 |
fstrm.open(s) | 打开名为s的文件,并与fstrm类绑定 |
fstrm.close() | 关闭与fstrm绑定的文件 |
fstrm.is_open() | 返回bool,指出与fstrm关联的文件是否成功打开且尚未关闭 |
创建一个文件流:
std::ifstream in(ifile);
std::ofstream out;
out.open(ifile+"copy");
if (out) //由于open可能失败,有必要检查out
/*其他操作*/
in.close();
in.open(otherFile);
文件流对象在离开作用域时将被析构,析构函数里自动调用close
文件模式
in | 以读方式打开 |
---|---|
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
- 只能对ofstream或fstream对象设定out模式
- 只可以对istream和fstream对象设定in模式
- 只有当out模式也被设定时才可以设定trunc模式
- 只要trunc没被设定,就可以设定app模式,在app模式下,文件总是以输出方式被打开
- 即使没有指定trunc,以out模式打开的文件也会被截断,为了保留文件内容,必须同时指定app模式;或同时指定in模式
- ate和binary模式可用于任何类型的文件流对象,且可以和其他任何类型组合
由于以out模式打开文件会丢失已有数据,阻止其清空文件内容的方法是指定app模式和in模式
ofstream app("file", ofstream::app);
ofstream app2("file", ofstream::out | ofstream::app);
string流
sstream头文件定义了三种类型来支持内存IO,这些类型可以向string写入数据,从string读取数据:
istringstream从string读取数据,ostringstream向string写入数据,stringstream可读可写
sstream strm | 创建一个未绑定的stringstream对象 |
---|---|
sstream strm(s) | 创建一个stringstream对象,保存string s的拷贝 |
strm.str() | 返回strm所保存的string的拷贝(调用以获得结果string) |
strm.str(s) | 将string s拷贝到strm中。返回void |
示例:要将一行来自文件的数据读取并存入类中:
lee 2015552368 862555012
morgan 6095550132
类定义:
class PersonInfo{
public:
std::string name;
std::vector<std::string> phones;
};
有:
#include<sstream>
/*...*/
std::string line,word; //保存来自输入的一行和一个单词
std::vector<PersonInfo> people; //保存所有来自文件的人物信息记录
std::ifstream in("file");
while (std::getline(in,line)) { //从文件中读取一行数据
PersonInfo temp;
std::istringstream recond(line); //绑定读取的一行数据
recond >> temp.name; //先读取名字
while (recond >> word) //读取电话
temp.phones.push_back(word);//保存电话
people.push_back(temp); //存入vector
}
当我们希望逐步构造输出最后一并打印时,可以用ostringstream。对于上一节的例子,我们希望逐个验证号码并改变其格式,如果所有号码都是有效的,我们希望输出一个新文件,包含改变格式后的号码,对于无效的号码,我们将不会输出其到新文件中,而是打印一条包含人名和无效号码的错误信息。我们只有在验证完所有号码时才将其输出到文件中,所有可以将其先输出到一个内存ostringstream流中:
std::ofstream out("file");
for (const auto& entry: people){
std::ostringstream formatted,badNums; //分别保存格式化后的和错误的号码
for (const auto& nums : entry.phones){
if (!valid(nums))
badNums << " " << nums
else
formatted << " " << format(nums);
}
if (badNums.str.empty()) //所有号码无错误
out << entry.name << " " << formatted.str() << std::endl;
else
std::cerr << "input error:" << entry.name << "invaild number(s)"
<< badNums.str() << std::endl;
}
流随机访问
tellg() | 返回输入流中标记的当前位置 |
---|---|
tellp() | 返回输出流中标记的当前位置 |
seekg(pos) | 在一个输入流或输出流中将标记重定位到给定的绝对位置,通常是前一个tellg或tellp返回的值 |
seekp(pos) | |
seekp(off,from) | 定位到from之前或之后off个字符,from有以下取值: |
seekg(off,from) | beg:偏移量相对于流开始位置,cur:当前位置,end:流解为位置(如:fstream::brg) |
g版本表示我们正在获得(gain)数据,p表示我们正在放置(place)数据.istrem和ifstream、istringstrem使用g版本,ostream和ofstream、ostringstream使用p版本,iostream和fstream、stringstream两个版本都可以使用
虽然有g、p版本之分,但是流中的标记只有一个,并不存在单独的读标记或者写标记
//记住当前的位置:
std::ostringstream writeStr;
std::ostringstream::pos_type mark = writerStr.tellp();
//...
if (cancelEntry)
//回到刚才记住的位置
writeStr.seekp(mark);
读写同一个文件
假定已经给定了一个要读取的文件,我们将要在此文件的末尾写入新一行,这一行包含文件中每行的相对起始位置。例如有以下文件:
abcd
efg
hi
j
将要输出:
abcd
efg
hi
j
5 9 12 14
注意每行不可见的换行符
程序将逐行读取文件,对每一行递增计数器,将刚刚读取的一行的长度加到计数器上,此即下一行的起始位置:
int main() {
std::fstream inOut("copyOut",std::fstream::ate | std::fstream::in | std::fstream::out);
if (!inOut){
std::cerr << "Unable to open file!" << std::endl;
return EXIT_FAILURE;
}
auto end_mark = inOut.tellg();
inOut.seekg(0,std::fstream::beg);
size_t count = 0;
std::string line;
while (inOut && inOut.tellg() != end_mark && getline(inOut,line)) {
cnt += line.size()+1; //读取一行便增加一行的偏移量并输出至文件末尾
auto mark = inOut.tellg(); //记住当前的读位置
inOut.seekp(0,std::fstream::end);
inOut << cnt;
if (mark != end_mark) inOut << " "; //如果没有读至最后一行,需要在两个数字之间输出一个分隔符
inOut.seekg(mark); //恢复到读的位置继续读
}
inOut.seekp(0,std::fstream::end);
inOunt << "\n"; //于末尾换行
return 0
}
iostrem迭代器
可用于IO类型对象的迭代器istream_iterator
和ostream_iterator
,使用时,应将之与一个流进行绑定(如cout)
isfream_iterator操作 | |
---|---|
istream_iterator in(is) | in从输入流is读取类型为T的值。T必须支持>>运算符 |
istream_iterator end | 读取类型为T的默认初始化istream_iterator迭代器,表示尾后位置 |
in1 = in2 | in1和in2必须读取相同的类型,如果它们都是尾后迭代器(即默认初始化的迭代器)或绑定到相同的输入,则true |
*in | 返回从流中读取的值 |
++in,in++ | 使用元素类型所定义的>>运算符从输入流中读取下一个值,前一个版本返回指向自增后迭代器的引用,后一个返回旧值 |
std::ifstream in("ifile");
std::istream_iterator<std::string> isIt(in),eof;//通常应该一并定义eof
while (isIt != eof)
std::cout << *isIt++;
使用算法操作流迭代器:
std::istream_iterator<int> inIt(in),eof;
int sum = std::accumulate(inIt,eof,0);
我们可以对任何具有输出运算符<<的类型定义ostream_iterator.在初始化一个该类型对象时,除了被绑定的流作为第一个参数,我们还可以提供第二个参数—一个C风格的字符串,表示在输出每个元素后都会打印此字符串。ostream_iterator无默认初始化,即没有eof
ostream_iterator操作 | |
---|---|
ostream_iterator out(os) | out将类型为T的对象写入os流中 |
ostream_iterator out(os,d) | 每个值后面将输出一个d,d指向一个空字符结尾的字符数组 |
out = val | 用<<将val写入out所绑定的ostream中 |
*out,++out,out++ | 不对out做任何事情,全部返回out |
std::ostream_iterator<int> outIt(std::cout," ");
for (auto i : vec)
outIt = i; //或*outIt++ = i,与istream_iterator对应的写法;
std::cout << std::endl;
拷贝算法接受三个迭代器,前两个表示一个输入范围第三个表示目的序列的起始位置,目的序列应至少包含与输入序列一样的元素;算法返回目的迭代器的值,指向拷贝至目的序列尾元素之后的值。
可以通过copy来打印vec中的元素,这比写循环更为简单(copy中含有循环,并自动递增目的迭代器):
std::ostream_iterator<int> outIt(std::cout," ");
std::copy(vec.begin(),vec.end(),outIt);
std::cout << std::endl;
格式化输入与输出
让bool类型输出为true/false而不是1/0:
std::cout << "default bool values: " << true << " " << false
<< "\nalpha bool values: " << boolalpha
<< true << " " << false << std::endl;
std::cout << noboolalpha //恢复为1/0格式
指定整数型的进制:
std::cout << "default: " << 20 << " " << 1024 << std::endl;
std::cout << "octal: " << std::oct << 20 << " " << 1024 << std::endl; //八进制
std::cout << "hex: " <<std::hex << 20 << " " << 1024 << std::endl; //十六进制
std::cout << "decimal: " << std::dec << 20 << " " << 1024 << std::endl; //十进制
//浮点型的表示形式不受影响
std::cout << std::showbase; //将输出当前的进制
std::cout << std::noshowbase
控制浮点数输出的三个格式:
- 精度
- 进制
- 没有小数部分的浮点值是否打印小数点
-
指定打印精度
使用IO对象的precision成员函数指定的精度,带int参数的版本用于设定,void返回当前的精度:
std::cout.precision(12); std::cout << "Precision: " << std::cout.precision() << ", Value: " << std::sqrt(2.0) << std::endl; //另一种设置精度的方式是使用setprecision std::cout << std::setprecision(3) << std::cout.precision() << ", Value: " << std::sqrt(2.0) << std::endl;
-
打印小数点:
默认情况下,当一个浮点数的小数部分为零时,不显示小数点,showpoint强制打印小数点:
std::cout << 10.0 << std::endl;//打印10 std::cout << std::showpoint << 10.0 //打印10 << std::noshowpoint << std::endl; // 恢复默认
输出补白:
- setw:指定下一个字符串或数字的最小空间
- left:左对齐输出
- right:右对齐输出(默认)
- internal:控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满中间空间
- setfill:允许指定一个字符代替默认的空格来补白输出
std::cout << "i: " << setw(12) << i << std::endl;
std::cout << std::setfill('#');
控制输入格式:
默认情况下,输入运算符会忽略空白符(包括回车),以下循环
char ch;
while (std::cin >> ch)
std::cout << ch;
当输入以下序列时,
a b cd
循环会执行4次,跳过中间的空格,输出为abcd。操作符noskipws会令输入运算符读取空白符。为恢复默认,可以使用skipws
std::cin >> noskipws;
while (cin >> ch)
std::cout << ch;
std::cin >> skipws; //恢复默认