StupidBeauty
??Read times:18????Posted at:Sun Oct 1 01:28:56 2017?? - no title specified

《C++面向对象软件设计及构建》文章翻译:2.6基本输入/输出,2.6 Basic Input/Output

流式操作符

操作符重载,是C++中输入和输出(I/O)的基础。对于输入输出来说,重载狠有用,因为,相同的(输入或输出)操作会被应用到不同的数据类型上去。这,也正是操作符重载本来的用意。在C++中,存在着一个重载的操作符,它能够处理所有内置数据类型的输入/输出事务。因此,同一个操作符会被用到所有的数据类型上去。在编译期间,会根据当时进行输入或输出的数据的类型来确定要使用重载操作符的哪个版本。

被重载用于处理输入/输出的那个操作符,其名字并不是"input"或"output"这样的,其名字是一个符号。符号操作符 << 表示输出 ,符号操作符 >> 表示输入。日后 ,妳会看到,大部分C++操作符(例如:+ - * / < > = ==)都可以重载。

C++中,输入/输出是基于一个流式模型来工作的。在流式模型中,输入数据被看作一个由数据组成的连续的流,它从某个数据源处流出,然后流入到向输入流提供的那一串变量中去。那些变量的类型,决定了,该如何对输入流进行解释,以便为那些变量提供对应的值。在输出过程中,各个变量的值,注入到一个(逻辑上)连续的流中去,并最终进入目的地。(输入的)源头和(输出的)目标可以是用户或某个文件。

交互式的流式输入/输出功能,由两个类提供:

class istream {...};          // 流式输入

class ostream {...};          // 流式输出

这些类,用到了C++语言中那些尚未被我们讲到的元素。对于C++中输入/输出类的学习,将在日后进行。目前,只需要知道如何使用这些类,就够了。

标准的C++输入/输出库中,包含了两个预定义的变量:

istream cin;            // 交互 式输入

ostream cout;           // 交互 式输出

这些变量,是在"stream.h"文件中定义的,可在自己的程序中使用 include 指令来包含该文件:

#include <stream.h>

任何单个 的内置数据类型数据,都可以使用 << 操作符来输出,如下所示:

cout << 10;             // 输出 一个整数

cout << 1.234;          // 输出 一个浮点数

cout << 'x';            // 输出 一个字符

cout << "Hello World."  // 输出 一个字符串

cout << '\n';           // 输出 一个“换行”符

由于输出可被看作是一个流,因此,以上的5条语句可以合并成以下1条语句:

cout <<  10  << 1.234 << 'x' << "Hello World." << '\n';

这些值,按照从左到右的顺序,进入到输出流中。为了提升可读性,通常会向输出流中插入空格,如下所示:

cout << 10 << "  " << 20 << '\n';

这样,会向10 和20 之间插入两个空格。换行符('\n'),可用来终止某一行输出内容。作为对换行符的替代器,标准输入/输出库中还定义了一个符号"endl" (表示“行结束”("end line")),它也能够终止掉当前的一行输出内容。因此,上面的示例可以改写成如下代码:

cout << 10 << "  " << 20 << endl;

此处我们用到了endl 符号。

注意,用于执行输出动作的代码的行数,与实际出现在输出内容中的文字的行数,这两者之间,并没有必然的关系。例如,以下三个代码片断中,都是输出了相同的两行文字内容:

(1)  cout << 10;

cout << endl;

cout << 20 << endl;

(2)  cout << 10 << endl << 20 << endl;

(3)  cout 10 << endl << 20;

cout << endl;

可使用流式操作符来输出变量的值,如下所示:

int x, y;               // 个整数

char c;                 // 一个字符

char *s = "Hello  ";      // 一个字符串

float z = 1.415;        // 一个浮点数值

x = 100; y = 200;

cout << x << "  " << y << endl << s << z << endl;

以上代码会产生以下输出内容:

100  200  

Hello  1.415

输出内容 是两行文字,因为, endl 符号在输出流中出现了两次。

交互 式输入,使用预定义的变量 "cin" 和操作符 >> ,其用法也类似。例如, 以下语句:

int x;

cin >> x;

会导致,从标准输入流中读取一个整数值,并将它的值赋予给变量 x 在读取过程中,“空白”字符会被跳过,比如空格和制表符。

与用户的对话过程中,通常包含着一个串行的提示/回复过程。程序会提示用户输入数据,然后读取该数据。例如,一个程序,需要读取两个整数值,分别用来表示小时数和分钟数,其代码就可以这样写:

int hour, minute;

cout << "Enter hour (integer) and minute (integer): " << endl;

cin >> hour >> minute;

从磁盘文件读取数据,以及向磁盘文件写入数据,使用的是类似的策略。在标准C++库中,定义了另外两个类:

