C++学习(3) —— C++输入输出流

文章目录

C++输入输出流

  • 输入输出的含义

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

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

  • C++输入输出流机制

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

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

  • C++常用流类型

C++ 的输入与输出包括以下3方面的内容:

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

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

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

常用的输入输出流如下:

类名 作用 头文件
istream 通用输入流 iostream
ostream 通用输出流 iostream
iostream 通用输入输出流 iostream
ifstream 文件输入流 fstream
oftream 文件输出流 fstream
fstream 文件输入输出流 fstream
istringstream 字符串输入流 sstream
ostringstream 字符串输出流 sstream
stringstream 字符串输入输出流 sstream

流的四种状态(重点)

IO 操作与生俱来的一个问题是可能会发生错误,一些错误是可以恢复的,另一些是不可以的。在C++ 标准库中,用 iostate 来表示流的状态,不同的编译器 iostate 的实现可能不一样,不过都有四种状态:

  • badbit表示发生系统级的错误,如不可恢复的读写错误。通常情况下一旦 badbit 被置位,流就无法再使用了。

  • failbit表示发生可恢复的错误,如期望读取一个int数值,却读出一个字符串等错误。这种问题通常是可以修改的,流还可以继续使用。

  • eofbit 表示到达流结尾位置, 流在正常输入输出的情况下结束,会被置为eofbit状态。

  • goodbit表示流处于有效状态。流在有效状态下,才能正常使用。如果 badbit 、 failbit 和 eofbit 任何一个被置位,则流无法正常使用。

这四种状态都定义在类 ios_base(路径:/usr/include/c++/11/bits/ios_base)中,作为其数据成员存在。在 GNU GCC7.4 的源码中,流状态的实现

通过流的状态函数实现

cpp 复制代码
bool good() const      //流是goodbit状态,返回true,否则返回false
bool bad() const       //流是badbit状态,返回true,否则返回false
bool fail() const      //流是failbit状态,返回true,否则返回false
bool eof() const       //流是eofbit状态,返回true,否则返回false

标准输入输出流

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

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

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

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

标准输入流

istream 类定义了一个全局输入流对象,即 cin , 代表的是标准输入,它从标准输入设备(键盘)获取数据,程序中的变量通过流提取符 ">>"(输入流符号) 从流中提取数据。

流提取符 ">>" 从流中提取数据时通常跳过输入流中的空格、 tab 键、换行符等空白字符。只有在输入完数据再按回车键后,该行数据才被送入键盘缓冲区,形成输入流,提取运算符 ">>" 才能从中提取数据。(流的缓冲机制在下一节中学习)

下面来看一个例子,每次从 cin 中获取一个字符:

cpp 复制代码
#include<iostream>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::string;

void check_stream_state(){
    cout << "cin goodbit:" << cin.good() << endl;
    cout << "cin badbit:" << cin.bad() << endl;
    cout << "cin failbit:" << cin.fail() << endl;
    cout << "cin eofbit:" << cin.eof() << endl;
}

void test(){
    check_stream_state();
    int num;
    cout << "------第一次输入-------" << endl;
    cin >> num;
    cout << "num: " << num << endl;
    check_stream_state();
    // 录入一个字符会出错
    cout << "------第二次输入-------" << endl;
     cin >> num;
    cout << "num: " << num << endl;
    check_stream_state();
    // 录入一个浮点数
    cout << "------第三次输入-------" << endl;
    cin >> num;
    cout << "num: " << num << endl;
    check_stream_state();

    cout << "------第四次输入-------" << endl;
    string word;
    cin >> word;
    cout << "word: " << word << endl;
    check_stream_state();


}


int main(){
    test();

      return 0;
}

在运行的时候会发现如果出线一次违法操作,就会直接运行结束,且下面的变量结果都是0.如果没有进行正确的输入,输入流会进入failbit的状态,无法正常工作,需要恢复流的状态。

查看C++参考文档,需要利用clear和ignore函数配合,实现这个过程

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

以下是完整代码,将恢复good状态封装成一个函数,在每次进行输入流的操作时进行检查恢复。

cpp 复制代码
#include<iostream>
#include<cstring>
#include<limits>
using std::cout;
using std::cin;
using std::endl;
using std::string;

