『 C++ 』IO流

文章目录

    • IO流概述
    • iostream 的标准对象
    • C++流和C标准库I/O函数的同步 sync_with_stdio
    • fstream 文件流
      • 文件流的打开标志
      • 二进制读写
      • 二进制读写的浅拷贝问题
      • 文本读写
    • 字符串流
    • 注意


IO流概述

请添加图片描述

流是指数据的有序传输序列,路表示数据从一个地方流向另一个地方的过程,流可以是输入流也可以是输出流,具体取决于数据的流动方向;

  • 输入流

    数据从外部设备(文件,键盘等)流入程序中;

  • 输出流

    数据从程序流向外部设备(如显示器,文件等);

IO流是指用于处理输入输出操作的流,C++中的IO流用于程序与外部环境(用户,文件系统,网络等)之间交换数据的机制;

IO流通过标准库中的一组类和对象实现允许程序员以统一的方式处理不同类型的输入输出设备;

  • <ios>

    <ios>C++中的一个基础头文件,包括了所有输入输出流类的功能;

    其并不是一个具体的六类型,而是提供了一些基类,如ios_baseios(basic_ios);

    是所有输入输出流类的基石;

    <ios>中的类定义了流对象的状态,格式化标志,异常处理等通用功能;

  • <istream>

    该头文件定义了istream类及相关的输入流操作;

    流类型是输入流;

    其作用为istream类用于处理从输入设备(键盘,文件等)读取数据的操作;

    标准输入流对象cin就是istream类的实例;

  • <iostream>

    这个头文件包含了输入输出流的基本类,包括istreamostream;

    流类型是输入输出流,其既包含了输入流也包含了输出流;

    iostream类是一个组合类,继承自istreamostream,可以同时进行输入和输出操作,是一个被棱形继承的类;

    该头文件通常用来处理标准输入输出流的操作(如cincout);

  • <ostream>

    这个头文件定义了处理输出操作的ostream类;

    流类型是输出流;

    ostream类用于将数据输出到输出设备,如屏幕,文件等;

    标准输出流对象cout,标准错误流对象cerr,标准日志流对象clog均为该类的一个实例;

  • <streambuf>

    这个头文件定义了一个streambuf类,这是所有流类的基础缓冲区类;

    流类型是流缓冲区;

    streambufiostream,ifstream,ofstream等类的核心组成部分,负责具体的数据存取操作;

    streambuf通常由更高层次的流类,如istream,ostream使用,但它也可悲用户自定义以实现特殊流的缓冲区操作,如自定义的文件格式或网络流操作;

  • <fstream>

    这个头文件定义了处理文件输入输出的流类,包括ifstream,ofstreamfstream;

    流类型是文件流;

    ifstream是输入文件流类,用于从文件读取数据;

    ofstream是输出文件流类,用于向文件写入数据;

    fstream是读写文件流类,其与iostream相同既具有读也具有写的功能;

  • <sstream>

    这个头文件定义了用于操作字符串的流类,包括istringstream,ostringstreamstringstream;

    流类型是字符串流;

    istringstream用于从字符串读取数据,ostringstream用于将数据写入字符串,stringstream则用于同时进行字符串的读写操作;

这一系列的库用于对标C语言的一系列操作,一个面向过程一个面向对象;

<iostream>主要对标C语言中的printf,scanf等接口,用于处理从输入设备读取数据的操作与处理需要写入至输出设备的数据;

<fstream>对标C语言中的fprintf,fscanf等接口,用于处理向文件内写入与处理从文件中读取的数据;

<sstream>对标C语言中的sprintfsscanf等接口,用于处理字符串输入输出的流类;


iostream 的标准对象

请添加图片描述