class ifstream {

private:

...

public:

ifstream(char* filename);   // 要用于输入的文件的名字

...

};

class ofstream {

private:

...

public:

ofstream(char* filename);   // 要用于输出的文件的名字

...

};

以下示例,展示的是,如何使用流式输入/输出操作符来对文件中的数据进行操作:

ifstream is("file.dat");

ofstream os("out.dat");

int x, y;

is >> x >> y;                   // 从file.dat中读取两个整数

os << "The sum is:  ";          // 将前置提示文字输出到 out.dat

os << (x + y) << '\n';          // 将两个数的和值和换行符输出

向窗口中进行流式输出

Frame 类的一个变种,TextFrame,可用来向窗口中进行流式输出。这个类,代表着流式输入/输出模型的一个简化版本;它受到限制,仅可用于输出,并且,只支持最基本的那些内置数据类型(intlongfloatdoublecharchar*)

下面展示了TextFrame 类的部分定义。TextFrame 的构造函数,与那些Frame 对象的构造函数类似,同样地,MoveToResize方法也类似。记住,TextFrame类并不是标准C++库中的组成部分 - 与Frame 类类似,它只是我们用来学习C++的资源中的组成部分。

TextFrame

class TextFrame {

 private:

                // 封装起来的隐藏数据

 public:

   TextFrame(char *name, int x, int y, int w, int h);

   TextFrame(char* name, int x, int y);

   TextFrame(char* name);

   TextFrame();

  ~TextFrame();

   void     MoveTo( Location newLocation);      // 改变位置

   void     Resize( Shape newShape);            // 改变形状

   void     Resize( float factor);              //

   int      IsNamed(char* n);                   // 测试,妳是否叫这个名字?

   ...

};

TextFrame类,可按如下方式使用。这砣代码,会创建两个Frame 对象和一个TextFrame 对象。在这砣代码中,每当鼠标事件发生时,就会将发生事件的Frame 对象的名字输出到TextFrame 中去。

Frame window1("Window1", 100,100, 200, 200);

Frame window2("Window2", 400,400, 200, 200);

TextFrame out("Display", 400, 20, 200, 200);

OnStart() {

window1.DrawText("Click in this window", 20,20);

window2.DrawText("Click in this window", 20,20);

out << "Name of window clicked in will appear below" << '\n';

}

OnPaint() {

window1.DrawText("Click in this window", 20,20);

window2.DrawText("Click in this window", 20,20);

out << "Name of window clicked in will appear below" << '\n';

}

OnMouseEvent(char* frameName, int x, int y, int buttonState) {

out << name ;

out << '\';

}

OnTimerEvent() {}

这个TextFrame 类,可用来向用户显示文字式的信息,也可以方便地在开发、测试和调试过程中用于显示状态信息。

字符串

那些流式输入/输出工具,可用来将格式化数据放置到一个字符串中去,或者,从字符串中读取得到格式化数据。尽管也会用到流式输入/输出操作符,但是,这种情况下,这些流式操作符只会导致信息在字符串和内存中其它变量之间流动。字符串流式操作符并不会用到输入/输出设备或文件。

在这种情况下,使用字符串进行流式处理狠有用:要将格式化数据(例如,多个整数数值)传递到某个仅接受单个字符串(一个char*)参数的接口中去。例如,在Frame 类的DrawText 方法中,即是这种情况。要想利用DrawText 来在Frame 中显示一个或多个整数数值的话,首先,就要将这些整数数据转换成单个字符串。在某个字符串流上使用流式输出操作符,就能够将整数数据写入到那个字符串流中去。这个字符串流,会将数据放置到一个字符串中去。流式输出操作符,会在最后被加入到字符串中去的字符之后放置一个字符串结束符('\0')。于是,这个字符串,就可以当成普通的字符串来操作。另外,还可以利用流式输入操作符来从字符串中读取得到格式化数据。

字符串的流式处理,是由标准C++库中定义的两个类来实现的:

class istrstream {...};

class ostrstream {...};

字符串 流对象的构造函数,要求提供一个指针,指向字符串( 或内存缓冲区 )的开头,以及一个整数参数,用于指定字符串 (或缓冲区) 的长度。构造完毕之后, 就可以将流式输出操作符(<<)应用到某个ostrstream 对象上去,也可以将流式输入操作符(>>)应用到某个istrstream 对象上去。下面 的示例中,展示了,如何使用字符串流式处理功能 。在这个示例中,向字符串中写入了一个简单的算术表达式,这个表达式的意义是计算两个整数值的和。此处 的这个简单表达式, 是由输出字符串流对象"expression"来格式化的,并由输入字符串流对象 parser 来解析。

char text[100];

ostrstream expression(text, 100);    // 创建字符串