void check_stream_state(){
    cout << "cin goodbit:" << cin.good() << endl;
    cout << "cin badbit:" << cin.bad() << endl;
    cout << "cin failbit:" << cin.fail() << endl;
    cout << "cin eofbit:" << cin.eof() << endl;
}

void resume_good(){
    if(!cin.good()){
        cout << "恢复状态" << endl;
    cin.clear();
    // 忽略最多n个字符,或者遇见分隔符
    cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
}

void test(){
    check_stream_state();
    int num;
    cout << "------第一次输入-------" << endl;
    cin >> num;
    cout << "num: " << num << endl;
    check_stream_state();
    resume_good();
    // 录入一个字符会出错
    cout << "------第二次输入-------" << endl;
     cin >> num;
    cout << "num: " << num << endl;
    check_stream_state();
    resume_good();
    // 录入一个浮点数
    cout << "------第三次输入-------" << endl;
    cin >> num;
    cout << "num: " << num << endl;
    check_stream_state();
    resume_good();
    cout << "------第四次输入-------" << endl;
    string word;
    cin >> word;
    cout << "word: " << word << endl;
    check_stream_state();
    resume_good();
}


int main(){
    test();

      return 0;
}

那么我们有了这个判断机制,可以写出更完善的代码,比如要求输入一个整型数据,如果输入违法字符,那么就通过检查标准输入流的状态来进行判断,如果是违法字符就要求重新输入。

cpp 复制代码
#include<iostream>
#include<cstring>
#include<limits>
using std::cout;
using std::cin;
using std::endl;



void test(){
    int num;
    cout << "请输入一个数字:" << endl;
    cin >> num;
    while(!cin.good()){
        cout << "<Input Error> 请输入一个数字:";
        cin.clear();
        cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
        cin >> num;
    }
    cout << "你输入了:" << num << endl;
}

int main(){

    test();
      return 0;
}

输入流运算符会默认以换行符、空格符、制表符作为分隔符,并且cin对象完成一次输入之后,返回值其实就是cin对象本身。

cpp 复制代码
void test(){
    int num, num2;
    //这行代码的执行过程等价于 (cin >> num) >> num2
    cin >> num >> num2;
    cout << num << endl;
    cout << num2 << endl;
}

此外,cin表达式的返回值由于是cin对象本身,所以如果直接将cin置于if条件判断中,会进行隐式转换为true或者false;如果当前流的状态既不是fail()或者bad()状态,则返回true;如果是fail()或者bad()状态,则返回false;

cpp 复制代码
void test(){
    int num;
    cin >> num;
    if(cin){
        cout << "流的状态ok" << endl;
    }
}

缓冲机制

在标准输入输出流的测试中发现,流有着缓冲机制。缓冲区 又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区输出缓冲区

输入或输出的内容会存在流对象对应的缓冲区,在特定情景下会从缓冲区释出。

  • 为什么要引入缓冲区?

    比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

    又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的 CPU 可以处理别的事情。因此缓冲区就是一块内存区,它用在输入输出设备和 CPU 之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU 能够协调工作,避免低速的输入输出设备占用 CPU,解放出 CPU,使其能够高效率工作。

  • 缓冲区要做哪些工作?

从上面的描述中,不难发现缓冲区向上连接了程序的输入输出请求,向下连接了真实的 I/O 操作。作为中间层,必然需要分别处理好与上下两层之间的接口,以及要处理好上下两层之间的协作。

输入或输出的内容会存在流对象对应的缓冲区,在特定情景下会从缓冲区释出。

  • 缓冲机制

    缓冲机制分为三种类型:全缓冲、行缓冲和不带缓冲

    全缓冲:在这种情况下,当填满缓冲区后才进行实际 I/O 操作。全缓冲的典型代表是对磁盘文件的读写。

    行缓冲:在这种情况下,当在输入和输出中遇到换行符时,执行真正的 I/O 操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的 I/O 操作。典型代表是cin。

    不带缓冲:也就是不进行缓冲,有多少数据就刷新多少。标准错误输出 cerr是典型代表,这使得出错信息可以直接尽快地显示出来。

标准输出流

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

如下几种情况会导致输出缓冲区内容被刷新:

  1. 程序正常结束

马上输出了1025个a

cpp 复制代码
void test(){
    for(int i = 0; i < 1025; ++i){
        cout << 'a';
    }
}
  1. 缓冲区满

马上输出了1024个a,等待2秒后输出了最后一个a

(在实验环境中cout对象的默认缓冲区大小是1024个字节,缓冲区满了还继续传输内容,就会刷新出了当前缓冲区中所有内容,后面还有一个字符,就要等程序正常结束时刷新出来)

include <unistd.h>

cpp 复制代码
void test(){
    for(int i = 0; i < 1025; ++i){
        cout << 'a';
    }
    sleep(3);
}
  1. 使用操纵符显式地刷新输出缓冲区,如endl ;

加上endl这种操作符,直接输出了5个a,等待2秒程序结束;如果不加endl,等待2秒程序结束时才会输出5个a

cpp 复制代码
void test(){
    for(int i = 0; i < 5; ++i){
        cout << 'a' << endl;
    }
    sleep(3);
}

------ 查看ostream头文件中endl的定义(刷新缓冲区 + 换行)

来看一个简单的例子:在使用cout时,如果在输出流语句末尾使用了endl函数,会进行换行,并刷新缓冲区

cpp 复制代码
void test0(){
    for(int i = 0; i < 1025; ++i){
        cout << 'a' << endl; 
    }
}

如果在使用cout时,没有使用endl函数,键盘输入的内容会存在输出流对象的缓冲区中,当缓冲区满或遇到换行符时,将缓冲区刷新,内容传输到终端显示。可使用sleep函数查看缓冲的效果。

cpp 复制代码
#include <unistd.h>
void test0(){
    for(int i = 0; i < 1024; ++i){
        cout << 'a'; 
    }
    sleep(2);
    cout << 'b';
    sleep(2);
}

GCC中标准输出流的默认缓冲区大小就是1024个字节。

如果不用sleep函数,即使没有endl或换行符,所有内容依然是直接输出

------因为程序执行完时也会刷新缓冲区。

  • 关于操作符

endl : 用来完成换行,并刷新缓冲区

flush : 用来直接刷新缓冲区的 cout.flush();

  • 标准错误流

ostream 类还定义了全局输出流对象 cerr,标准错误流(不带缓冲)

试试看如下的代码运行会有什么效果

cpp 复制代码
#include <unistd.h>
void test1(){
	cerr << 1;
	cout << 3;
	sleep(2);                                                           
}

文件输入输出流(重点)

所谓"文件",一般指存储在外部介质上数据的集合。一批数据是以文件的形式存放在外部介质上的。操作系统是以文件为单位对数据进行管理的。要向外部介质上存储数据也必须先建立一个文件(以文件名标识),才能向它输出数据。外存文件包括磁盘文件、光盘文件和U盘文件。目前使用最广泛的是磁盘文件。

文件流是以外存文件为输入输出对象的数据流。

文件输入流 是从外存文件流向内存的数据,文件输出流 是从内存流向外存文件的数据。每一个文件流都有一个内存缓冲区与之对应。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。

C++ 对文件进行操作的流类型有三个:

ifstream(文件输入流)

ofstream(文件输出流)

fstream (文件输入输出流)

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

cpp 复制代码
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);

