现代C++学习——标准IO库的使用
本文最后更新于 545 天前,其中的信息可能已经有所发展或是发生改变。

IO 库引入

IO 库基本结构(缩进表示继承层级):

ios(一般用不到)

​ → istream / ostream(属于头文件<iostream>)

​ → ifstream / ofstream(属于头文件<fstream>)

​ → istringstream / ostringstream(属于头文件<sstream>)

  1. 以上的输入输出都是对内存的输入输出

  2. 继承的类也重载了 << >> 运算符,为文件 / 字符串提供了同样的输入输出功能

  3. 标准库中模板类的继承关系如下(去掉 basic 前缀的就是 <char> 的特化版,一般也只讨论他们):

    std-io-complete-inheritance.svg

IO 库注意事项:

  1. 关于 IO 对象的构造和赋值等函数

    • delete 了拷贝构造和 operator=:拷贝流对象数据会导致内存混乱。应该使用引用指向内存

      因此,重载流运算符的返回值都是引用

    • 右值拷贝是 protected,用于继承和 extern 定义全局对象(比如 cin),而不能手动实例化

  2. IO 对象的状态

    IO 库定义了 iostate 表示 IO 对象的状态,在 VS 中是 int 形式,通过将二进制位置 0/1 设置状态

    • IO 对象状态有 4 种:

      1. goodbit,没有错误

      2. failbit,错误,各种错误

        eofbit,文件结束,同时设置 failbit

        badbit,系统级错误,同时设置 failbit

      • 也就是说只要出现了错误,都会设置 failbit,cin.good() 等效 !cin.fail()
    • 同时标准库对象提供了一些状态相关的函数:

      // 返回 bool
      cin.good();
      cin.fail();
      cin.eof();
      cin.bad();
      // 少数调试用,可以忽略
      int state = rdstate();   // 返回当前IO状态(直接以int形式)
      cin.setstate(flag);// 设置state
      // 重置状态
      cin.clear(); // 重置为good
      cin.clear(flag); // 重置特定位
      
      // 提取输入并丢弃
      cin.ignore(streamsize, delimeter);   // ignore的长度,到哪个字符处停止
  • 一段企业级输入demo:

    #include 
    
    int main(){
      int i = 10;
      while(std::cin >> i, !std::cin.eof()){    // 逗号用于前面的式子只执行不返回值的情况
          if(std::cin.bad()){       // 系统级错误抛出异常
              throw std::runtime_error("cin is corrupted");
          }
          if(std::cin.fail()){  // 剩下的只有普通错误
              std::cin.clear();
              std::cin.ignore(std::numeric_limits::max(), '\n');   // 忽略当前整行
              std::cout << "format error, please try again" << std::endl;
              continue;
          }
          std::cout << "i = " << i << std::endl;
      }
      std::cout << "process complete" << std::endl; 
    }

    basic_istreambasic_ostream 重载了 operator bool(),用于实现作为判断条件的返回值逻辑

    但是 operator bool() 一个函数不能区分不同的错误,所以上述代码使用了成员函数来判断

iostream 的补充

  • IO 库提供的其他输入:

    std::getline(istream, string, [delimeter])basic_istream

    std::string str;
    // 属于头文件
    std::getline(std::cin, str);

    std::cin.get(char)basic_istream

    char c;
    std::cin.get(c);
    • std::getline( )cin.getline( ) 的区别:

      前者是符号截取末尾再给 string,后者是手动指定长度再给 char*,显然前者相对好用些

    • 实际工程中需要进行错误处理

文件流 fstream

相比 <iostream><fstream> 没有提供默认的流对象

但相对的,fstreamifstreamofstream 支持了 public 构造(包括右值拷贝),可以手动构造

  • 这三个类没有继承关系,所以在文件功能上各自独立,fstream 正好同时实现了输入和输出
// 第二个参数可以省略
std::fstream fs("test.txt", std::ios::in | std::ios::out);
// 构造和打开文件可以分开,但一定要关闭
std::ifstream ifs;
ifs.open("test.txt");
ifs.close();
// 模式有很多种
std::ofstream ofs("test.txt", std::ios::app);
  1. 默认的模式都是 std::ios::instd::ios::out

  2. 文件对象的内存不在栈或堆,而是操作系统内核,是智能指针也管不到的地方,open 之后只能手动 close

  3. 文件读写模式(可以类比指针):

    app 写的时候跳到末尾;in 直接读;out 直接写;

    trunc 打开时清空内容;ate 打开直接跳到末尾;

    binary 二进制模式;(noreplace 独占模式,C++23)

  • 模式比较玄学,以下是目前总结的:

    • 没有在读文件时,才会在文件不存在时新建文件

    • 尚无法确认 outtrunc 的区别,但有以下规则:

      1. fstream 不能直接用 truncostreamtruncin 共存仍会清空文件

      2. 只有 out 时会清空重写,当 inout 共存时则不会

      3. truncfstream 中要配合 out,在 ofstream 中不仅不需要 out 还可以无视 in

      • 所以不想清空的时候一律加上 in,想清空的话使用 ostream+trunc,追加则手动设为 app
    • atebinary 是辅助用的,不会与其他模式产生奇妙的反应

  • 一段企业级读文件demo:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::string fileName;
    std::string fileLine;
    while(std::cin >> fileName, !std::cin.eof()) {
        if(std::cin.bad()) {
            throw std::runtime_error("cin corrupted");
        }
        std::ifstream ifs(fileName);
        if(ifs.is_open()) {
            while(std::getline(ifs, fileLine)) {
                std::cout << fileLine << std::endl;
            }
            if(ifs.bad()) {
                throw std::runtime_error("file stream corrupted");
            }
        }else {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "file not exist, please try again" << std::endl;
            continue;
        }
    }
    std::cout << "process complete" << std::endl;
}

字符串流 sstream

<fstream> 同为 <iostream> 下类的派生,继承关系与 fstream 系列完全一致,需要手动初始化

此外,因为字符串流两端都是内存中的对象,所以有很多独有的特性

// 内置一个string,默认构造也会保存一个空string
std::stringstream ss;
std::cout << ss.str() << std::endl; // 无参 str() 返回内置串
// 普通构造,拷贝一个串进去
std::ostringstream oss("test");
// 带参 str(string) 拷贝串
std::istringstream iss;
iss.str("test");
特性演示

体现一些 iostream 处理字符串的特性(强大的 << >>

  • 利用类提供的操作将串变成数
#include <iostream>
#include <sstream>

int main() {
    std::string str("12");
    std::stringstream ss(str);

    int i = 0;
    ss >> i;
    if(ss.bad()) {
        throw std::runtime_error("ss corrupted");
    }else if(ss.fail()) {
        ss.clear();
        ss.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "string format error" << std::endl;
    }else {
        std::cout << i << std::endl;    // 12
    }

    return 0;
}
  • 将数转为串
#include <iostream>
#include <sstream>

int main() {
    int test = 100;
    std::stringstream ss;
    ss << test << std::endl;
    if(ss.bad()) {
        throw std::runtime_error("ss corrupted");
    }else {
        std::cout << ss.str() << std::endl;
    }
}
  • 串的切分(模仿 cin
#include <iostream>
#include <sstream>

int main() {
    std::string src_str("hello world mgc xcr");
    std::string dest_str;
    std::stringstream ss(src_str);
    while (ss >> dest_str) {
        std::cout << dest_str << std::endl;
    }
    if(ss.bad()) {
        throw std::runtime_error("ss ccorrupted");
    }
}
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