C++(week11):C++基础 第三章: 输入输出流

三、C++输入输出流

1.概念

(1)概念

  • 输入输出的含义
    以前所用到的输入和输出,都是以终端为对象的,即从键盘输入数据,运行结果输出到显示器屏幕上。从操作系统的角度看,每一个与主机相连的输入输出设备都被看作一个文件。除了以终端为对象进行输入和输出外,还经常用磁盘(光盘)作为输入输出对象,磁盘文件既可以作为输入文件,也可以作为输出文件

在编程语言中的输入输出含义有所不同。程序的输入指的是从输入文件将数据传送给程序(内存),程序的输出指的是从程序(内存)将数据传送给输出文件。


  • C++输入输出流机制
    C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。

就 C++ 程序而言, I/O 操作可以简单地看作是从程序移进或移出字节,程序只需要关心是否正确地输出了字节数据,以及是否正确地输入了要读取字节数据,特定 I/O 设备的细节对程序员是隐藏的。


  • C++常用流类型
    C++ 的输入与输出包括以下3方面的内容:

(1)对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称标准 I/O 。

(2)以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件 I/O 。
文件输入:从文件读入内存
文件输出:从内存输出到文件

(3)对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称 I/O 。


(2)流之间的关系

在这里插入图片描述


①通用输入流,包含:标准输入流、文件输入流、字符串输入流
②通用输出流,包含:标准输出流、文件输出流、字符串输出流

在这里插入图片描述



2.流的四种状态 (重点)

IO 操作与生俱来的一个问题是可能会发生错误,一些错误是可以恢复的,另一些是不可以的。在C++ 标准库中,用 iostate 来表示流的状态,不同的编译器 iostate 的实现可能不一样,不过都有四种状态:
goodbit 表示流处于有效状态。流在有效状态下,才能正常使用。如果 badbit 、 failbit 和 eofbit 任何一个被置位,则流无法正常使用。
badbit 表示发生系统级的错误,如不可恢复的读写错误。通常情况下一旦 badbit 被置位,流就无法再使用了。
failbit 表示发生可恢复的错误,如期望读取一个int数值,却读出一个字符串等错误。这种问题通常是可以修改的,流还可以继续使用。
eofbit表示到达流结尾位置, 流在正常输入输出的情况下结束,会被置为eofbit状态。

通过流的状态函数实现:

bool good() const      //流是goodbit状态,返回true,否则返回false
bool bad() const       //流是badbit状态,返回true,否则返回false
bool fail() const      //流是failbit状态,返回true,否则返回false
bool eof() const       //流是eofbit状态,返回true,否则返回false
if(cin){	//若是cin.good()隐式转换为true,其他状态转换为false

}

封装一下打印流状态的函数:

void printStreamStatus(istream & is){
    cout << "is' goodbit:"  << is.good() << endl;
    cout << "is' badbit:"  <<  is.bad()  << endl;
    cout << "is' failbit:"  << is.fail() << endl;
    cout << "is' eofbit:"  <<  is.eof()  << endl;
}



3.标准输入输出流

#include <iostream>
using std::istream;
using std::ostream;

对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准输入输出,简称标准 I/O

C++标准库定义了三个预定义的标准输入输出流对象,分别是 std::cinstd::coutstd::cerr。它们分别对应于标准输入设备(通常是键盘)、标准输出设备(通常是显示器)和标准错误设备(通常是显示器)。

标准输入、输出的内容包含在头文件iostream中。

有时候会看到通用输入输出流的说法,这是一个更广泛的概念,可以与各种类型的输入输出设备进行
交互,包括标准输入输出设备、文件、网络等。


(1)标准输入流:istream、cin

①恢复流的状态

1.clear():恢复流的状态

cin.clear();

2.ignore():清空缓冲区中的内容

cin.ignore(20, '\n');
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

如果没有进行正确的输入,输入流会进入failbit的状态,无法正常工作,需要恢复流的状态。
查看C++参考文档,需要利用clear和ignore函数配合,实现这个过程

#include <limits>

    if(!cin.good()){
        //恢复流的状态
        cin.clear();
        //清空缓冲区,才能继续使用
        cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        cout << endl;
        printStreamStatus(cin);
    }

3.思考,如何实现输入一个整型数据(如果是非法输入则继续要求输入)
链接:https://github.com/WangEdward1027/iostream/blob/main/InputInt.cpp

void InputInt(int & number){
    cout << "请输入一个int型数据" << endl;
    cin >> number;
    while(!cin.eof()){
        if(cin.bad()){
            cout << "cin has broken!" << endl;
            return;
        }else if(cin.fail()){
            cin.clear();
            cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            cout << "请输入一个int型的数据, 看清楚一点!" << endl;
            cin >> number;
        }else{
            cout << "num:" << number << endl;
            break;
        }
    }
}

4.逗号表达式整体的值为最后一个逗号之后的表达式的值。
逗号表达式前面的内容会执行,但不影响最终判断的条件。