构造函数中的explicit关键字表示禁止隐式类型转换。比如下面案例中的Point pt = 5;便是使用了隐式类型转换,实际上,编译器会内部处理为Point pt = Point(5,0).加上explicit关键字,表示禁止隐式类型转换。

cpp 复制代码
#include <iostream>
using std::cout;
using std::endl;

class Point{
private:
 int _x;
 int _y;
public:
 explicit
 Point(int x = 0, int y = 0)
 :_x(x),
  _y(y)
 {
     cout << "invoke constructor" << endl;
 }

 void print(){
     cout << "_x=" << _x << " and _y=" << _y << endl;
 }
};

void test(){
 Point pt = 5;
 pt.print();
}

隐式类型转换没有绝对的好与不好,不同的场景下,可能效果不同。比如string s = "hello world".这里面便是使用了隐式类型转换。

文件输入流

文件输入流对象的创建

首先我们要明确使用文件输入流的信息传输方向:文件 --》 文件输入流对象的缓冲区 --》 程序中的数据结构

根据上述的说明,我们可以将输入流对象的创建分为两类:

  1. 可以使用无参构造创建ifstream对象,再使用open函数将这个文件输入流对象与文件绑定(若文件不存在,则文件输入流进入failbit状态);

  2. 也可以使用有参构造创建ifstream对象,在创建时就将流对象与文件绑定,后续操作这个流对象就可以对文件进行相应操作。

