C++程序设计:原理与实践(进阶篇)17.7 使用Shape类

简介:

17.7 使用Shape类


本节介绍图形库的一些基本工具:Simple_window、Window、Shape、Text、Polygon、Line、Lines、Rectangle、Function、Color、Line_style、Point、Axis。目的是让你知道这些工具能够实现什么功能,而并非详细理解某个类。下一章将会介绍每个类的设计与实现。

下面来学习一个简单的程序,我们将逐行解释代码,并给出每一行代码在屏幕上的显示效果。在程序运行时,你会看到当我们向窗口添加形状以及改变已有形状时,屏幕上图像的变化情况。大体上,我们是通过分析程序的执行情况来“动画演示”代码的流程。

17.7.1 图形头文件和主函数

首先,我们包含定义了图形和GUI工具接口的头文件:

 

或者

 

你可能已经猜到,Window.h包含与窗口有关的工具,Graph.h包含在窗口上绘制形状(包括文本)的有关工具,这些工具都定义在Graph_lib名字空间中。为简化起见,我们使用名字空间指令,使得Graph_lib中的名字可以直接在程序中使用。

 

照例,main()函数包含我们要(直接或间接)执行的代码及例外处理:

 

main()函数进行编译时,必须已定义了exception。如果我们照例包含了std_lib_facilities.h,就会得到exception,否则我们会从标准头文件处开始直接处理,此时需要包含<stdexcept>。

17.7.2 一个几乎空白的窗口

在这里,我们不讨论错误处理(参见第5章,特别是5.6.3节),直接进入main()函数中的图形代码:

 

这段代码首先创建一个Simple_window,即一个有“Next”按钮的窗口,并将它显示在屏幕上。显然,为了得到Simple_window对象,我们应该包含头文件Simple_window.h而不是Window.h。在这里,我们明确给出了窗口在屏幕上的显示位置:其左上角位于Point{100, 100}。这个位置很接近屏幕的左上角,但没有过于靠近。很显然,Point是一个类,其构造函数有两个整型参数,表示点在屏幕上的(x, y)坐标。我们可以将代码写为:

 

然而,为了便于多次使用点(100, 100),我们还是选择给它一个符号名称。600和400分别是窗口的宽度和高度,Canvas是在窗口框上显示的标签。

为了真正将窗口绘制在屏幕上,我们必须将控制权交给GUI系统。我们通过调用win.wait_for_button()来达到这一目的,结果如下:

 

在窗口的背景中,我们看到了一个笔记本电脑的桌面(已经临时清理过了)。如果你对桌面背景这种不相关的事情感到好奇,我可以告诉你,我拍摄照片时正站在安提布的毕加索资料馆附近俯瞰尼斯湾。隐藏在程序窗口之后的黑色控制台窗口是用来运行我们的程序的。控制台窗口不太美观,而且也不是必需的,但当一个尚未调试通过的程序陷入无限循环或无法继续执行时,我们可以通过它来终止程序。如果你仔细观察,会发现我们使用的是微软C++编译器,当然你可以使用其他的编译器(如Borland或者GNU)。

在之后的介绍中,我们将去掉程序窗口周围分散注意力的内容,仅仅给出窗口本身:

 

窗口的实际尺寸(以像素计算)依赖于屏幕的分辨率。某些屏幕的像素要比其他屏幕更大。

17.7.3 坐标轴

一个几乎空白的窗口没有什么意思,最好给它添加一些信息。希望添加些什么内容呢?注意:并不是所有的图形都是有趣的或者是关于游戏的,我们将从坐标轴——一种严肃的、有点复杂的图形开始。一个没有坐标轴的图形通常是很难看的。没有坐标轴的帮助,我们通常难以弄清数据的含义。或许你可以借助伴随的文字来解释,但使用坐标轴要保险得多;人们通常不会阅读文字描述,而且好的图形表示通常与其语境是分离的。因此,图形需要坐标轴:

 

 

操作步骤为:创建坐标轴对象,将其添加到窗口,最后进行显示:

 

可以看到,Axis::x是一条水平线,其上有指定数量的“刻度”(10个)和一个标签“x axis”。通常,标签用于解释坐标轴和刻度的含义。我们通常把x轴放在窗口底端附近。在实际应用中,我们更喜欢用符号常量来表示高度和宽度,这样“在底端上方附近”就可以用y_max-bottom_margin这样的符号表示,而不是用300这样的“魔数”(参见4.3.1节和20.6.2节)。

为了帮助识别程序的输出,我们用Window的成员函数set_label()将该窗口的标签重新设置为“Canvas #2”。

现在,添加一个y坐标轴:

 

我们将y轴和标签的颜色分别设置为cyan和dark_red(只是为了展示一些工具的使用)。

 

我们并不认为x轴和y轴使用不同颜色是一个好主意。这里只是为了说明如何设置形状或者其中某个元素的颜色。使用很多种颜色未必是个好主意,特别是初学者更容易热衷使用很多颜色。

17.7.4 绘制函数图

接下来做什么呢?现在,我们已经有了一个包含坐标轴的窗口,因此看起来画出一个函数是个好主意。我们创建一个形状来表示正弦函数,并将它添加到窗口:

 

在这段代码中,名为sine的Function对象使用标准库函数sin()产生的值绘制一条正弦曲线。我们将在20.3节详细讨论如何绘制函数图。现在,你只需知道,绘制函数图时必须给出起始点的位置和输入值集合(值域),并且还需给出一些信息来说明如何将这些内容塞入窗口(缩放)。

 

