C++ —— 使用流

Posted 我受到了惊吓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ —— 使用流相关的知识,希望对你有一定的参考价值。

内容总结于书《C++高级编程》第四版

    作为I/O机制,C语言中的printf()和scanf()确实很灵活,可以读取特定格式的数据,或输出格式化代码允许的任何值。然而这些函数不能很好的处理错误,并且在C++面向对象的语言中,它们根本不是面向对象的。

    C++通过一种称为流(stream)的机制提供了更精良的输入输出方法。流是一种灵活且面向对象的I/O方法。


1 流的含义

    所有的流都可以看成数据滑槽,流的方向不同,关联的目的地和来源也不同。例如,cout流是一个输出流,因此他的方向是流出;这个流将数据写入控制台,因此它关联的目的地是“控制台”。下表描述了所有预定义的流。



说明
cin

An input stream, reads data from the “input console.

cout

A buffered output stream, writes data to the “output console.

cerr

An unbuffered output stream, writes data to the “error console,” which is often the same as the “output console.

clog

A buffered version of cerr.


    缓冲的流和非缓冲的流的区别在于,前者不是立即将数据发送到目的地,而是将缓冲输入的数据,然后以块的方式发送;而非缓冲的数据则立即发送,缓冲的目的通常是提高性能,对于某些目的地(如文件)而言一次性写入较大的块时速度更快。可以使用flush()方法刷新缓冲区,强制要求缓冲的流将其当前所有的缓冲数据发送到目的地。


2 流式输出

    2.1 输出的基本概念

    输出流定义在<ostream>头文件中。我们经常会在程序中包含<iostream>头文件,这个头文件又包含输入流和输出流头文件。<iostream>头文件还声明了所有预定义的流实例:cout、cin、cerr、clog以及对应的宽版本。

    使用输出流最简单的方法是使用<<运算符。通过<<可输出C++的基本类型,如int、指针、double和字符。C++的string类也兼容<<,C风格的字符串也能正确输出。下面列举使用<<的示例:

int i = 7;cout << i << endl;
char ch = 'a';cout << ch << endl;
string myString = "Hello World.";cout << myString << endl;

    输出结果:

7Hello World.

    cout是写入控制台的内建流,控制台也称标准输出(standard output)。可将<<的使用串联起来,从而输出多个数据段。这是因为<<运算符返回一个流的引用,因此可以立即对同一个流再次应用<<运算符。

    C++流可以正确解析C风格的转义字符,例如包含\n的字符串,也可以使用std::endl开始一个新行。\n和endl的区别是,\n仅开始新的一行,而endl还会刷新缓存区。使用endl时要小心,因为过多的缓存区刷新会降低性能。


    2.2 输出流的方法

    put()和write()

    put()接受单个字符,write()接受一个字符数组。传给这些方法的数据按照原本的形式输出,没有做任何特殊的格式化和处理操作。下面一段代码接受一个C风格的字符串,并将它输出到控制台,这个函数没有使用<<运算符:

const char* test = "hello there\n";cout.write(test, strlen(test));

    下面的代码段通过put()方法,将C风格字符串的给定索引输出到控制台:

cout.put('a');

    

    flush()

    在以下任意一种条件下,流将刷新积累的数据

  • 遇到sentine(如endl标记)时。

  • 流离开作用域被析构时。

  • 要求从对应的输入流输入数据时。

  • 流缓存满时。

  • 显示的要求流刷新缓存时。    

    显示要求流刷新缓存的方法是调用flush方法:

cout << "abc";cout.flush(); // abc is written to the console.cout << "def";cout << endl; // def is written to the console.


    2.3 输出操作算子

    流的一项独特特性是,放入滑槽的内容并非仅限于数据。C++流还能识别操作算子,操作算子是能修改流行为的对象,而不是流能够操作的数据。

    endl就是一个操作算子。endl操作算子封装了数据和行为。它要求流输出一个行结束序列,并且刷新缓存。下面列出了其他有用的操作算子,大部分定义在<ios>和<iomanip>标准头文件中。

  • boolalpha和noboolalpha:要求流将布尔值输出为true和false或1和0。默认是noboolalpha。

  • hex、oct、和dec:分别以十六进制、八进制和十进制输出数据。

  • setprecision:设置输出小数时的小数位数。

  • setw:设置输出数值数据的宽度

  • setfill:当数字宽度小于指定宽度时,设置用于填充的字符。

  • showpoint和noshowpoint:对于不带小数部分的浮点数,强制流总是显示或者不显示小数点。

  • put_money:向流写入一个格式化的货币值。

  • put_time:向流写入一个格式化的时间值。

  • quoted:把给定的字符串封装在引号中,并转义嵌入的引号。