通过参考文档中对ifstream的构造函数的描述,文件输入流对象的有参构造需要输入文件名,可以指定打开模式(不指定则使用in模式,为读打开)

cpp 复制代码
//引入头文件
#include <fstream>
void test0(){
    ifstream ifs;
    ifs.open("test1.cc");
    
    ifstream ifs2("test2.cc");
    
    string filename = "test3.cc";
    ifstream ifs3(filename);
}
cpp 复制代码
#include <iostream>
#include <string>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;
using std::string;

void test(){
    ifstream ifs("temp.txt");
    if(!ifs.good()){
        cerr << "ifstream is not good" << endl;
        return;
    }

    string word;
    while(ifs >> word){
        cout << word;
    }
    cout << endl;
    ifs.close();
}

int main()
{
    test();
    return 0;
}
  • 文件模式

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

in: 输入,文件将允许做读操作;如果文件不存在,打开失败

out : 输出,文件将允许做写操作;如果文件不存在,则直接创建一个

app : 追加,写入将始终发生在文件的末尾,强制所有写入都发生在文件末尾

ate : 末尾,立即定位到文件的末尾,但是后续可以自由移动指针到其他位置进行读写

trunc : 截断,如果打开的文件存在,其内容将被丢弃,其大小被截断为零

binary : 二进制,读取或写入文件的数据为二进制形式

按行读取

使用<string>提供的getline方法,工作中更常用

cpp 复制代码
#include <iostream>
#include <string>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;
using std::string;

void test(){
    ifstream ifs("1_buffer.cpp");
    if(!ifs.good()){
        cerr << "ifstream is not good" << endl;
        return;
    }

    string word;
    while(getline(ifs, word)){
        cout << word << endl;
    }
    cout << endl;
    ifs.close();
}

int main()
{
    test();
    return 0;
}

文件输出流

文件输出流的作用是将流对象保存的内容传输给文件

ofstream对象的创建与ifstream对象的创建类似

cpp 复制代码
#include <fstream>
void test0(){
    ofstream ofs;
    ofs.open("test1.cc");
    
    ofstream ofs2("test2.cc");
    
    string filename = "test3.cc";
    ofstream ofs3(filename);
}

推测一下,如果文件输出流对象绑定的文件不存在,可以吗?

------ 可以,如果文件不存在,就创建出来

  • 通过输出流运算符写内容

ofstream对象绑定文件后,可以往该文件中写入内容

cpp 复制代码
string filename = "test3.cc";
ofstream ofs3(filename);

string line("hello,world!\n");
ofs << line; 

ofs.close();

内容传输的过程是string中的内容传给ofs对象,再传给这个对象绑定的文件。

但是我们会发现进行多次写入,并没有保留下多次的内容,因为这种创建方式会使打开模式默认为std::ios::out,每次都会清空文件的内容

为了实现在文件流结尾追加写入内容的效果,可以在创建流对象时指定打开模式为std::ios::app(追加模式)

cpp 复制代码
string filename = "test3.cc";
ofstream ofs3(filename,std::ios::app);
  • 通过write函数写内容

除了使用输出流运算符<< 将内容传输给文件输出流对象(传给ofstream对象就是将内容传到其绑定的文件中),还可以使用write函数进行传输

cpp 复制代码
char buff[100] = "hello,world!";
ofs.write(buff,strlen(buff));
  • 动态查看指令

为了更方便地查看多次写入的效果(动态查看文件的内容)可以使用指令

cpp 复制代码
tail 文件名 -F   //动态查看文件内容

ctrl + c        //退出查看

读取指定字节数的内容(文件输入输出流)