请注意在到达窗口右边界时曲线是如何停止的。当我们绘制的点超出窗口矩形区域时,将被GUI系统简单忽略掉,永远不会真正显示出来。

17.7.5 Polygon

函数图是表示数据的一种方法,在第20章将会看到更多实例。我们还可以在窗口中绘制不同类型的对象:几何形状。我们使用几何形状来进行图形演示,可以表示用户交互组件(如按钮),通常还能使演示更加生动。多边形(Polygon)被描述为一个点的序列,这些点通过线连接起来就构成Polygon类。第一条线连接第一个点到第二个点,第二条线连接第二个点到第三个点,以此类推,最后一条线连接最后一个点到第一个点。

 

 

这段代码首先展示了如何改变正弦曲线的颜色。然后,与17.3节的例子一样,我们添加了一个三角形,作为一个多边形的例子。然后我们再次设置了颜色,最后设置了线型。Polygon的线都有“线型”,默认线型为实线,但我们也可根据需要改为虚线、点状线等(参见18.5节)。这段程序显示如下图形:

 

17.7.6 Rectangle

屏幕是矩形,窗口是矩形,一张纸也是一个矩形。实际上,现实世界中有很多形状都是矩形(至少是圆角矩形)。原因在于矩形是最容易处理的形状。例如:矩形易于描述(左上角和宽度、高度,或者左上角和右下角,诸如此类),易于判断一个点在矩形之内还是之外,易于用硬件快速绘制像素构成的矩形。

与其他封闭的形状相比,大多数高级图形库能够更好地处理矩形。因此,我们将矩形类Rectangle从多边形类Polygon中独立出来。一个Rectangle可以用左上角坐标、宽度和高度来描述:

 

由此可得:

 

请注意,将位置正确的四个点连接起来并不一定得到一个Rectangle。当然,在屏幕上创建一个看起来像Rectangle的Closed_polyline是很简单的(你甚至可以创建一个看起来像是Rectangle的Open_polyline),例如:

 

 

实际上,poly_rect对应的屏幕图像(image)是一个矩形。但内存中的poly_rect对象并不是一个Rectangle对象,而且它也不“知道”有关矩形的任何内容。验证这一点的最简单方法是再添加一个点:

 

矩形是不会有5个点的:

 

对于我们分析程序非常重要的一点是:Rectangle不仅仅是在屏幕上看起来像是一个矩形而已,它还应该从根本上保证此形状(几何意义上)始终是一个矩形。这样,我们编写代码时就可以信赖Rectangle——它确实表示屏幕上的一个矩形,而且保证不会改变为其他形状。

17.7.7 填充

前面绘制形状都是绘制轮廓,我们也可以使用某种颜色“填充”一个矩形:

 

我们还觉得对三角形(poly)当前的线型不满意,所以将其线型设置为“粗(正常线型的4倍粗细)虚线”,我们也改变了poly_rect(现在已经看起来不像是矩形了)的线型。

 

如果你仔细观察poly_rect,你会发现轮廓是在填充色上层显示的。

任何封闭的形状都可以被填充(参见18.9节)。矩形很特殊,它非常容易填充(填充速度也非常快)。

17.7.8 Text

最后,任何一个绘图系统都不可能完全没有简单的文本输出方式——将每个字符看作线的集合来绘制,并保证不会剪切掉字符。我们已经展示了如何为窗口和坐标轴设置标签,但我们也能使用Text对象将文本放置在任何位置。

 

 

利用此例中的基本图形元素,你可以生成任何复杂、微妙的显示效果。请注意本章所有代码的一个共同特点:没有循环和选择语句,而且所有数据都是“硬编码的”。输出内容只是基本图形元素的简单组合。一旦我们开始使用数据和算法来组合这些基本图形,就可以得到更复杂、更有趣的输出效果了。

我们已经看到过如何改变文本的颜色了:坐标轴的标签(参见17.7.3节)本身就是一个Text对象。此外,我们还可以为文本选择字体和字号:

 

这段代码将Text的字符串“Hello, graphical world!”中的字符放大到20号字,字体设置为粗体Times。

 

17.7.9 Image

我们还可以从文件中加载图片:

 

执行上述代码,将在窗口中显示文件名为image.jpg的照片,照片中两架飞机正在突破音障:

 

这幅照片比较大,我们刚好把它放在了文本和图形上层。因此,为了清理窗口,我们将它稍微移开一点:

 

 

请注意不在窗口区域之内的部分图片是如何被简单忽略掉的。超出窗口区域的内容都会这样被图形系统“剪裁”掉。

17.7.10 更多未讨论的内容

下面代码展示了图形库更多的特性,在这里不再进行详细解释:

 

 

你能猜出这段代码显示什么内容吗?是不是很容易猜?

 

代码与屏幕显示内容的关联是很直接的。如果你还未看出这段代码是如何产生这样的输出的,请继续学习后续章节,很快就会搞清楚的。请注意我们是如何使用ostringstream(参见11.4节)来格式化输出尺寸的文本对象的。

相关文章
|
15天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
26 2
|
21天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
55 5
|
28天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
57 4
|
29天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
71 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
23 1
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
29 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
25 4
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
2月前
|
存储 编译器 C语言
【C++类和对象(上)】—— 我与C++的不解之缘(三)
【C++类和对象(上)】—— 我与C++的不解之缘(三)