C++输入输出与IO流

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类型,如ostreamfstream等)

流状态类型 说明
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的falibitbadbit置位,则返回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默认inout模式
  • out只支持ofs和fs流; in只支持ifs和fs流
  • out被设定时,才可以设定trunc
  • trunc没被设定时,才可以设定app模式
  • app模式下,没有显式指定out,文件依然以输出方式被打开
  • atebinary模式可用于任何类型的文件流对象

注意1:以out模式打开文件会丢弃已有数据

  • 使用ofstream打开一个文件,会将文件原有的数据丢弃。可以同时采用显式指定appin的方式打开,避免造成数据丢失。

注意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类型数据时,输出字符型的truefalse,而非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';
}

image-20240429223253041

综合练习与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,不能覆盖)

image-20240429230735986

原始读取文件

image-20240429230808682

原始存储文件
#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;
}

image-20240429231037247

控制台输出

image-20240429231057014

运行后的save文件
上一篇:1. 在Java中,为何枚举类型的比较推荐==而不是equals


下一篇:力扣930.和相同的二元子数组