//用逗号表达式重写该函数
void InputInteger(int & num){
    cout << "请输入一个int型数据" << endl;
    //逗号表达式整体的值,为最后一个逗号之后的表达式的值
    //逗号表达式前面的内容会执行,但不影响最终判断的条件。
    while(cin >> num , !cin.eof()){
        if(cin.bad()){
            cout << "cin has broken!" << endl;
            return;
        }else if(cin.fail()){
            cin.clear();
            cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            cout << "请输入一个int型数据" << endl;
        }else{
            cout << "num" << num << endl;
            break;
        }
    }
}

5.判断时,if(cin) 和 if(cin.good())是等价的。
在这里插入图片描述

6.连续输入:输入流运算符会默认以换行符、空格符、制表符作为分隔符

在这里插入图片描述

7.每次输入完成后,返回值为cin对象本身。可用取地址来验证:&cin 和 &(cin >> num) 相等
在这里插入图片描述


(2)缓冲机制

1.为什么引入缓冲区:
解决磁盘和CPU速度不匹配的问题,减少磁盘读写次数,提高计算机运行速度。


2.缓冲机制分为三种类型:全缓冲、行缓冲和不带缓冲。
①全缓冲:在这种情况下,当填满缓冲区后才进行实际 I/O 操作。全缓冲的典型代表是对磁盘文件的读写。
②行缓冲:在这种情况下,当在输入和输出中遇到换行符时,执行真正的 I/O 操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的 I/O 操作。典型代表是cin。
③不带缓冲:也就是不进行缓冲,有多少数据就刷新多少。标准错误输出 cerr是典型代表,这使得出错信息可以直接尽快地显示出来。


(3)标准输出流:ostream、cout

1.ostream 类定义了全局输出流对象 cout,即标准输出,在缓冲区刷新时将数据输出到终端。

2.三种情况刷新输出缓冲区:
(1)程序正常结束
(2)缓冲区满
(3)使用操纵符显式地刷新输出缓冲区
①endl : 刷新缓冲区,并换行
②flush : 直接刷新缓冲区

cout.flush();

3.ostream的拷贝构造函数被删除了, = delete

4.补充:输出流运算符对char *指针有默认重载效果,不会输出地址,而是访问其地址上的内容
在这里插入图片描述


对空指针解引用,会导致中断

#include <iostream> 
using std::cout;
using std::endl;

int main()
{
    char * p = nullptr;
    cout << "nullptr" << endl;
    cout << p << endl;      //对空指针解引用,会引发程序中断
    cout << "over" << endl; //若未打印,则说明程序发生了中断
    return 0;
}

在这里插入图片描述


(4)标准错误流

标准错误流
ostream 类还定义了全局输出流对象 cerr,标准错误流,是无缓冲的,立即输出



4.文件输入输出流:fstream

C++ 对文件进行操作的流类型有三个:
①ifstream:文件输入流
②ofstream:文件输出流
fstream:文件输入输出流


他们的构造函数形式都很类似:

ifstream();
explicit ifstream(const char* filename, openmode mode = ios_base::in);
explicit ifstream(const string & filename, openmode mode = ios_base::in);

ofstream();
explicit ofstream(const char* filename, openmode mode = ios_base::out);
explicit ofstream(const string & filename, openmode mode = ios_base::out);

fstream();
explicit fstream(const char* filename, openmode mode = ios_base::in|out);
explicit fstream(const string & filename, openmode mode = ios_base::in|out);

(1)文件输入流

文件输入流对象的创建:打开文件的三种方式

1.文件输入流的信息传输方向:文件 -> 文件输入流对象的缓冲区 -> 程序中的数据结构

2.打开文件的三种方式

#include <fstream>
using std::ifstream;

void test0(){
	//1.open方式打开
    ifstream ifs; 
    ifs.open("test1.cc");
    
    //2.括号方式打开
    ifstream ifs2("test2.cc"); 
    
    //3.变量方式打开
    string filename = "test3.cc"; 
    ifstream ifs3(filename);   //最常用
	
	//判断文件是否打开成功
    if(!ifs.good()){
        cout << "ifstream open" << filename << "failed"  << endl;
        return;
    }
}

②文件打开模式

根据不同的情况,对文件的读写操作,可以采用不同的文件打开模式。文件模式在 GNU GCC7.4 源码实现中,是用一个叫做 openmode 的枚举类型定义的,它位于 ios_base 类中。

文件模式一共有六种,它们分别是:
①in: 输入,文件将允许做读操作;如果文件不存在,打开失败。默认为in模式
②out: 输出,文件将允许做写操作;如果文件不存在,则直接创建一个
③app: 追加,每次写入前寻位到文件末尾
④ate: 末尾,打开后立即寻位到文件末尾
⑤trunc : 截断,如果打开的文件存在,其内容将被丢弃,其大小被截断为零
⑥binary : 以二进制模式打开

//openmode使用ate模式,打开文件时就让游标处于文件结尾位置,省略一次设置文件游标的过程
ifstream ifs("explicit.cpp", std::ios::ate);

在这里插入图片描述


按行读取:getline
<1>C风格字符串的getline

方法一:使用ifstream类中的成员函数getline,这种方式是兼容C的写法

1.头文件

#include <iostream>
#include <fstream>
using std::istream;
using std::ifstream;

2.函数原型