C++标准库中iostream头文件定义了一些常用的标准流对象,这些对象在全局范围内可用,用于处理常见的输入输出任务;

  • cin

    标准输入流对象;

    用于从标准输入设备中读取数据(例如键盘);

    cinistream类型的一个实例,支持多种输入操作,可以从输入缓冲区中读取字符,整数,浮点数,字符串等不同类型的数据;

    cin的输入操作是通过提取运算符>>来完成的,这个运算符也被称为 “流提取” ;

    cin提供了一系列的接口(具体参考 istream - C++ Reference (cplusplus.com)):

    但最常用的接口还是operator >>;

    arithmetic types (1)	
    istream& operator>> (bool& val);
    istream& operator>> (short& val);
    istream& operator>> (unsigned short& val);
    istream& operator>> (int& val);
    istream& operator>> (unsigned int& val);
    istream& operator>> (long& val);
    istream& operator>> (unsigned long& val);
    istream& operator>> (long long& val);
    istream& operator>> (unsigned long long& val);
    istream& operator>> (float& val);
    istream& operator>> (double& val);
    istream& operator>> (long double& val);
    istream& operator>> (void*& val);
    stream buffers (2)	
    istream& operator>> (streambuf* sb );
    manipulators (3)	
    istream& operator>> (istream& (*pf)(istream&));
    istream& operator>> (ios& (*pf)(ios&));
    istream& operator>> (ios_base& (*pf)(ios_base&));
    

    这个操作符用于从输入流中提取数据并存储到对应的变量中;

    >>操作符会根据变量的类型自动进行推导,这种操作可以针对多种基本数据类型(内置类型,布尔值,容器等)以及一些特殊的类型(指针,streambuf对象等)进行处理;

    除此之外cin作为istream的实例,还提供了其他的成员函数,如getline(),ignore(),get()等;

    #include <iostream>int main() {int num;char ch;std::cout << "Enter a number: ";std::cin >> num;// 输出读取到的数字std::cout << "You entered the number: " << num << std::endl;// 忽略输入缓冲区中的下一个字符(通常是换行符)std::cin.ignore(1, '\n');std::cout << "Enter a character: ";std::cin.get(ch);std::cout << "You entered the character: " << ch << std::endl;return 0;
    }/*运行结果为:$ ./mytest Enter a number: 42You entered the number: 42Enter a character: aYou entered the character: a*/
    

    在这段代码中使用流提取读取一个整型数据,调用cin.ignore()成员函数忽略一个字符,即\n;

    忽略该字符后该字符不会被下面的cin.get()读取从而程序不会出现不符合需求的现象,否则\n将会被cin.get()读取;

    通常情况下这些函数在使用时查看对应的文档即可;

    • 使用cin进行循环输入

      在一些OJ题目中需要循环进行输入,这种情况下可以使用while(operator>> (std::cin,variable))的方式进行输入;

      int main() {string s1;
      while (cin>>s1) {
      //  while (operator>>(cin, s1)) {cout << s1 << endl;}return 0;
      }/*$ ./mytest hellohelloworldworld^Z[1]+  Stopped                 ./mytest
      */
      

      这里的operator>>其原型为:

      istream& operator>> (istream& is , string& str);
      

      其返回的是istream&的输入流类型引用;

      这里返回的输入流类型引用可以作为判断条件的原因是该类有一个operator bool()的重载,来判断流是否出现错误;

      同时,istream还有一个operator!(),它返回一个布尔值,指示流是否处于错误状态;

      因此当输入成功时,条件为true,循环继续;

      当输入失败时,条件为false,循环终止;

  • cout

    标准输出流对象;

    用于将数据输出至输出设备中(如显示器);

    coutostream类型的一个实例,支持多种输出和写入操作,可以将缓冲区中的整数,浮点数,字符串等不同类型的数据输出(写入)至对应的设备或文件中;

    cout的输出操作是通过插入运算符<<来完成的,这个运算符也被称为 “流插入” ;

    cout提供了一系列的接口(具体参考ostream - C++ Reference (cplusplus.com)):

    cin相同该对象最常用的成员函数为operator <<;

    arithmetic types (1)	
    ostream& operator<< (bool val);
    ostream& operator<< (short val);
    ostream& operator<< (unsigned short val);
    ostream& operator<< (int val);
    ostream& operator<< (unsigned int val);
    ostream& operator<< (long val);
    ostream& operator<< (unsigned long val);
    ostream& operator<< (long long val);
    ostream& operator<< (unsigned long long val);
    ostream& operator<< (float val);
    ostream& operator<< (double val);
    ostream& operator<< (long double val);
    ostream& operator<< (void* val);
    stream buffers (2)	
    ostream& operator<< (streambuf* sb );
    manipulators (3)	
    ostream& operator<< (ostream& (*pf)(ostream&));
    ostream& operator<< (ios& (*pf)(ios&));
    ostream& operator<< (ios_base& (*pf)(ios_base&));
    

    该运算符被称为 “流插入” 运算符,将数据从右侧的表达式插入到左侧的输入流中,即std::cout;

    同样的<<操作符会根据变量的类型自动进行推导,这种操作可以针对多种基本数据类型(内置类型,布尔值,容器等)以及一些重载了operator<<函数的自定义类型;

    coutostream的实例,继承了其原生的一些成员和接口,如刷新缓冲区的内容的flush(),设置浮点数的输出精度的precision()等;

    int main() {std::cout.precision(3);std::cout << 3.14159265 << std::endl;std::cout << "Processing..." << std::flush;// 一些耗时的操作std::cout << "Done!" << std::endl;return 0;
    }
    /*运行结果为:$ ./mytest 3.14Processing...Done!
    */
    

    但通常情况下由于C++C 兼容,编写C++程序时常常使用CC++混编的方式,如在使用精度控制的时候就可以使用C语言的精度控制,若是遇到需要使用这些接口的场合查看文档即可;

    int main() {double d = 3.1415626;printf("d = %.2f\n", d);return 0;
    }
    /*运行结果为:$ ./mytest d = 3.14	
    */
    
  • cerr,clog

    cerr,clogcout一样都是ostream类的一个实例;

    其继承了ostream的各个成员及接口;

    本质上cerrclog分别都是用于打印错误信息与打印日志信息的;

    • cerr

      cerr是不带缓冲的,这意味着当使用cerr打印一个错误信息时该错误信息将被直接打印而不会被存储在缓冲区中,使使用者能够在发生error时第一时间发现对应的错误信息;

      std::cerr << "Error: Something went wrong!" << std::endl;
      
    • clog

      clogcerr不同,默认带缓冲,这意味着当使用clog打印日志信息时日志信息将先被存储在缓冲区中;

      直到缓冲区被写满或是显示调用刷新缓冲区才会将对应的日志信息进行打印;

      std::clog << "Log: Application started" << std::endl;
      

    cerrclog默认都是绑定到标准错误输出流stderr的,因此通常会直接被显示在终端中;

    如果需要也可以调用其成员函数rebuf()将输出重定向到文件或是其他输出设备;

    #include <iostream>
    #include <fstream>int main() {std::ofstream error_file("errors.log"); // 实例化对应的 ofstream 对象std::ofstream log_file("logs.log");std::cerr.rdbuf(error_file.rdbuf()); // 重定向 cerr 到 error_file std::clog.rdbuf(log_file.rdbuf());   // 重定向 clog 到 log_filestd::cerr << "Error: This will go to errors.log" << std::endl;std::clog << "Log: This will go to logs.log" << std::endl;return 0;
    }
    

