StupidBeauty
??Read times:40????Posted at:Sat Sep 23 03:30:25 2017?? - no title specified

《C++面向对象软件设计及构建》文章翻译:2.3创建及操作一个对象,2.3 Creating and Operating on an Object

执行操作


在某个类的公有接口中定义的那些操作,可以在该类的任意对象上执行。继续观摩Frame 类的示例,可以通过以下代码来创建及操作一个Frame 对象:

Frame display("Test Window", 10,20, 100, 200);

display.MoveTo(50, 50);

display.Resize(200,200);

display.DrawText("Really Neat!", 50,50);

此处 ,声明了一个名为 display 的Frame 对象。 Frame 的构造函数会被隐式地调用,并且会传入声明语句中提供的参数。 在这些构造函数参数的作用下,会创建一个Frame,它的左上角位于(10,20),它的高度和宽度分别是100 和200。

当display 对象创建完毕之后,就可以对它做操作了。在这个示例中,首先使用 MoveTo 方法来display 将对象移动到(50,50)位置,再使用 Resize 方法将这个Frame 的形状变成200 x 200 的正方形。最后,在Frame 中画出文字"Really Neat!",它的位置是,相对于Frame 的左上角的(50,50)位置。

注意 "."(小数点)操作符 ,用于调用某个对象上的某个方法。因此 display.MoveTo(...) ,表示的是,调用 display 对象上的 MoveTo 方法。使用面向对象编程语言 的人士, 经常会这样说,“ 让display 对象移动自己 位置 ”, 以表 在对象上进行的操作 这个,反映了这样一种观点,那就是,对象 ,就是某个能够做出特定动作的实体 (例如,Frame对象知道如何 将自己移动到一个新的位置 )

简单编程环境

对图形用户界面对象进行编程(例如此处的Frame 类,以及日后要说的其它类),需要用到一个编程环境,这个编程环境,与那些入门级编程课程中的编程环境有显著的不同。编程环境的不同之处,在于这样一个事实,就是,图形用户界面系统,是 事件驱动 或者说 响应 的系统,这就意味着,系统是由外部事件(鼠标点击、时钟变动)驱动的,它必须对这些事件做出响应。一个事件驱动或响应式的系统,其典型的生命周期如下:

  1. 1.接收到通知,告知某个事件已发生。

  2. 2.利用与当前系统状态相关的信息来决定,该如何对该事件做出响应。

  3. 3.对该事件做出响应,具体就是,更新界面上的显示,并且改变系统的状态信息。

  4. 4.返回并等待下一个通知。

当有输入信息产生,或者控制动作发生时,程序并不会读入这些输入信息或控制信息,它只是对它们的出现做出响应。下面给出一些简单示例,示例中会使用Frame 类的对象。

图形用户界面中的事件,来自两个不同的来源:一个来源是用户;另一个来源是硬件时钟。在一个典型的工作站环境中,用户通过以下方式来产生事件:移动鼠标;按下及松开鼠标按钮;或者,按下键盘上的按键。如果装有其它外设的话,用户还能够通过以下方式来产生事件:移动游戏控制杆;移动轨迹球;或者,移动虚拟现实设备(手套、头盔显示器,等等)。在我们这第一个简单的编程环境中,只会对鼠标事件做出响应。硬件时钟,能够产生时序事件,这种事件,会用于在动画式的用户界面中创建动画式组件。对于这种动画式组件,有一个简单的例子,就是,闪烁的文字,它会将用户的注意力吸引到界面上某个重要部位。在程序中,要想让文字能够闪烁的话,程序本身就必须能够测量时间的流逝。一个流式或序列式的定时器事件组,就能够提供这种能力。

此处,我们会使用一个简单的编程环境,它与那些在常见的面向对象窗口系统(它与这个东西非常近似:Java抽象窗口工具包(Java Abstract Windowing Toolkit)(事实上,它与那个东西的基本模式是等同的)。)中出现的编程环境类似。然而,最初版本的编程环境,却不是面向对象的。随着妳学习更多关于面向对象编程的知识,我们会将这个环境中的各个部分逐渐改写成更好的面向对象形式。最终,整个图形用户界面程序,都会变成由一个应用程序对象来表示。