std::istream& ifstream::getline(char* s, std::streamsize n);
std::istream& ifstream::getline(char* s, std::streamsize n, char delim);

参数:
①s:指向要存储字符到的字符串的指针
②count:s所指向的字符串的大小
③delim:分隔符

3.举例

ifs.getline(buff, sizeof(buff))
#include <iostream> 
#include <fstream>
#include <string>
#include <string.h>
using std::cin;
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;
using std::string;

//方法一:兼容C的写法,使用较少
void test1(){
    ifstream ifs("test.txt");
    if(!ifs.good()){
        cerr << "文件打开失败" << endl;
    }

    char buff[100] = {0};
    while(ifs.getline(buff, sizeof(buff))){
        cout << buff << endl;
        memset(buff, 0, sizeof(buff)); //完成最后一次读取后清空缓冲区buff
    }
}

//只读一行,读取ifs和cin
void test3(){
    ifstream ifs("test.txt");
    char buff[100]= {0};
    ifs.getline(buff, sizeof(buff));
    cout << buff << endl;
    cin.getline(buff, sizeof(buff));
    cout << buff << endl;
}

int main()
{
    test1();
    cout << endl;
    
    test3();
    return 0;
}

<2>C++风格的getline
string line;
getline(cin, line);

方法二:使用<string>提供的getline方法,工作中更常用
传入输入流对象、string、分隔符(默认换行符为分隔符)

1.头文件

#include <iostream>
#include <string>
using std::istream;
using std::string;

2.函数原型

istream& getline(istream& is, string& str);  //省略分隔符,默认使用换行符`\n`作为分隔符
istream& getline(istream& is, string& str, char delim);

3.举例:

getline(ifs,line)
#include <iostream> 
#include <fstream>
#include <string>
#include <string.h>
using std::cin;
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;
using std::string;

//方法二:更方便,工作中更常用
void test2(){
    ifstream ifs("test.txt");
    if(!ifs.good()){
        cerr << "文件打开失败" << endl;
    }

    string line;
    while(getline(ifs,line)){
        cout << line << endl;
    }
}

//只读一行,读取ifs和cin
void test4(){
    ifstream ifs("test.txt");
    string line;
    getline(ifs, line);
    cout << line << endl;;
    getline(cin, line);
    cout << line << endl;;
}

int main()
{
    test2();
    cout << endl;
    
    test4();
    return 0;
}

代码链接:https://github.com/WangEdward1027/CppStream/blob/main/getline2.cpp


④读取指定字节数的内容

1.read

ifs.read(pdata,length);

2.tellg

ifs.tellg();

3.seekg

ifs.seekg(0, std::ios::end);

在这里插入图片描述
在这里插入图片描述

获取文件长度:

//获取文件有多少个字符
ifs.seekg(0, std::ios::end);
size_t length = ifs.tellg();
ifs.seekg(0); //或相对位置  ifs.seekg(0, std::ios::beg); 

char * pdata = new char[length + 1]();
ifs.read(pdata, length);
cout << pdata << endl;

(2)文件输出流

文件输出流:从内存中输出到文件。

#include <fstream>
using std::ofstream;

文件输出流对象绑定的文件可以不存在。如果不存在,会将这个文件创建出来。
如果文件存在,每次写入内容都会将文件中原本的内容清空,再写入内容。

ofstream ofs("result.txt");
if(!ofs){
	cerr << "ofstream open file failed!" << endl;
	return;
}

动态查看指令:

tail 文件名 -F	//动态查看文件内容
ctrl + c  	//退出查看



5.字符串输入输出流

字符串I/O是内存中的字符串对象与字符串输入输出流对象之间做内容传输的数据流,通常用来做格式转换。

C++ 对字符串进行操作的流类型有三个:
①istringstream (字符串输入流)
②ostringstream (字符串输出流)
③stringstream (字符串输入输出流)


(1)字符串输入流

字符串输入流:把输入的字符串的内容拆开

#include <iostream>
#include <sstream>  //头文件
using std::istringstream;

①字符串输入流的应用场景:读取配置文件
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using std::cerr;
using std::cout;
using std::endl;
using std::ifstream;
using std::istringstream;
using std::string;

void readConfig(const string & filename){
    ifstream ifs(filename);
    if(!ifs){
        cerr << "ifstream open file failed" << endl;
        return;
    }
    
    string line;
    string key;
    string value;
    while(getline(ifs,line)){
        //字符串输入流相当于在内存中单独准备一块缓冲区
        //将一行的内容缓存下来
        istringstream iss(line);
        iss >> key >> value;
        cout << key << " -------> " << value << endl;
    }
}

void test0(){
    readConfig("myserver.conf");
}

int main(void){
    test0();
    return 0;
}

(2)字符串输出流

字符串输出流:将各种类型的数据合并为字符串,输出给缓冲区oss

int num = 123, num2 = 456;
ostringstream oss;
//把所有的内容都传给了流对象
oss << "num = " << num << ", num2 = " << num2 << endl;
string res = oss.str(); //str() 返回string类型
cout << res;

.str():返回string类型
.c_str():返回指向C风格字符串的指针 const chat *

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值