C++流和C标准库I/O函数的同步 sync_with_stdio

请添加图片描述

sync_with_stdio是C++标准库中用于控制C++流和C标准库I/O函数(如printf,scanf)之间同步行为的函数;

默认情况下C++流(如cin,cout)与C标准库的I/O函数是同步的,意味着每次进行输入或是输出操作时C++流都会刷新其内部缓冲区并与C标准库的I/O缓冲区同步;

这种同步操作是为了确保使用 C++流C标准库 I/O函数时,输出结果的一致,但这种同步操作也会带来一定的性能开销,特别是在需要频繁进行I/O操作的情况下;

可以使用std::ios::sync_with_stdio(false)来关闭 C++流C标准库 I/O函数之间的同步;

关闭同步后,C++流不再与其内部缓冲区和C标准库I/O缓冲区进行同步;

这可以提高I/O操作的效率,特比是对于大量数据的输入和输出;

  • 注意事项

    关闭同步之后,如果在程序中混合使用 C++流C标准库 I/O函数时需要小心,以确保不会出现数据竞争或者输出顺序混乱的问题;

    一旦关闭同步后就无法再重新开启同步;

    int main() {// 关闭 C++ 流和 C 标准库 I/O 函数之间的同步std::ios::sync_with_stdio(false);int age = 25;// 使用 C++ 流输出提示信息std::cout << "请输入您的年龄: ";// 使用 C 标准库函数读取用户输入scanf("%d", &age);// 使用 C++ 流输出用户输入的年龄std::cout << "您的年龄是: " << age << std::endl;return 0;
    }
    /*运行结果为:$ ./mytest 27请输入您的年龄: 您的年龄是: 27
    */
    

    在这个例子中再关闭同步之后先使用std::cout输出提示信息,然后使用std::scanf读取用户输入;

    由于关闭了同步,std::cout的输出缓冲区可能没有被立即刷新到屏幕上;

    因此当程序执行到scanf时用户可能看不到提示信息导致程序行为异常;


fstream 文件流

请添加图片描述

C++提供了一个文件流用于处理文件输入输出的对象,可以从文件中读取数据到程序中或者将程序中的数据写到文件中;

C++标准库提供了三种主要的文件流类:

  • ofstream

    用于向文件写入数据(输出);

  • ifstream

    用于从文件读取数据(输入);

  • fstream

    用于同时进行文件的读写操作;

这三个流类存在对应的函数get/read/>>put/write/<<分别对标C语言的fputc/fwrite/fprintffgetc/fread/fscanf;

提供三种主要的文件流类主要是支持C++的面向对象,即可以支持将类对象中对应的数据写入至文件当中,C语言只能支持将内置类型写入文件内;

流插入和流提取能够更好的支持内置类型和自定义类型;


文件流的打开标志

请添加图片描述

在C++中打开文件时通常使用fstream,ifstreamofstream来打开一个文件流,本质上是定义一个文件流对象;

fstream为例;

文件流对象可以通过open成员函数传入一个const char*字符串或是const string&类型作为文件名打开;

也可以使用构造函数传入一个const char*字符串或是const string&对象作为文件名将文件打开;

/* open 成员函数声名 */
void open (const char* filename,ios_base::openmode mode = ios_base::in | ios_base::out);
void open (const string& filename,ios_base::openmode mode = ios_base::in | ios_base::out);/* 构造函数 */
explicit fstream (const char* filename,ios_base::openmode mode = ios_base::in | ios_base::out);
explicit fstream (const string& filename,ios_base::openmode mode = ios_base::in | ios_base::out);