需要注意,这个简单的环境中,并没有主程序。面向对象的编程语言,包括C++在内,都拥有一个主程序,其中定义了执行代码的起点,并且,在狠多程序中,主程序都是由应用程序开发者编写的。然而,在图形用户界面程序中(以及其它的,使用了较复杂的运行时库的程序中),主程序通常都是由运行时库的实现者来编写的,该运行时库提供了狠多的底层支持函数。

这个简单编程环境中,有4个过程函数,位于同一个文件中,文件名为Program.cc,如下所示。在那些过程函数之外,即是一个全局的作用域,在其中,可声明整个程序级别的对象(例如长期存在Frame对象)。在这个全局作用域中,还可以定义一些用于标示应用程序当前状态的变量。在那4个过程函数中,都可对这些全局对象和全局变量做出操作。注意,这里的文件名,Program.cc,是随意选取的,它在C++或图形用户界面编程中并没有什么一般意义。

简单编程环境

// 文件Program.cc

#include "Program.h"

// 在此处包含任何必要的头文件(例如"Frame.h")

// 在此处定义任何全局对象或全局变量

void OnStart(void) {}

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

void OnTimerEvent(void){}

void OnPaint( void ){}

OnStart函数,仅会被调用一次。它可用于对任何的全局对象和数据进行初始化,以及,创建即将被用户看到的初始显示内容。每当系统认为,用户界面可能已经损坏、应当重绘的时候,就会调用OnPaint 方法。常见的能够触发这个函数的动作包括:窗口被移动;窗口改变大小;之前被某个窗口部分地或全部地遮挡,如今变得可见了。每当用户在Frame 对象的显示区域中点击鼠标按钮或者移动鼠标时,就会调用OnMouseEvent 函数。这个函数,有若干个输入参数:事件在其中发生的那个Frame 对象的名字;鼠标当前位置的 x y 坐标;鼠标按钮的当前状态。每当时钟事件发生时,就会调用OnTimerEvent 函数。下表中,简短地说明了,在简单编程环境中,这4个函数的作用。

简单编程环境中的函数概要

OnStart

初始化全局对象、全局变量和用户界面显示内容

OnPaint

在必要的时候,重绘用户界面

OnTimerEvent

对时钟事件做出响应

OnMouseEvent

对鼠标点击和/或鼠标移动事件做出响应

尽管 ,常规的(通常也是强烈的且正确的)建议中,要求避免使用全局数据,但是,存在 着两个原因,使得,在简单编程环境中需要使用全局变量。第一, 这些全局变量,只是一个临时的权宜之计 - 随着 妳学习到更多关于面向对象编程的知识,狠多(即使不是所有的) 的全局数据都会消失。此时此刻 ,我们暂时忍受这些全局数据, 以便能够开始进行基本的面向对象编程实验。第二,全局 对象 ,相比于全局 变量 来说,并不是那么讨厌。最少 ,在对象中定义了一个接口,它能保护其中 封装 的数据,不被 用。全局数据 就没有这种保护能力了。 进一步来说, 在构建对象之间关联关系的过程中,并不总是能够完全排 除掉全局数据的。 有些时候,那些组成关联关系的对象,必须被定义为全局数据。 打个比方,将汽车看作一个关联关系,那么,其中 的某些组件就无法隐藏起来,例如方向盘、刹车 和转向灯。

在这个简单编程环境中编写程序,就需要,提供代码 ,用于实现简单编程环境中定义的4个函数中的部分或全部。 这会产生 一个可执行程序,它会使用妳所定义的这些函数。通过编辑Program.cc文件, 即可为这4个函数提供代码。然后 ,就必须编译这个文件,并且将它链接到适当的运行时库。 这个辅助步骤, 由一个现成的“制造”(" make ")文件来完成。编辑 完Program.cc 之后,直接运行"make"命令即可 ,不带任何参数。 make程序 会创建出一个可执行文件,名为" Program " 为了简单起见,这里的两个名字, Program.cc Program ,不可改变。

