跳转至

输入输出

约 1247 个字 115 行代码 预计阅读时间 6 分钟

  • cin 标准输入流
  • cout 标准输出流
  • cerr 标准错误流
  • clog 标准日志流/带缓冲的错误流
Note

1> 重定向stdout, 2>重定向stderr, >换成>>: 覆写文件换成在文件尾部追加

ostream

ostream是一个类, cout是预定义的ostream类的对象 cerr和clog也是ostream类的对象, cerr无缓冲区(实际有, 但只是每次都flush()), clog有缓冲区

注意clog和cerr都与C语言的stderr相关联, 很多时候它们会绑定到相同streambuf(同一个stderr缓冲区), 只是cerr默认每次都调用.flush(), 而clog并不会每次都flush(). 不过标准并不保证它们共享缓冲区, 若需要clog立刻输出, 应直接换成cerr或手动调用clog.flush().

fmtflags

C++用bitmask表示各种格式控制开关, 而整体的掩码可以通过os.flags()获得.std::ios::fmtflags是一个bitmask类型, ios被包含在iostream里. fmtflags包含不同功能的几个字段, 可以通过cout.setf直接设置某个字段的bitmask, 比如可以通过: - std::cout.setf(std::ios::hex, std::ios::basefield);把进制字段basefield 清空,再置表示16进制的比特位. - std::cout.setf(std::ios::hex);按位或上表示16进制的比特位(没有把其它位置零, 可能冲突).

但一般不会直接操纵bitmask, 而是通过对应的操纵子(manipulator)实现, 例如上述设置hex的操作等价于 cout << hex. 这里是<<被重载, 使得其接受下一个操作数hex为函数指针, 再调用hex函数, 而hex函数就包装了setf(std::ios::hex, std::ios::basefield).

常用的fmtflag操纵子

  1. 数字进制

    • std::dec
    • std::dec
    • std::oct
  2. 浮点格式

    • std::fixed
    • std::scientific
    • std::hexfloat
  3. 对齐方式

    • std::left // 左对齐
    • std::right // 右对齐(默认)
    • std::internal // 符号在左,数字右对齐(常配合宽度和填充)
  4. 其它

flag 操纵子 作用
showbase std::showbase 显示进制前缀:0x0
showpos std::showpos 正数前加 +
showpoint std::showpoint 浮点数即使是整数也显示小数点与尾零
uppercase std::uppercase EX 等字母大写
boolalpha std::boolalpha booltrue/false 输出而不是 1/0
noboolalpha std::noboolalpha bool1/0 输出
skipws std::skipws 输入时跳过前导空白(默认开)
std::noskipws 输入时不跳过空白
unitbuf std::unitbuf 每次输出都立即 flush

iomanip

iomanip库存储带参数的格式操纵子和一些高级格式调整的工具. 需要#include <iomanip>

  • 设定宽度: 注意:不同于其它manipulator, setw只影响下一次<<

    cout << setw(4) << num;
    

  • 设定小数精度:

    double num = 1.23456;
    cout << setprecision(3) << num; //得到1.235
    

  • 设定定点小数位数(与fmtflagsstd::fixed连用)

    double num = 1.2,num2 = 1.2345;
    cout << fixed<<setprecission(3)<<num; //1.200
    cout << fixed<<setprecission(3)<<num2; //1.235
    

  • 设置前导零

    cout << setw(4) <<setfill('0')<< 1 //得到0001(默认std::right)
    

  • 设置进制(对fmtflags的封装), 只支持8,10,16

    cout << setbase(8) << 16 //20 
    

  • 包装fmtflags

    ios::fmtflags f = ios::showbase | ios::uppercase | ios::showpos;
    cout << setiosflags(f) << hex << 255;
    
    void apply_format(std::ostream& os, std::ios::fmtflags f) {
        os << std::setiosflags(f);
    }
    

改输出格式后恢复

  • 对于fmtflags, 可以使用std::ios::fmtflags flags = os.flags()保存状态;
    void print_hex(std::ostream& os, int x) {
        auto old = os.flags();  // 保存
    
        os << std::hex << std::showbase << x;
    
        os.flags(old);          // 恢复
    }
    

若使用了不在fmtflags范围内的格式控制选项(如下所示), 还应:

  • 使用std::streamsize prec = os.precision()保存输出精度状态, 再使用os.precision(prec);恢复;

  • 使用char fill_char = os.fill()保存填充字符状态, 再使用os.fill(fill_char)恢复;

  • 注意: setw也不在fmtflags范围之内但其只影响下一次<<;

使用guard class来封装一个不影响整体的输出函数

struct ios_guard {
    std::ios& os;
    std::ios::fmtflags flags;
    std::streamsize prec;
    char fill_char;

    explicit ios_guard(std::ios& os_)
        : os(os_), flags(os_.flags()),
          prec(os_.precision()), fill_char(os_.fill()) {}

    ~ios_guard() {
        os.flags(flags);
        os.precision(prec);
        os.fill(fill_char);
    }
};