其中mode表示打开文件时的模式,可以使用一些标志来控制文件流的行为;

这些标志与Linux底层的文件打开接口相似,以二进制标志位的方式标明文件的打开模式,即mode = a | b | c;

常见的标识有:

  • in

    以输入模式打开(读取);

  • out

    以输出模式打开文件(写入),如果文件不存在将创建文件;

    如果文件存在则清空文件内容(除非另有其他标志影响,如app);

  • binary

    以二进制模式打开文件,这意味着数据将以字节流的形式读写,不进行任何格式的转换;

  • ate

    打开文件后,将文件指针定位到文件末尾,但可以在文件的任意位置读写数据;

  • app

    以追加模式打开文件,数据写入时会自动定位到文件末尾,不会覆盖文件中的现有内容;

  • trunc

    如果文件存在,打开时会清空其内容,这是out模式的默认行为;

关闭文件时调用其close成员函数即可;

打开文件后进行读写时同样调用对应的成员函数,writeread;

  • write

    ostream& write (const char* s, streamsize n);
    

    这里的const char*类型并不表示将其作为字符串读取,而是取到该数据的地址后将其以二进制的形式写至文件中,写入大小为streamsize n;

  • read

    istream& read (char* s, streamsize n);
    

    这里的char *类型同样的是以二进制的形式写进并写至对应的内存中,streamsize n表示需要读取文件的大小;


二进制读写

请添加图片描述

二进制读写操作是指以二进制格式从文件中读取或向文件中写入数据;

这种方式直接操作数据的内存表示,更加高效的且没有数据格式的转换,特别适合处理如图像,音频,视频文件等需要保持精度数据格式的文件;

在二进制模式下,数据是以字节流的形式直接读写的,这意味着数据从内存传输到文件或者从文件传输到内存时不会进行任何转换;

但由于不经过任何格式的转换且在内存中数据具有类型而在磁盘/硬盘中数据不存在类型所以写入后无法在磁盘中直接进行查看(数据表示不同);

// Date类的定义,包含友元函数声明和私有成员变量
class Date {// 友元函数声明,允许<<运算符访问Date类的私有成员friend ostream& operator<<(ostream& out, const Date& d);// 友元函数声明,允许>>运算符访问Date类的私有成员friend istream& operator>>(istream& in, Date& d);public:// 构造函数,默认值为1年1月1日Date(int year = 1, int month = 1, int day = 1): year_(year), month_(month), day_(day) {}// bool类型转换函数,当year_为0时返回false,否则返回trueoperator bool() {if (year_ == 0) {return false;}return true;}// 打印操作  void Print(){printf("year= %d ,month= %d ,day= %d\n",year_,month_,day_);}private:// 年份int year_;// 月份int month_;// 日期int day_;
};// 重载<<运算符,用于将Date对象输出到ostream
ostream& operator<<(ostream& out, const Date& d) {out << d.year_ << " - " << d.month_ << " - " << d.day_ << endl;return out;
}// 重载>>运算符,用于从istream输入流中读取Date对象
istream& operator>>(istream& in, Date& d) {in >> d.year_ >> d.month_ >> d.day_;return in;
}// 定义一个结构体testClass,包含成员变量_d1,_d2和_date
struct testClass {char _s1[32];  // 字符数组作为字符串,用于存储某些数据double _d2;  // 双精度浮点类型变量,用于存储某些数据Date _date;  // Date类型变量,用于存储日期
};class BinIO {public:// 构造函数 传入一个 const char* 类型作为文件名BinIO(const char* filename) : filename_(filename) {}// 写void Write(const testClass& wt) {// fstream 实例化 一个对象ofs用于打开文件(以二进制的形式)用于写入操作fstream ofs(filename_, fstream::out | fstream::binary);// 调用write成员函数进行写入ofs.write((const char*)&wt,sizeof(wt));}// 读void Read(testClass& rt) {// fstream 实例化 一个对象ifs用于打开文件(以二进制的形式)用于读取操作fstream ifs(filename_, fstream::in | fstream::binary);// 调用read成员函数进行读取ifs.read((char*)&rt,sizeof(rt));}private:string filename_;  // 文件名
};

这段代码定义了一个用于处理日期的Date类型和一个包含日期及其他数据的结构testClass,并实现了一个用于二进制文件读写的类BinIO;

其中Date类重载了>>运算符和<<运算符使得Date对象可以与流(如coutcin)直接交互;