在运行的时候,使用简单编程环境开发的程序,首先会显示一个开始(Start)窗口,其中提供了一些控件,可用于:初始化程序;终止程序;以及控制某个简单定时器。下图中展示了开始窗口。其中的开始(Start)按钮,在按下之后,会调用OnStart 函数,然后,开始按钮会消失。在任何时候,可通过点击文件(File)菜单中的退出(Quit)选项来终止整个程序。定时器(Timer)菜单中,包含两个条目,分别用于开启及关闭某个定时器(即为时钟事件来源)。时钟事件的产生,会导致OnTimerEvent 函数被调用。这个定时器的时间间隔(两次定时器事件之间的间隔),由带有"Timer Control"字样的滑动条控制。滑动条和定时器的时间单位是毫秒。滑动条的最初数值是500毫秒(半秒钟)。定时器时间间隔的取值范围是50毫秒(20分之1秒)1000毫秒(1秒钟)。当定时器处于运行状态时,要想改变时间间隔的话,就必须,先停止定时器,再改变滑动条位置,再重新启动定时器。当定时器正在运行时,直接改变滑动条的值,并不会实际地改变定时器时间间隔。

开始窗口

简单程序


我们提供了3个简单程序,以展示这个简单编程环境。第一个程序,是一个简单的“世界妳好”("Hello World")程序。第二个程序,在第一个“世界妳好”程序的基础上做修改,加入对鼠标按钮点击事件的处理。第三个程序,在第二个程序的基础上做修改,加入对定时器事件的使用。

下表中展示了世界妳好程序。这个程序,包含了"Frame.h"头文件,因为它需要使用Frame 类的定义来声明一个Frame 对象,其名字为 window ,位于屏幕的(200,200)处,宽度为400 像素,高度为400 像素。当这个窗口显示出来的时候,它的标题会是"Hello World Program"。在OnStart 和OnPaint 函数中,会清空窗口(window)对象的内容,并在窗口左上角附近显示字符串"Hello World"。

世界妳好程序

#include "Frame.h"

Frame window("Hello World Program", 200, 200, 400, 400);

void OnStart(void)

{

    window.Clear();

    window.DrawText("Hello World!", 20, 20);

}

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

{}

void OnTimerEvent(void)

{}

void OnPaint(void)

{

    window.Clear();

    window.DrawText("Hello World!", 20, 20);

}

第二个程序展示了如何处理鼠标事件。在这个版本的世界妳好程序中,会在用户点击鼠标左键的地方显示出字符串"Hello World!"。每当鼠标被移动或者某个鼠标按钮被点击时,就会调用OnMouseEvent 函数。在这个程序中,OnMouseEvent 函数会检查鼠标的状态,以判断,鼠标左键是否处于“按下”状态(也就是说,被按下了)。如果那个按钮是处于按下状态(表明用户点击了鼠标按钮),那么,窗口会被清空,并将字符串"Hello World!"显示到鼠标事件发生的坐标处。OnMouseEvent 函数中的"buttonState"参数,是一个位映射。这句代码:

if( buttonState & leftButtonDown)...

即可简单地检测,表明鼠标左键被按下的那个位是否已经被设置了。如果该个位已经被设置了,则表明对应的鼠标按钮处于按下状态。此处的"leftButtonDown",是某个枚举中的一个值,它是在文件Program.h 中定义的。其它的值包括:rightButtonDownmiddleButtonDown;和isDragging。在这个程序中的另一个重要的注意事项就是,使用了由变量 lastx lasty 表示的状态信息,它们会记录上次显示"Hello World!"字符串的位置。对这个状态信息进行记录是有必要的,因为,当窗口需要重绘时,需要在OnPaint 函数中知道该在窗口中的哪里放置这个字符串。注意, lastx lasty 是在OnStart 函数中初始化的,并在OnMouseEvent 函数中发生鼠标点击事件时更新。

带有鼠标事件处理能力的世界妳好程序

#include "Program.h"

#include "Frame.h"

Frame window("Hello World Program", 200, 200, 400, 400);

int lastx;

int lasty;

void OnStart(void)

{

    window.Clear();

    window.DrawText("Hello World!", 20,20);

    lastx = 20;

    lasty = 20;

}

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