void myPrint(std::ostream& os, double x) {
    ios_guard _{os};  // 作用域内临时改格式

    os << std::showpos
       << std::hexfloat
       << std::setprecision(5)
       << std::setfill('0')
       << x;
}   // 离开作用域自动恢复 flags/precision/fill

无格式输出

需要按严格字节输出, 或者需要高性能地写大块缓冲区, 且不需要格式控制时使用.

  • cout.put(char ch) 输出一个字符, 忽略所有格式(即使之前设置过), 不会自动flush缓冲区(有缓冲)

    cout.put(65);输出字母A.

  • cout.write(const char* s, std::streamsize n) 从地址s开始, 输出n个字符(不会遇到\0而停止), 无自动flush.

输出重定向

  • 1 > file重定向标准输出, 2 > file重定向标准错误

istream

整行输入 cin.getline()string::getline()

  • 作为char[]输入: cin.getline(char* , streamsize ) 参数为C风格字符数组和最大输入长度
    char buf[1024]{0};
    cin.getline(buf, sizeof(buf)-1);//-1为了留出'\0'的空间
    
    也可以读入直到指定分隔符 cin.getline(char*, streamsize, delemeter)
  • 作为string类输入 (需#include<string>):
    • 读入包含空格的一整行: getline(cin, line_var)
    • 读入直到指定分隔符:getline(cin, line_var, delimeter)

按字符输入 cin.get()

  • 输入一行按空格分隔的数字 cin >> str 会默认跳过开始的空格, 并以空格或换行作为输入结尾(空格或换行仍保留在输入流中!);

    int num;
    vector<int> nums;
    while(cin >> num){
        nums.push_back(num);
        if(cin.get()=='\n'){
            break;
        }
    }
    

  • 输入直到换行或指定分隔符的另一种方法

    string str;
    for(;;){
        char c = cin.get()
        if(cin.get()=='\n'){
            ......
            break;
        }
        str += c;
    }
    

cin错误处理

cin.rdstate()

ios::iostate rdstate()是输入输出流的一组状态位, 其包含以下标志位:

ios::goodbit  // 0,表示目前没有错误
ios::eofbit   // 读到 EOF(文件/输入结束)
ios::failbit  // 格式错误,或操作失败(但流本身没坏)
ios::badbit   // 输入流错误(比如底层设备损坏)
每次cin输入后,根据操作是否出错以及何种错误, 会自动更新rdstate() - 正常状态: cin.rdstate() == ios::goodbit 为true 注意: goodbit值为0 - 出错: 若多种错误同时发生, rdstate等于多种错误标志位的按位或 若手动判断, 可以如下进行:
auto state = cin.rdstate();
if (state & ios::eofbit)  { /* 到 EOF 了 */ }
if (state & ios::failbit) { /* 输入格式错误 or 读取失败 */ }
if (state & ios::badbit)  { /* 输入流损坏(输入设备坏了)*/ }

判断函数good()/fail()/bad()/eof()

good()/fail()/bad()/eof()是对rdstate()判断的封装:

cin.good()  // 等价于 rdstate() == goodbit
cin.eof()   // rdstate() & eofbit
cin.fail()  // rdstate() & (failbit | badbit)  
// 注意:fail() 包含 badbit
cin.bad()   // rdstate() & badbit

failbit主要出现在输入格式不匹配的情况.

常见用法是:

  • 通过流对象的operator bool()重载, 流对象在条件判断里返回!fail()

    while(cin>>x){...} //等价于while(!(cin>>x).fail()){ }
    

  • 通过流对象的operator !()重载, !之后的流对象会返回fail()

    if(!cin){...} ///等价于if(cin.fail()
    

while (cin >> x)为什么不等于while ((cin >> x).good()?)

while ((cin >> x).good()会读不到”恰好在文件结尾的那个字符”

例如文件内容”1 2 3”(无多余空格)

while (cin>>x){...}
在第三次循环时会读到3, 此时eofbit被设置, cin.good()为false, cin.fail()为false. 若采用while ((cin >> x).good(), 第三次循环将不执行循环体直接结束, 而采用while (cin >> x)则将执行函数体, 并在第四次循环时因为读取不到任何字符而设置failbit, cin.fial()为true, 退出.

错误处理并输出导致错误的输入

  • 使用cin.clear()恢复状态;
  • 需要读入引起错误的输入, 否则会堵塞;
    #include<iostream>
    #include<string>
    
    for(;;){
        int x{0};
        cin >> x;
        if(cin.fail()){
            cin.clear() //恢复rdstate状态为正常
            cout << "cin fail" <<endl;
    
            string line;
            getline(cin, line);//把引起错误的输入取出, 否则输入会阻塞在这里
            cout << "cin fail due to: "<<line <<endl;
    
            continue;
        }
    }
    

stringstream

#include <sstream>

stringstream继承于basic_iostream, 支持cout和cin的绝大多数用法, 只是不再绑定终端+缓冲区(比如在stringstream中插入endl, 只是插入一个换行, 因为没有刷新缓冲区的概念)

应用场景: 1. 不是通过设备输入输出, 而是通过字符串, 比如通过网络发送/接收string类的消息体并解析, 这种消息体可能包含很复杂的格式.

  1. 为了避免各种类型与string的类型转换, 输出流自带把所有东西当成字符串处理的功能.

stringstream基础用法

#include <sstream>
#include <iostream>
#include <string>
using namespace std;

int main(){
    stringstream ss;
    ss << "test stringstream" << hex<<100<<boolalpha << true; //同cout语法

    string str1, str2;
    ss >> str1 >> str2 ;  //同cin语法
    cout << str1 << str2; //输出test stringstream 

    string str3 = "123\n456\n789";
    stringstream ss3(str3);
    string line;
    
    for(;;){
        getline(ss3, line);
        cout << line<<endl;
        if(ss.eof()) break;
    }
    
    
}
  • 输出stringstream的内容:cout << ss.str() << endl

  • 清空stringstream: ss.str("")将空字符串传给stringstream (注意, 同cin一样, ss.clear()是清空错误状态, 不是清空内容)

istringstream, ostringstream

istringstream 可以作为输入流; ostringstream 可以作为输出流;

输入空格分隔的一行多整数

string line = "10 20 30"
istringstream iss(line);
int x;
vector<int> a;
while(iss>>x) a.push_back(x);

输入指定分隔符的元素

string s = "1.2.3.4"
istringstream iss(s);
string part;
while(getline(iss,part,'.')){
    //part可能为空,例如"1..3"会得到一个空part
}

自定义输入函数

vector<int> parseInput(string& s){
    istringstream iss(s);
    vector<int> nums;
    string part;
    while(getline(iss,part,'.')){
        nums.push_back(stoi(part));
    }
    return nums;
}


int main(){
    string s;
    getline(cin,s);
    vector<int> nums;
    nums=parseInput(s);
}

实现类似sprintf的输出到字符串

double x = 3.14159; int n = 7;
ostringstream oss;
oss << "x=" << fixed << setprecision(2) << x
    << ", n=" << setw(3) << setfill('0') << n; // 001
string out = oss.str();   // "x=3.14, n=007"

fstream

include<fstream>

类型:fstream, ifstream, ofstream

  • 基础用法:
    • 打开文件 fstream fs("text.txt")或者fs.open("test.txt")
    • 关闭文件 fs.close(). 当文件流对象析构时也会自动close, 但需注意有的系统有文件句柄的总数限制, 所以不使用文件时应主动关闭fs. close也会自动flush缓冲区.
    • 文件是否打开fs.is_open()
    • 打开模式
      std::ios::in     // 以输入(读)方式打开
      std::ios::out    // 以输出(写)方式打开
      std::ios::app    // 追加写,每次写都在末尾
      std::ios::trunc  // 打开时截断文件为长度 0(清空), 在ios::out或者ostream时默认启用, 有ios::in或者istream时不生效.
      std::ios::binary // 二进制模式
      std::ios::ate    // 打开后定位到文件末尾(但仍可在任意位置读写)
      
      打开模式是bitmask, 所以按位或 fs.open("test.txt", std::ios::binary|std::ios::app)

若使用fstream, 则必须指明读/写/读写

文件写入

  • 可以使用fstreamofstream

  • ios::binary, 在Windows平台和Linux平台默认对\r\n的处理不同, 可能存在默认的转换. 而ios::binary不对此进行处理或转换. 尤其是对于图像视频等二进制文件, 应使用ios::binary防止写入时文件损坏.

  • fstreamostream写文件是默认清空源文件开始写!

  • ios::app: 每次写入时都从结尾开始写入, 文件不存在会被创建

//以写入模式打开文件
fstream wfs(testfile, ios::out|ios::binary|ios::app);
//或者: wfs.open(testfile, ios::out|ios::binary);
wfs<<"1234567890\n";
wfs.write("987654321", 9);

文件读取

  • ios::ate仅在打开时移到文件末尾, 但并非每次读写自动都在末尾

  • 处理打开失败

    testfile = "test.txt";
    fstream rfs(testfile, ios::in|ios::binary)
    
    if(!rf.is_open()){
        cerr << "open file" << testfile << "failed!"<<endl;
        return -1;
    }
    

  • 输出读到的字符数

    char buf[4090]{0};
    rfs.read(buf,sizeof(buf)-1)
    
    cout<<rfs.gcount()<<endl;//输出读了的字符数
    cout<<buf<<endl
    

  • 获取文件大小:

    //先移动文件读取指针到尾部
    ifstream ifs(testfile, ios::binary|ios::ate)
    
    //获取文件读取指针的位置, 即尾部位置
    cout << testfile << "filesize=" <<ifs.tellg();
    

  • 实时读取文件的新增内容

#include<string>
#include<iostream>
#include<fstream>
using namespace std;

int main(){
    string testfile = "test.txt";
    ifstream ifs(testfile, ios::ate|ios::binary);
    string line;

    for(;;){
        getline(ifs,line);//由于已经在结尾, getline会失败(ifs.fail()为true)
        if(!line.empty()){//或者!ifs.fail()
            cout<<"newLine: "<<line <<endl; 
        }
        ifs.clear();//恢复ifs的rdstate, 否则会一直处于fail状态
    }
}

评论