BinIO用于文件的二进制读写操作;

  • int main() {// 创建一个 testClass 对象 t1,初始化 _d1 为 "hello world",// _d2 为 3.14,_date 为 2001年1月1日testClass t1{"hello world", 3.14, {2001, 1, 1}};// 创建一个 BinIO 对象 b,初始化文件名为 "log.txt"BinIO b("log.txt");// 将 testClass 对象 t1 以二进制形式写入到 "log.txt" 文件中b.Write(t1);return 0;  // 返回 0,表示程序正常结束
    }
    

    这段代码的主要功能是将一个 testClass 对象以二进制形式写入到文件 log.txt 中。

    • 首先使用初始化列表创建了一个 testClass 对象 t1;

      t1_d1 成员变量为字符串 "hello world";

      _d23.14_date 成员为 2001年1月1日;

    • 然后创建了一个 BinIO 对象 b,并将文件名 "log.txt" 传递给它,准备进行文件操作;

    • 接着调用 BinIO 类的 Write 函数,将 t1 的数据以二进制的形式写入到 log.txt 文件中;

    运行程序数据以二进制的方式被写至文件当中;

  • int main() {// 创建另一个 testClass 实例 t2,用于从文件中读取数据testClass t2;// 从文件 "log.bin" 中读取数据到 t2 对象b.Read(t2);// 输出读取到的字符串和 double 类型数据cout << "s1 : " << t2._s1 << " d1 : " << t2._d1 << endl;// 输出读取到的 Date 类型数据cout << "date :" << t2._date << endl;return 0;  
    }
    /*运行结果为:$ ./mytest s1 : hello world d1 : 3.14date :2001 - 1 - 1
    */
    

    在这段代码中创建了另一个testClass类型对象t2用于将文件数据进行读取;

    读取后依次将t2对象中的内容进行打印;


二进制读写的浅拷贝问题

请添加图片描述

在进行二进制读写的时候可能会出现浅拷贝问题导致在对数据进行读取的时候出现内存错误;

其他代码与上述无异;

struct testClass {string _s1;  // 字符数组作为字符串,用于存储某些数据double _d2;  // 双精度浮点类型变量,用于存储某些数据Date _date;  // Date类型变量,用于存储日期
};

假设testClass类中的_s1string类型;

由于string是一个容器,其自行将在内存的堆中开辟空间并进行管理;

在管理过程中该string对象被写入至文件中,被写入至文件内的实际上还有该对象在内存中的空间指针(内容可能并未被拷贝);

当分为两个进程对这个文件进行读写操作时(两次执行,一次进行写一次进行读),在进行读的时候原本的string对象的指针会被读取进新的_s1成员中,但指针所指向的空间已经被销毁从而导致野指针问题;

$ ./mytest 
Segmentation fault

在同一个进程中对文件进行读写操作,可能数据一样会被写入至对应的string对象中但仍会因为野指针出现问题,故在进行二进制的文件读写操作时需要尽可能避免使用容器,否则需要手动将其序列化;


文本读写

请添加图片描述

文本读写与二进制读写不同,文本读写需要将所有的内容转换为字节流才能写入至文件当中;

对于内置类型而言可以使用C++中的to_string将其转换为字符串,在进行读取的时候可以调用C++中的stoi,stod函数将其转换为内置类型;

  • C语言可以使用sprintfsscanf将内置类型转换为自定义类型(尤其是double这种较为复杂的内置类型)

其余代码不变:


// 定义一个结构体testClass,包含成员变量_s1,_d2和_date
struct testClass {string _s1;double _d1;  // 双精度浮点类型变量,用于存储某些数据Date _date;  // Date类型变量,用于存储日期
};class TextIO {public:// 构造函数 传入一个 const char* 类型作为文件名TextIO(const char* filename) : filename_(filename) {}// 写void Write(const testClass& wt) {fstream ofs(filename_, fstream::out | fstream::trunc);// 不同的编译器/平台实现不同// 有些平台可以直接使用默认打开方式if (!ofs) {cerr << "Error opening file for writing!" << endl;return;}ofs << wt._s1 << endl;ofs << wt._d1 << endl;ofs << wt._date << endl;  // 这里调用了 Date 类的 operator << 流插入// 调用的本质是因为 fstream 继承于 iostream// 这里将对应的数据写入至ofs文件流中}// 读void Read(testClass& rt) { 	fstream ifs(filename_);ifs >> rt._s1; // 调用内置类型的流提取ifs >> rt._d1;ifs >> rt._date; // 调用Date重载的流提取}private:string filename_;  // 文件名
};