expression << 10 << " + " << 20 << endl;

...

istrstream parser(text, 100);

int value1, value2;

char operator;

parser >> value1             // value1   = 10

>> operator           // operator = '+'

>> value2;            // value2   = 20

注意,这两个字符串流,在其构造过程中,传入了"text"这个字符串作为参数。"expression"就是向这个字符串中写入格式化的数据,而"parser"也是从这个字符串中读取字符并解析出格式化的数据。

流上的其它方法

除了流式输入/输出操作符以外,流对象还提供了其它的可使用小数点操作符(.)来调用的方法。这些方法,可用来查询流的状态,以及,做出那些以操作符方式暂时无法表示的操作。

用于查询流的状态的操作符,其中的一些示例包括:检查某个输入流是否已经到达末尾;或者,检查,最后一次在某个流上进行的操作,是成功了还是失败了。以下代码片断中,展示了这两个方法的用法:

if (cin.eof()) ...      // 到达标准输入 流的末尾

if (cin.fail()) ...     // 最后 一次输入操作失败了

某些情况下,操作会失败,例如:流无法解析输入流中的内容,以提供出所要求的数据类型的值。具体例子是:接下来需要从输入流中读取一个整数,可是,输入流中接下来的文字内容是"abcd",这砣文字是无法被当作整数值读入的。

要想读入整个字符串,或者读入整行文字输入内容的话,也需要使用小数点操作符形式的方法来进行。因为,这种操作,并不适合用在流式操作符模型上(也就是说,在C++中,不存在哪种数据类型是对应着一整行输入内容的)。用于读入一个字符串或者一行内容的方法,其名字是"get",它有三个参数:

  1. 1.一个字符串,数据将会被读入到其中

  2. 2. 要从输入流中读取并放入到该字符串中的字符的最大个数(不要超过字符串长度减1的值)

  3. 3.一个字符,当流中出现这个字符时,就会终止读取动作

以下细节,有助于理解get 方法的操作:

  • •. 从输入流中读取 最后一个字符之后,会向字符串中放置一个字符串结束符 ('\0') 这样,在任何一个需要以空白(null)结束的字符串 的地方,都可以使用这个字符串。由于字符串结束 符会占用字符串中的一个位置,所以,能够 从输入流中读取得到的最大的字符个数,即是,字符串长度 减1。

  • •.以下情况,任何一个发生了,就会导致get 方法停止从流中读取字符:达到了最大长度;或者,遇到了那个特殊的终止字符。

  • •.个特殊的终止字符,并不会实际从流中读取并删除,同时,它也不会被放置到目标字符串中。

以下代码示例,展示了,如何使用 get 方法来读取一行输入内容:

char line[100];

...

cin.get(line, 100, '\n');

注意,此处,使用了小数点操作符来调用标准输入流对象的get 方法。

任务

  1. 1.编写一个程序,将所有参数都使用默认值,来创建一个Frame 对象。然后,在这个程序中,应当从一个名为"window.dat"的文件中读取得到位置和形状信息,以及一个字符串。然后,根据从文件中读取到的位置和形状信息,将该Frame 对象移动到对应的位置,并改变其大小,并将从文件中读取到的字符串写入到Frame 对象中去。

  2. 2.鼠标跟踪器:编写一个程序,它会在某个Frame 中显示当前的鼠标坐标。坐标必须以(x,y)这种形式(包括括号和逗号)来显示,并且显示在当前的鼠标位置。必须使用一个字符串流来对要显示的字符串进行格式化,再显示到Frame 中。

  3. 3. 最简单的定时器:编写一个程序,每当发生定时器事件时,就增加某个整数变量的值,并将它的值输出到某个TextFrame 中去。该个 TextFrame 中,最初应当显示0。

  4. 4. 鼠标报告器:编写一个程序,每当鼠标左键在某个Frame 对象上点击时,就会向某个TextFrame 中输出一行文字。输出的一行文字,其形式应当是(x,y)(包括括号和逗号),其中,x 和y 是鼠标事件的坐标。

  5. 5. 鼠标报告器之2:编写一个程序,它对之前的鼠标报告器程序进行扩展。具体地,要有两个Frame 对象,并且,TextFrame 中的每行文字要变成这种形式name:(x,y),其中,name表示当前发生鼠标点击事件的Frame 的名字,xy是鼠标事件的坐标。

  6. 6.文件查看器:编写一个程序,它会从磁盘文件"viewer.dat"中读取内容。每次读取一行,并将读取的内容写入到TextFrame 中去。

  7. 7.文件扫描器:修改前面的文件查看器,使得,每当发生一次定时器事件时,从文件中读取一行内容,并写入到TextFrame 中去。

美人吃面笑咪咪

未知美人