{

if (buttonState & leftButtonDown)

    {

        window.Clear();

        window.DrawText("Hello World!",x,y);

        lastx = x;    

        lasty = y;

    }

}

void OnTimerEvent(void)

{}

void OnPaint(void)

{

    window.Clear();

    window.DrawText("Hello World!", lastx, lasty);

}

第三个程序,展示的是,如何处理定时器事件。在这个程序中,"Hello World!"字符串会变成闪烁文字,具体做法就是,在相邻的定时器事件中,交替地清除和显示出字符串。闪烁频率是由定时器的时间间隔来控制的。要注意,必须使用开始(Start)窗口中的定时器(Timer)菜单来开启定时器,才会有定时器事件产生。另外,开始窗口中的滑动条用于控制定时器事件之间的间隔。

这个程序中,还使用了另一个状态信息,它由变量 visible 来记录,记录的是,字符串当前是处于可见状态还是不可见状态。除了OnTimerEvent 之外,OnMouseEvent 和OnPaint 函数也会使用这个状态信息,以决定,是否要在窗口中显示这个字符串。

带有鼠标事件处理和定时器事件处理能力的世界妳好程序

#include "Program.h"

#include "Frame.h"

Frame window("Hello World Program", 200, 200, 400, 400);

int lastx;

int lasty;

int visible;

void OnStart(void)

{

    window.Clear();

    window.DrawText("Hello World!", 20, 20);

    lastx = 20;

    lasty = 20;

    visible = 1;

}

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

{

if (buttonState & leftButtonDown)

    {

        window.Clear();

if (visible)

            window.DrawText("Hello World!",x,y);

        lastx = x;  

        lasty = y;

    }

}

void OnTimerEvent(void)

{

    window.Clear();

if (visible)

        visible = 0;

else

    {

        visible = 1;    

        window.DrawText("Hello World!", lastx, lasty);  

    }

}

void OnPaint(void)

{

    window.Clear();

if (visible)

        window.DrawText("Hello World!", lastx, lasty);

}

以下练习中,会实现一系列有意思的小程序,妳可以使用Frame 类和简单编程环境来编写它们。

任务

  1. 1.编写一个声明语句,它会创建一个Frame 对象,位置在(20,30),宽度为150,高度为175。

  2. 2.编写一砣代码,将妳在上一个步骤中创建的Frame 移动到(50,50)位置,改变它的宽度为100,高度为200。

  3. 3.编写一个程序,它会将妳的全名显示在某个Frame的正中心位置,而那个Frame 本身也会显示在屏幕的正中心位置。注意,妳可能需要多次试验,以确定适当的尺寸和位置。

  4. 4.编写一个程序,在一个尺寸为 400 x 400 的Frame 中,每个边角处,绘制一个半径为 20 的圆。

  5. 5.模拟时钟:编写一个程序,它会在某个Frame 中显示一个模拟时钟的图片。可使用一个圆来表示时钟的表盘,使用一条线段来表示指针。Place the hand pointing straight up.

  6. 6.带有两个指针的模拟时钟:修改前面的模拟时钟程序,使得它拥有一个较短的(小时)指针和一个较长的(分钟)指针。调整两个指针的角度和位置,使得时钟上显示的是3点。

  7. 7. 带数字的模拟时钟:修改之前的模拟时钟程序,使得,在表盘外侧,显示出1 到12 的数字。

  8. 8.动画式模拟时钟:修改之前的任何一个模拟时钟程序,使得指针会移动。在每个定时器事件发生时,将指针移动到下一个位置。

  9. 9.角落漫游:编写一个程序,它将一个圆放置在某个Frame的左上角。然后,在程序中,应当将这个圆移动到右上角、右下角和左下角,最后再将它移动到最初位置。将这个循环重复10次。在位于每个位置时,在窗口中适当位置显示一行文字,表明圆的位置,例如"Upper Left"" Upper Right""Lower Right""Lower Left"。

  10. 10. 边境游走:编写一个程序,它让一个圆以较小的步幅沿着屏幕边缘移动,起点是屏幕的左上角。妳能否做到,让移动过程显得流畅?

猴骑猪

Melissa Satta