这段代码其他代码与上文例子相同不变,定义了一个TextIO的文本读写操作,对应的testClass类中的_s1成员的类型换成string类型();

  • 写入操作直接使用流插入<<操作符即可;

    对于内置类型而言会去调用库中的operator<<流插入;

    对于自定义类型而言会去调用在Date中重载的operator<<流插入;

    // 重载<<运算符,用于将Date对象输出到ostream
    ostream& operator<<(ostream& out, const Date& d) {out << d.year_ << " - " << d.month_ << " - " << d.day_ << endl;return out;
    }
    

    其中fstream能够调用ostream的本质原因是fstream是由ostream派生出来的;

    int main() {testClass t1{"hello world", 3.14, {2001, 1, 1}};TextIO b("log.txt");b.Write(t1);return 0;
    }
    

    在这段代码中定义了一个testClass类型对象t1,并创建了一个TextIO类型的对象b类作为文本文件读写操作;

    调用Write成员函数,运行结果文件被写入至log.txt文件中;

    hello world
    3.14
    2001 - 1 - 1
    
  • 对于读操作也是相同,直接调用对应的operator>>流提取即可;

    int main() {testClass t;TextIO b("log.txt");b.Read(t);cout << t._s1 << endl;cout << t._d1 << endl;cout << t._date << endl;return 0;
    }
    

    这里创建一个t用于接收数据,创建一个b对象用于文件的读写;

    读写后打印出t的数据;

    $ ./mytest 
    hello
    0
    1 - 1 - 1
    

    发现文件读取错误;

    这里读取错误的原因有两个;

    • 流插入读取字符串

      在流插入读取字符串时,当字符串与字符串之间存在空格时默认该空格为分隔符;

      此处的_s1类型为string,内容为hello world;

      为避免这个问题可以使用getline单独读取该字符串;

      void Read(testClass& rt) {fstream ifs(filename_);getline(ifs, rt._s1);ifs >> rt._d1 >> rt._date;}
      

      同样的这里的getline传入的是ifs文件流,其类型fstream是IO流中的子类;

      运行结果为:

      $ ./mytest 
      hello world
      3.14
      2001 - 0 - 1
      
    • Date类写入时格式控制不正确

      同样的在进行读取的时候为避免使用流提取的时候不增加过多的操作,在进行文件写入时就需要对格式进行控制;

      Date类在重载operator<<时内容为:

        out << d.year_ << " - " << d.month_ << " - " << d.day_;
      

      同样的在进行流提取时,当出现多个空格时空格会被视作分隔符;

      因此在进行写入的时候就应该进行格式控制;

      ostream& operator<<(ostream& out, const Date& d) {out << d.year_ << " " << d.month_ << " " << d.day_;return out;
      }
      

    修改完上述两处问题后重新进行写入再进行读取;

    $ make # 写入文件
    g++ -o mytest Main.cc -g -Wall -std=c++11
    $ ./mytest # 文件内容正确 
    $ cat log.txt 
    hello world
    3.14
    2001 1 1# 代码修改为读取代码后重新编译
    $ make
    g++ -o mytest Main.cc -g -Wall -std=c++11
    $ ./mytest 
    hello world
    3.14
    2001 1 1
    # 结果正确
    

同样的可以使用getline,其中getline可以自定义分隔符;

istream& getline (istream&  is, string& str, char delim);
istream& getline (istream&& is, string& str, char delim);

字符串流

请添加图片描述

C++字符串流stringstream是C++标准库中的一种流类,用于再内存中处理字符串,属于标准库中的sstream;

字符串流允许我们像处理文件流一样处理字符串,它将字符串作为数据源或数据目的地,使得再内存中进行字符串流更加灵活方便;

文件流是以 内存->磁盘 的方式,字符串流则是以 内存->内存 的方式(内存中数据具有类型);

C++中的字符串流主要有三种类型:

  • std::istringstream(输入字符串流)

    用于从字符串中提取数据,相当于字符串作为输入源;

  • std::ostringstream(输出字符串流)

    用于将数据格式化为字符串并存储,相当于将字符串作为输出目的地;

  • std::stringstream(输入/输出字符串流)

    可以同时进行输入和输出操作;

这三种类型实际上是对标C语言中的sprintfsscanf接口,提供了对自定义类型的操作与安全性;

字符串流通常应用于以下几种场景:

  • 字符串的格式化输出

    将不同类型的字符串合并成一个字符串;

  • 字符串的解析

    将字符串分解成不同的部分(如解析逗号分割的值等);

  • 类型转换

    在不同数据类型之间进行安全转换;

假设需要生成一条SQL语句;

利用C语言则是调用sprintf,即:

int main() {char sql[128];char name[24];scanf("%s", &name);sprintf(sql, "select * from t_test name = '%s' ", name);printf("%s\n", sql);return 0;
}
/*运行结果为:$ ./mytest 张三select * from t_test name = '张三' 
*/

此处查找的为内置类型,但若是需要查找自定义类型则需要更复杂的操作;

C++中的字符串流则是可以解决对于自定义类型的操作问题,本质上是重载自定类型的operator<<operator>>,即流插入和流提取操作,而stringstream流则是所有IO流的最底层类,即最后的派生类,可以调用父类封装的成员函数来进行操作;