案例:使用键盘的录入功能,录入5个数字,并且存入文件中,随后读取文件的数据,将之前录入的数据显示出来。但是该代码的演示结果有一些小问题。

cpp 复制代码
#include <iostream>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
using std::cin;
using std::fstream;

void test(){
    fstream fs("num.txt");
    if(!fs.good()){
        cerr << "fs is not good" << endl;
        return;
    }
    //写入数据到文件中
    int number;
    for(size_t i = 0;i < 5;i++){
        cin >> number;
        fs << number << " ";
    }
    //读取数据
    int number2;
    for(size_t i = 0;i < 5;i++){
        fs >> number2;
        cout << number2 << " ";
    }
}

int main()
{
    test();
    return 0;
}

从文件中读取内容时存在一个文件游标,读取是从文件游标的位置开始读取的。tellg就是用来获取游标位置的,而seekg则是用来设置游标位置的。

调用seekg时有两种方式,一种是绝对位置(比如将游标设为流的开始位置,可以直接传参数0);一种是相对位置,传入偏移量和基准点------第一个参数:相对基准点需要向前偏移则传入负数,不偏移则传入0,需要向后偏移则传入正数。第二个参数格式为std::ios::beg (以流的开始位置为例)

如图示:

cpp 复制代码
#include <iostream>
#include <fstream>
#include <string>
using std::cout;
using std::endl;
using std::cerr;
using std::cin;
using std::fstream;
using std::string;

void test(){
    fstream fs("num.txt");
    if(!fs.good()){
        cerr << "fs is not good" << endl;
        return;
    }
    //写入数据到文件中
    int number;
    for(size_t i = 0;i < 5;i++){
        cin >> number;
        fs << number << " ";
    }
    //读取数据
    int index = fs.tellg();
    cout << index << endl;

    //fs.seekg(0);
    fs.seekg(0,std::ios::beg);
    int index2 = fs.tellg();
    cout << index2 << endl;

    string word;
    for(size_t i = 0;i < 5;i++){
        fs >> word;
        cout << word << " ";
    }
}

int main()
{
    test();
    return 0;
}

案例2:读取一个文本文件中指定下标区域之间的全部数据,并将数据写入到一个字符串中

read函数 + seekg函数 + tellg函数

通过文件输入流对象读取到的内容交给字符数组,同时需要传入要读取的字符数

cpp 复制代码
#include <iostream>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
using std::fstream;

void test(){
    fstream fs("num.txt");
    if(!fs.good()){
        cerr << "fs is not good" << endl;
        exit(-1);
    }
    //将游标移动到末尾
    fs.seekg(0, std::ios::end);
    //统计出文本的长度
    size_t index = fs.tellg();
    cout << index << endl;
    //再次将文件移动到最开始的位置
    fs.seekg(0, std::ios::beg);
    char * pstr = new char[index + 1]();
    //读取指定长度的数据到字符串中
    fs.read(pstr, index);
    cout << pstr << "---" << endl;
    delete [] pstr;
    pstr = nullptr;
}

int main()
{
    test();
    return 0;
}

还可以在创建输入流对象时指定ate模式,游标默认在末尾,省去了需要将游标移动到末尾统计文件长度的步骤。

cpp 复制代码
#include <iostream>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;

void test(){
    ifstream fs("num.txt",std::ios::ate);
    if(!fs.good()){
        cerr << "fs is not good" << endl;
        return;
    }
    //统计出文本的长度
    size_t index = fs.tellg();
    cout << index << endl;
}

int main()
{
    test();
    return 0;
}

还可以在创建输出流对象时指定app模式,游标默认在末尾,则此时是追加模式(不带app则是截断模式,每次都会重新写入数据到文件中)。需要注意的是,输出流对象需要使用tellp()来统计文本的长度

cpp 复制代码
#include <iostream>
#include <fstream>
using std::cout;
using std::endl;
using std::cerr;
using std::ofstream;

void test(){
    ofstream fs("num.txt",std::ios::app);
    if(!fs.good()){
        cerr << "fs is not good" << endl;
        return;
    }
    //统计出文本的长度
    //输出流对象需要使用tellp
    size_t index = fs.tellp();
    cout << index << endl;
}

int main()
{
    test();
    return 0;
}

