面向懒惰程序员的 C++20 教程(一)

原文:C++20 for Lazy Programmers

协议:CC BY-NC-SA 4.0

一、入门指南

本书前半部分的大多数程序使用了 SDL 和 SSDL 的图形和游戏库, 1 理论上,观看彩色图形在屏幕上移动并互相射击比打印文本更有趣。别担心。当你完成后,你将能够用这个库或不用这个库来编写程序——如果我对此有什么要说的,你会从中得到乐趣。

如果你已经选择了你的平台,很好。如果没有,我的建议是:

  • 如果你只是想在一个简单易管理的平台上学习 C++,微软 Visual Studio 很棒。

  • 如果您是一名 Unix 系统管理员,或者有很好的访问权限,并且希望使用这个流行且功能强大的平台,那么就去使用它吧。

  • 要学习 g++ 并在 Windows 中用相对简单的设置制作来自 Unix 世界的强大工具,请使用 MinGW。

不同平台之间的编程不会有太大差异。但是系统设置可能是一个问题。

初始设置

首先,你需要教材的源代码。您可以通过位于 www.apress.com/9781484263051 的下载源代码按钮访问代码。

然后拉开拉链。在 Unix 中,unzip命令应该可以工作;在 Windows 中,您通常可以双击它或右键单击并选择“提取”或“全部提取”。

…在 Unix 中

在 Unix 中漫游不在本书的讨论范围之内,但是不用担心。复制文件、移动文件等基础知识很容易掌握。 2

Unix 系统管理的方式超出了本书的范围。 3 但是安装 SSDL 很容易。在你刚刚解压的文件夹里

  • 进入external/SSDL/unix,输入make。这将 SSDL 构建在一个源代码中的程序知道在哪里可以找到它的地方。

  • 进入ch1/test-setup

  • cp Makefile.unix Makefile

  • make

  • ./runx

你应该看到(听到)图 1-1 中所示的程序。(如果没有,一定是遗漏了什么——请参见附录 a。)您可能需要一点时间来尝试来自ch1的另一个程序,比如1-hello。像你在test-setup一样经营它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-1

test-的输出setup

…在 MinGW 中

你可以在 sourceforge.net 和其他地方找到 MinGW。试试在网上搜索“MinGW 下载”

一旦安装完成,让它添加 C++ 的基础知识;启动 MinGW 安装管理器(mingw-get.exe),至少安装mingw32-gcc-g++-binmingw32-gdb-binmsys-make-bin

不会需要安装 SDL 或者 SSDL;它们在你解压的源代码里。

所以让我们试试吧。打开 Windows 命令提示符(单击开始菜单并键入cmd)并转到源代码的ch1/test-setup文件夹。这里有一个简单的方法:在该文件夹的窗口中,单击地址栏左边的文件夹图标,该部分显示类似于... > ch1 > test-setup的内容。它将被一个高亮显示的路径代替,如图 1-2 所示。按 Ctrl-C 复制它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-2

在 Windows 中获取用于命令提示符的路径