// Boolean valuesbool myBool = true;cout << "This is the default: " << myBool << endl;cout << "This should be true: " << boolalpha << myBool << endl;cout << "This should be 1: " << noboolalpha << myBool << endl;
// Simulate "%6d" with streamsint i = 123;printf("This should be ' 123': %6d\n", i);cout << "This should be ' 123': " << setw(6) << i << endl;
// Simulate "%06d" with streamsprintf("This should be '000123': %06d\n", i)cout << "This should be '000123': " << setfill('0') << setw(6)<< i << endl;
// Fill with *cout << "This should be '***123': " << setfill('*') << setw(6)<< i << endl;// Reset fill charactercout << setfill(' ');
// Floating point valuesdouble dbl = 1.452;double dbl2 = 5;cout << "This should be ' 5': " << setw(2) << noshowpoint <<dbl2 << endl;cout << "This should be @@1.452: " << setw(7) << setfill('@') <<dbl << endl;// Reset fill charactercout << setfill(' ');
// Instructs cout to start formatting numbers according to yourlocation.cout.imbue(locale(""))
// Format numbers according to your locationcout << "This is 1234567 formatted according to your location: "<< 1234567<< endl;
// Monetary value. What exactly a monetary value means dependson your //location. For example, in the USA, a monetary value of 120000means 120000// dollar cents, which is 1200.00 dollars.cout << "This should be a monetary value of 120000, "<< "formatted according to your location: "<< put_money("120000") << endl;
// Date and timetime_t t_t = time(nullptr); // Get current system timetm* t = localtime(&t_t); // Convert to local timecout << "This should be the current date and time "<< "formatted according to your location: "<< put_time(t, "%c") << endl;
// Quoted stringcout << "This should be: \"Quoted string with \\\"embeddedquotes\\\".\": "<< quoted("Quoted string with \"embedded quotes\".") <<endl;


3 流式输入

    3.1 输入的基本概念

    通过输入流可以采取两种办法来读取数据。第一种方法是使用运算符>>。通过>>从输入流读入数据时,代码提供变量保存接受的值。如下:

string userInput;cin >> userInput;cout << "User input was " << userInput << endl;

    默认情况下,>>运算符根据空白字符对输入值进行标志化。例如,如果用户运行以上程序,并键入hello world作为输入,那么只有第一个空白字符之前的字符才会存储在userInput变量中。输出如下所示:

User input was hello

    在输入中包含字符的一种方法是使用get()。

    通过输入流可以读入多个值,并且可根据需要混合和匹配类型。例如:

string s;int a;cin>>s>>a;

    

    3.2 处理输入错误

    输入流提供了一些方法用于检测异常情况。大部分和输入流有关的错误条件都发生在无数据可读时。例如,可能到达流尾。查询输入流状态的最常见的方法是在条件语句中访问输入流。例如,只要cin保持在“良好”状态,下面的循环就会继续进行:

while (cin) { ... }

    同时可以输入数据:

while (cin >> ch) { ... }

    还可以调用good()、bad()和fail()方法。还有一个eof()方法,如果到达文件尾部,就返回true。与输出流类似,遇到文件结束标记时,good()和fail()都会返回false。

    下面展示了从流中读取数据并处理错误的常用模式。

cout << "Enter numbers on separate lines to add. "<< "Use Control+D to finish (Control+Z in Windows)." <<endl;int sum = 0;if (!cin.good()) { cerr << "Standard input is in a bad state!" << endl; return 1;}int number;while (!cin.bad()) { cin >> number; if (cin.good()) {sum += number;}  else if (cin.eof()) { break// Reached end of file  }   else if (cin.fail()) {// Failure!   cin.clear(); // Clear the failure state.   string badToken;   cin >> badToken; // Consume the bad input.   cerr << "WARNING: Bad input encountered: " << badToken<< endl;   }  }cout << "The sum is " << sum << endl;


    3.3 输入方法

    get()

    get()最简单的版本返回流中的下一个字符,其他版本一次读如多个字符。get通常避免>>运算的自动标志化。例如,下面这个函数从输入流中读如一个由多个单词构成的名字,一直读到流尾。

string readName(istream& stream){ string name;  while (stream) { // Or: while (!stream.fail())    int next = stream.get();   if (!stream || next == std::char_traits<char>::eof())   break;   name += static_cast<char>(next);// Append character.  }  return name;}

    get的返回值保存在int而不是char变量中,因为get()会返回一些特殊的非字符值,例如std::char_traits<char>::eof(),因此使用int。


    unget()

    对于大多数场合来说,理解输入流的正确方式时将输入流理解为单向的滑槽。数据被丢入滑槽,然后进入变量。unget()打破了这个模型,允许将数据塞回滑槽。调用unget()会导致流退回一个位置,将读如的前一个字符放回流中。


    peek()

    peek()方法可以预览调用get()后返回的下一个值,可以想象为查看一下滑槽,但不把值取出来。


    getline()

    从输入流中获取一行数据是一种常见的需求。getline()方法用一行数据填充字符缓冲区。指定的大小包括\0字符。因此,以下代码从cin读取最多kBufferSize-1个字符,或者直到读取行尾序列为止:

char buffer[kBufferSize] = { 0 };cin.getline(buffer, kBufferSize);

    调用getline()时,从输入流中读取一行,读到行尾位置。不过行尾不会出现在字符串中。行尾序列和平台相关,可以是\n\r、\r或\r\n。

    有个版本的get()函数执行的操作和getline()一样,区别在于get()把换行序列留在输入流中。


4 对象的输入输出

    即使不是基本类型,也可以通过重载<<和>>运算符的方式输入输出对象。下面给出重载示例:

class SpreadsheetCell{ // Omitted for brevity};std::ostream& operator<<(std::ostream& ostr, constSpreadsheetCell& cell);std::istream& operator>>(std::istream& istr, SpreadsheetCell&cell);
ostream& operator<<(ostream& ostr, const SpreadsheetCell& cell){ ostr << cell.getValue(); return ostr;}
istream& operator>>(istream& istr, SpreadsheetCell& cell){ double value; istr >> value; cell.set(value); return istr;}


以上是关于C++ —— 使用流的主要内容,如果未能解决你的问题,请参考以下文章

这些 C++ 代码片段有啥作用?

有趣的 C++ 代码片段,有啥解释吗? [复制]

以下代码片段 C++ 的说明

C++ 代码片段执行

C++编程基础: 14. 文件的读写

此 Canon SDK C++ 代码片段的等效 C# 代码是啥?