// Date类的定义,包含友元函数声明和私有成员变量
class Date {// 友元函数声明,允许<<运算符访问Date类的私有成员friend ostream& operator<<(ostream& out, const Date& d);// 友元函数声明,允许>>运算符访问Date类的私有成员friend istream& operator>>(istream& in, Date& d);public:// 构造函数,默认值为1年1月1日Date(int year = 1, int month = 1, int day = 1): year_(year), month_(month), day_(day) {}// bool类型转换函数,当year_为0时返回false,否则返回trueoperator bool() {if (year_ == 0) {return false;}return true;}void Print() {printf("year= %d ,month= %d ,day= %d\n", year_, month_, day_);}private:// 年份int year_;// 月份int month_;// 日期int day_;
};// 重载<<运算符,用于将Date对象输出到ostream
ostream& operator<<(ostream& out, const Date& d) {out << d.year_ << "/" << d.month_ << "/" << d.day_;return out;
}// 重载>>运算符,用于从istream输入流中读取Date对象
istream& operator>>(istream& in, Date& d) {in >> d.year_ >> d.month_ >> d.day_;return in;
}int main() {Date d = {2023, 12, 1};  // 初始化日期对象string sql;stringstream st;st << d;  // 将日期对象格式化为字符串sql += "select * from t_data date = '";sql += st.str();  // 插入格式化后的日期sql += "'";cout << sql << endl;  // 输出SQL语句return 0;
}/*运行结果为:$ ./mytest select * from t_test name = '2023/12/1'
*/

在这段代码中创建了一个Date类,初始化为{2023, 12, 1};

创建了一个string对象sql作为sql语句的存储地;

定义了一个stringstream字符串流st用于自定义类型Date的转换;

当使用流插入时将去调用对应的operator<<将自定义类型中的数据转换为字符串并存储在stringstream字符串流的缓冲区中;

最后使用string类的+=将字符串进行组装;

  • stringstream的序列化和反序列化

    序列化是指将对象的状态转换为一种可存储或传输的格式的过程;

    这样可以将对象保存到文件,数据库,或者通过网络传输;

    序列化之后的数据可以在以后反序列化(也称为反序列化或解串)回原来的对象,从而恢复对象的状态;

    stringstream支持一些较为简单的序列化和反序列化;

    本质上就是调用自定义类型重载的operator<<operator<<,即流插入与流提取;

    但由于stringstream不能很好的支持复杂类型,如标准库的容器等等;

    通常情况下复杂内容的序列化和反序列化可以使用第三方库如Json等;


注意

请添加图片描述

本文中所有代码测试均在Linux-centOS7上进行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/406190.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用Python在Ashare获取金融数据官方文档解读

Ashare&#xff0c;也被写作AKShare&#xff0c;是一个基于Python的开源完全免费的财经数据接口库。它的主要目的是为用户提供股票、期货、期权、基金、外汇、债券、指数、加密货币等金融产品的基本面数据、实时和历史行情数据、衍生数据的采集、清洗和落地的一整套工具。AKSha…

算法的学习笔记—把二叉树打印成多行(牛客JZ78)

&#x1f600;前言 在算法面试中&#xff0c;二叉树的层序遍历是一个经典的题目。而这道题的要求是进一步将二叉树的每一层结点值打印成多行&#xff0c;即同一层结点从左至右输出&#xff0c;最终结果存放到一个二维数组中返回。接下来&#xff0c;我们将通过代码实例详细解析…

什么是光伏气象站—光伏气象站的简述

随着全球对可再生能源需求的日益增长&#xff0c;光伏发电作为清洁、可再生的能源形式&#xff0c;正逐步成为能源结构转型的重要力量。然而&#xff0c;光伏电站的发电效率受到多种气象因素的影响&#xff0c;如太阳辐射强度、温度、风速、湿度等。为了最大化光伏系统的发电潜…

C/C++ 多线程[1]---线程创建+线程释放+实例

文章目录 前言1. 多线程创建2. 多线程释放3. 实例总结 前言 说来惭愧&#xff0c;写了很久的代码&#xff0c;一个单线程通全部。可能是接触的项目少吧&#xff0c;很多多线程的概念其实都知道&#xff0c;但是实战并没有用上。前段时间给公司软件做一个进度条&#xff0c;涉及…

Java 2.4 - JVM

一、Java 内存区域详解&#xff08;重点&#xff09; 本篇讨论的是 HotSpot 虚拟机 相比于 C 而言&#xff0c;程序员不需要对每个 new 操作都写对应的 delete / free 操作&#xff0c;这些操作我们会交给虚拟机去做。因此&#xff0c;如果不了解虚拟机的原理&#xff0c;一旦…

【Vue3】集成 Ant Design Vue

【Vue3】集成 Ant Design Vue 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗…

Android TableLayout中TextView文本不居中问题

概述 | 平台 RK3288 Android 8.1 compileSdkVersion 26. | 问题 使用了TableLayout布局电话的拨号按键界面, 效果如下图 (正常): 在后续开发过程的某次修改后, 出现效果图(不正常): 合并两张效果图可看得更明显(红线参考位置): 在布局中 TextView 的 android:g…

SEO优化:如何优化自己的文章,解决搜索引擎不收录的问题