在命令窗口中,输入您复制的路径的前两个字符(在我的例子中是C:);然后输入cd,粘贴路径(Ctrl-V),再按回车键(见图 1-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-3

在命令提示符下找到正确的文件夹

然后

copy Makefile.mingW Makefile
make
bash runw

你应该看到(听到)图 1-1 中所示的程序。(如果没有,请参见附录 a。)您可能需要一点时间来尝试来自ch1的另一个程序,比如1-hello。像你在test-setup一样经营它。

…在 Microsoft Visual Studio 中

目前,Visual Studio 完全免费。进入微软的下载页面(目前是visual studio . Microsoft . com/downloads/)下载社区版。

安装要花很长时间。确保用 C++ 进行桌面开发(图 1-4 ,右上角)——否则,你会有 Visual Studio,没错,但它不知道 C++。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-4

安装 Visual Studio 的 C++ 部分

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传安装完成后,进入该书的源代码文件夹,进入ch1子文件夹;双击解决方案文件,ch1.slnch1。(如果它要求您登录,而您现在还没有准备好,请注意“现在不行,以后再说”这一行。)

现在,在解决方案浏览器窗口中(见图 1-5 ,你应该在底部看到一个名为测试设置的项目。右键单击它,并选择调试➤启动新实例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-5

Visual Studio 中的ch1解决方案,突出显示了test-setup项目

你应该看到和听到图 1-1 中的节目。(如果没有,请参见附录 a。)您可能需要一点时间来尝试来自ch1的另一个程序,比如1-hello。像你在test-setup一样经营它。

简单的程序

从小处着手是明智的。更少的事情会出错。

所以我们将从一个简单的程序开始,它写着“你好,世界!”在屏幕上。我们会一行一行的看里面有什么。(在下一节中,我们将编译并运行它。现在,坐着别动。)

// Hello, world! program, for _C++ for Lazy Programmers_
//   Your name goes here
//   Then the date4

//    It prints "Hello, world!" on the screen.
//    Quite an accomplishment, huh?

#include "SSDL.h"

int main (int argc, char** argv)
{
     sout << "Hello, world!  (Press any key to quit.)\n";

     SSDL_WaitKey ();        // Wait for user to hit any key

     return 0;
}

Example 1-1“Hello, world!” is a classic program to start a new language with. (I think it’s a law somewhere.) This program is in source code, in the ch1 folder, as 1-hello

第一组行是注释。评论看起来是这样的——//Something on a line after two slashes——它们仅仅是为你或者是为后来试图理解你的程序的人准备的。最好善待你自己和你的维护者——帮助他们容易地知道程序在做什么,而不必去搜索和弄清楚。

接下来,我们有一个include文件。一些语言特性内置于 C++ 编译器本身,比如注释标记//#include。其他仅在需要时加载。在这种情况下,我们需要知道如何使用 SSDL 库在屏幕上打印东西,所以我们包含了文件SSDL.h

接下来,我们有了main函数。main ()特殊;它告诉编译器,“这就是我们在程序中要做的;从这里开始。”现在我将推迟解释这个奇怪的顶行——我们将在第二十五章的“命令行参数”一节中讨论它——只是说,现在,我们总是用同样的方式写它。否则,C++ 之神会用无法理解的错误消息来惩罚我们。

在这种情况下,main ()只做两件事:

首先,它使用sout对象打印"Hello, world!"消息,读作“S-out”\ n的意思是“继续下一行”

第二,它调用SSDL_WaitKey (),在它结束程序之前等待你按一个键。否则,程序会在您有机会看到它的消息之前关闭。

我们return 0是因为main ()必须return一些东西,很大程度上是因为历史原因。实际上,我们几乎从不关心main返回什么。

花括号{}告诉main ()从哪里开始采取行动,在哪里结束;无论你想让程序做什么,都在花括号之间。

编译器对你输入的东西非常挑剔。去掉一个;,程序就不会编译。改变某些东西的大小写,C++ 不会识别它。

如果你想知道没有 SSDL 这样一个简单的程序会是什么样子,请看第二十九章。它不适合初学者,但以后应该会有意义。

Extra

“你好,世界!”通常是初学者用新语言编写的第一个程序。虽然它最初是 C 语言中的一个简单例子——c++ 是这种语言的起源——但是将它作为第一个程序来编写的做法已经传播开来。这里是“你好,世界!”在 BASIC 中:

10 PRINT "Hello, world!"

不错吧。

这是它在 APL 中的样子。APL ( A P 编程 L 语言)被描述为“只写”语言,因为据说你不能阅读自己写的程序。APL 需要符号,如、∇和ρ:

□t0□

尽管这些看起来比 C++ 的版本简单,但 C++ 的版本既不是最长的也不是最难的。为了节约资源,我就不赘述了(一个例子是 Redcode 语言有 158 行,这可能是你从未听说过 Redcode 的原因),但这里有一个很难的例子,来自一种有时被称为 BF 的故意困难的语言:

++++++++++++++++[>++++>++++++>+++++++>+++>++<<<<<-]>++++++++.>+++++.+++++++..+++.>>----.>.<<+++++++.<.>-----.<---.--------.>>>+.

更多“你好,世界!”在撰写本文时,可在hello world collection . de/找到相关示例。

间隔

编译器不关心间距。只要你不在单词里面放空格,你可以把它放在你喜欢的任何地方。你可以选择换行或者不换行;它不会在意,只要你不弄坏一个//comment或者一个"quotation"

// Hello, world! program, for _C++ for Lazy Programmers_
//    It prints "Hello, world!" on the screen.
//    Quite an accomplishment, huh?

      #include "SSDL.h"

            int main (int argc, char** argv) {
      sout <<
"Hello, world!  (Press any key to quit.)\n";

            SSDL_WaitKey ();    // Wait for user to hit any key

return 0;
      }

Example 1-2A blatant instance of evil and rude5 in programming

编译器不会在意间距——但是必须理解你的 500 页程序的可怜灵魂会在意!示例 1-2 的间距对于后来维护你的代码的人来说是一件残酷的事情。

可读性是一件好事。努力理解你的意思的程序员很可能就是写完它几天后的你。软件开发的大部分费用是程序员的时间;你不会想浪费你的时间去破译你自己的代码。说清楚。

Tip

在你写代码的时候,而不是之后*,让你的代码变得清晰。可读代码有助于开发,而不仅仅是未来的维护。*

为了更加清晰,我在示例 1-1 中使用了一些东西,比如初始注释、#includemain (),用空行隔开。这有点像在英语论文中写段落;每一节都是它自己的“段落”空行增加可读性。

我也以一种使程序易于阅读的方式缩进。默认的缩进是左边距。但是如果一个东西包含在另一个东西中——就像主函数中包含的sout语句——它会缩进几个空格。

这类似于论文的大纲格式或目录的布局(图 1-6 )。包含在其他内容中的内容会稍微缩进。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-6

就像英文论文大纲一样,C++ 程序是缩进的,子部分相对于它们所属的部分缩进

继续读下去,你会看到很多清晰缩进的例子。

Golden Rule of Indenting

当某个东西是之前的一部分时,它应该缩进(像这样)。

当它独立时,它保持相同的缩进级别。

创建 SSDL 项目

…使用 g++ (Unix 或 MinGW)

要创建自己的项目,进入newWork目录,将basicSSDLProject复制到一个有适当名称的新目录中——类似于cp -R basicSSDLProject myNewProject

然后将Makefile.unix复制到Makefile(如果你用的是 Unix)或者将Makefile.mingw复制到Makefile(如果你用的是 MinGW)。Makefile告诉系统如何编译,在哪里找到库,诸如此类的事情。

你还需要打开你的文本编辑器。在 Unix 上,你可能会使用 vi/vim(我觉得这很难,但也许你不会)、emacs、 7 或者其他一些编辑器。在 Windows 上,Notepad++ 是一个不错的选择。根据需要熟悉一下,打开main.cpp进行编辑。

这个程序是有效的,但是它没有做任何有趣的事情,所以你需要给它一些内容。现在,你可以输入 Hello,world!来自示例 1-1 的程序。要进行编译,请在命令提示符下键入make

也许你会犯一些错别字。如果是这样,make会给你一个错误信息列表。有时信息的意思很清楚,有时又不清楚。这里有一个典型的:我忘了一个;

main.cpp:11:53: error: expected ';' before 'SSDL_WaitKey'

随着时间的推移,您会更加理解模糊错误消息的含义。现在,将您键入的程序与示例 1-1 进行比较,并解决任何差异,直到您获得图 1-7 中的成功结果。(该程序实际上是在黑色上打印白色,不像显示的那样。书,大块的黑色墨水,不是一个好的组合。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-7

你好,世界!运转

你创建的文件

在新文件夹中,根据提示键入lsdir。您可能会看到一些文件:

a.out main.cpp main.cpp~ main.o #and a bunch of other stuff.

a.out是可执行程序。main.cpp是你写的让它产生的代码。main.cpp ~是你的编辑可能对你的.cpp文件做的备份文件。main.o是一个“对象”文件,g++ 可以在创建程序的过程中构建它。如果你看到它,你可能看不到,删除它是绝对安全的:

rm main.o

要删除此处列出的您不需要的东西,请键入make clean

很酷的命令行技巧
  • 重复一个命令 : 经常在命令提示符下,你可以按向上箭头重复上一个命令,或者多次重复一个更早的命令。如果这样不起作用,!后面跟着命令的前几个字母可能会重复它的最后一个实例。

  • 在目录名中使用通配符: cd partialname*通常可以节省时间。cd partialname后面跟着 Tab 键也可能起作用。

Extra:

tar Files for Unix (MinGW 用户,参见下面的“Extra:zipFiles”)

想要将该目录放入一个文件中进行邮寄或存储吗?在删除任何你不想要的大文件之后(make clean),进入一个目录(cd ..)并tar它:

tar -czvf project1.tar.gz project1

#for a directory named project1

您现在应该有一个文件project1.tar.gz,适合作为您最喜欢的邮件程序的附件发送。

要解包,把它放在你想放的地方(确保那里还没有一个project1目录,以防止覆盖),然后说

tar -xzvf project1.tar.gz

Unix 安装各不相同;您可能需要稍微修改命令——但是这在许多机器上都是可行的。

防错法

在“反欺诈”部分,我们考虑可能出错的事情以及如何修复或防止它们。例如:

  • 你运行程序,它永远不会停止。它可能在等待一些输入(比如按下一个键继续),或者它可能已经永远进入啦啦啦状态。可以用 Ctrl-C 杀死它(按住 Ctrl,按 C)。

  • 它以信息 Segmentation fault : core dumped停止。这或多或少意味着,“发生了不好的事情。”现在,只需删除核心文件(rm core)并在程序中查找问题。

你可能想现在跳到“如何不痛苦(无论你的平台是什么)”这一小节

…在 Microsoft Visual Studio 中

最简单的开始方式如下:

  1. 在源代码的newWork文件夹中,复制basicSSDLProject子文件夹,将你的副本保存在相同的位置,这样它就可以找到 SDL 和 SSDL。

  2. 适当改名(hello,也许?).

  3. 打开它的解决方案文件SSDL_Project.sln。您应该会看到类似图 1-8 的内容。 8

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-8

一个 SSDL 项目。在解决方案资源管理器窗口中(我的在左边;你的可能在别处),点击 SSDL 项目旁边的三角箭头,然后源文件;然后双击main.cpp查看主程序的(不完整)内容

如果你想从头开始,请参阅附录 a 中的说明。

编译你的程序

你的程序还没有做任何事情,所以你要给它一些内容。现在,你可以输入 Hello,world!来自示例 1-1 的程序。

也许你会犯一些错别字。

如果是这样的话,编辑器可能会在它反对的内容下面划一条弯弯曲曲的红线来警告你(图 1-9 )。将鼠标指针放在有问题的部分,它会给出一个提示,告诉你哪里出错了(尽管这个提示可能并不总是很清楚)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-9

Visual Studio 突出显示并正确识别错误

尽管这很有帮助,但你不能确定编辑是正确的。直到你试着编译并运行时,你才能确定。

要编译您的程序,请转到构建➤构建解决方案。要运行它,请转到调试➤不调试启动。或者,单击标签为“本地 Windows 调试器”的窗口顶部附近的绿色箭头或三角形

如果你的程序不能编译,它会给出一个错误列表。有时信息的意思很清楚,有时又不清楚。这里有一个典型的,用“…”来使它更简短:我忘了一个;

c:\...\main.cpp(13): error C2146: syntax error: missing ';' before identifier 'SSDL_WaitKey'

随着时间的推移,您会更加理解模糊错误信息的含义。现在,将您键入的程序与示例 1-1 进行比较,并解决任何差异,直到您获得这个成功的结果:一个显示消息Hello, world! (press any key to quit)的窗口。当它运行时,按任意键结束它。

Extra

在 Visual Studio 中,如果您试图运行一个未编译的程序,您可能会看到图 1-10 中的对话框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-10

“你愿意建造它吗?”窗户。每次回答都是一种痛苦

如果是这样,点击“不再显示此对话框”,然后点击“是”这意味着如果需要的话,它会在运行前尝试重新编译。

如果有错误,您可能会看到图 1-11 中的方框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-11

运行上一次成功的构建?从不

单击“不再显示此对话框”并单击“否”(否则,当您进行更改时,它将返回到以前的版本以找到一个可用的版本,而不是您的最新版本。扑朔迷离!)

如果您想再次看到这些对话框,比如说,如果您在想说“否”时单击了“是”,您可以通过菜单来修复它:工具➤选项➤项目和解决方案➤构建和运行。将“运行中…”空白重置为您想要的值(图 1-12 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-12

如何重置图 1-10 和 1-11 中设置的偏好

你创建的文件

现在翻翻你的文件夹。(通过 Windows 资源管理器或在 Windows 中打开其文件夹来访问它,随您喜欢。)你应该会看到类似图 1-13 的东西。(布局可能会有所不同,为了简单起见,这里没有显示一些文件。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-13

项目文件夹中的文件

Extra: File Extensions

如果你看到的一些文件被命名为(比方说)main而不是main-点什么的,我 非常强烈地推荐 你改变这个,这样你就可以看到点后面的“文件扩展名”。这有助于了解您正在处理什么类型的文件!

为此,在 Windows 10 中,在文件夹的视图选项卡(图 1-14 )中,单击文件扩展名和隐藏项目的框。你完蛋了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-14

更改文件夹和搜索选项以显示文件扩展名

这里有另一种方式,将在早期的 Windows 版本也工作:选项➤改变文件夹和搜索选项,或组织菜单➤文件夹和搜索选项。你应该看到一个写着文件夹选项的框。选择文件夹选项框的视图选项卡(图 1-15 )。一旦有,取消选中隐藏已知文件类型的扩展名;并选择显示隐藏的文件、文件夹或驱动器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-15

Microsoft Windows 中的文件夹选项窗口

文件夹中的重要文件包括

  • SSDL_Project.sln“解决方案”文件:知道其他文件在哪里的主文件。

  • SSDL_Project.vcxproj,“项目”文件:它知道程序存储在main.cpp和其他几个东西里。没有它就无法编译。

  • 你的程序。

  • Debug(或者,有时,Releasex64)文件夹:它包含你的可执行文件。

Tip

您可以删除任何DebugRelease.vsx64文件夹。Visual Studio 将根据需要重新创建它们。如果空间很重要,这一点很重要——比如说,如果你计划通过电子邮件发送文件夹。

如果您看不到.vs文件夹,请参阅前面的“附加:文件扩展名”

重新打开您的项目

如果您的计算机设置正确,您可以双击hello.sln启动 Visual Studio 并重新打开您正在处理的内容。(双击其他内容可能无法打开您需要的所有文件。)

Tip

重新打开.sln文件,而不是.vcxproj.cpp文件。

防错法

在“反欺诈”部分,我们将考虑可能出错的事情以及如何修复或防止它们。

以下是您在使用 Microsoft Visual Studio 时会发现的一些常见问题:

  • 你打开一个溶液(。sln)文件,但是它说没有一个项目会加载。也许你移动了源代码文件夹中的东西;他们必须呆在原地。

也可能你根本不在文件夹里,而是在 zip 文件里!zip 文件的列表看起来像一个文件夹,但它不是。请确保您解压缩源代码(参见本章开头的“初始设置”),并使用新文件夹,而不是 zip 文件。

  • 无法打开包含文件或. lib 文件。错误消息可能会说类似fatal error C1083: Cannot open include file: 'SSDL.h': No such file or directory

或者

1>LINK : fatal error LNK1104: cannot open file 'sdl2_ttf.lib'

此时最有可能的解释是您的项目文件夹不在源代码存储库中的正确位置。确保它与basicSSDLProject文件夹在同一个位置。

  • **它乐于接受编辑,但不提供编译选项;或者如果有,编辑没有效果。**很可能你没有打开.sln文件,而是打开了main.cpp或其他文件。关闭您正在处理的文件(保存到某个地方,以便您可以使用这些编辑!)并通过双击.sln文件重新打开。

  • **你输入了一些它应该能识别的东西,但是编辑器没有按照你预期的方式给它上色,或者在它下面画了一条弯弯曲曲的红线。**通常它会识别returnvoid并给它们上色,以表明它知道它们是关键词。你可能打错了。或者编辑可能会感到困惑。重新编译以确保正确。

  • 它说。无法打开 exe 文件进行写入。你可能还在运行这个程序;它不能覆盖程序,因为它正在使用中。终止程序并重试。

  • 它给出了一些其他的错误信息,并且不会完成构建。通常,再试一次就足以让它成功。

  • 运行完程序需要很长时间。你可以耐心等待,也可以通过 Windows 任务管理器将其杀死。如果这种情况持续发生,很可能是你的程序有问题。(还是在等你打东西?)

  • 它抱怨 Windows SDK :

    C:\...\Microsoft.Cpp.WindowsSDK.targets(46,5): error MSB8036: The Windows SDK version <some number or other> was not found. <More details.>

    或者在试图编译之前就失败了。

    解决方案:右键单击项目(不是解决方案或main.cpp),选择重定目标项目,并同意它所说的内容。

  • 你试图打开一个源代码解决方案,它警告你“你应该只打开来自可靠来源的项目”好消息是:我值得信任,所以你可以点击确定。取消勾选“为该解决方案中的每个项目询问我”的复选框,这样你就不会那么烦恼了。

Extra:

zip 文件

您可能想通过电子邮件将您的项目发送给某人;或者您可能希望将其紧凑地存放起来。

通常的方法是右击文件夹,选择添加到 Zip(或添加到<folder name>.zip)或发送到… ➤压缩文件夹。然后你可以把它附在电子邮件里,如果这是你的计划的话。

不管你怎么做,确保你首先删除了所有的DebugRelease.vs文件夹。它节省空间,如果你不这样做,一些邮件程序不会发送附件。

如何不痛苦(无论你的平台是什么)

无论您使用什么编译器,都可能会遇到这些问题:

  • 你会得到无数的错误。这并不意味着你做了无数的错事。有时一个错误会让编译器非常困惑,它会认为后面出现的都是错的。为此,**先修正第一个错误。**它可能会消除上百条后续错误信息。

  • **出错的那一行看起来没问题。**也许问题出在前面的线路上。编译器直到下一行才能判断出错误,所以它报告错误的时间比您预期的要晚。丢失;的情况经常发生。

  • 你得到警告 **,但是程序仍然准备运行。你应该吗?有错误,有警告。**如果只是给出警告,编译器仍然可以生成程序,但是错误会阻止编译。您可以忽略警告,但它们通常是一些确实需要修复的问题的很好的提示。

  • 你写的每一个程序!似乎一开始就充满了错误。你怀疑自己是不是傻。如果是这样,那么我们其他人也是。我也许能得到一个你好,世界!程序第一次工作。再久一点,就算了。

这里有一个大问题:

  • 你犯了一个错误,这个一直运行良好的程序现在根本无法运行了。每当你做出重大改变时(重大意味着“你害怕自己可能无法撤销”)…
    • Windows:复制包含您的项目的文件夹(.sln文件、.vcxprojcpp,全部),然后粘贴它(跳过任何它不让你复制的文件——反正会是你不在乎的东西),这样就创建了一个备份。

    • Unix:复制您的.cpp文件,比如说cp main.cpp main.cpp.copy1。你也可以用cp -R复制整个目录。

对于大型项目来说,一系列备份副本是绝对必要的。我敦促你现在就养成习惯*。如果不是…你已经在你的项目上工作了 6 个月。你做了一些使它崩溃或拒绝编译或给出错误输出的事情;更糟糕的是,你昨天就做了,而且从那以后你已经做了几次更新。回到昨天的代码并获得几乎可以工作的版本,而不是重新创建 6 个月的工作,这不是很好吗?备份副本是每个程序员,不管懒不懒,都需要的。*

*Golden Rule of Not Pulling Your Hair Out

编辑程序时制作备份-----------------------------。

Unix 和 Windows 在如何结束一行上意见不一。将一个文件从一个系统移到另一个系统并阅读,你可能会看到所有的东西都明显地挤在一行上,或者在每一行的末尾显示一个^M

如果它是一个 Unix 文件,而你在 Windows 系统中,试试 Notepad++ 或微软的写字板。如果是在 Unix 中显示的 Windows 文件,您可以忽略有趣的符号,或者(如果已安装)使用以下命令:

dos2unix < windowsfile.txt > unixfile

另一方面,使用unix2dos

Exercises

  1. 使用你的编译器,输入 Hello,world!程序,并让它工作。

  2. 再写一个程序把一首歌的歌词打印在屏幕上。

  3. 拿着你好,世界!编程并故意引入错误:去掉分号或花括号;在中间打断一句引语;尝试几种不同的东西。你会得到什么样的错误信息?

  4. 清理你的文件夹(即删除那些多余的大文件)并压缩它。

形状和绘制它们的函数

当然,我们想做的不仅仅是向世界问好。为了开始学习图形,让我们再看一下运行 SSDL 程序时创建的空白窗口(图 1-16 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-17

在屏幕中央画一个点

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-16

基本 SSDL 窗的尺寸

我们放入该窗口的形状的位置在(x,y)坐标中。左上角是(0,0),右下角是(639,479)——所以 y 坐标在页面上向下而不是向上。横跨 640 个位置(0–639 包括在内), 480 个位置向下。每个(x,y)位置称为一个“像素”(图片元素)。

这一部分展示了我们可以做的一些事情。第一个例子 1-3 ,在位置(320,240)画一个点。(它不会像图 1-17 那么大,但是我想让它显现出来,所以我增强了它。)

Finding and Compiling Source Code

所有编号的示例都可以在本书的源代码中找到,在示例章节的文件夹中(在本例中为ch1),带有一些以示例编号开头的描述性名称。例子 1-3 的名字,没有想象力的叫3-drawDot

用你在本章第一节所做的test-setup一样的方式编译它。在源代码库的ch1文件夹中

在 Unix 中,进入示例的目录(3-drawDot)并输入cp Makefile.unix Makefilemake

对于 MinGW,进入示例的目录(3-drawDot)并输入copy Makefile.mingw Makefilemake

对于 Visual Studio,进入ch1文件夹,打开ch1解决方案;右键单击3-drawDot并选择调试➤启动新实例。

// Draw a dot at the center of the screen
//        -- from _C++ for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char** argv)
{
     // draws a dot at the center position (320, 240)
     SSDL_RenderDrawPoint (320, 240);

     SSDL_WaitKey ();

     return 0;
}

Example 1-3Program to draw a dot at the center of the screen. It’s in source code under ch1, as 3-drawDot

. Output is in Figure 1-17

绘制基本形状的功能列于表 1-1 中。int表示整数,即整数。形式为void <function-name> (<bunch of stuff>);的函数声明是对如何调用函数的精确描述——它们的名字以及它们在()' s. SSDL_RenderDrawPoint之间期望什么样的值,它的两个参数xy采用两个整数。SSDL_RenderDrawLine取四:x1y1x2y2。等等。

表 1-1

常见的 SSDL 绘图功能。有关更多 SSDL 函数,请参见附录 H

| `void``SSDL_RenderDrawPoint` | 在(`x`,`y`)处画一个点。 | | `void``SSDL_RenderDrawLine``int x2, int y2);` | 从(`x1`、`y1`)到(`x2`、`y2`)画一条线。 | | `void``SSDL_RenderDrawCircle``int radius) ;` | 以此半径画一个圆,圆心在(`x`,`y`)。 | | `void``SSDL_RenderFillCircle``int radius) ;` | 以此半径画一个实心圆,圆心在(`x`,`y`)。 | | `void``SSDL_RenderDrawRect``int w, int h);` | 画一个以(`x1`,`y1`)为左上角,宽度`w`,高度`h`的方框。 | | `void``SSDL_RenderFillRect``int w, int h);` | 画一个以(`x1`,`y1`)为左上角的填充框,宽度`w`,高度`h`。 |

作为他们使用的一个例子,这行代码在左上角附近画了一个圆(见图 1-18 ,左):SSDL_RenderDrawCircle (100, 100, 100);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-18

左边是一个带SSDL_RenderDrawCircle (100, 100, 100);的程序。右边,一个带有SSDL_RenderDrawCircle (0, 0, 100)的程序;

而这个给你一个居中在左上角,所以你只能看到它的四分之一(图 1-18 ,右):SSDL_RenderDrawCircle (0, 0, 100);。不显示可视区域之外的内容称为“剪辑”

现在让我们用这些函数来做一个有趣的设计。我们需要提前计划。很快会有一个关于提前计划的部分,但是现在,你可以像电影制作人或漫画制作人一样,为你想做的任何设计制作一个故事板。

我们可能需要绘图纸(图 1-19 ,源代码文件夹中有可打印的页面)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-19

查看区域的图形,用于设计您想要显示的内容

我决定做一个虫子脸:大眼睛,大脑袋,触角。于是我就画了我想要的(图 1-20 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-21

一只虫子的头

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-20

bug 头程序的绘制

我现在可以看到位置了。左眼的中心在(320,250)左右,其半径大致为 45。大圆的圆心在(430,250)附近,半径约为 150。等等。

下面是我的程序(例 1-4 )。我最初写的时候犯了几个错误——把直径和半径搞混了,把图形线读错了。你也会的。如果没有,好吧,这是真正的简历素材。

// Program to draw a cartoonish bug's head on the screen
//      -- from _C++ for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char** argv)
{
    SSDL_RenderDrawCircle (430, 250, 200);     // draw the bug's head

    SSDL_RenderDrawCircle (320, 250,  45);     // the left eye
    SSDL_RenderDrawCircle (470, 270,  45);     // the right eye

    SSDL_RenderDrawLine   (360, 140, 280,  40);// left antenna
    SSDL_RenderDrawLine   (280,  40, 210,  90);

    SSDL_RenderDrawLine   (520, 140, 560,  40);// right antenna
    SSDL_RenderDrawLine   (560,  40, 620,  80);

    SSDL_RenderDrawLine   (290, 350, 372, 410);// the smile
    SSDL_RenderDrawLine   (372, 410, 490, 400);

    SSDL_WaitKey ();                           // Wait for user to hit a key

    return 0;
}

Example 1-4A bug’s head. Found in source code’s ch1 folder as 4-bugsHead. The resulting output is shown in Figure 1-21

请注意,我是如何在注释中严格记录我所做的一切的目的的。假设我没有把那些评论放进去:

// Program to draw a cartoonish bug's head on the screen
//        -- from _C++ for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char** argv)
{
    SSDL_RenderDrawCircle (430, 250, 200);

    SSDL_RenderDrawCircle (320, 250,  45);
    SSDL_RenderDrawCircle (470, 270,  45);

    SSDL_RenderDrawLine   (360, 140, 280,  40);
    SSDL_RenderDrawLine   (280,  40, 210,  90);

    SSDL_RenderDrawLine   (520, 140, 560,  40);
    SSDL_RenderDrawLine   (560,  40, 620,  80);

    SSDL_RenderDrawLine   (290, 350, 372, 410);
    SSDL_RenderDrawLine   (372, 410, 490, 400);

    SSDL_WaitKey ();

    return 0;
}

真是一场噩梦!你几个月后回来重用或升级这个程序,看到代码,然后想,我到底在做什么?哪条线做什么?

然后你试着运行它,然后……你的系统管理员升级了编译器或者库,程序不再工作了。(软件腐烂;至少,有些东西会让你的程序随着时间的推移停止工作。)你有一个不工作的程序,即使要确定这些部分是用来做什么的,也需要进行侦查工作。

最好加上注释,这样你就可以理解、维护并根据需要更新你的程序。这里(例 1-5 )我决定给眼睛加瞳孔。根据评论,很容易判断他们去了哪里。

// Program to draw a cartoonish bug's head on the screen
//       -- from _C++20 for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char** argv)
{
     SSDL_RenderDrawCircle (430, 250, 200);       // draw the bug's head

     SSDL_RenderDrawCircle (320, 250,  45);       // the left eye
     SSDL_RenderFillCircle (300, 250,   5);       // ... and its pupil
     SSDL_RenderDrawCircle (470, 270,  45);       // the right eye
     SSDL_RenderFillCircle (450, 270,   5);       // ... and its pupil
     ...
}

Example 1-5A bug’s head, with pupils in the eyes. Found in source code’s ch1 folder as 5-bugsHead

. Output is in Figure 1-22

防错法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-27

示例 1-8 的输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-26

示例 1-7 的输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-25

示例 1-6 的输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-24

Visual Studio 提示输入函数参数的类型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-23

Microsoft Visual Studio“智能感知”自动完成函数名

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-22

一只虫子的头,加上了瞳孔

  • 你调用了一个 SSDL 函数,但它不起作用。此时,最有可能的猜测是它在可视区域外画东西,所以你看不到。确定问题所在的最好方法是检查你给出的论点,确保它们是合理的。

  • **(针对 Visual Studio)你记不清具体怎么调用一个函数,也不想查。**那不是 bug,但却是现实,而且表现出了令人钦佩的懒惰,那就顺其自然吧。你有时可以在打字时得到提示(图 1-23 )。当你打开括号时,它可能会给你一个函数的描述或者它所期望的(图 1-24);如果您在描述上看到一个箭头或三角形,请单击它以查看多个选项。

…有时什么也没发生。或者它在完全正常的事情上画红色的曲线。尝试重新键入代码行或编译代码——其中一种方法通常可以做到这一点。

Exercises

  1. 设计一些你自己的东西,并写一个程序在屏幕上显示出来。

  2. 画一个立方体,看起来不太直,就像这里显示的。

const s 和颜色

自然,我们也想给我们的形状涂上颜色。

电脑上的颜色分为三部分:红色、绿色和蓝色。在我们的库中,它们的范围从 0(最低)到 255(最高)。黑色是 0,0,0;白色是 255,255,255;红色是 255,0,0(红色最大,其他为零)。其他组合出其他颜色。你可以使用像 www.colorpicker.com 这样的网站来找到你想要的颜色的红色、绿色和蓝色成分。

您可以使用 SSDL 内置的几种颜色(BLACK, WHITE, RED, GREEN,BLUE)或创建自己的颜色,如下所示:

const SSDL_Color

MAHOGANY = SSDL_CreateColor  (192,  64,   0);

这里,SSDL_Color MAHOGANY说我们正在创建一种颜色,并将其命名为MAHOGANYSSDL_CreateColor (192, 64, 0)给了它我们想要的数字。

颜色不会改变,所以我们将使用 C++ 的const关键字来强调这一点,并让编译器防止它们被错误地改变。常量全部用大写字母书写,以使程序的读者清楚它们不会改变。(你习惯了,就明白无误了。)

要开始使用颜色,请执行以下操作:

SSDL_SetRenderDrawColor (RED);    // draw things in RED, from now
                                  // till the next call to this function

要清除屏幕,请执行以下操作:

SSDL_RenderClear (BLACK);        // erase the screen and make it BLACK

这是一个程序,它使用内置的和新的颜色在屏幕上绘制方框。输出如图 1-25 所示。

// Displays boxes of colors
//      -- from _C++ for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char** argv)
{
    SSDL_SetWindowTitle ("Four squares in different colors");

    // We'll use 2 new colors, plus GREEN and WHITE...
    const SSDL_Color MAHOGANY   = SSDL_CreateColor (192,  64,   0);
    const SSDL_Color DARK_GREY  = SSDL_CreateColor (100, 100, 100);

    // Make a dark grey background
    SSDL_RenderClear            (DARK_GREY);

    // We'll have two squares across
    SSDL_SetRenderDrawColor     (GREEN);    // First square
    SSDL_RenderFillRect         (  0,   0, 100, 100);
    SSDL_SetRenderDrawColor     (MAHOGANY); // Second
    SSDL_RenderFillRect         (100,   0, 100, 100);

    // Program's end.
    // Must restore color to white, or we'll get mahogany text!
    SSDL_SetRenderDrawColor (WHITE);
    sout << "Hit any key to end.\n";

    SSDL_WaitKey();

    return 0;
}

Example 1-6Use of colors to paint some rectangles. Found in source code’s ch1 folder as 6-colorSquares

表 1-2 包含与颜色和清除屏幕相关的功能。这里的一些声明与附录中的不完全匹配:它们被简化了,但是足够接近了。

表 1-2

与颜色相关的 SSDL 函数

| `SSDL_Color SSDL_CreateColor``(int r, int g, int b);` 9 | 创建并返回一种颜色。(`r` )ed、(`g` )reen、(`b` )lue 的最大值为 255。 | | `void` `SSDL_SetRenderDrawColor``(SSDL_Color c);` | 设置后续绘图,包括文本,使用颜色`c`。 | | `void` `SSDL_SetRenderEraseColor``(SSDL_Color c);` | 设置擦除,包括清除屏幕,以使用颜色`c`。 | | `SSDL_Color``SSDL_GetRenderDrawColor` | 获取当前绘图颜色。例如,`const SSDL_Color FOREGROUND =``SSDL_GetRenderDrawColor();` | | `SSDL_Color` `SSDL_GetRenderEraseColor ();` | 获取当前擦除颜色。 | | `void``SSDL_RenderClear` | 将屏幕清除为当前擦除颜色。 | | `void` `SSDL_RenderClear (SSDL_Color c);` | 清除屏幕颜色`c`。 |

有些函数(以void开头的那些)不会为你计算一个值;它们只是做一些事情(比如画一个形状,清空屏幕,或者设置一种颜色)。其他人,像SSDL_CreateColor,有计算答案的工作。这个创建了一个颜色,所以它的“返回类型”不是void,而是SSDL_Color

我们将在第七章进一步讨论函数和返回类型。

Exercises

  1. 将颜色添加到您编写的在屏幕上绘制图形的程序或本书源代码中的另一个程序中。

  2. 为你最喜欢的节日制造一个场景:万圣节的橙色恐怖脸或者绿色圣诞树。或者在色彩节胡里节尽情狂欢。

  3. 通过交替调用SSDL_RenderClearSSDL_WaitKey使屏幕闪烁各种颜色。

  4. 写出几种颜色的名称,每种都用那种颜色写(“红”用红色写,等等。).

文本

sout、转义序列和字体

你可以用 SSDL 库的sout打印多种东西——不仅仅是文本,还有数字:

sout << "The number pi is " << 3.14159 << ".\n";
sout << "...and the number e is "
     << 2.71828
     << ".\n";

你在程序中如何设置行间距不会改变打印的内容;当到达\ n字符,即“行尾”字符时,该行结束。用一种方式而不是另一种方式来分隔代码行的唯一原因是为了清晰。(对我来说,前面的版本看起来不错。)

但是引号内的间距很重要。注意我在“是”字后面加的空格;如果不放置它,您的第一行输出将如下所示:

The number pi is3.14159.

还有其他的转义序列,又名“转义码”——以\开头的特殊字符:

  • \t,制表符,带你到下一个制表位。制表位排列在 0、8 个空格、16 个空格等位置。(由于我们大部分字体都是变宽的,所以不能指望八个 Is 或者八个 Ms 和八个空格一样宽;它将是近似值。)

  • \ ",《人物》。如果我们只是把"放在文本中,就像"Quoth the raven, "Nevermore"",C++ 会被多余的"弄糊涂。所以我们这样写:

    "Quoth the raven, \"Nevermore.\""

  • \\,\字符(因为单个\字符会让 C++ 试图找出您要开始的转义序列)。

有关所有可用的转义序列,请参见附录 e。

你也可以决定文本出现在屏幕上的什么位置。下面是如何 光标设置到 x 位置 100,y 位置 50:

SSDL_SetCursor (100, 50);

并且可以更改字体 和字体大小。字体文件必须是 TTF (TrueType 字体)格式;C++ 希望它们与您的项目在同一个文件夹中:

const SSDL_Font FONT = SSDL_OpenFont       ("myFont.ttf", 18);
                                                  // my font; 18 point
SSDL_SetFont (FONT);

如果您想要一个系统自带的字体,在标准字体文件夹中,您可以使用这个调用:

const SSDL_Font FONT = SSDL_OpenSystemFont ("verdana.ttf", 18);
                                                  // Verdana font; 18 point

SSDL_SetFont (FONT);

你可以在微软 Word 中或者(在撰写本文时)在 en 上查看微软视窗/微软核心网页字体中的可用字体。维基百科。org/wiki/Core _ fonts _ for _ the _ Web。文件名不总是显而易见的;例如,在 Microsoft Word 中显示为“Bookman Old Style”的实际上是四个文件—bookos.ttfbookosb.ttfbookosbi.ttfbookosi.ttf,分别对应普通、粗体、粗斜体和斜体。

在 Unix 中,您可以使用这个命令获得已安装字体的列表:fc-list。它们可能在/usr/share/fonts或其子文件夹中。

SDL2_ttf 库很乐意把你给它的字体做成斜体,粗体,或者其他什么,但是它无法和人类艺术家竞争。在可能的情况下,使用字体中的增强版本,比如用Times_New_Roman_Bold.ttftimesbd.ttf表示 Times New Roman Bold。 10

即使没有粗体或斜体,SDL 有时也很难让字体看起来流畅。如果你的字体看起来太不均匀,尝试另一种字体或更大的尺寸。

示例 1-7 展示了这些新功能。

// Prints an excerpt from Sir Walter Scott's _The Lady of the Lake_
//         -- from _C++ for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char** argv)
{
    // Window setup
    SSDL_SetWindowTitle ("Hit any key to end");
    // Always tell user what's expected...

    // We'll be using Times New Roman font, bold...
    //    so load it, and tell SSDL to use it
    const SSDL_Font FONT = SSDL_OpenSystemFont ("timesbd", 24);
    SSDL_SetFont (FONT);

    SSDL_SetCursor (0, 50);   // Start 50 pixels down

    // And now, the poem (or part of it)
    sout << "from The Lady of the Lake\n";
    sout << "\tby Sir Walter Scott\n\n";
    // Tab over for author's name, then
    //    double space at the end of the line

    sout << "\"Tis merry, 'tis merry, in Fairy-land,\n";
    sout << "\tWhen fairy birds are singing,\n";
    sout << "When the court cloth ride by their monarch's side,\n";
    sout << "\tWith bit and bridle ringing...\"\n";

    // End when user hits a key
    SSDL_WaitKey ();

    return 0;
}

Example 1-7Using escape sequences, cursor, and fonts to print a poem. Found in source code’s ch1 folder as 7-quotation. Output is in Figure 1-26

表 1-3

字体和文本位置的 SSDL 函数

| `void``SSDL_SetCursor` | 将光标定位在`x`、`y`处,以便下次使用`sout`或`ssin`(稍后描述)。 | | `SSDL_Font` `SSDL_OpenFont``(const char* filename, int point);` 11 | 从`filename`为 TrueType 字体和`point`创建字体。 | | `SSDL_Font` `SSDL_OpenSystemFont``(const char* filename, int point);` | 相同,但从系统字体文件夹加载。 | | `void``SSDL_SetFont` | 使用`f`作为文本字体。 |

ssdl _ rendertext、ssdl _ rendertext 中心

我们可以通过以下两个函数调用将光标和字体的设置以及打印合并到一个语句中(以及居中文本),详见表 1-4 。如果您不指定字体,它将使用您已经在使用的任何字体:

const SSDL_Font FONT_FOR_YEAR = SSDL_OpenSystemFont ("verdana.ttf", 14);
SSDL_RenderText ("When did King Sejong publish the Korean alphabet?", 0, 0);
       // didn't specify font; use whatever we were using before...

SSDL_RenderText (1446, 500, 0, FONT_FOR_YEAR); //...use new font here
       // Year was 1446\. Print at location 500, 0.

如果你说SSDL_RenderTextCentered,你给出的位置将是文本的中心,而不是它的左侧。

表 1-4 总结了这两种功能。

如果行尾字符在您正在打印的文本中,它会将您带到下一行——如果是SSDL_RenderTextCentered则仍然居中,如果不是,则仍然缩进到您指定的位置——但是不支持制表符。

为了说明这一点,示例 1-8 采用了先前的示例 1-6 来使用这些新函数显示一些标签。

表 1-4

一些用于打印的 SSDL 函数

| `void` `SSDL_RenderText``(T thing, int x, int y,``SSDL_Font font = currentFont);` | 在`x`、`y`位置打印`thing`(可以是任何可打印类型),如果指定使用`font`,否则使用当前字体。 | | `void` `SSDL_RenderTextCentered``(T thing, int x, int y,``SSDL_Font font = currentFont);` | 打印`thing`,如上,以`x`、`y`为中心。 |
// Displays boxes of colors, labeled
//       -- from _C++ for Lazy Programmers_

#include "SSDL.h"

int main(int argc, char** argv)
{
    SSDL_SetWindowTitle("Two colored squares, with labels");

    // New colors
    const SSDL_Color MAHOGANY  = SSDL_CreateColor(192,  64,   0);
    const SSDL_Color DARK_GREY = SSDL_CreateColor(100, 100, 100);

    // Make a dark grey background
    SSDL_RenderClear(DARK_GREY);

    // First square:
    SSDL_SetRenderDrawColor(GREEN);
    SSDL_RenderFillRect    (0, 0, 100, 100);
    SSDL_SetRenderDrawColor(WHITE);
    SSDL_RenderTextCentered("GREEN", 50, 50);     // dead center of
                                                  // green square

    // Second square:
    SSDL_SetRenderDrawColor(MAHOGANY);
    SSDL_RenderFillRect    (100, 0, 100, 100);
    SSDL_SetRenderDrawColor(WHITE);
    SSDL_RenderTextCentered("MAHOGANY", 150, 50); // dead center of
                                                  // mahogany square

    // Report number of colors, thus demonstrating non-centered text
    SSDL_RenderText         ("Number of colors:  ", 0, 100);
    SSDL_RenderText         (2, 150, 100);        // two colors

    sout << "Hit any key to end.\n";

    SSDL_WaitKey();

    return 0;
}

Example 1-8An adaptation of Example 1-6 to include labels. Found in source code’s ch1 folder as 8-labelSquares

图 1-27 显示了输出。清注意

  • SSDL_RenderTextSSDL_RenderTextCentered不影响sout的光标。所以sout还是从页面顶部开始。

  • SSDL_RenderTextCentered只能从左到右居中;它不关注 y 值。要使标签真正在盒子中居中,我们必须计算 Y 位置或者只是猜测。SSDL 的默认字体是 Arial 14 磅;14 的一半是 7,所以我们可以从 Y 方向的盒子的真实中心减去它,50,并把 50–7 传递给y参数到SSDL_RenderTextCentered,如果我们关心的话。

Exercises

  1. 将一些适当的文本放入您之前编写的或在源代码中找到的程序中。例如,你可以让虫子的头说点什么。

  2. 使用SSDL_WaitKeySSDL_RenderClear逐页打印一首长诗或一段文字。使用合适的字体和大小。

  3. 编造一些统计数据——通常不都是这么做的吗?–并使用\t字符来排列一个表格,就像这样:

    Character                   Coolness
    =========               ===============
    Greta Garbo                  83%
    Humphrey Bogart              87%
    Marilyn Monroe               98%
    me, if I were                99%
         in the movies
    
    
  4. 画一个停止标志:一个中间写着 stop 的八角形。

  5. 画一个屈服标志:中间是屈服的倒三角形。

SDL 提供图形、声音和友好的交互,包括鼠标输入。SSDL,代表简单的 SDL,是一个“包装器”库,将 SDL 的功能包装成更易于使用的版本。这两个库在简介中有更详细的描述。

2

我推荐 UNIX 初学者教程,在www . ee . surrey . AC . uk/Teaching/UNIX/。到教程 4,现在应该没问题了。或者搜索自己的。

3

好吧,我不能就这么算了。附录 A 给出了如何安装你需要的其他工具(g++,SDL 等)的建议。).但是 Unix 的发行版各不相同,所以知道自己在做什么会有所帮助。

4

从现在开始,我将放入文本的标题,而不是名称和日期,因为这对教科书的例子更有用。一般来说,程序员的名字和日期更有助于记录做了什么,以及如果不成功该找谁。

5

“邪恶和粗鲁”是一个技术术语,意思是“恶意的可怕。”关于程序员俚语中的其他术语,请参见新版黑客词典,目前在线网址为 www . catb . org/congonal。

6

好事:黑客俚语,指的是所有人都知道(或者应该知道)的非常棒的事情。

7

对于 emacs 的快速入门,您可以尝试在 www . GNU . org/software/Emacs/Tour/上浏览 Emacs。为了更快地开始,请转到“基本编辑命令”并跳过第一个表格。

8

如果出现一个对话框,询问您是否要“重定项目目标”,请接受默认设置,然后单击“确定”。如果你的机器和我的机器的 Windows 库版本稍有不同,就会发生这种情况。

9

您可以给出可选的第四个参数“alpha”,它可以使颜色透明:

SSDL_Color SSDL_CreateColor (int r, int g, int b, int alpha);

Alpha 的范围从 0(完全透明)到 255(完全不透明)。例如,

const SSDL_Color GHOSTLY_GREY =

SSDL_CreateColor (100, 100, 100, 128);

给我们一种半透明的颜色。

我们不会使用这个,因为我们很少想要透明的几何形状,我们将用于图像的 PNG 格式允许没有任何特殊处理的透明度。但如果你想尝试,它就在那里。

10

如果不能,有一个函数TTF_SetFontStyle,它可以生成新的样式(尽管它看起来有点粗糙),它的名字是

TTF_SetFontStyle (myFont, TTF_STYLE_BOLD); //bold

或者

TTF_SetFontStyle (myFont, TTF_STYLE_BOLD | TTF_STYLE_ITALIC); //bold italic

可用的样式有TTF_STYLE_BOLDTTF_STYLE_ITALICTTF_STYLE_UNDERLINETTF_STYLE_STRIKETHROUGH,默认为TTF_STYLE_NORMAL

11

我们稍后会谈到const char*。现在,将其解释为文本,如”verdana.ttf”

*

二、图像和声音

这些线条画够多了。让我们来点漂亮的。

我们将会玩一个豪华的图形编辑包;我喜欢瘸子。参见附录 A (Unix)或 www.gimp.org (Windows)进行安装。

图像和窗口特征

让我们从使用示例 2-1 中的代码显示图像开始。以与上一章相同的方式运行这个示例和后续示例:转到源代码,然后转到相关的子文件夹(在本例中是ch2)。对于 g++,进入例子的文件夹,把Makefile.unix或者Makefile.MinGW复制到Makefile,还有make。对于 Visual Studio,打开ch2.sln,右键单击适当的项目,选择调试➤启动新实例。

// Program to show an image on the screen
//       -- from _C++20 for Lazy Programmers_

#include "SSDL.h"

int main (int argc, char **argv)
{
    // Show image
    const SSDL_Image BEACH = SSDL_LoadImage ("beach.jpg");
    SSDL_RenderImage 

(BEACH, 0, 0);

    SSDL_WaitKey();

    return 0;
}

Example 2-1Displaying an image. Found in source code’s ch2 folder as 1-beach. Other numbered examples are found similarly by chapter and example number

这个程序加载一个名为beach.jpg的图像,并在屏幕上的位置 0,0 显示它。

就这样。

C++ 将在与a.out (g++)或.vcxproj文件(Visual Studio)相同的文件夹中查找图片。如果我们有一个以上的图像,文件夹可能会变得凌乱。让我们把这些图像放在一个名为media的子文件夹中,然后这样加载一个图像:const SSDL_Image BEACH = SSDL_LoadImage ("media/beach.jpg");…其中media/的意思是“在名为media的文件夹中” 1

您目前可以加载 GIF(“jiff”)、JPG(“J-peg”)、BMP(“bitmap”)或 PNG(“ping”)格式或 LBM、PCX、PNM、SVG、TGA(“Targa”)、TIFF、WEBP、XCF、XPM 或 XV 格式的图像。

如果你有另一种格式,尝试在 GIMP 或其他图形编辑器中加载它,并保存/导出为 JPG 或 PNG。我推荐 PNG,因为它支持透明。

当你看到图 2-1 、*中的结果时,你可能想知道我们能缩放图像吗?*是的,SSDL_RenderImage(BEACH, 0, 0, 640, 480);会把它做成 640 × 480 的图像。但是拉伸它可能会使图像模糊,所以让我们调整窗口大小以适应图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1

显示图像

首先,我们会发现它有多大。如果在 GIMP 中加载,顶栏会告诉你:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Unix 用户也可以说exiv2 beach.jpg。如果没有安装exiv2,和你的系统管理员好好谈谈。

Windows 用户可以右键单击文件夹中的文件(在ch2/beach/media中)并选择属性,然后选择细节选项卡。你会看到如图 2-2 所示的宽度和高度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2

Microsoft Windows 中图像的属性

然而,我们得到的信息,我们会告诉程序,使窗口的大小相同,给它的宽度和高度参数的顺序:

SSDL_SetWindowSize (400, 300); // make a 400x300 window

因为我们想让事情变得更酷,所以我们也给窗口本身添加一个标签:

SSDL_SetWindowTitle ("My trip to the beach ");

这将My trip to the beach放在显示窗口的顶栏上。例 2-2 在一个完整的程序中这样做;结果如图 2-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3

调整了标题窗口的大小,以显示没有多余空间的图像

// Program to show an image on the screen
//       -- from _C++20 for Lazy Programmers_

#include "SSDL.h"

int main(int argc, char **argv)
{
    // Set window parameters
    SSDL_SetWindowSize  (400, 300);              // make a 400x300 window
    SSDL_SetWindowTitle ("My trip to the beach");

    // Show image
    const SSDL_Image BEACH = SSDL_LoadImage ("media/beach.jpg");
    SSDL_RenderImage (BEACH, 0, 0);

    SSDL_WaitKey();

    return 0;
}

Example 2-2Displaying an image, resized and titled

在表 2-1 中是我们与图像和窗口属性相关的新函数的声明。

表 2-1

一些 SSDL 图像和窗口功能

| `SSDL_Image` `SSDL_LoadImage``(const char* filename);` | 加载名为`filename`的图像并提供一个`SSDL_Image`。 | | `void SSDL_RenderImage``(SSDL_Image img, int x, int y);` | 在位置`x, y`显示图像`img`,使用`img`的宽度和高度。 | | `void SSDL_RenderImage``(SSDL_Image img, int x, int y,``int width, int height);` | 在位置`x, y`显示图像`img`,指定宽度和高度。 | | `void` `SSDL_SetWindowSize``(int width, int height);` | 调整窗口大小。(这在一些平台上抹去了标题;先调整大小。) | | `void` `SSDL_SetWindowTitle``(const char* title);` | 给窗户一个`title.` | | `int``SSDL_GetWindowHeight` | 返回窗口高度。 | | `int``SSDL_GetWindowWidth` | 返回窗口宽度。 |

最后两个函数返回整数,就像SSDL_CreateColor返回一个SSDL_Color一样,所以我们可以在任何有意义的地方使用它们。将示例 2-3 中高亮显示的行添加到我们的程序中,我们会得到图 2-4 中的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-4

使用SSDL_GetWindowWidthSSDL_GetWindowHeight居中文本

int main(int argc, char **argv)
{
    // Set window parameters
    SSDL_SetWindowSize  (400, 300);    // make a 400x300 window
    SSDL_SetWindowTitle ("My trip to the beach");

    // Show image
    const SSDL_Image BEACH = SSDL_LoadImage("media/beach.jpg");
    SSDL_RenderImage(BEACH, 0, 0);

    // Make a label in the middle, centered
    SSDL_RenderTextCentered("BALI? BORA BORA? BEAUTIFUL, WHEREVER!",
                            SSDL_GetWindowWidth () / 2,
                            SSDL_GetWindowHeight() / 2);

    SSDL_WaitKey();

    return 0;
}

Example 2-3Using SSDL_GetWindowWidth () and SSDL_GetWindowHeight () to center a message on the screen

防错法

本章中的常见问题,无论是崩溃还是不可见的东西,都是以下两种情况之一:

  • 图像未加载。

  • 一种字体未加载。

以下是可能的元凶:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-5

Microsoft Windows 中一种无法识别的文件类型

  • 文件夹位置:文件应该和你的a.out或者.vcxproj文件在同一个文件夹中,或者在指定的子文件夹中,比如media/

  • 拼写错误:如果你像我一样,你把名字拼错。调试前面的程序时,我把beach.jpg拼错成了myImage.jpg。去想想。

  • (微软 Windows)你分不清是什么样的文件。看不到文件扩展名,所以看不出是不是。jpg,。png,或者 SDL 不能使用的非常不同的东西(见图 2-5 )。

解决方案:不隐藏已知文件类型的扩展名(参见第一章“创建你自己的项目”下的附加文件扩展名)…在微软视窗系统中”。

  • 文件已损坏或具有您的图像加载器无法处理的功能。一个技巧是在图形编辑器中加载文件。如果加载了该文件,请将其导出为不同的格式或不同的导出选项,然后尝试新文件。

  • 它正在加载,但正被粘贴到屏幕之外。试着把它放在位置 0,0,看看它是否变得可见。

  • 不是这样的。你能做什么?

    • 如果一个新的特性(比如说,图像)有问题,我可以做一个程序,或者从源代码复制一个程序,只做一个特性,并确保它能正常工作。

      当它出现时,我会添加一些东西,使它更像我想要的最终版本。一旦成功了,我就添加一个又一个的修改,每次都备份上一个工作的程序,所以如果我把新版本搞砸了,我可以回到我刚才的版本。 2

对我来说,这种备份方式对于新功能的运行至关重要。

  • 即使在您信任的示例程序中,您正在努力解决的那个特性也不起作用。你有合适的项目文件或 Makefile 吗?你是否从你的源文件中复制了这个文件夹,没有改变或者重新排列,并且全部复制正确吗?

    这不太可能,但是编译器和库错误发生。比如在调用SSDL_SetWindowSize之后,在 Unix 的一些发行版中,SDL_GetWindowSize(需要SSDL_GetWindowWidthSSDL_GetWindowHeight)可能会返回旧的窗口尺寸。这个问题很容易绕过:自己跟踪维度。我总是能够解决编译器或库的问题,即使我后来没有发现这一直是我的错误。

多个图像在一起

用 SDL 库粘贴多个图像很容易——你只需把它们从后到前按顺序放在屏幕上,就可以了。如果它们是部分透明的,那就更好了。

你可以通过互联网图片搜索找到类似图 2-6 中的透明图片,要求文件类型为 PNG。将示例 2-4 中的新代码粘贴到BEACH背景之后,火烈鸟应该会出现,背景通过透明部分显示出来。结果如图 2-7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-7

粘贴到背景上的部分透明的图像

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-6

透明的巴布亚新几内亚

// Load images
const SSDL_Image BEACH      = SSDL_LoadImage("media/beach.jpg");
const SSDL_Image FLAMINGO   = SSDL_LoadImage("media/flamingo.png");

// Paste in the background image, and the flamingo
constexpr int    FLAMINGO_X = 0, FLAMINGO_Y = 175;
                 // Flamingo's on left, down screen

SSDL_RenderImage(BEACH,    0,                   0);
SSDL_RenderImage(FLAMINGO, FLAMINGO_X, FLAMINGO_Y);

Example 2-4Adding code to display a PNG image

用 GIMP 增加透明度

我的火烈鸟图片没有背景。你可能一心想要照片中的某样东西,比如图 2-8 中可爱的小狗——但你只是想要小狗,而不是背景。这是我知道的最简单的方法,使背景透明,并保留小狗。(我建议你使用自己选择的图片。Pixabay.com 是一个很好的来源。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-8

一幅 JPG 的图像,见于ch2/5-pupdog/media

警告:除非你是一个真正的艺术家,否则最终的图像可能看起来边缘参差不齐。

加载您的图像在一个豪华的图形编辑器。我将在我的例子中假设 GIMP。

接下来,告诉 GIMP 您希望允许透明。在图层菜单下,选择透明度➤添加阿尔法通道。什么是阿尔法通道?Alpha 是像素的透明度。增加频道意味着透明是可能的。图 2-9 显示了这可能是什么样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-9

在 GIMP 中增加透明度

现在我们将删除背景,留下一个透明的区域。你将需要 GIMP 所谓的“模糊选择工具”(见图 2-10 ),它选择相似颜色的区域(在这种情况下,是地板上的颜色)。这个工具看起来像仙女教母的魔杖。别问我是怎么知道的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-10

模糊选择工具

在部分背景上单击魔杖,然后单击删除。(如果你搞砸了,用 Ctrl-Z–按住 Control,按 Z–撤销。)你应该会看到一个棋盘图案,这意味着你看到的是后面的图像。您也可以使用矩形选择、其他选择或橡皮擦进行清理,如果您愿意,还可以使用裁剪选择、缩放图像或其他方法。图 2-11 显示了它可能的样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-12

粘贴到背景上的两个透明图像

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-11

背景透明的图像。我还剪了它

当你欣赏完自己的作品后,保存——不,导出3——成 PNG 格式,并在你的程序中使用结果,就像我在示例 2-5 中做的那样。

// Program that pastes two images onto a background
//      -- from _C++20 for Lazy Programmers_

#include "SSDL.h"

int main(int argc, char **argv)
{
    // Set window parameters
    SSDL_SetWindowSize(400, 300);       // make a 400x300 window
    SSDL_SetWindowTitle("Pup dog and flamingo at the beach");

    // Load images
    const SSDL_Image BEACH     = SSDL_LoadImage("media/beach.jpg");
    const SSDL_Image FLAMINGO  = SSDL_LoadImage("media/flamingo.png");
    const SSDL_Image PUPDOG    = SSDL_LoadImage("media/pupdog.png");

    // Locations and dimensions for .png images
    constexpr int FLAMINGO_X   =   0, FLAMINGO_Y    = 175;
                                    // Flamingo's on left, down screen
    constexpr int PUPDOG_X     = 320, PUPDOG_Y      = 225;
                                    // Pupdog's on right down screen
    constexpr int PUPDOG_WIDTH =  50, PUPDOG_HEIGHT =  75;
                                    // Pup dog is bigger than I want, so I
                                    //  make her 50x75\. It's better to
                                    //  resize when making the image, but
                                    //  this works too

    // Paste in the background image, plus flamingo and pupdog
    SSDL_RenderImage (BEACH,    0,          0);
    SSDL_RenderImage (FLAMINGO, FLAMINGO_X, FLAMINGO_Y);
    SSDL_RenderImage (PUPDOG,   PUPDOG_X,   PUPDOG_Y,
                      PUPDOG_WIDTH, PUPDOG_HEIGHT);
    SSDL_WaitKey();

    return 0;
}

Example 2-5Multiple images, with transparency. Output is in Figure 2-12

Exercises

  1. 制作一个幻灯片,展示小狗和火烈鸟(或你自己的狗或庭院侏儒)去过的所有精彩地点。如果你想让幻灯片自动播放,而不是等待用户按键,你可以使用SSDL_Delay。当程序点击SSDL_Delay时,它会停止给定的时间,然后继续:

    ssdl

声音

声音在 SSDL 也很容易。我知道你在想什么:我会判断那个。但是你会同意的——除非你的声音决定不加载,程序崩溃。

有两种声音:一种是在背景中持续播放,让用户烦得要死的声音,称为“音乐”,另一种是随着特定事件(如碰撞)发生的声音,称为“声音”一句话:背景音是音乐;音效就是声音。

我们一次只能播放一首音乐,但是多种声音都可以。对这两种类型你能做的主要事情是加载它,播放它,暂停或恢复它,以及暂停它。我们通常使用的格式是 WAV,但是音乐也可以是 MP3 格式。(如果你有另一种格式的声音文件,而 SSDL 无法处理,那就找一个在线转换器。)

最常见的功能在表 2-2 中;更完整的列表在附录 h 中。当您看到给定了默认值的参数时,如SSDL_PlaySound (SSDL_Sound s, int repeats=0)中的repeats,这意味着如果您省略该参数,它将使用默认值:

表 2-2

常见的 SSDL 声音和音乐功能

| `SSDL_Music` `SSDL_LoadMUS``(const char* filename);` | 从`filename`载入音乐。 | | `void` `SSDL_PlayMusic``(SSDL_Music m,``int numTimesToPlay=-1);` | 播放指定次数的音乐;–1 表示永远重复。 | | `void``SSDL_PauseMusic` | 暂停音乐。 | | `void SSDL_ResumeMusic` `();` | 恢复音乐。 | | `int``SSDL_VolumeMusic` | 设置音量,应该是从 0 到`MIX_MAX_VOLUME`(128),返回新的音量。如果 volume 为–1,则仅返回卷。 | | `void``SSDL_HaltMusic` | 停止音乐。 | | `SSDL_Sound``SSDL_LoadWAV` | 从文件中加载声音。不管名字如何,它可以是`WAV`格式或其他支持的格式。有关详细信息,请参见 SDL2_mixer 的在线文档。 | | `void` `SSDL_PlaySound``(SSDL_Sound sound,``int repeats=0);` | 播放此声音,并重复指定的次数。如果 repeat 为–1,则永远重复。 | | `void``SSDL_PauseSound` | 暂停声音。 | | `void``SSDL_ResumeSound` | 恢复声音。 | | `int` `SSDL_VolumeSound``(SSDL_Sound snd,``int volume=MIX_MAX_VOLUME);` | 设置音量,从 0 到`MIX_MAX_VOLUME`,128;还卷。如果 volume 参数为–1,则仅返回体积。 | | `void``SSDL_HaltSound` | 停止声音。 |
SSDL_PlaySound (mySound, 2);  // repeat sound twice after you play it
SSDL_PlaySound (mySound);     // repeat sound 0 times after playing it --
                              // that's the default

经常可以在网上找到声音;在网上搜索“免费 WAV”或诸如此类的东西。将您需要的内容复制到您的media文件夹中。

示例 2-6 显示了一个简单的程序,当你按下一个键时,它会播放音乐并敲锣。

// Program to play sounds
//       -- from _C++20 for Lazy Programmers_

#include "SSDL.h"

int main(int argc, char** argv)
{
    // Initial window setup
    SSDL_SetWindowTitle("Simple sound example");

    // Load our media
    SSDL_Music

music = SSDL_LoadMUS ("Media/457729__razor5__boss-battle-2-0.wav");
    SSDL_Sound bell = SSDL_LoadWAV ("Media/321530__robbo799__church-bell.wav");

    SSDL_VolumeMusic (MIX_MAX_VOLUME/2);  // play music at half volume,
                                          //   because...that was LOUD.
    SSDL_PlayMusic (music, SSDL_FOREVER); // ...looping continuously
                                          // SSDL_FOREVER means -1
    sout << "Hit a key to hear the bell.\n";
    SSDL_WaitKey();
    SSDL_PlaySound(bell);

    sout << "Hit another key to end.\n";
    SSDL_WaitKey();

    return 0;
}

Example 2-6A simple music and sound program

防错法

几乎任何一个声音出错都会让程序崩溃。主要怀疑是文件名错误或在错误的文件夹中,或使用不支持的文件类型。

如果音质有问题,请参见附录 a。

Exercises

  1. 制作您自己的音乐视频,包括歌词、图像和声音,并播放它。您需要记录幻灯片之间的延迟时间;参见上一节中的练习 1。

  2. 播放一首歌曲,在每四拍中加入锣声或其他恼人的声音。

是的,有经验的 Windows 用户,那真的是/不是\。这将在 Windows 和 Unix 中工作,并且操作系统之间的可移植性是一件好事。为了简洁明了,我将在随后的文本中使用/作为分隔符,因为我是为两个平台编写的。MinGW 用户,记得在cmd窗口中使用\

2

记住第一章中不要拔头发的黄金法则:在你做改变的时候,保留大量的备份。

3

图形编辑器不让你用有用的格式保存;保存是为了自己的格式。你得用出口来代替。

三、数字

数字是计算机世界运转的动力,所以让我们来看看如何让计算机为我们处理这些数字。

变量

变量可能看起来像我们在代数中使用的字母——y = MX+b,诸如此类——但在 C++ 中,它们只是存储值的地方。示例 3-1 展示了我们创建变量时的样子。

(最后提醒:与所有编号的示例一样,您可以在源代码的相应章节中找到示例 3-1——参见第一章的“形状和绘制它们的函数”一节,了解如何找到并运行它。)

int main (int argc, char** argv)
{
    int seasonsOfAmericanIdol                   = 18;
                                     // after a while you lose track
    float hoursIveWatchedAmericanIdol           = 432.5F;
                                     // missed half an episode, dang it
    double howMuchIShouldCareAboutAmericanIdol  = 1.0E-21;
                                     // 1x10 to the -21 power
    double howMuchIDoCareAboutAmericanIdol      = 0.000000000000001;
                                     // So why'd I watch it if I don't care

    sout << "Through " << seasonsOfAmericanIdol << " seasons of American Idol...";

    // ...and some more output...

    // end program
    SSDL_WaitKey();
    return 0;
}

Example 3-1Variable declarations for my American Idol obsession

这给了我们一个整数变量;一个浮点变量,可以取小数位;和两个 double 变量,可以取更多的小数位。(还有多少取决于你用的机器。)

432.5F上的尾随F意味着它是一个float,而不是一个double值。(如果不指明,那就是一个double。)如果你搞混了这些,可能会得到编译器的警告。为了避免警告,我使用了double,忘记了F

1.0E-21就是 C++ 怎么写 1.0 × 10 21

你可以把 main 函数想象成包含这些名称的位置,每个位置可以存储一个适当类型的值(见图 3-1 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-1

main中存储数值的变量

我们一做好变量就给它们赋值,在同一行上。我们没必要,但这是很好的练习。当你发现你的银行账户中的美元数是–6800 万时,这是令人失望的,因为你没有告诉计算机用什么值来初始化它,它只是碰巧以一个非常不合适的数字开始。

Golden Rule of Variables

初始化它们。

**也可以像前面那样做描述性的名字。**搜索代码试图找出"z""x"的意思是令人沮丧的。但是你很清楚seasonsOfAmericanIdol是什么意思。

变量名以字母开头(可能以_开头),但之后可以有数字。大写事项:tempTemp是不同的变量。

Extra

变量名和常量名应该是描述性的不应该与 C++ 中的任何内置关键字相同(constintvoid等)。).

按照惯例,C++ 常量全部用大写字母书写,是为了向程序员表明这是一个常量,而不是变量值。例如,使用 _: MAX_LENGTH来分隔挤在一起的单词。

变量名的约定很灵活。我使用“camel case”变量:你将单词组合在一起构成一个变量,将单词的所有首字母大写,除了第一个——firstEntryminXValue。我为像SSDL_Image这样的创造类型保留初始资本。首字母“_”代表编译器自己的标识符。还有其他公约;无论你使用什么约定,最好是尽可能清楚。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你认为它的名字是怎么来的?

常量

我们已经设定了一些常量:

const SSDL_Color MAHOGANY   = SSDL_CreateColor    (192,  64,   0);
const SSDL_Font  FONT       = SSDL_OpenSystemFont ("timesbd", 24);

现在请考虑这两个更简单的常量声明:

constexpr double PI               = 3.14159265359;
constexpr int    DAYS_PER_FORTNIGHT = 7+7;  // A fortnight is two weeks, so...

有什么区别?const简单地说就是“不变”constexpr表示“不改变并且在编译时被设置”后者让程序快一点。我们不会注意到前面的声明,但是随着程序变得更大更复杂,它可能会更重要。(我不能用SSDL_CreateColorSSDL_OpenSystemFont来做这件事,因为只有在运行时启动 SDL,它们才能工作。)

当初始值只是数字的时候我用constexpr(比如3.14159265359或者7+7),当是函数调用的时候我用const(比如SSDL_OpenSystemFont ("times", 18))。最终我们会完善这一点(见第二十六章),但目前来说还是不错的。

何时使用常量,而不是文字值

什么时候应该使用文字值,比如100,什么时候应该使用常量符号,比如CENTURY?答案几乎总是:使用常量而不是简单的文字值。原因有二:

一个是要明确的是,如前所示。你正在回顾一个程序,你看到一个对7的引用。七什么?一周中的几天?《死罪》的数量?你写第一个程序的时候是多少岁?你必须做一些调查工作来找出它,尤其是如果你的程序中有不止一个7。侦探工作不适合懒人。最好用一个清晰的名字记录下来。

另一个原因是容易改变值。例如,按照惯例有七宗罪,但是用程序员的术语来说,使用像7这样简单的数字文字是一个非常致命的错误。所以也许那个constexpr int NUMBER_OF_DEADLY_SINS = 7;需要更新为8。如果你用了这个常量,你有一行需要修改。如果你让7贯穿你的程序,你将不得不考虑哪些7需要改变,哪些不需要改动。又是侦探工作。

底线是清晰。我们不会回到第一章的 bug face 程序,用constexpr代替所有的数字,因为这会让程序更难理解;每个值都是唯一的,给它命名并不能让它更清晰。(反正我们有评论说明什么意思。)但虫脸程序是个例外。通常,值应该被命名。

Golden Rule of Constants

任何时候,如果数字文字值的用途不明显,就将其定义为常量符号,全部大写,并在引用它时使用该名称。

Extra: Adding

constexpr 向 Bug-Head 程序

是的,我勉强承认,出于前面给出的原因,第一章的 bug-head 程序中的简单数字可以保留。但是如果我们不止一次引用值呢?用它们来计算脸部的位置?在这种情况下,我们需要常量。因此

// draw the bug's head
SSDL_RenderDrawCircle (430, 260, 200);

// left eye, and right
SSDL_RenderDrawCircle (430-80, 260, 50);
SSDL_RenderDrawCircle (430+80, 260, 50);

会变成

constexpr int HEAD_X     = 430, HEAD_Y   = 260, HEAD_RADIUS = 200;
constexpr int EYE_RADIUS =  50;
constexpr int EYE_OFFSET =  80; // How far lt/rt an eye is from center

// draw the bug's head
SSDL_RenderDrawCircle (HEAD_X,            HEAD_Y, HEAD_RADIUS);

// left eye, and right

SSDL_RenderDrawCircle (HEAD_X-EYE_OFFSET, HEAD_Y, EYE_RADIUS);
SSDL_RenderDrawCircle (HEAD_X+EYE_OFFSET, HEAD_Y, EYE_RADIUS);

当然,现在它更长了——但是它已经从“这些数字是如何相互关联的?为什么 430 和 260 一直出现?”一个内在的解释。不错。(完整的程序在源代码中的ch3bugsHead-with-constexpr;照常用make运行(g++)或者通过 ch3.sln (Visual Studio)。输出如图 3-2 所示。)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2

一个虫子的头,用常量和计算画出来的

数学运算符

表 3-1 包含了你可以在 C++ 中使用的算术运算符。你可能会想到它们的用法:2.6+0.4alpha/beta-2*(5+3)

表 3-1

算术运算符

|

运算符

|

意义

|
| — | — |
| + | 添加 |
| - | 减法,否定 |
| * | 增加 |
| / | 分开 |
| % | 系数 |

整数除法

在你学习分数之前,当你只用整数时,结果总是一个整数:5 除以 2 等于 2,余数是 1。C++ 的整数除法也是一样:5/2给你另一个整数,2,不是2.5——那是浮点值。

这可能会令人困惑。1/2看起来确实应该是0.5,但是由于12是整数,1/2也必须是整数:0

为了与我们划分整数的方式保持一致,C++ 还提供了%,即模数运算符,意思是“除取余数”。5%2给出了1,5 除以 2 后的余数。我们将在第八章“随机数”一节中看到更多的%

赋值(=)运算符

我们已经在使用=了:

const SSDL_Color MAHOGANY   = SSDL_CreateColor  (192,  64,   0);
int   seasonsOfAmericanIdol = 18;

常量不能超过第一行,否则它们就不是常量,但是变量可以随时变化:

x = 5; y = 10;
x = 10;             // I changed my mind: put a 10 in X, replacing the 5

seasonsOfAmericanIdol = seasonsOfAmericanIdol + 1; // Another year! Yay!

后者意味着取那个seasonsOfAmericanIdol存储单元中的任何数字,加 1,然后把结果值放回那个相同的位置。

也可以这么写:seasonsOfAmericanIdol + = 1;

它们的意思是一样的:在seasonsOfAmericanIdol上加 1。1

它适用于其他算术运算符: -=*=/=%=都以相同的方式定义。

跳水板的例子

现在,让我们用一个将数学用于体育的程序来实践这一点。有人要离开跳水板了。我们将一秒一秒地拍摄角色跳入水中的图像(例如 3-2 )。

// Program to draw the path of a diver
//              -- from _C++20 for Lazy Programmers_

#include "SSDL.h"

int main(int argc, char** argv)
{
    SSDL_SetWindowTitle("Sploosh!  Hit a key to end");

    // Stuff about the board
    constexpr int BOARD_WIDTH        = 60,2
                  BOARD_THICKNESS    =  8,
                  BOARD_INIT_Y       = 20;

    SSDL_RenderDrawRect(0, BOARD_INIT_Y,
                        BOARD_WIDTH, BOARD_THICKNESS);

    // ...the water

    constexpr int SKY_HEIGHT         = 440;
    SSDL_SetRenderDrawColor(BLUE);
    SSDL_RenderFillRect(0, SKY_HEIGHT,
                        SSDL_GetWindowWidth(),
                        SSDL_GetWindowHeight() - SKY_HEIGHT);
                                  // height is window height - sky height

    // ...the diver
    constexpr int
        WIDTH              = 10, // Dimensions of "diver"
        HEIGHT             = 20,
        DISTANCE_TO_TRAVEL = 20, // How far to go right each time
        FACTOR_TO_INCREASE =  2; // Increase Y this much each time

    constexpr int INIT_X   = 50,
                  INIT_Y   = 10;
    int                x   = INIT_X; // Move diver to end of board
    int                y   = INIT_Y; // and just on top of it

    const SSDL_Color DIVER_COLOR = SSDL_CreateColor(200, 150, 90);
    SSDL_SetRenderDrawColor(DIVER_COLOR);

    // Now draw several images, going down as if falling, and right
    // Remember x+=DISTANCE_TO_TRAVEL means x=x+DISTANCE_TO_TRAVEL
    //   ...and so on

    SSDL_RenderFillRect(x, y, WIDTH, HEIGHT);
    x += DISTANCE_TO_TRAVEL;  // go right the same amount each time,
    y *= FACTOR_TO_INCREASE;  //  down by an ever-increasing amount
    SSDL_Delay(100);          // 100 ms -- 0.1 seconds

    // Same thing repeated several times

    SSDL_RenderFillRect(x, y, WIDTH, HEIGHT);
    x += DISTANCE_TO_TRAVEL; y *= FACTOR_TO_INCREASE;
    SSDL_Delay(100);          // 100 ms -- 0.1 seconds

    SSDL_RenderFillRect(x, y, WIDTH, HEIGHT);
    x += DISTANCE_TO_TRAVEL; y *= FACTOR_TO_INCREASE;
    SSDL_Delay(100);          // 100 ms -- 0.1 seconds

    SSDL_RenderFillRect(x, y, WIDTH, HEIGHT);
    x += DISTANCE_TO_TRAVEL; y *= FACTOR_TO_INCREASE;
    SSDL_Delay(100);          // 100 ms -- 0.1 seconds

    SSDL_RenderFillRect(x, y, WIDTH, HEIGHT);
    x += DISTANCE_TO_TRAVEL; y *= FACTOR_TO_INCREASE;
    SSDL_Delay(100);          // 100 ms -- 0.1 seconds

    SSDL_RenderFillRect(x, y, WIDTH, HEIGHT);
    x += DISTANCE_TO_TRAVEL; y *= FACTOR_TO_INCREASE;
    SSDL_Delay(100);          // 100 ms -- 0.1 seconds

    // end program
    SSDL_WaitKey();
    return 0;
}

Example 3-2A program to show a diver’s path, using constexprs and math operators

注意事项:

  • 我一如既往地初始化所有变量。

  • 在任何计算或变量初始化中没有空的数字文本;一路都是定值。

  • 我重复同样的一对线六次。真的吗?那是懒吗?我们在第五章会有更好的方法。

图 3-3 就是结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-3

显示潜水员入水路径的程序

这很有效,并且在某种程度上唤起了我从高台跳水时的恐惧。

数学运算符的无忧列表

这里有一些 C++ 会很自然地处理的事情,你不需要为它们记住任何东西:

  • 优先级 : 考虑一个数学表达式,2*5+3。在 C++ 中,就像在代数课上一样,我们在加法之前先做乘法;这是指(2*5)+3 = 13,而不是2*(5+3) = 16。同样的,在8/2-1中,我们先除后减。总的来说,用对你有意义的方式去做,它就会是正确的。如果不是,用括号强制它按照你的方式进行:8/(2-1)

  • **关联性:**在27/3/3中,哪个除法先出现?是像27/(3/3),还是像(27/3)/3那样做?算术运算从左到右执行。赋值是从右到左进行的:x=5+2要求你在对x做任何事情之前先评估5+2

优先级和结合性的精确细节在附录 b 中。

  • 强制 : 如果你想把一种类型的变量塞进另一种类型,C++ 会做到:

double Nothing = 0;  // Nothing becomes 0.0, not 0

int Something = 2.7; // ints can't have``decimal places

// C++ throws away the .7;

// Something becomes 2\. No rounding, alas

如果在计算中混合整数和浮点数,结果将是信息最多的版本,即浮点。比如10/2.0,给你5.0

Exercises

  1. 使用厘米每英寸(2.54)和英寸每英尺(12)的常量,将某人的身高从英尺和英寸转换为厘米,并报告结果。

  2. 现在反过来做:厘米到英尺和英寸。

  3. Accumulate this sum for as far as you’re willing to take it, 1/2 + 1/4 + 1/8 + 1/16 +…, using +=. Do you think if you did it forever you would reach a particular number? Or would it just keep getting bigger? The ancient philosopher Zeno of Elea would have an opinion on that (https://en.wikipedia.org/wiki/Zeno%27s_paradoxes, at time of writing). But he’d be wrong.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    你不能从这里到达那里。—芝诺。算是吧。

  4. 编写一个程序,让一个盒子以 0.1 秒的跳跃在屏幕上移动——就像潜水员移动一样,但每次跳跃都要清空屏幕,这样它看起来就像真的在移动。也许可以缩短延迟时间,以获得更好的运动错觉。

内置函数和转换

现在我想做一个几何图形,五角星。但是忘了第一章的图表吧。我想让电脑帮我算出来。让它自己做(虚拟的)绘图纸。

如果我把星星想象成一个圆,我可能知道圆心,所以我需要计算的是边缘的点。每个点都比前一个点绕圆远五分之一,所以如果一个圆是 360 度,它们之间的角度是 360/5 度。如果你用弧度而不是角度,比如 C++,那就是两点之间的 2π/5 弧度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SDL 使用 x,y 坐标,所以我们需要一种方法从这个角度得到它。我们可以使用图 3-4 中的图片来完成。由于角度θ的正弦是 y 距离除以半径(如果数学不是你的菜,相信我),y 距离就是半径* sin (θ)。同样,x 距离是半径* cos (θ)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-4

与 x 和 y 相关的正弦和余弦

像大多数 C++ 数学函数一样,sincos函数在一个名为cmath、 3 、的包含文件中进行声明。因此

#include <cmath>  // System include files (those that come with the compiler)
                 // have <>'s not ""'s.
#include "SSDL.h"

这个程序的意思是从中心到边缘画一条线,绕圆转五分之一圈,再做一遍,一直走,总共五行。

// Program to make a 5-point star in center of screen
//              -- from _C++20 for Lazy Programmers_

#include <cmath>
#include "SSDL.h"

int main(int argc, char** argv)
{
    constexpr double PI = 3.14159;

    // Starting out with some generally useful numbers...

    // center of screen

    const     int CENTER_X           = SSDL_GetWindowWidth () / 2,
                  CENTER_Y           = SSDL_GetWindowHeight() / 2;
    constexpr int RADIUS             = 200,
                  NUMBER_OF_POINTS   =   5;

    // angle information...
    double    angle                  = 0;     // angle starts at 0
    constexpr double ANGLE_INCREMENT = (2 / NUMBER_OF_POINTS) * PI;
                            // increases by whole circle/5 each time

    // ...now we make the successive lines
    int x, y;               // endpt of line (other endpt is center)

    x = CENTER_X + int(RADIUS * cos(angle));       // calc endpoint
    y = CENTER_Y + int(RADIUS * sin(angle));
    SSDL_RenderDrawLine(CENTER_X, CENTER_Y, x, y); // draw line
    angle += ANGLE_INCREMENT;                      // go on to next

    x = CENTER_X + int(RADIUS * cos(angle));       // calc endpoint
    y = CENTER_Y + int(RADIUS * sin(angle));
    SSDL_RenderDrawLine(CENTER_X, CENTER_Y, x, y); // draw line
    angle += ANGLE_INCREMENT;                      // go on to next

    x = CENTER_X + int(RADIUS * cos(angle));       // calc endpoint
    y = CENTER_Y + int(RADIUS * sin(angle));
    SSDL_RenderDrawLine(CENTER_X, CENTER_Y, x, y); // draw line
    angle += ANGLE_INCREMENT;                      // go on to next

    x = CENTER_X + int(RADIUS * cos(angle));       // calc endpoint
    y = CENTER_Y + int(RADIUS * sin(angle));
    SSDL_RenderDrawLine(CENTER_X, CENTER_Y, x, y); // draw line
    angle += ANGLE_INCREMENT;                      // go on to next

    x = CENTER_X + int(RADIUS * cos(angle));       // calc endpoint
    y = CENTER_Y + int(RADIUS * sin(angle));
    SSDL_RenderDrawLine(CENTER_X, CENTER_Y, x, y); // draw line
    angle += ANGLE_INCREMENT;                      // go on to next

    // end program

    SSDL_WaitKey();
    return 0;
}

Example 3-3A star using sin and cos functions

图 3-5 显示了结果。什么事?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-5

一颗五角星——至少,它应该是

一旦我们在第九章中讨论了调试器,这将是一件轻而易举的事情,但是现在,我们只需要打开夏洛克·福尔摩斯的频道。那条线不变,说明angle不变,说明ANGLE_INCREMENT一定是 0。为什么会是 0?

看那个计算:ANGLE_INCREMENT = (2/NUMBER_OF_POINTS)*PI。首先要做的是用 2 除以NUMBER_OF_POINTS,即 5。因为两者都是整数,我们做整数除法:5 除以 2(余数是 2,不管值不值得),所以2/5给我们零。零点乘以PI为零。所以ANGLE_INCREMENT是零。

我们需要浮点除法。

一种方法是强制 2 和 5 为floatdouble。你可以这样说。

double (whatEverYouWantToBeDouble)

这叫铸造

double (2/NUMBER_OF_POINTS)不起作用,因为它将 2 除以 5,得到 0,然后将 0 转换为 0.0。它还在做整数除法。

以下任何一种都可以。只要/的参数之一是doublefloat,就会得到一个带小数位的结果:

double (2)/NUMBER_OF_POINTS
2/double (NUMBER_OF_POINTS)
2.0 / NUMBER_OF_POINTS

因此,将main的开头改为你在示例 3-4 中看到的形式可以修复这个问题。

int main(int argc, char** argv)
{
...

    // angle information...
    double        angle = 0;  // angle starts at 0
    constexpr double ANGLE_INCREMENT
               = (2 / double (NUMBER_OF_POINTS)) * PI;
                              // increases by whole circle/5 each time
    ...

Example 3-4A new beginning to main, to make Example 3-3 work

图 3-6 显示了结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-6

五角星

好像不是垂直的。练习 1 是关于把它转直。

其他常用的数学函数包括asinacos(反正弦和反余弦)、pow(将一个数提升到幂)、abs(绝对值)和sqrt(平方根)。更多信息见附录 F。

防错法

  • 你调用了一个返回值的函数,但是没有效果。下面是我们之前看到的一个函数示例:

    // Center "Blastoff!" on the screen

    SSDL_GetScreenWidth();

    SSDL_RenderTextCentered (320, 240, "Blastoff!");

    当然,你叫了SSDL_GetWindowWidth()…但是你从来没有对结果做任何事情!C++ 很乐意让你浪费时间去调用函数,不去用它们给你的东西。(这是一种“让程序员搬起石头砸自己的脚,然后大笑”的语言。)如果要使用该值,请在需要该值的任何地方引用它:

    SSDL_RenderTextCentered(SSDL_GetScreenWidth ()/2,

    SSDL_GetScreenHeight()/2,

    "Blastoff!");

    或者将其放入变量或常量中以备后用:

    const int SCREEN_WIDTH  = SSDL_GetScreenWidth ();

    const int SCREEN_HEIGHT = SSDL_GetScreenHeight();

    SSDL_RenderTextCentered (SCREEN_WIDTH/2, SCREEN_HEIGHT/2, "Blastoff!");

  • **你将两个整数相除,得到一个介于 0 和 1 之间的浮点数,得到的却是 0。**见本节示例 3-3 。/符号的其中一个操作数应该被强制转换为floatdouble

  • 你会得到一个关于类型之间转换的警告。你可以忽略它,但是要让它消失,把讨厌的东西丢到你想要的地方。那么编译器就会知道这是故意的。

Exercises

  1. 调整示例 3-4 中的星形,使星形的顶点垂直向上。

  2. 制作一个钟面:一个在适当位置标有数字 1-12 的圆圈。

  3. (用力)下面是如何以秒为单位获得系统时间:

    #include <``ctime

    ...

    int timeInSeconds = int 4 (time (nullptr));

    使用%/运算符以小时、分钟和秒为单位查找当前时间。由于您所在的时区,时间可能会有所不同;可以适当调整。

  4. 完成 2 和 3 后,制作一个显示当前时间的钟面。

如果你想加 1,而不是其他数字,有一个特殊的“增量”操作符:

++seasonsOfAmericanIdol;

我们将在第五章中再次看到这一点,以及“减量”(--)。

2

所有这些都排列得如此精确。这是不是矫枉过正?看看找到我所有的变量和它们的值有多容易!另一个需要养成的好习惯。

3

包括从 C++ 祖先 C 继承的文件,以“C”开始:例如cmathcstdlib

4

我们使用int来避免“反欺诈”中提到的转换警告time函数返回一个time_t(不管是什么);我们会强制它成为一个int

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值