C++ 输入输出与I/O流
文章目录
- C++ 输入输出与I/O流
- IO类型与基础特性
- 概念与特性
- IO状态
- 输出缓冲区
- 文件输入输出
- 文件模式
- string流
- IO处理中常用的函数及操作符
- 综合练习与demo
- 一、 创建文件并写入
- 二、控制台输入数据并拆分存储
- 三、读写电话簿
IO类型与基础特性
C++11标准提供了几种IO处理操作。其中最为熟悉的就是控制台IO:iostream
,除此之外,还提供了文件操作IO:fstream
以及string处理IO:stringstream
,这两种IO操作都继承自iostream
,因此在iostream上可以执行的操作,在另外两种IO类型中亦可执行。(i继承i,o继承o)
IO库类型及头文件
头文件 | 类型 | 作用 |
---|---|---|
iostream | istream, wistream | 从流读取数据 |
ostream, wostream | 向流写入数据 | |
iostream, wiostream | 读写流 | |
fstream | ifstream, wifstream | 从文件中读取数据 |
ofstream, wofstream | 向文件写入数据 | |
fstream, wfstream | 读写文件 | |
sstream | istringstream, wistringstream | 从string中读取数据 |
ostringstream, wostringstream | 向string中写入数据 | |
stringstream, wstringstream | 读写string |
注:w开头的类型用于操纵wchar_t
类型的数据。
cin
:一个istream对象,从标准输入中读取数据cout
: 一个ostream对象,向标准输出写入数据cerr
:一个ostream对象,通常用于输出程序错误信息,写入到标准错误>>
:用来从一个istream对象读取输入数据<<
: 用来向一个ostream对象写入输出数据eof()
: 用来判断流是否到达末尾
概念与特性
- IO对象不能被拷贝或赋值
- 如果程序崩溃,输出缓冲区是不会被刷新的。
- 当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流
IO状态
strm::iostate
是C++中表示流的状态的一种类型,是一个枚举类型。(这里的strm
是一种IO类型,如ostream
、fstream
等)
流状态类型 | 说明 |
---|---|
strm::badbit | **表示流发生了无法恢复的错误,通常是系统级错误。**如果其对应置位被clear() 清除,也只是将流状态恢复为有效,不会解决问题。 |
strm::falibit | 表示流操作失败,这种错误通常是可恢复的 |
strm::eofbit | 表示流到达了文件结束 |
strm::goodbit | 表示流处于正常状态,所有的标志位都是0 |
标准库提供了一组函数用来查询这些标志位的状态。其中good()
和fail()
是确定流总体状态的方法,当我们将流当做条件使用,如while(cin >> word)
时,其代码就等价于while(!cin.fail())
状态查询方法 | 说明 |
---|---|
s.eof() | 若流s的eofbit 置位,则返回true |
s.fail() | 若流s的falibit 或badbit 置位,则返回true |
s.bad() | 若流s的badbit 置位,则返回true |
s.good() | 若流s处于有效状态,则返回true |
s.clear() | 将流s中所有条件状态复位,将流的状态设置为有效。void类型 |
s.clear(flags) | 复原指定的flags标志位。void类型 |
s.setstate(flags) | 复原给定的flags标志位。void类型 |
s.rdstate() | 返回的是当前流的状态,以 iostate 枚举值表示。 |
输出缓冲区
每个输入流都管理一个缓冲区,用来保存程序读写的数据。
当程序正常结束、缓冲区满、手动指定缓冲(如操作符endl,cerr等)缓冲区都会被刷新。
操作符 | 说明 |
---|---|
std::endl | 换行并刷新缓冲区 |
std::flush | 刷新缓冲区,且不附加额外字符 |
std::ends | 输出空字符,并刷新缓冲区 |
unitbuf与nounitbuf
cout << unitbuf; // 所有输出操作后都会立刻刷新缓冲区
// 任何输出都立刻刷新,无缓冲
...
cout << nounitbuf; // 恢复常规模式
文件输入输出
fstream特有的操作
操作 | 说明 |
---|---|
fstream fstrm; | 创建一个未绑定的文件流 |
fstream fstrm(s); | 创建一个文件流,并打开名为s的文件(s可为string,亦可为C风格字符串之指针)。此构造函数是explicit的 |
fstream fstrm(s, mode) | 与上者类似,按指定mode打开指定s文件 |
fstrm.open(s) | 打开名为s的文件,并将文件与fstrm绑定 |
fstrm.close() | 关闭与fstrm绑定的文件。void类型 |
fstrm.is_open() | 返回一个bool值,说明关联文件是否成功打开且尚未关闭 |
- 当一个fstream对象被销毁时,close会自动被调用
文件模式
文件模式,用于指出文件流如何使用文件
文件模式 | 说明 |
---|---|
in | 以读的方式打开 |
out | 以写的方式打开 |
app | 每次写操作之前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
ifstream
默认in
模式,ofstream
默认out
模式,fstream
默认in
和out
模式out
只支持ofs和fs流;in
只支持ifs和fs流out
被设定时,才可以设定trunc
trunc
没被设定时,才可以设定app
模式app
模式下,没有显式指定out
,文件依然以输出方式被打开ate
和binary
模式可用于任何类型的文件流对象
注意1:以out模式打开文件会丢弃已有数据
- 使用ofstream打开一个文件,会将文件原有的数据丢弃。可以同时采用显式指定
app
或in
的方式打开,避免造成数据丢失。
注意2:每次调用open是都会确认文件模式
- 我们使用同一个ofstream流打开文件,在第一个文件指定了什么模式打开,并不会在第二个文件中依然存在,只会选择默认方式
- 所以我们利用与out相关的文件流打开文件时,一定要注意最好显式指定文件模式
我们使用文件流时通常包含以下步骤
- 创建一个文件流
- 默认文件流
- 指定绑定文件(打开or不打开)
- 打开文件
- 显式指定打开模式
- 关闭文件
void fstreamTest(){
string filename = "F:xxxxxx\\test.txt";
// 创建一个文件流对象
std::fstream fstrm(filename);
// fstrm.open(filename); 也可以创建一个默认的fstream实例,用这种方式打开
string line;
// 是否成功打开与其关联的文件
if(fstrm.is_open()){
while(std::getline(fstrm, line)){
std::cout<< line << std::endl;
}
}else{
std::cerr<<"Flie is not open!"<<std::endl;
}
// 最后用完关闭
fstrm.close();
}
string流
stringstream特有操作
操作 | 说明 |
---|---|
sstream strm | 一个未绑定的string流对象 |
sstream strm(s) | 创建一个string流对象,并保存string s的拷贝(该构造函数explicit) |
strm.str() | 返回strm保存的string的拷贝 |
strm.str(s) | 将s拷贝到strm中。void类型 |
IO处理中常用的函数及操作符
getline(cin, str)
- getline函数会从输入流中读取字符,直到遇到换行符或者指定分隔符位置,会将读取到的字符存储在str中
template <class charT, class traits, class Allocator>
std::basic_istream<charT,traits>& getline (std::basic_istream<charT,traits>& is, std::basic_string<charT,traits,Allocator>& str, charT delim);
is
是输入流,例如std::cin
。str
是要存储读取到的一行数据的字符串。delim
是可选参数,用于指定行结束的分隔符,默认为换行符\n
。
std::boolalpha
- 使控制台在输入bool类型数据时,输出字符型的
true
或false
,而非0和1 - 关闭使用:
std::noboolalpha
void boolalphaTest(){
std::cout<<"Default bool type: "<<true<<" "<<false<<'\n'
<<std::boolalpha
<<"use boolalpha: "<< true<<" "<<false<<'\n'
<<std::noboolalpha
<<"close boolalpha: "<< true<<" "<<false<<'\n';
}
综合练习与demo
一、 创建文件并写入
std::ofstream outFile; // 创建写入文件流
outFile.open("emptable1.txt"); // 创建该txt文件
if (!outFile)
{
std::cerr << "无法打开文件" << std::endl;
return;
}
outFile << "on, "; // 写入数据
for (auto s : stepMap)
{
outFile << s.second << ", ";
}
outFile << endl;
outFile.close(); // 关闭文件
二、控制台输入数据并拆分存储
/*1、输入一行数据并按空格拆分*/
string input;
vector<string> latex;
cout << "Please input: ";
// 将一行输入存储到input中
std::getline(std::cin, input);
// 创建istring流,并将input拷贝过去
std::istringstream iss(input);
string str;
// istring流将通过空格进行拆分,传递赋值给str
while (iss >> str)
{
latex.push_back(str);
}
/*2、输入多行数据并按行存储*/
std::vector<std::string> lines;
std::string line;
std::cout << "Enter multiple lines of text (enter an empty line to finish):" << std::endl;
// 使用 getline 在循环中读取每一行输入
while (true) {
std::getline(std::cin, line);
if (line.empty()) {
// 当输入为空行时,结束输入
break;
}
lines.push_back(line);
}
三、读写电话簿
给定一个电话簿文件,里面格式大致为name number1 number2
。
读取该文件,并且存储人物及电话号码信息,存储时判断号码是否符合规定
将正确格式化后的字符,输出到结果文件中(结果文件包含title,不能覆盖)
#include <fstream>
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
struct PersonInfo
{
/* data */
string name;
vector<string> phones;
};
// 检查电话号码是否合法
bool checkNumber(string s)
{
if (s.size() != 11 && (s.size() < 4 || s.substr(0, 4) != "0831"))
{
return false;
}
else
{
for (auto &c : s)
{
if (!isdigit(c) && c != '-')
{
return false;
}
}
}
return true;
}
// 阅读最初的电话簿并存储
vector<PersonInfo> getOriginNumbers(string s)
{
ifstream ifstm;
// 文件输入流以默认模式打开文件
ifstm.open(s);
vector<PersonInfo> people;
if (ifstm.is_open())
{
string line, number;
// line以行存储文件中的内容
while (getline(ifstm, line))
{
PersonInfo info;
// 使用istringstream流时,record会根据空白符号对string进行拆分
istringstream record(line);
// 录入名字
record >> info.name;
// 录入多个号码
while (record >> number)
{
info.phones.push_back(number);
}
people.push_back(info);
}
}
else
{
cerr << "Open file is faild! " << endl;
}
ifstm.close();
return people;
}
// 将正确的电话存储到电话簿中
void saveRightNumbers(vector<PersonInfo> &people, string s)
{
ofstream ofs;
// 以追加形式打开,避免覆盖文件中原有的内容
ofs.open(s, ofstream::app);
if (ofs.is_open())
{
for (const PersonInfo &p : people)
{
ofs<<'\n'; // 先换行
// 记录哪些电话号码有误
ostringstream badNums;
ofs << p.name<<" ";
for (const auto &number : p.phones)
{
// 如果电话号码错误,存储到该string输出流中
if (!checkNumber(number))
{
// 存储多个错误号码
badNums << " " << number;
}else{
ofs<<number << " ";
}
}
if (!badNums.str().empty())
{
// 输出谁的电话号码有问题
cerr << "[input error] name: " << p.name
<< ", invalid number(s): " << badNums.str() << endl;
}
}
}
else
{
cerr << "Open savefile is faild! " << endl;
}
ofs.close();
}
int main()
{
string originfile = "F:\\xxxxxx\\numberDict.txt";
string savefile = "F:\\xxxxxxx\\save.txt";
vector<PersonInfo> people = getOriginNumbers(originfile);
saveRightNumbers(people, savefile);
return 0;
}