可以使用bing的URL检查&#xff0c;来检查自己的文章是不是负荷收录准测&#xff0c;如果页面有严重的错误&#xff0c;搜索引擎是不会进行收录的&#xff0c;而且还会判定文章为低质量文章&#xff01; 检查是否有问题。下面的页面就是有问题&#xff0c;当然如果是误报你也可…

Stage模型应用程序包结构

一、开发态包结构 在DevEco Studio上创建一个项目工程&#xff0c;并尝试创建多个不同类型的Module。根据实际工程中的目录对照本章节进行学习&#xff0c;可以有助于理解开发态的应用程序结构。 工程结构主要包含的文件类型及用途如下&#xff1a; 文件类型说明配置文件 包括…

ISO 26262中的失效率计算:IEC 61709-Clause 17_Switches and push-buttons

概要 IEC 61709是国际电工委员会&#xff08;IEC&#xff09;制定的一个标准&#xff0c;即“电子元器件 可靠性 失效率的基准条件和失效率转换的应力模型”。主要涉及电学元器件的可靠性&#xff0c;包括失效率的基准条件和失效率转换的应力模型。本文介绍IEC 61709第十七章&…

docker部署postgresSQL 并做持久化

先安装docker&#xff0c;安装docker 方法自行寻找方法 然后安装pgsql 拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/postgres:latest运行容器 docker run -it --name postgres --privileged --restart always -e POSTGRES_PASSWORDYo5WYypu0mCCh…

Ansys Rocky在电池制造行业应用

Ansys Rocky在电池制造行业应用 对于电池电极制造的理解 干湿混合应用 砑光应用 微尺度电极干燥应用 更多应用 材料生产 电极和电池生产

2024前端面试题-css篇

1.p和div区别 p自带有一定margin-top和margin-bottom属性值&#xff0c;而div两个属性值为0&#xff0c;也便是两个p之间有不一定间距&#xff0c;而div没有。 2.对css盒模型的理解 标准盒模型&#xff1a;content不包括padding、border、margin ie盒模型&#xff1a;conten…

2024最新Python+PyCharm保姆级安装教程【附激活码】

PyCharm 是由捷克的 JetBrains 公司开发的一款强大的 Python 集成开发环境&#xff08;IDE&#xff09;&#xff0c;它为 Python 开发者提供了一个全面的编程工具集&#xff0c;支持从代码编写到代码测试、调试和优化等各个环节 &#xff0c;它支持代码自动完成、代码检查、实时…

OpenDDS的Rtps_Udp传输协议可靠性QoS收发基本流程

OpenDDS中,实现了Rtps_Udp传输协议(非纯udp)的可靠性传输。传输的线程包括: 1)发送方线程主要线程和定时器 《1》应用线程 《2》网络异步发送线程 《3》Heartbeat定时器 《4》Nak_response定时器 2)接收方主要线程和定时器 《1》网络异步接收线程 《2》heartbeat_respons…

下载官方llama

1.官网.pth格式 去官网&#xff08;Download Llama (meta.com)&#xff09;申请 具体可以看这个B站视频 Llama2模型申请与本地部署详细教程_哔哩哔哩_bilibili&#xff08;视频是llama2&#xff0c;下载llama3是另外一个git&#xff09; 相关代码如下 git clone https://g…

【C++例题 / 训练】二分算法(模板 例题)

引言 二分也就是二分查找&#xff0c;又叫折半查找。这种算法正如其名&#xff0c;每一次都要分一半。 二分算法可以分为二分查找和二分答案。 以在一个升序数组中查找一个数为例&#xff0c;每次考察数组当前部分的中间元素&#xff0c;如果中间元素刚好是要找的&#xff0…

Java MessagePack序列化工具(适配Unity)

Java MessagePack序列化工具&#xff08;适配Unity&#xff09; 前言项目代码编写 结 前言 前后端统一用MessagePack&#xff0c;结果序列化的结果不一样&#xff0c;发现C#侧需要给每个类增加描述字段数量的Head&#xff0c;而Java却不用&#xff0c;所以在Java侧封装一下序列…

运行微信小程序报错:Bad attr data-event-opts with message

问题 使用uniapp 编译&#xff0c;运行微信小程序环境时&#xff0c;报错 Bad attr data-event-opts with message。&#xff08;这个错误报错原因很多&#xff0c;这里只解决一个&#xff09; 原因 原因是&#xff1a;代码中有&#xff1a; :key"swiperList i"…

近年国际重大网络安全事件深度剖析:安全之路任重道远

引言 在当今数字化时代&#xff0c;网络安全已成为全球关注的焦点。随着信息技术的飞速发展&#xff0c;网络攻击的手段和规模也在不断升级&#xff0c;给个人、企业和国家带来了巨大的威胁。本文将盘点近年来国际上发生的重大网络安全事件&#xff0c;分析其影响和教训&#…