字符串输入输出流

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

C++ 对字符串进行操作的流类型有三个:

istringstream (字符串输入流)

ostringstream (字符串输出流)

stringstream (字符串输入输出流)

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

cpp 复制代码
istringstream(): istringstream(ios_base::in) { }
explicit istringstream(openmode mode = ios_base::in);
explicit istringstream(const string& str, openmode mode = ios_base::in);

ostringstream(): ostringstream(ios_base::out) { }
explicit ostringstream(openmode mode = ios_base::out);
explicit ostringstream(const string& str, openmode mode = ios_base::out);

stringstream(): stringstream(in|out) { }
explicit stringstream(openmode mode = ios_base::in|ios_base::out);
explicit stringstream(const string& str, openmode mode = ios_base::in|ios_base::out);

字符串输入流

将字符串的内容传输给字符串输入流对象,再通过这个对象进行字符串的处理(解析)

创建字符串输入流对象时传入C++字符串,字符串的内容就被保存在了输入流对象的缓冲区中。之后可以通过输入流运算符将字符串内容输出给不同的变量,起到了字符串分隔的作用。

------如下,将字符串s的内容传给了两个int型数据

cpp 复制代码
void test0(){
    string s("123 456");
    int num = 0;
    int num2 = 0;
    //将字符串内容传递给了字符串输入流对象  
    istringstream iss(s);
    iss >> num >> num2;
    cout << "num:" << num << endl;
    cout << "num2:" << num2 << endl;
}

因为输入流运算符会默认以空格符作为分隔符,字符串123 456中含有一个空格符,那么传输时会将空格前的123传给num,空格后的456传给num2,因为num和num2是int型数据,所以编译器会以int型数据来理解缓冲区释出的内容,将num和num2赋值为123和456

字符串输入流通常用来处理字符串内容,比如读取配置文件

cpp 复制代码
//myserver.conf
ip 192.168.0.0
port 8888
dir ~/53th/day06
    
//readConf.cc
//当函数参数为对象时,首先应当考虑使用const引用的形式作为形参
//一方面引用可以避免实参和形参结合时的复制
//另一方面const的意义在于避免函数体内通过引用修改实参,同时也可以绑定右值,也就是临时对象
void readConfig(const string & filename){
    ifstream ifs(filename);
    if(!ifs.good()){
        cout << "open file fail!" << endl;
        return;
    }
    
    string line;
    string key, value;
    while(getline(ifs,line)){
        istringstream iss(line);
        //字符串输入流默认以空格为分隔符
        iss >> key >> value;
        cout << key << " -----> " << value << endl; 
    }
}

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

字符串输出流

通常的用途就是将各种类型的数据转换成字符串类型

cpp 复制代码
void test0(){
    int num = 123, num2 = 456;
    ostringstream oss;
    //把所有的内容都传给了字符串输出流对象
    oss << "num = " << num << " , num2 = " << num2 << endl;
    cout << oss.str() << endl;
}

将字符串、int型数据、字符串、int型数据统统传给了字符串输出流对象,存在其缓冲区中,利用它的str函数,全部转为string类型并完成拼接。

文章大纲

相关推荐
十月的皮皮1 小时前
C语言学习学习笔记20260704-中缀表达式求值(双栈法)
c语言·笔记·学习
CAU界编程小白1 小时前
CAU抢课脚本
c++·脚本
MOONICK1 小时前
windows原生条件变量支持
c++·windows
汉克老师2 小时前
GESP2026年6月认证C++二级( 第三部分编程题(1、完全平方数计数))精讲
c++·循环·枚举算法·gesp2级·平方数·逆向枚举·区间判断
wuminyu2 小时前
markword在高并发场景下变化剖析
java·linux·c语言·jvm·c++
星夜夏空992 小时前
C++学习(1) ——C与C++
c语言·c++·学习
旖-旎2 小时前
QT界面优化(6)
开发语言·c++·qt
24计网1王仔寿2 小时前
Linux 系统运维全栈学习路线|从 Shell 脚本到容器云 OpenStack 完整学习指南
linux·学习·openstack
UP_Continue2 小时前
AutoCAD--图形命令和选项
c++·autopilot