Visual C++ 2013入门经典(第7版)


Visual C++ 2013 入门经典(第7版) [美] Ivor Horton 著 李周芳 江 凌 译 Ivor Horton's Beginning Visual C++ 2013 .NET开发经典名著 Join the discussion @ p2p.wrox.com Wrox Programmer to ProgrammerTM   这本最新的C++语言畅销书遵循Ivor Horton一贯的畅销 风格。Horton非常详尽地介绍了标准C++语言和Visual C++。 整本书都根据Visual C++ 2013进行了更新,展示了如何使用 Visual C++建立实用的应用程序。阅读本书不需要任何编程经 验。作者使用大量循序渐进的编程示例,引导读者掌握C++ 开发的全部奥秘。 本书特色 ◆ 介绍C++编程的基本概念以及Visual C++开发环境的要点 ◆ 从简单的过程式程序开始解释C++语言,逐步过渡到类和 面向对象编程 ◆ 演示如何在各种环境下应用标准模板库(STL) ◆ 揭示每个Windows桌面应用程序中的基本元素 ◆ 展示如何使用MFC建立图形用户界面以编写Windows应用 程序 ◆ 通过一个在Windows 8下执行的游戏示例,说明如何开发 Windows Store应用程序 作者简介   Ivor Horton是世界著名计算机图书作家,独立顾问, 帮助无数程序员步入编程殿堂。他曾在IBM工作多年,以优 异成绩拥有数学学士学位。他的资历包括:使用大多数语言 (如在多种机器上使用汇编语言和高级语言)进行编程,实时编 程,设计和实现实时闭环工业控制系统。Horton拥有丰富的 面向工程师和科学家的编程教学经验(教学内容包括C、C++、 Fortran、PL/1、APL等)。同时,他还是机械、加工和电子CAD 系统、机械CAM系统和DNC/CNC系统方面的专家。 源代码下载及技术支持   http://www.wrox.com   http://www.tupwk.com.cn/downpage 著名编程图书作家Ivor Horton引导 学习Visual C++ 2013 wrox.com Programmer Forums Join our Programmer to Programmer forums to ask and answer programming questions about this book, join discussions on the hottest topics in the industry, and connect with fellow programmers from around the world. Code Downloads Take advantage of free code samples from this book, as well as code samples from hundreds of other books, all ready to use. Read More Find articles, ebooks, sample chapters and tables of contents for hundreds of books, and more reference resources on programming topics that matter to you. 定价:99.80元 Visual C++ 2013 入门经典 (第7版) Visual C++ 2013 入门经典 (第 7 版) [美] Ivor Horton 著 李周芳 江 凌 译 北 京 Ivor Horton's Beginning Visual C++ 2013 EISBN:978-1-118-84571-4 Copyright © 2014 by John Wiley & Sons, Inc. All Rights Reserved. This translation published under license. 本书中文简体字版由 Wiley Publishing, Inc. 授权清华大学出版社出版。未经出版者书面许可,不得以任何方式 复制或抄袭本书内容。 北京市版权局著作权合同登记号 图字:01-2014-7591 Copies of this book sold without a Wiley sticker on the cover are unauthorized and illegal. 本书封面贴有 Wiley 公司防伪标签,无标签者不得销售。 版权所有,侵权必究。侵权举报电话:010-62782989 13701121933 图书在版编目(CIP)数据 Visual C++ 2013 入门经典(第 7 版) / (美) 霍尔顿(Horton,I.) 著;李周芳,江凌译. —北京 : 清华大学出 版社,2015 (.NET 开发经典名著) 书名原文: Ivor Horton’s Beginning Visual C++ 2013 ISBN 978-7-302-38505-9 .Ⅰ V…① .Ⅱ ①霍… ②李… ③江… .Ⅲ C① 语言-程序设计 .Ⅳ TP312① 中国版本图书馆 CIP 数据核字(2014)第 260961 号 责任编辑:王 军 于 平 装帧设计:牛静敏 责任校对:成凤进 责任印制: 出版发行:清华大学出版社 网 址:http://www.tup.com.cn,http://www.wqbook.com 地 址:北京清华大学学研大厦 A 座 邮 编:100084 社 总 机:010-62770175 邮 购:010-62786544 投稿与读者服务:010-62776969,c-service@tup.tsinghua.edu.cn 质 量 反 馈:010-62772015,zhiliang@tup.tsinghua.edu.cn 印 刷 者: 装 订 者: 经 销:全国新华书店 开 本:185mm×260mm 印 张:49.5 字 数:1328 千字 版 次:2015 年 1 月第 1 版 印 次:2015 年 1 月第 1 次印刷 印 数:1~4000 定 价:99.80 元 —————————————————————————————————————————————— 产品编号: 译 者 序 Visual C++是一个功能强大的面向对象的可视化集成编程系统。它不但具有程序框架自动生成、 灵活方便的类管理、代码编写和界面设计集成交互操作、可开发多种程序等优点,而且通过简单的 设置就可使其生成的程序框架支持数据库接口、OLE2,WinSock 网络、3D 控制界面。自 1993 年 Microsoft 公司推出 Visual C++1.0 后,随着其新版本的不断问世,Visual C++已成为专业程序员进行 软件开发的首选工具。而 C++是一种高效实用的程序设计语言,它既可进行过程化程序设计,也可 进行面向对象程序设计,因而成为编程人员最广泛使用的工具。学好 C++,很容易触类旁通其他软 件,C++架起了通向强大、易用、真正的软件开发应用的桥梁。 本书是久负盛名的 Visual C++经典教程,其内容是世界著名的计算机图书大师 Ivor Horton 丰富 的实践经验和对 C++标准深入理解的完美结合,已经帮助全球无数程序员学会了 Visual C++。本版 对前一版进行了彻底的修订,内容经过了重新组织,既显著改善了可读性,又充分体现了 C++语言 的最新进展和当前的业界最佳实践。 本书的第一部分通过一个详细的循序渐进式教程,讲授了使用 Visual Studio Professional 2013 编 写 C++程序的基础知识。您将了解 C++语言的语法和用法,并通过有效的示例,获得实际运用它的 经验和信心。该教程也介绍和说明了 C++标准库功能的用法,因为开发程序时极有可能使用它们。 还将学习标准模板库(Standard Template Library,STL)提供的强大工具。本书最后通过循序渐进地开 发一个有效的游戏示例,来学习如何使用 Microsoft 基本类(Microsoft Foundation Classes,MFC)来开 发 Windows 桌面应用程序。并附带说明如何编写面向平板电脑、运行 Windows 8 的应用程序。 书中不但包含大量教学辅助内容,而且附带了许多来自实战的示例,用于强调重要的知识点, 提醒常见的错误,推荐优秀的编程实践,给出使用提示,并在每章结束时给出了一组练习,读者可 以应用所学的技术来试着解答这些练习。 对 C++基本概念和技术全面而且权威的阐述,对现代 C++编程风格的强调,使本书成为 C++初 学者的最佳指南。有一定 Visual C++基础,希望使用最新的工具和技术,扩展在 Windows 环境下编 程技能的读者,也可以把本书作为能随时查阅的参考资料。 在这里要感谢清华大学出版社的编辑,他们为本书的翻译投入了巨大的热情并付出了很多心血。 没有他们的帮助和鼓励,本书不可能顺利付梓。本书全部章节由李周芳、 江凌翻译,参与本次翻译 活动的还有孔祥亮、陈跃华、杜思明、熊晓磊、曹汉鸣、陶晓云、王通、方峻、李小凤、曹晓松、 蒋晓冬、邱培强、洪妍、李亮辉、高娟妮、曹小震、陈笑。在此一并表示感谢!对于这本经典之作, 译者在翻译过程中力求“信、达、雅”,但是鉴于译者水平有限,错误和失误在所难免,如有任何意 见和建议,请不吝指正。 译 者 作 者 简 介 Ivor Horton 原来是一位数学家,却因向往信息技术工作轻松而收入丰厚,因而涉足信息技术领 域。尽管现实情况常常是工作辛苦而收入却相对不高,但他仍坚持从事计算机工作至今。在不同的 时期,他从事过的工作包括程序设计、系统设计、顾问工作以及管理和实现相当复杂的项目。 Horton 在计算机系统的设计和实现方面,拥有多年的工作经验,这些系统应用于多种行业的工 程设计和制造运营。他不仅能运用多种编程语言开发特殊用途的应用程序,而且还为科研人员和工 程人员提供教学,以帮助他们完成这类工作,在这些方面他都拥有相当丰富的经验。他多年来一直 从事程序设计方面书籍的撰写工作,目前出版的著作有 C、C++和 Java 等教程。目前,他既没有忙 于写书,也不提供咨询服务,而是在钓鱼、旅游和尽情地享受生活。 技术编辑简介 Giovanni Dicanio 是一位 Microsoft Visual C++ MVP、计算机程序员和 Pluralsight 作家。他的计 算机编程生涯可以追溯到 Commodore 64 和 Commodore 500 时期,他最初使用 C=64 BASIC,然后 转向汇编语言、Pascal、C、C++、Java 和 C#。Giovanni 在意大利计算机杂志上发表了关于 C++、 MFC、OpenGL 和其他编程主题的计算机编程文章。他还为一些开源项目提供代码,包括为 QCAD 的第一个版本提供 C++数学表达式解析器。Giovanni 还拥有 C++、Win32、COM 和 ATL 的 Windows 编程经验。他最喜欢的编程语言是 C 和 C++。 他最近开始对移动平台和嵌入式系统感兴趣。 他的联系方式是 giovannid.dicanio@gmail.com。 Marc Gregoire 是来自比利时的一位软件工程师。他毕业于比利时天主教鲁汶大学,获得了 “Burgerlijk ingenieur in de computer wetenschappen”学位(等同于计算机科学工程的科学硕士学位)。 此后,他以优异成绩获得了同一所大学的人工智能硕士学位,并开始供职于一家大型软件咨询公司 (Ordina 公司网址:http://www.ordina.be)。他在西门子和诺基亚西门子通信公司为大型电信运营商开 发运行于 Solaris 上至关重要的 2G 和 3G 软件,这需要在国际团队中工作,包括南美、USA、EMEA 和亚洲。现在,Marc 在尼康公司开发 3D 扫描软件。 他主要擅长 C/C++,具体地说就是 Microsoft VC++和 MFC framework。他在开发全天候运行在 Windows 和 Linux 平台上的 C++程序方面,也具有一定的经验,例如 KNX/EIB 家用自动控制软件。 除 C/C++之外,他也喜欢 C#,并使用 PHP 制作网页。 由于在 Visual C++方面具有杰出的专业技能,Marc Gregoire 自从 2007 年 4 月开始,每年都荣获 了 Microsoft MVP(Most Valuable Professional)大奖。 Marc 不仅是 Belgian C++ 用户组(www.becpp.org) 的创始人、C++ 高级编程的作者 (ISBN978-047-0-93244-9,Wrox 出版社出版)和 CodeGuru 论坛的活跃分子(会员名是 Marc G)。他 维护其博客 www.nuonsoft.com/blog/。 致 谢 在为本书的出版而付出劳动的所有人员组成的大型团队中,作者只是其中的一员。感谢 John Wiley & Sons 公司和 Wrox 出版社的编辑和生产团队,感谢他们自始至终提供的帮助和支持。 尤其要感谢技术编辑 Marc Gregoire 和 Giovanni Dicanio,感谢他们审阅本书,并认真核对了书 中提供的所有代码段和示例。他们对以更好的方式呈现书中内容提出了很多建设性意见和建议, 这毫无疑问使本书成为一本更出色的教程。 前 言 欢迎使用本书。通过学习本书,你可以使用 Microsoft 公司最新的应用程序开发系统 Visual Studio 2013,成为优秀的 C++程序员。本书旨在讲述 C++程序设计语言,然后讲述如何运用 C++语言开发 自己的 Windows 应用程序。在此过程中,读者将了解这一最新 Visual C++版本所提供的很多激动人 心的新功能。 Visual C++ 2013 是 Microsoft 开发环境 Visual Studio Professional 2013 的所有版本的一部分,本 书提到 Visual C++时,都是指 Visual Studio Professional 2013 包含的 Visual C++ 2013 功能。注意 Visual Studio Express 2013 版本没有提供本书的全部功能。第 11 到 18 章的示例不能用 Visual Studio Express 2013 创建。 0.1 本书读者对象 本书针对任何想要学习如何使用 Visual C++编写在 Microsoft Windows 操作系统下运行的 C++ 应用程序的读者。阅读本书不需要预先具备任何特定编程语言的知识。如果属于下列 3 种情形之一, 你就适合学习本教程: ● 属于编程新手,十分渴望投入编程世界,并最终掌握 C++。要取得成功,你至少需要对计算 机的工作原理有大体的理解。 ● 具备一些其他语言的编程经验,如 BASIC;渴望学习 C++,并想提升实际的 Microsoft Windows 编程技能。 ● 有一些使用 C 语言或 C++语言的经验,但使用环境不是 Microsoft Windows;希望使用最新 的工具和技术,扩展在 Windows 环境下编程的技能。 0.2 本书主要内容 本书的第一部分通过一个详细的循序渐进式教程,讲授了使用 Visual Studio Professional 2013 编 写 C++程序的基础知识。你将了解 C++语言的语法和用法,并通过有效的示例,获得实际运用它的 经验和信心,示例代码演示了 C++的几乎所有方面。本书也提供了一些练习,可以检验所学的知识, 并且可以下载练习题答案。 本语言教程也介绍和说明了 C++标准库功能的用法,因为开发程序时极有可能使用它们。随着 深入地学习 C++语言,你的标准库知识会不断增加。还将学习标准模板库(Standard Template Library, STL)提供的强大工具。 对 C++的运用有信心之后,就可以继续学习 Windows 编程了。通过创建超过 2000 行代码的大 型可运行的应用程序,学习如何使用 MFC 来开发 Windows 桌面应用程序。开发此应用程序贯穿多 Visual C++ 2013 入门经典(第 7 版) VI 章内容,用到了 MFC 提供的一系列用户界面功能。还要学习如何编写面向平板电脑、运行 Windows 8 的应用程序,通过循序渐进地开发一个有效的游戏示例,来学习如何创建带有 Windows 8 现代界 面的应用程序。 0.3 本书结构 本书内容的结构安排如下: ● 第 1 章介绍使用 C++编写程序所需要理解的基本概念,以及在 Visual C++开发环境中体现 的主要思想,还叙述了如何使用 Visual C++的功能来创建本书其余部分要学习的各种 C++ 应用程序。 ● 第 2~9 章讲授 C++语言。首先是简单的过程式程序示例,然后学习类和面向对象的编程。 ● 第 10 章介绍如何使用标准模板库(Standard Template Library,STL)。STL 是一组功能强大且 全面的工具,用来组织和操作 C++程序中的数据。由于 STL 是独立于应用程序的,因此可 以在上下文中大量应用它。 ● 第 11 章讨论 Microsoft Windows 桌面应用程序的组织方式,并描述和展示了在所有为 Windows 操作系统编写的桌面应用程序中都存在的基本元素。本章通过基础示例解释了 Windows 应用程序的工作原理,还将创建使用 C++语言、Windows API 和 MFC 的程序。 ● 第 12~17 章讲述 Windows 桌面应用程序的编程。详细描述了如何使用 MFC 提供的构建 GUI 的功能编写 C++ Windows 应用程序。我们将学习如何创建并使用通用控件来构建应用程序 的图形用户界面,还将学习如何处理因用户与程序的交互作用而产生的事件。除了学习构 建 GUI 的技术以外,还将从开发该应用程序的过程中学到如何打印文档,以及应用程序如 何处理文件。 ● 第 18 章讲述为 Windows 8 编写应用程序的基本概念,开发一个使用 Windows 8 现代用户界 面的完整、有效的应用程序。 本书各章内容都包括许多工作示例,通过这些示例阐明所讨论的编程技术。每章结束时都总结 了该章所讲述的要点,大多数章节都在最后给出了一组练习,可以应用所学的技术来试着解答这些 练习。练习的答案连同书中的所有代码都可以从 Wrox 出版社的网站上下载。 0.4 使用本书的前提 Visual Studio 2013 有几个版本,它们都有不同的功能。本书假定你安装了 Visual Studio Professional 2013(或更高版本)。换言之,只要安装付费的 Visual Studio 2013 版本即可。如果你是 全日制学生,则可以使用低成本的学生版本。只安装免费的 Express 版本是不够的。 如果安装了 Visual Studio 和 Windows 7 或 Windows 8,就可以使用第 1~17 章的使用示例和练习, 要使用第 18 章的示例,Visual Studio 的版本必须安装在 Windows 8 环境下。 第 2~10 章的示例可以使用 Windows 桌面的 Visual Studio Express 2013 创建和执行,但第 11~18 章的示例不行。 前 言 VII 0.5 源代码 读者在阅读本书提供的代码时,既可以亲自输入所有代码,也可以使用随书提供的代码文件。 本书所有代码均可以从 http://www.wrox.com/或 http://www.tupwk.com.cn/downpage 网站下载。进入该 网站后,读者可以根据本书的书名查找本书(既可以使用搜索框,也可以使用书名列表进行查找), 然后单击本书详细内容页面上提供的 Download Code 链接,就可以下载本书提供的所有代码。 注意: 由于许多书籍名称与本书类似,读者也可以通过 ISBN 进行查找,本书的 ISBN 为: 978-1-118-84571-4。 另外,读者可以从前面提到的 CodePlex 网站下载本书或其他 Wrox 书籍的代码,也可以从 Wrox 的 代码下载页面 http://www.wrox.com/dynamic/books/download.aspx 和http://www. tupwk.com.cn/downpage 下 载本书或其他 Wrox 书籍的代码。 0.6 练习 许多章节都有一组练习用于检验你所学的知识。尽量完成所有的练习。如果有问题,可以从 http://www.wrox.com/go/beginingvisualc 上下载练习题的答案。 0.7 勘误表 为了避免本书文字和代码中存在错误,我们已经竭尽全力。然而,世界上并不存在完美无缺的 事物,所以本书可能仍然存在错误。如果读者在我们编写的某本书籍中发现了诸如拼写错误或代码 缺陷等问题,那么请告诉我们,我们对此表示感谢。利用勘误表反馈错误信息,可以为其他读者节 省大量时间,同时,我们也能够受益于读者的帮助,这样有助于我们编写出质量更高的专业著作。 如果读者需要参考本书的勘误表,请在网站 http://www.wrox.com 中用搜索框或书名列表查找本 书书名。然后,在本书的详细内容页面上,单击 Book Errata 链接。在随后显示的页面中,读者可以 看到与本书相关的所有勘误信息,这些信息是由读者提交、并由 Wrox 的编辑们加上的。通过访问 http://www.wrox.com/misc-pages/booklist.shtml,读者还可以看到 Wrox 出版的所有书籍的勘误表。 如果读者没有在 Book Errata 页面上找到自己发现的错误,那么请转到页面 http://www. wrox.com/contact/techsupport.shtml,针对你所发现的每一项错误填写表格,并将表格发给我们,我们 将对表格内容进行认真审查,如果确实是我们书中的错误,我们将在该书的 Book Errata 页面上标明 该错误信息,并在该书的后续版本中改正。 0.8 关于 p2p.wrox.com 论坛 如果读者希望能够与作者进行讨论,或希望能够参与到读者的共同讨论中,那么请加入 Visual C++ 2013 入门经典(第 7 版) VIII p2p.wrox.com 论坛。该论坛是一个基于 Web 的系统,读者可以在论坛发表与 Wrox 出版的书籍及相 关技术的信息,并与其他读者和技术用户进行讨论。论坛提供了订阅功能,可以将与读者所选定主 题相关的新帖子定期发送到读者的电子邮箱。Wrox 的作者、编辑、业界专家,以及其他读者都会 参与论坛中的讨论。 读者可以在 http://p2p.wrox.com 参与多个论坛的讨论,这些论坛不仅能够帮助读者更好地理解 本书,还有助于读者更好地开发应用程序。如果读者希望加入论坛,那么请按照以下步骤执行: (1) 进入 http://p2p.wrox.com 页面,单击 Register 链接。 (2) 阅读使用条款,然后单击 Agree 按钮。 (3) 填写必要的信息及可选信息,然后单击 Submit 按钮。 (4) 随后读者会收到一封电子邮件,邮件中说明了如何验证账户并完成整个加入过程。 读者无须加入 P2P 论坛即可阅读论坛消息,但如果需要发表主题或发表回复,那么必须加入论坛。 成功加入论坛后,读者就可以发表新主题了。此时,读者还可以回复其他用户发表的主题。读者 在任何时间都可以阅读论坛信息,如果需要论坛将新的信息发送到自己的电子邮箱,那么可以单击论 坛列表中论坛名称旁的 Subscribe to this Forum 图标完成这项功能设置。 如果读者需要获得更多与 Wrox P2P 相关的信息,请阅读 P2P FAQs,这样可以获得大量与 P2P 和 Wrox 出版的书籍相关的具体信息。阅读 FAQs 时,请单击 P2P 页面上的 FAQs 链接。 目 录 第 1 章 使用 Visual C++编程 .......................1 1.1 使用 Visual C++学习 .......................... 1 1.2 编写 C++应用程序 ............................. 2 1.3 学习桌面应用程序的编程 ................. 2 1.3.1 学习 C++.................................. 3 1.3.2 C++概念................................... 3 1.3.3 控制台应用程序...................... 4 1.3.4 Windows 编程概念.................. 4 1.4 集成开发环境简介.............................. 6 1.4.1 编辑器 ...................................... 6 1.4.2 编译器 ...................................... 6 1.4.3 链接器 ...................................... 6 1.4.4 库.............................................. 7 1.4.5 标准 C++库.............................. 7 1.4.6 Microsoft 库............................. 7 1.5 使用 IDE .............................................. 7 1.5.1 工具栏选项.............................. 8 1.5.2 可停靠的工具栏...................... 9 1.5.3 文档 .......................................... 9 1.5.4 项目和解决方案...................... 9 1.5.5 设置 Visual C++的选项 ........ 16 1.5.6 创建和执行 Windows 应用程序................................ 17 1.6 小结 .................................................... 19 1.7 本章主要内容.................................... 19 第 2 章 数据、变量和计算..........................21 2.1 C++程序结构..................................... 21 2.1.1 main()函数 ............................. 28 2.1.2 程序语句................................ 28 2.1.3 空白 ........................................ 30 2.1.4 语句块 .................................... 30 2.1.5 自动生成的控制台程序........ 30 2.2 定义变量.............................................32 2.2.1 命名变量.................................32 2.2.2 关键字.....................................32 2.2.3 声明变量.................................33 2.2.4 变量的初始值.........................33 2.3 基本数据类型.....................................34 2.3.1 整型变量.................................34 2.3.2 字符数据类型.........................35 2.3.3 整型修饰符.............................36 2.3.4 布尔类型.................................36 2.3.5 浮点类型.................................37 2.3.6 C++中的基本类型.................37 2.3.7 字面值.....................................38 2.3.8 定义类型的别名 ....................39 2.4 基本的输入/输出操作 .......................40 2.4.1 从键盘输入.............................40 2.4.2 到命令行的输出 ....................40 2.4.3 格式化输出.............................41 2.4.4 转义序列.................................42 2.5 C++中的计算 .....................................44 2.5.1 赋值语句.................................44 2.5.2 算术运算.................................44 2.5.3 计算余数.................................49 2.5.4 修改变量.................................49 2.5.5 增量和减量运算符 ................50 2.5.6 计算的顺序.............................52 2.6 类型转换和类型强制转换................53 2.6.1 赋值语句中的类型转换 ........54 2.6.2 显式类型转换.........................54 2.6.3 老式的类型强制转换 ............55 2.7 auto 关键字.........................................55 2.8 类型的确定.........................................56 2.9 按位运算符.........................................56 Visual C++ 2013 入门经典(第 7 版) X 2.9.1 按位 AND 运算符................. 57 2.9.2 按位 OR 运算符 .................... 58 2.9.3 按位 XOR 运算符 ................. 59 2.9.4 按位 NOT 运算符 ................. 60 2.9.5 移位运算符............................ 60 2.10 lvalue 和 rvalue ................................ 61 2.11 了解存储时间和作用域.................. 62 2.11.1 自动变量 ............................ 62 2.11.2 决定变量声明的位置........ 65 2.11.3 全局变量 ............................ 65 2.11.4 静态变量 ............................ 68 2.12 具有特定值集的变量...................... 68 2.12.1 旧枚举................................ 68 2.12.2 类型安全的枚举................ 70 2.13 名称空间.......................................... 72 2.13.1 声明名称空间.................... 73 2.13.2 多个名称空间.................... 74 2.14 小结 .................................................. 75 2.15 练习 .................................................. 75 2.16 本章主要内容.................................. 76 第 3 章 判断和循环.....................................79 3.1 比较数据值........................................ 79 3.1.1 if 语句..................................... 80 3.1.2 嵌套的 if 语句........................ 81 3.1.3 嵌套的 if-else 语句................ 85 3.1.4 逻辑运算符和表达式............ 87 3.1.5 条件运算符............................ 89 3.1.6 switch 语句............................. 91 3.1.7 无条件转移............................ 94 3.2 重复执行语句块................................ 95 3.2.1 循环的概念............................ 95 3.2.2 for 循环的变体 ...................... 98 3.2.3 while 循环 ............................ 105 3.2.4 do-while 循环....................... 107 3.2.5 基于范围的循环.................. 108 3.2.6 嵌套的循环.......................... 108 3.3 小结 ...................................................111 3.4 练习 ...................................................111 3.5 本章主要内容...................................111 第 4 章 数组、字符串和指针....................113 4.1 处理多个相同类型的数据值..........113 4.1.1 数组.......................................114 4.1.2 声明数组...............................114 4.1.3 初始化数组...........................117 4.1.4 使用基于范围的 for 循环....118 4.1.5 多维数组...............................119 4.2 处理 C 样式的字符串......................123 4.2.1 字符串输入...........................124 4.2.2 字符串字面量.......................125 4.2.3 给字符串使用基于范围的 for 循环.................................126 4.3 间接数据访问...................................128 4.3.1 指针的概念...........................128 4.3.2 声明指针...............................128 4.3.3 使用指针...............................129 4.3.4 初始化指针...........................130 4.3.5 指向 char 类型的指针 .........132 4.3.6 sizeof 操作符........................136 4.3.7 常量指针和指向常量的 指针.......................................136 4.3.8 指针和数组...........................138 4.4 动态内存分配...................................144 4.4.1 堆的别名—— 空闲存储器...144 4.4.2 new 和 delete 操作符 ...........145 4.4.3 为数组动态分配内存 ..........146 4.4.4 多维数组的动态分配 ..........148 4.5 使用引用...........................................149 4.5.1 引用的概念...........................149 4.5.2 声明并初始化 lvalue 引用 ....149 4.5.3 在基于范围的 for 循环中 使用引用...............................150 4.5.4 创建 rvalue 引用...................151 4.6 字符串的库函数 ..............................151 4.6.1 确定以空字符结尾的 字符串的长度 ......................152 目 录 XI 4.6.2 连接以空字符结尾的 字符串.................................. 152 4.6.3 复制以空字符结尾的 字符串.................................. 153 4.6.4 比较以空字符结尾的 字符串.................................. 154 4.6.5 搜索以空字符结尾的 字符串.................................. 154 4.7 小结 .................................................. 156 4.8 练习 .................................................. 156 4.9 本章主要内容.................................. 157 第 5 章 程序结构(1)..................................159 5.1 理解函数.......................................... 159 5.1.1 需要函数的原因.................. 160 5.1.2 函数的结构.......................... 161 5.1.3 替代的函数语法.................. 163 5.1.4 使用函数.............................. 163 5.2 给函数传递实参.............................. 166 5.2.1 按值传递机制...................... 167 5.2.2 给函数传递指针实参.......... 168 5.2.3 给函数传递数组.................. 169 5.2.4 给函数传递引用实参.......... 173 5.2.5 使用 const 修饰符 ............... 175 5.2.6 rvalue 引用形参................... 176 5.2.7 main()函数的实参............... 178 5.2.8 接受数量不定的函数实参 ... 179 5.3 从函数返回值.................................. 181 5.3.1 返回指针.............................. 181 5.3.2 返回引用.............................. 184 5.3.3 函数中的静态变量.............. 186 5.4 递归函数调用.................................. 188 5.5 小结 .................................................. 191 5.6 练习 .................................................. 191 5.7 本章主要内容.................................. 192 第 6 章 程序结构(2)..................................193 6.1 函数指针.......................................... 193 6.1.1 声明函数指针...................... 194 6.1.2 函数指针作为实参.............. 196 6.1.3 函数指针的数组 ..................198 6.2 初始化函数形参 ..............................198 6.3 异常...................................................200 6.3.1 抛出异常...............................202 6.3.2 捕获异常...............................202 6.3.3 重新抛出异常.......................204 6.3.4 MFC 中的异常处理.............204 6.4 处理内存分配错误 ..........................205 6.5 函数重载...........................................206 6.5.1 函数重载的概念 ..................207 6.5.2 引用类型和重载选择 ..........209 6.5.3 何时重载函数.......................210 6.6 函数模板...........................................210 6.7 使用 decltype 操作符.......................212 6.8 使用函数的示例 ..............................215 6.8.1 实现计算器...........................215 6.8.2 从字符串中删除空格 ..........217 6.8.3 计算表达式的值 ..................218 6.8.4 获得项值...............................220 6.8.5 分析数...................................221 6.8.6 整合程序...............................224 6.8.7 扩展程序...............................225 6.8.8 提取子字符串.......................227 6.8.9 运行修改过的程序 ..............229 6.9 小结...................................................229 6.10 练习.................................................229 6.11 本章主要内容.................................230 第 7 章 自定义数据类型 ...........................233 7.1 C++中的结构 ...................................233 7.1.1 结构的概念...........................234 7.1.2 定义结构...............................234 7.1.3 初始化结构...........................234 7.1.4 访问结构的成员 ..................235 7.1.5 伴随结构的智能感知帮助....238 7.1.6 RECT 结构 ...........................239 7.1.7 使用指针处理结构 ..............240 7.2 数据类型、对象、类和实例..........241 7.2.1 类的起源...............................243 7.2.2 类的操作...............................243 Visual C++ 2013 入门经典(第 7 版) XII 7.2.3 术语 ...................................... 244 7.3 理解类.............................................. 244 7.3.1 定义类 .................................. 244 7.3.2 声明类的对象...................... 245 7.3.3 访问类的数据成员.............. 245 7.3.4 对象成员的初始化................ 247 7.3.5 初始化类成员...................... 248 7.3.6 类的成员函数...................... 248 7.3.7 在类的外部定义成员函数 ... 250 7.3.8 内联函数.............................. 251 7.4 类构造函数...................................... 252 7.4.1 构造函数的概念.................. 252 7.4.2 默认的构造函数.................. 254 7.4.3 默认的形参值...................... 256 7.4.4 在构造函数中使用初始化 列表...................................... 258 7.4.5 声明显式的构造函数.......... 259 7.4.6 委托构造函数...................... 260 7.5 类的私有成员.................................. 260 7.5.1 访问私有类成员.................. 263 7.5.2 类的友元函数...................... 263 7.5.3 默认复制构造函数.............. 266 7.6 this 指针 ........................................... 267 7.7 类的 const 对象 ............................... 269 7.7.1 类的 const 成员函数 ........... 270 7.7.2 类外部的成员函数定义...... 271 7.8 类对象的数组.................................. 271 7.9 类的静态成员.................................. 273 7.9.1 类的静态数据成员.............. 273 7.9.2 类的静态函数成员.............. 276 7.10 类对象的指针和引用.................... 277 7.10.1 类对象的指针.................... 277 7.10.2 类对象的引用.................... 279 7.11 小结 ................................................ 280 7.12 练习 ................................................ 280 7.13 本章主要内容................................ 281 第 8 章 深入理解类...................................283 8.1 类析构函数...................................... 283 8.1.1 析构函数的概念 ..................284 8.1.2 默认的析构函数 ..................284 8.1.3 析构函数与动态内存分配....286 8.2 实现复制构造函数 ..........................289 8.3 运算符重载.......................................291 8.3.1 实现重载的运算符 ..............291 8.3.2 实现对比较运算符的 完全支持...............................294 8.3.3 重载赋值运算符 ..................298 8.3.4 重载加法运算符 ..................303 8.3.5 重载递增和递减运算符 ......307 8.3.6 重载函数调用操作符 ..........308 8.4 对象复制问题...................................309 8.4.1 避免不必要的复制操作 ......309 8.4.2 应用 rvalue 引用形参 ..........312 8.4.3 命名的对象是 lvalue............314 8.5 默认的类成员 ..................................319 8.6 类模板...............................................320 8.6.1 定义类模板...........................320 8.6.2 根据类模板创建对象 ..........323 8.6.3 有多个形参的类模板 ..........326 8.6.4 函数对象模板.......................328 8.7 完美转发...........................................329 8.8 模板形参的默认实参 ......................332 8.8.1 函数模板的默认实参 ..........332 8.8.2 类模板的默认实参 ..............333 8.9 类模板的别名...................................337 8.10 模板特例.........................................337 8.11 使用类.............................................341 8.11.1 类接口的概念...................341 8.11.2 定义问题...........................341 8.11.3 实现 CBox 类...................341 8.12 组织程序代码 ................................358 8.13 字符串的库类 ................................359 8.13.1 创建字符串对象 ..............359 8.13.2 连接字符串.......................361 8.13.3 访问与修改字符串 ..........364 8.13.4 比较字符串.......................367 8.13.5 搜索字符串.......................370 目 录 XIII 8.14 小结 ................................................ 378 8.15 练习 ................................................ 378 8.16 本章主要内容................................ 379 第 9 章 类继承和虚函数 ...........................381 9.1 面向对象编程的基本思想 ............. 381 9.2 类的继承.......................................... 382 9.2.1 基类的概念.......................... 383 9.2.2 基类的派生类...................... 383 9.3 继承机制下的访问控制.................. 386 9.3.1 派生类中构造函数的操作 ... 389 9.3.2 声明类的保护成员.............. 392 9.3.3 继承类成员的访问级别...... 395 9.4 派生类中的复制构造函数 ............. 396 9.5 禁止派生类...................................... 399 9.6 友元类成员...................................... 399 9.6.1 友元类 .................................. 401 9.6.2 对类友元关系的限制.......... 401 9.7 虚函数.............................................. 401 9.7.1 虚函数的概念...................... 403 9.7.2 确保虚函数的正确执行...... 405 9.7.3 禁止重写函数...................... 406 9.7.4 使用指向类对象的指针...... 406 9.7.5 使用引用处理虚函数.......... 408 9.7.6 纯虚函数.............................. 408 9.7.7 抽象类 .................................. 409 9.7.8 间接基类.............................. 411 9.7.9 虚析构函数.......................... 413 9.8 类类型之间的强制转换.................. 416 9.8.1 定义转换运算符.................. 417 9.8.2 显式类型转换运算符.......... 417 9.9 嵌套类.............................................. 417 9.10 小结 ................................................ 421 9.11 练习 ................................................ 421 9.12 本章主要内容................................ 423 第 10 章 标准模板库.................................425 10.1 标准模板库的定义........................ 425 10.1.1 容器.................................. 426 10.1.2 容器适配器...................... 428 10.1.3 迭代器.............................428 10.2 智能指针.........................................430 10.3 算法.................................................433 10.4 STL 中的函数对象........................433 10.5 STL 容器范围 ................................434 10.6 序列容器.........................................434 10.6.1 创建矢量容器 ..................435 10.6.2 矢量容器的容量和大小...438 10.6.3 访问矢量中的元素 ..........442 10.6.4 在矢量中插入和 删除元素...........................443 10.6.5 在矢量中存储类对象 ......446 10.6.6 矢量元素的排序 ..............451 10.6.7 存储矢量中的指针 ..........452 10.6.8 双端队列容器 ..................457 10.6.9 使用列表容器 ..................460 10.6.10 使用 forward_list 容器....469 10.6.11 使用其他序列容器 ........471 10.6.12 tuple< >类模板...............480 10.7 关联容器.........................................483 10.7.1 使用映射容器 ..................483 10.7.2 使用多重映射容器 ..........494 10.8 关于迭代器的更多内容................495 10.8.1 使用输入流迭代器 ..........495 10.8.2 使用插入迭代器 ..............498 10.8.3 使用输出流迭代器 ..........500 10.9 关于函数对象的更多内容............502 10.10 关于算法的更多内容 ..................503 10.11 类型特质和静态断言 ..................505 10.12 λ表达式.......................................506 10.12.1 capture 子句..................507 10.12.2 捕获特定的变量 ..........508 10.12.3 模板和λ表达式 ..........508 10.12.4 命名λ表达式 ..............512 10.13 小结...............................................514 10.14 练习...............................................515 10.15 本章主要内容 ..............................515 第 11 章 Windows 编程的概念.................517 11.1 Windows 编程基础 ........................517 Visual C++ 2013 入门经典(第 7 版) XIV 11.1.1 窗口的元素...................... 518 11.1.2 Windows 程序与 操作系统 .......................... 519 11.1.3 事件驱动型程序.............. 519 11.1.4 Windows 消息.................. 520 11.1.5 Windows API.................... 520 11.1.6 Windows 数据类型.......... 521 11.1.7 Windows 程序中的符号... 521 11.2 Windows 程序的结构.................... 522 11.2.1 WinMain()函数................ 523 11.2.2 处理 Windows 消息......... 533 11.3 MFC................................................ 538 11.3.1 MFC 表示法 .................... 539 11.3.2 MFC 程序的组织方式.... 539 11.4 小结 ................................................ 543 11.5 本章主要内容................................ 543 第 12 章 使用 MFC 编写 Windows 程序....545 12.1 MFC 的文档/视图概念................. 545 12.1.1 文档的概念...................... 545 12.1.2 文档界面.......................... 546 12.1.3 视图的概念...................... 546 12.1.4 链接文档和视图.............. 547 12.1.5 应用程序和 MFC............ 548 12.2 创建 MFC 应用程序..................... 549 12.2.1 创建 SDI 应用程序 ......... 550 12.2.2 MFC Application Wizard 的输出.............................. 554 12.2.3 创建 MDI 应用程序........ 563 12.3 小结 ................................................ 565 12.4 练习 ................................................ 565 12.5 本章主要内容................................ 565 第 13 章 处理菜单和工具栏 .....................567 13.1 与 Windows 通信........................... 567 13.1.1 了解消息映射.................. 568 13.1.2 消息类别.......................... 570 13.1.3 处理程序中的消息.......... 570 13.2 扩展 Sketcher 程序........................ 571 13.3 菜单的元素.................................... 572 13.4 为菜单消息添加处理程序............575 13.4.1 选择处理菜单消息的类....576 13.4.2 创建菜单消息函数 ..........576 13.4.3 编写菜单消息函数的 代码...................................578 13.4.4 添加更新菜单消息的 处理程序...........................581 13.5 添加工具栏按钮 ............................584 13.5.1 编辑工具栏按钮的属性....585 13.5.2 练习使用工具栏按钮 ......586 13.5.3 添加工具提示 ..................586 13.6 小结.................................................587 13.7 练习.................................................587 13.8 本章主要内容 ................................587 第 14 章 在窗口中绘图 .............................589 14.1 窗口绘图的基础知识 ....................589 14.1.1 窗口客户区.......................589 14.1.2 Windows 图形设备界面....590 14.2 MFC 的绘图机制...........................592 14.2.1 应用程序中的视图类 ......592 14.2.2 CDC 类 .............................593 14.3 实际绘制图形 ................................601 14.4 对鼠标进行编程 ............................603 14.4.1 鼠标发出的消息 ..............603 14.4.2 鼠标消息处理程序 ..........604 14.4.3 使用鼠标绘图 ..................606 14.5 绘制草图.........................................627 14.5.1 运行示例...........................628 14.5.2 捕获鼠标消息 ..................629 14.6 小结.................................................630 14.7 练习题.............................................630 14.8 本章主要内容 ................................631 第 15 章 改进视图.....................................633 15.1 Sketcher 应用程序的缺陷 .............633 15.2 改进视图.........................................634 15.2.1 更新多个视图 ..................634 15.2.2 滚动视图...........................635 目 录 XV 15.2.3 使用 MM_LOENGLISH 映射模式.......................... 640 15.3 删除和移动元素............................ 640 15.4 实现上下文菜单............................ 641 15.4.1 关联菜单和类.................. 642 15.4.2 选中上下文菜单项.......... 643 15.5 标识位于光标下的元素................ 644 15.5.1 练习弹出菜单.................. 645 15.5.2 突出显示元素.................. 645 15.5.3 实现移动和删除功能...... 649 15.6 处理屏蔽的元素............................ 655 15.7 小结 ................................................ 657 15.8 练习 ................................................ 657 15.9 本章主要内容................................ 657 第 16 章 使用对话框和控件 .....................659 16.1 理解对话框.................................... 659 16.2 理解控件........................................ 660 16.3 创建对话框资源............................ 660 16.3.1 给对话框添加控件.......... 661 16.3.2 测试对话框...................... 662 16.4 对话框的编程................................ 662 16.4.1 添加对话框类.................. 662 16.4.2 模态和非模态对话框...... 664 16.4.3 显示对话框...................... 664 16.5 支持对话框控件............................ 666 16.5.1 初始化对话框控件.......... 667 16.5.2 处理单选按钮消息.......... 668 16.6 完成对话框的操作........................ 668 16.6.1 给文档添加线宽.............. 669 16.6.2 给元素添加线宽.............. 669 16.6.3 在视图中创建元素.......... 671 16.6.4 练习使用对话框.............. 672 16.7 使用微调按钮控件........................ 673 16.7.1 添加 Scale 菜单项和 工具栏按钮...................... 673 16.7.2 创建微调按钮.................. 673 16.7.3 生成比例对话框类.......... 674 16.7.4 显示微调按钮.................. 677 16.8 使用缩放比例 ................................678 16.8.1 可缩放的映射模式 ..........678 16.8.2 设置文档的大小 ..............679 16.8.3 设置映射模式 ..................680 16.8.4 同时实现滚动与缩放 ......681 16.9 使用状态栏.....................................683 16.9.1 给框架窗口添加状态栏....683 16.9.2 CString 类.........................687 16.10 使用编辑框控件 ..........................688 16.10.1 创建编辑框资源 ..........688 16.10.2 创建对话框类 ..............689 16.10.3 添加 Text 菜单项 .........690 16.10.4 定义文本元素 ..............691 16.10.5 实现 CText 类...............691 16.11 小结...............................................696 16.12 练习...............................................696 16.13 本章主要内容 ..............................696 第 17 章 存储和打印文档 .........................697 17.1 了解序列化.....................................697 17.2 序列化文档.....................................698 17.2.1 文档类定义中的序列化....698 17.2.2 文档类实现中的序列化....699 17.2.3 基于 CObject 的类的 功能...................................701 17.2.4 序列化的工作方式 ..........702 17.2.5 如何实现类的序列化 ......703 17.3 应用序列化.....................................704 17.3.1 记录文档修改 ..................704 17.3.2 序列化文档.......................706 17.3.3 序列化元素类 ..................707 17.4 练习序列化.....................................711 17.5 打印文档.........................................713 17.6 实现多页打印 ................................716 17.6.1 获取文档的总尺寸 ..........716 17.6.2 存储打印数据 ..................717 17.6.3 准备打印...........................718 17.6.4 打印后的清除 ..................719 17.6.5 准备设备上下文 ..............719 Visual C++ 2013 入门经典(第 7 版) XVI 17.6.6 打印文档.......................... 720 17.6.7 获得文档的打印输出...... 724 17.7 小结 ................................................ 724 17.8 练习 ................................................ 724 17.9 本章主要内容................................ 725 第 18 章 编写 Windows 8 应用程序.........727 18.1 Windows Store 应用程序.............. 727 18.2 开发 Windows Store 应用程序..... 728 18.3 Windows Runtime 的概念 ............ 729 18.3.1 WinRT 名称空间............. 729 18.3.2 WinRT 对象..................... 730 18.4 C++/CX.......................................... 730 18.4.1 C++/CX 名称空间........... 730 18.4.2 定义 WinRT 类类型........ 731 18.4.3 ref 类类型的变量 ............ 733 18.4.4 访问 ref 类对象的成员.... 734 18.4.5 事件处理程序.................. 734 18.4.6 转换 ref 类引用的类型.... 735 18.5 XAML............................................ 735 18.5.1 XAML 元素..................... 735 18.5.2 XAML 中的 UI 元素 ...... 737 18.5.3 附加属性.......................... 739 18.5.4 父元素和子元素.............. 740 18.5.5 控件元素.......................... 740 18.5.6 布局元素.......................... 740 18.5.7 处理 UI 元素的事件........741 18.6 创建 Windows Store 应用程序 .....742 18.6.1 应用程序文件 ..................742 18.6.2 定义用户界面 ..................742 18.6.3 创建标题...........................745 18.6.4 添加游戏控件 ..................746 18.6.5 创建包含纸牌的网格 ......748 18.6.6 实现游戏的操作 ..............752 18.6.7 初始化 MainPage 对象....755 18.6.8 初始化一副纸牌 ..............756 18.6.9 建立 cardGrid 的子元素....757 18.6.10 初始化游戏.....................758 18.6.11 洗牌.................................760 18.6.12 突出显示 UI 纸牌..........761 18.6.13 处理翻牌事件 ................762 18.6.14 处理图形事件 ................764 18.6.15 确认赢家.........................765 18.6.16 处理游戏控件的 按钮事件.........................766 18.7 缩放 UI 元素 ..................................768 18.8 平移.................................................770 18.8.1 应用程序的启动动画 ......770 18.8.2 故事板动画.......................771 18.9 小结.................................................773 18.10 本章主要内容 ..............................773 使用 Visual C++编程 本章要点 ● Visual C++的主要组件 ● 解决方案和项目的概念及创建过程 ● 控制台程序 ● 如何创建并编辑程序 ● 如何编译、链接并执行 C++控制台程序 ● 如何创建并执行基本的 Windows 程序 本章源代码下载地址(wrox.com): 打开网页 http://www.wrox.com/go/beginningvisualc,单击 Download Code 选项卡即可下载本章源 代码。这些代码在 Chapter 1 文件夹中,文件都根据本章的内容单独进行了命名。 1.1 使用 Visual C++学习 Windows 编程并不难。事实上,Microsoft Visual C++使之变得相当容易,读者在本书所有章节 中都将领会到这一点。学习过程中的唯一障碍是:在接触 Windows 编程细节之前,必须十分熟悉 C++编程语言的功能,特别是该语言在面向对象方面的功能。面向对象的技术决定了 Visual C++为 Windows 编程提供的所有工具的有效性,因此很好地理解这些技术是必需的,而这正是本书所要详 述的内容。 本章概述了用 C++编程涉及的一些基本概念,同时带领读者快速浏览一下随同 Visual C++一起 提供的集成开发环境(Integrated Development Environment,IDE)。IDE 在操作方面十分简单,通常也 较直观,因此读者在本章将能够完全掌握该环境的用法。熟悉 IDE 的最好方法是完成创建、编译并 执行某个简单程序的整个过程。现在让我们打开计算机,启动 Windows,运行强大的 Visual C++, 然后开始我们的旅程。 1 第 章 Visual C++ 2013 入门经典(第 7 版) 2 1.2 编写 C++应用程序 就使用 Visual C++可以开发的应用程序和程序组件的类型而言,我们拥有非常大的灵活性。可 以开发的应用程序有两大类:桌面应用程序和 Windows Store 应用程序。我们熟悉并喜欢桌面应用 程序,它们有应用程序窗口,这些窗口上一般有一个菜单栏、一个工具栏,在应用程序窗口的底部 常常有一个状态栏。本书主要介绍桌面应用程序。 Windows Store 应用程序不同于桌面应用程序,它们的用户界面完全不同于桌面应用程序。其重 点是用户与数据直接交互操作的内容,而不是与菜单项和工具栏按钮等控件的交互操作。 一旦学习了 C++,本书就重点关注如何使用 Microsoft Foundation Classes(MFC)和 C++建立桌面 应用程序。Windows 桌面应用程序的应用程序编程接口称为 Win32。Win32 的历史很长,在面向对 象编程方法出现之前很早就有了,因此没有任何面向对象的特征。如果在今天编写 Win32 API,就 肯定有这个特质。MFC 包含一组 C++类,它们封装了创建和控制用户界面的 Win32 API,因此大大 简化了程序的开发过程。不管怎样,没有人强迫我们使用 MFC。如果需要的是最佳性能,那么可以 编写能直接访问 Windows API 的 C++代码。但显然这不大容易。 图 1-1 给出了开发 C++应用程序时的基本选择。 图 1-1 是不全面的。桌面应用程序可以面向 Windows 7、Windows 8 或 Windows Vista。Windows Store 应用程序只能在 Windows 8 上执行,且必须在 Windows 8 或更新版本上安装 Visual Studio 2013, 才能开发它们。Windows Store 应用程序通过 Windows 运行库 WinRT 与操作系统通信。第 18 章将 介绍 Windows 8 应用程序的编写。 桌面应用程序 Windows Store 应用程序 本地C++ MFC Windows运行库(WinRT)WindowsAPI(Win32) Windows 7/8 Windows 8 硬件 本地 C++ 本地 C++ 图 1-1 1.3 学习桌面应用程序的编程 对于在 Windows 下执行的交互式桌面应用程序来说,总是有两个基本方面要考虑:需要创建图 形用户界面(Graphical User Interface,GUI)的代码,用户将与 GUI 进行交互;还需要处理这些交互 第 1 章 使用 Visual C++编程 3 的代码,以提供应用程序的功能。Visual C++在这两个方面都提供了大量帮助。如本章后面所述, 可以在根本不编写任何代码的情况下,创建一个能够工作的 GUI Windows 程序。创建 GUI 的全部 基本代码都可以由 Visual C++自动生成,但是必须理解这种自动生成的代码的工作过程,因为我们 需要扩展并修改这种代码,从而使其完成我们希望它完成的事情。而要达到上述目的,就需要全面 理解 C++。 因此,我们将首先学习 C++,但不考虑任何 Windows 编程事项。在熟悉 C++之后,将学习如何 开发成熟的 Windows 应用程序。这意味着在学习 C++时,将使用仅涉及命令行输入和输出的程序。 通过使用这种相当有限的输入和输出功能,我们将能够集中于 C++语言工作过程的细节,从而避免 在 GUI 构建和控制方面不可避免的复杂性。在熟悉 C++之后,我们将发现在 Windows 应用程序的 开发中应用 C++是一件容易、自然的事情。 1.3.1 学习 C++ Visual C++支持 2011 年发布的最新 ISO/IEC C++标准所定义的 C++语言。该标准在 ISO/IEC 14882:2011 文档中定义,通常称为 C++ 11。Visual C++编译器支持这个最新标准引入的所有新语言 特性,且包含下一个标准草案 C++ 14 中的某些功能。虽然程序使用的库函数(特别是与构建图形用 户界面有关的函数)是迁移难易程度的主要决定因素,但是用标准 C++编写的程序可以相当容易地从 一种系统环境迁移到另一种系统环境。ISO/IEC 标准的 C++一直是许多专业程序开发人员的首选, 因为该版本得到非常广泛的支持,而且是今天功能最强大的编程语言之一。 本书第 2~第 9 章将讲述 C++语言,介绍一些最常用的 C++标准库功能。第 10 章解释如何使用 C++的标准模板库(STL)来管理数据集合。 1.3.2 C++概念 与几乎所有编程语言一样,解释 C++也有一个“鸡生蛋、蛋生鸡”的问题。肯定会有这种情形: 在详细讨论某种语言特性之前,就需要引用或利用它。本节概述 C++的主要语言元素,以帮助解决 这个难题。当然,这里提及的所有内容都将在本书后面详细解释。 1. 函数 每个 C++程序通常包含许多函数(至少包含一个函数)。函数是一个命名的可执行代码块,使 用其名称来调用它。C++程序必须总是有一个 main 函数,且系统总是从 main()函数开始执行。函数 名后面的括号可以指定调用函数时传递给它的信息。本书总是在函数名的后面加上括号,以便与其 他语言元素区分开。程序里所有的可执行代码都包含在函数中。最简单的 C++程序只包含 main() 函数。 2. 数据和变量 数据存储在变量中。变量是一个命名的内存区域,可以存储特定类型的数据项。有几种标准的 如第 18 章所述,Windows Store 应用程序是不同的。它要在 XAML 中指定 GUI, XAML 用于给 GUI 元素生成 C++程序代码。 Visual C++ 2013 入门经典(第 7 版) 4 基本数据类型来存储整数、小数和字符数据。还可以定义自己的数据类型,这更便于编写出处理实 际对象的程序。对象存储在自定义类型的变量中,因为每个变量只能存储给定类型的数据,所以 C++ 是一种类型安全的语言。 3. 类和对象 类是定义数据类型的代码块。类的名称就是数据类型的名称。类类型的数据项称为对象。创建 可以存储自定义数据类型的变量时,就使用类类型的名称。 4. 模板 程序常常需要几个不同的类或函数,它们之间的区别仅在于所处理的数据类型。对于这种情形, 使用模板可以大大减少编码量。 模板是用户创建的一个处方或规范,编译器使用模板可以在需要时自动在程序中生成代码。可 以定义类模板,编译器使用它可以生成一系列类。还可以定义函数模板,编译器使用它可以生成函 数。每个模板都有一个名称,需要编译器创建模板的实例时,就可以使用该名称。 编译器从模板中生成的类或函数代码取决于一个或多个模板变元。这些变元通常是类型,但并 不总是类型。在使用类模板时,一般应显式指定模板变元。编译器通常可以通过上下文推断出函数 模板的变元。 5. 程序文件 C++程序代码存储在两种文件中。源文件包含可执行代码,扩展名是.cpp。头文件包含可执行代 码使用的元素的定义,例如类和模板的定义。头文件的扩展名是.h。 1.3.3 控制台应用程序 Visual C++控制台应用程序允许编写、编译并测试没有任何 Windows 桌面应用程序所需元素的 C++程序。这些程序称为控制台应用程序,因为用户是在字符模式中通过键盘和屏幕与它们通信的, 所以它们实质上就是基于字符的命令行程序。 第 2~10 章仅使用控制台应用程序。编写控制台应用程序似乎偏离了 Windows 编程的主要目标, 但就学习 C++而言,它是最好的方法。即使简单的 Windows 程序中也有大量代码,而学习 C++细节 时不被 Windows 的复杂性分散注意力非常重要。因此,在本书前面与 C++工作过程相关的几章,我 们将花费些时间来讨论一些轻量级控制台应用程序,然后接触 Windows 中重量级的代码段。 1.3.4 Windows 编程概念 Visual C++提供的项目创建工具可以自动生成各种 C++应用程序的框架代码。与典型的控制台 程序相比,Windows 程序具有完全不同的结构,而且更复杂。在控制台程序中,可以得到来自键盘 的输入,并将输出直接写回命令行。但 Windows 程序只能利用主机环境提供的函数来访问计算机的 输入和输出设备,直接访问硬件资源是不允许的。因为在 Windows 下可能同时活动着多个程序,所 以 Windows 必须确定给出的原始输入(如单击鼠标或按下键盘上的某个按键)是针对哪个应用程序 的,然后相应地通知有关程序。因此,Windows 操作系统总是控制着与用户的所有通信。 另外,用户和 Windows 桌面应用程序之间的界面的本质是:任何给定时刻通常都可能有各种不 第 1 章 使用 Visual C++编程 5 同的输入。用户可能选择许多菜单选项中的任意一个,可能单击某个工具栏按钮,或者在应用程序 窗口中的某个位置单击鼠标。因为无法预知将要发生的是什么类型的输入,所以精心设计的 Windows 应用程序必须准备好在任何时刻处理任何可能类型的输入。首先操作系统收到这些用户动作,并且 它们会被 Windows 认为是事件。应用程序用户界面中发生的事件通常将导致执行一段特定程序代 码。因此,程序的执行过程是由用户的动作序列决定的。以这种方式工作的程序称为事件驱动程序, 它们与只有单一执行顺序的传统过程化程序不同。过程化程序的输入是由程序代码控制的,而且只 能发生在程序允许它发生的时候。因此,Windows 程序主要是由响应事件的代码段组成的,而这些 事件是由用户动作或 Windows 本身引起的。图 1-2 说明了这类程序的结构。 在图 1-2 中,桌面应用程序块中的每个方块代表一段为处理特定事件而专门编写的代码。由于 有很多相互分离的代码块,因此这种程序可能看起来有点儿零碎,但是将程序组合成一个整体的首 要因素是 Windows 操作系统本身。我们可以将程序看作定制 Windows 以提供一组特定的功能。 事件 键盘输入 单击 右击 其他事件 Windows7/8 处理键盘 输入 处理 单击 处理 右击 处理 其他事件 程序数据 桌面应用程序 图 1-2 当然,为各种外部事件(如选择菜单或单击鼠标)提供服务的模块,通常都可以访问特定程序中 应用程序专用的一组公用数据。这种应用程序数据包含与程序正在执行的操作有关的信息—— 例如, 跟踪棒球队表现的程序中记录球员得分的文本块,还包含与程序执行过程中发生的某些事件有关的 信息。这种共享的数据集合使看起来独立的程序的不同部分能够相互通信,并以协作和综合的方式 进行工作。本书后面的章节将更加详细地探讨这一点。 即使是基本的 Windows 程序也包括若干行代码。对于用随 Visual C++一起提供的 Application Visual C++ 2013 入门经典(第 7 版) 6 Wizard 生成的 Windows 程序来说,“若干行”就变成了“许多行”。为了简化理解 C++工作原理的 过程,需要某种尽可能简单的环境,同时,有相应的工具来简化代码段的创建和导航。幸运的是, Visual C++就提供了一种专用于该目的的环境。 1.4 集成开发环境简介 随 Visual C++一起提供的 IDE 是一个用于创建、编译、链接、测试和调试各种 C++程序的完全 独立的环境,它还是一个很好的学习 C++的环境(尤其是当与一本很好的教材结合起来时)。 IDE 包括许多完全集成的工具,设计这些工具的目的是使编写 C++程序的整个过程更轻松。本 章就会介绍这些工具中的一部分,但与其被抽象、枯燥的功能和选项的叙述所折磨,还不如首先看 一下基本功能,以了解 IDE 是怎样工作的,然后继续学习时在具体环境中再了解其他功能。 作为 IDE 组成部分提供的 IDE 基本部件有编辑器、C++编译器、链接器和库。这些部件是编写 和执行 C++程序所必需的基本工具。 1.4.1 编辑器 编辑器提供了创建和编辑 C++源代码的交互式环境。除了那些肯定已经为人所熟知的常见功能 (如剪切和粘贴)之外,编辑器还提供了许多其他功能,例如: ● 代码会自动使用标准的缩进量和空格来布置。这是代码的默认布局,也可以从菜单中选择 Tools | Options,在打开的对话框中自定义代码的组织方式。 ● 编辑器能够自动识别 C++语言中的基本单词,并根据其类别给它们分配一种颜色。这不仅 有助于使代码的可读性更好,而且在输入这些单词出错时可以提供清楚的指示。 ● IntelliSense 会在用户输入代码时进行分析,用红色波浪线标记出不正确的地方或 IntelliSense 不能识别的单词,还可以提供在代码中需要输入什么选项的提示。这可以减少输入量,因 为只需要从列表中选择。 1.4.2 编译器 给程序输入了 C++代码后,就执行编译器。编译器将源代码转换为目标代码,并检测和报告编 译过程中的错误。编译器可以检测各种因无效或不可识别的程序代码引起的错误,还可以检测结构 性错误(如部分程序永远不能执行)。编译器输出的目标代码存储在扩展名是.obj 的目标文件中。 1.4.3 链接器 链接器组合编译器根据源代码文件生成的各种模块,从作为 C++组成部分提供的标准库中添加 所需的代码模块,并将所有模块整合成可执行的整体,通常放在.exe 文件中。链接器也能检测并报 告错误(如程序缺少某个组成部分),或者引用了不存在的库组件。 IntelliSense 不仅可用于 C++,还可用于 XAML。 第 1 章 使用 Visual C++编程 7 1.4.4 库 库是预先编写的例程集合,它通过提供专业制作的标准代码单元,支持并扩展了 C++语言。我 们可以将这些代码合并到自己的程序中,以执行常见的操作。库实现了一些操作,由于节省了用户 亲自编写并测试这些操作的代码所需的工作,从而大大提高了生产率。 1.4.5 标准 C++库 标准 C++库定义了一组为所有 ISO/IEC 标准 C++编译器所共用的基本例程,其中包括各种各样 的常用例程,如计算平方根及计算三角函数这样的数值函数,分类字符以及比较字符串这样的字符 处理例程和字符串处理例程等。它还定义了数据类型和标准模板,以生成定制的数据类型和函数。 随着 C++知识的增加,我们将知道相当多的基本例程。 1.4.6 Microsoft 库 Windows 桌面应用程序是由称作 MFC 的库支持的。MFC 大大减少了为应用程序建立图形用户 界面所需的工作。研究完 C++语言的细节后,我们将了解更多有关 MFC 的内容。桌面应用程序还 有其他 Microsoft 库,但本书不讨论它们。 1.5 使用 IDE 在本书中,所有程序的开发和执行都是在 IDE 内完成的。当启动 Visual C++后,会出现一个如 图 1-3 所示的应用程序窗口。 图 1-3 Visual C++ 2013 入门经典(第 7 版) 8 图 1-3 中左边的窗格是 Solution Explorer 窗口,目前显示 Start 页面的中间窗格是 Editor 窗口, 窗格底部可见的选项卡是 Output 窗口。右边的 Properties 面板显示程序中各种实体的属性。Solution Explorer 窗口允许浏览程序文件,将程序文件的内容显示在 Editor 窗口中,并向程序中添加新文件。 Solution Explorer 窗口可以显示其他选项卡(图 1-3 只显示了 3 个选项卡),可以在 View 菜单上选择要 显示的其他窗口。拖动标记就可以重新安排窗口。Editor 窗口是输入和修改应用程序的源代码及其 他组件的地方。Output 窗口显示编译并链接项目时所产生的输出。我们可以从 View 菜单选择显示 其他窗口。 注意,在 Visual Studio 应用程序窗口中,一般可以取 消窗口停靠。这只需要右击想要取消停靠的窗口的标题 栏,并从弹出菜单中选择 Float 项即可。本书显示的窗口 一般都处于取消停靠的状态。要将窗口还原到停靠状态, 只需要右击它的标题栏,并从弹出菜单中选择 Dock 项即 可,或者用鼠标左键把它拖放到应用程序窗口中需要的位 置上。 1.5.1 工具栏选项 在工具栏区域内右击,可以选择显示哪些工具栏。列 表中工具栏的范围取决于所安装的 Visual Studio 2013 的版 本。出现的弹出式菜单包括工具栏列表(见图 1-4),当前显 示在窗口内的工具栏都带有复选标记(在旁边)。 可以在工具栏列表中决定在任何时候都让哪些工具栏 可见。确保选中 Build、Debug、Formatting、Layout、Standard 和 Text Editor 菜单项,就可以使自己的工具栏组与图 1-4 所显示的相同。如果某个工具栏未被选中,那么单击其左 边的灰色区域即可选中该工具栏,使其显示出来;单击某 个被选中的工具栏的复选标记,就会取消选中此工具栏, 并隐藏对应的工具栏。 不必将所有可能在某个时刻需要的工具栏都堆放在应用程序窗口中。有些工具栏在需要时将自 动出现,因此默认的工具栏选择在大多数情况下完全可以满足要求。开发应用程序时,如果能够使 用某些未显示出来的工具栏将更加方便。只要认为合适,就可以改变可见的工具栏组,其方法是在 工具栏区域内右击,然后从上下文菜单中选择。 图 1-4 工具栏不一定会显示所有可用的按钮。单击按钮组右边的向下箭头,就可以给工 具栏添加或删除按钮。 Text Editor 工具栏中的几个按钮会使一组突出显示的语句缩进 显示或取消缩进显示,另外几个按钮可以给一组突出显示的语句添加或删除注释符 号,这些按钮都非常有用。 第 1 章 使用 Visual C++编程 9 1.5.2 可停靠的工具栏 可停靠的工具栏就是可以用鼠标到处拖动,以便放在窗口中某个方便位置的工具栏。任何工具 栏都可以停靠在应用程序窗口 4 个边框的任意一个边框上。如果右击工具栏区域,并从弹出菜单中 选择 Customize 项,则会显示 Customize 对话框。我们可以选择某个特定工具栏的停靠位置,方法 是选中它,并单击 Modify Selection 按钮。然后,从下拉列表中选择想要将工具栏停靠在哪里。 许多工具栏图标也存在于其他 Windows 应用程序中,但读者可能还没有准确地理解这些图标在 Visual C++环境中的作用,因此笔者将在使用它们的时候详细描述。 因为我们将为每个要开发的程序使用新项目,所以介绍一下项目究竟是什么,并理解定义项目 的机制是怎样工作的,这将成为我们掌握 Visual C++的一个很好的起点。 1.5.3 文档 如果想知道关于 Visual C++及其功能和选项的更多信息,按 Ctrl +F1 组合键可以在浏览器中显 示在线产品文档。在代码中把光标放在 C++语言或标准库的元素上,按下 F1 通常会打开浏览器窗 口,显示该元素的文档。Help 菜单也提供了进入文档的不同路径,还提供了访问程序示例和技术支 持的路径。 1.5.4 项目和解决方案 项目是构成某个程序的全部组件的容器,该程序可能是控制台程序、基于窗口的程序或某种其 他程序。项目通常由几个包含用户代码的源文件组成,可能还要加上其他包含辅助数据的文件。某 个项目的所有文件都存储在相应的项目文件夹中,关于该项目的详细信息存储在一个扩展名 为.vcxproj 的 XML 文件中,该文件同样存储在相应的项目文件夹中。项目文件夹还包括其他文件夹, 它们用来存储编译及链接项目时所产生的输出。 解决方案是一种将一个或多个程序和其他资源(它们是某个具体的数据处理问题的解决方案)聚 集到一起的机制。例如,用于企业经营的分布式订单录入系统可能由若干不同的程序组成,而各个 程序可能是作为同一个解决方案内的项目开发的,因此,解决方案就是存储与一个或多个项目有关 的所有信息的文件夹,这样就有一个或多个项目文件夹是解决方案文件夹的子文件夹。与某个解决 方案中的项目有关的信息存储在扩展名为.sln 的文件中。当创建某个项目时,如果没有选择在现有 的解决方案中添加该项目,那么系统将自动创建一个新的解决方案。.suo 文件没有那么重要,甚至 可以删除.suo 文件,Visual C++会在打开解决方案时重新创建它。 当创建项目及解决方案时,可以在同一个解决方案中添加更多的项目。我们可以在现有的解决 方案中添加任意种类的项目,但通常只添加在某个方面与该解决方案内现有项目相关的项目。一般 来说,各个项目都应该有自己的解决方案,除非我们有很好的理由不这样做。本书中创建的各个实 例都是其解决方案内的单个项目。 与许多其他 Windows 应用程序类似,工具栏也带有工具提示。 只需要将鼠标指针 停留在某个工具栏按钮上方一两秒钟,就会出现一个显示该工具栏按钮功能的标签。 Visual C++ 2013 入门经典(第 7 版) 10 1. 定义项目 编写Visual C++程序的第一步,是使用主菜单上的File | New | Project菜单项,或者按Ctrl+Shift+N 组合键,为该程序创建一个项目;也可以简单地在 Start 页面上单击 New Project 项来创建一个项目。 除包含定义所有代码及其他数据的文件之外—— 这些代码和数据将构成我们的程序,项目文件夹内 的 XML 文件还记录了为项目设置的选项。目前,介绍性的内容有这些已经足够,是时候开始动手 实践了。 试一试:为 Win32 控制台应用程序创建项目 首先,选择 File | New | Project 菜单项,或者用前面提到的其他方法之一,打开 New Project 对 话框。New Project 对话框中的左边窗格显示了可以创建的项目类型,在这个例子中,单击 Win32。 该操作同时也确定了为本项目创建初始内容的应用程序向导。右边窗格显示了可供左边窗格选定的 项目类型使用的模板列表。这里选择的模板将用于创建构成项目的文件。在下一个对话框中,可以 定制当单击该对话框中的 OK 按钮时创建的文件。就大多数类型/模板选项而言,应用程序向导都会 自动创建一组基本的程序源文件。在这个例子中选择 Win32 Console Application。 现在,在 Name:文本框中为该项目输入一个合适的名称(如 Ex1_01),或者使用其他项目名称。 Visual C++支持长文件名,因此我们拥有很大的灵活性。解决方案文件夹的名称出现在底部的文本 框中,默认情况下,该名称与项目的名称相同。如果需要,也可以修改此项。该对话框还允许修改 包含本项目的解决方案的位置,这可以在 Location:文本框中实现。如果仅仅输入项目名称,解决方 案文件夹就自动设置为与项目同名的文件夹,其存储路径显示在 Location:文本框中。默认情况下, 如果该解决方案文件夹不存在,那么将自动创建它。如果想为解决方案文件夹指定不同的路径,那 么只需要在 Location:文本框中输入新路径即可。另外,还可以使用 Browse 按钮来为解决方案选择 其他路径。单击 OK 按钮将显示 Win32 Application Wizard 对话框。 该对话框解释了当前有效的设置。在这个例子中,可以单击左边的 Application Settings 项,以 显示该向导的 Application Settings 页面(如图 1-5 所示)。 图 1-5 第 1 章 使用 Visual C++编程 11 Application Settings 页面允许选择希望在本项目中应用的选项。目前我们是在创建控制台应用程 序,而不是 Windows 应用程序。Precompiled header 选项用于编译源文件,例如标准库中不频繁修改 的源文件。对代码进行修改或添加后,重新编译程序时,预先编译的、没有修改的代码就会重用,这 样程序的编译会更快。可以取消选择 Security Development Lifecycle Checks 复选框,这个功能可用于 管理大型专业项目,这里不使用它。在对话框的右边有使用 MFC(如前所述)和ATL(Application Template Library,这超出了本书的范围)的选项。在本例中,我们可以让所有选项都保持原状,并单击 Finish 按 钮。之后,应用程序向导将创建一个包含所有默认文件的项目。 项目文件夹的名称将与前面给出的项目名称相同,该文件夹还将容纳构成该项目定义的所有文 件。如果不加修改,则解决方案文件夹具有与项目文件夹相同的名称,而且包含项目文件夹和定义 解决方案内容的文件。如果使用 Windows Explorer 来查看解决方案文件夹的内容,那么将看到该文 件夹包含如下 4 个文件: ● 扩展名为.sln 的文件,记录关于解决方案中项目的信息。 ● 扩展名为.suo 的文件,记录应用于该解决方案的用户选项。 ● 扩展名为.sdf 的文件,记录与解决方案的 IntelliSense 有关的数据。IntelliSense 是在 Editor 窗口中输入代码时提供自动完成和提示功能的 工具。 ● 扩展名为.opensdf 的文件,记录关于项目状态的 信息。此文件只在项目处于打开状态时才有。 如果使用 Windows Explorer 查看 Ex1_01 项目文件夹, 那么将看到最初有 7 个文件,其中名称为 ReadMe.txt 的文 件包含已经为该项目创建的所有文件的内容摘要。如图 1-6 所示,创建的项目将自动在 Solution Explorer 窗格中打开。 Solution Explorer 窗格给出了当前解决方案内所有项 目及其包含文件的视图—— 这里当然只有一个项目。只需 要双击 Solution Explorer 选项卡中某个文件的名称,就可以在 Editor 窗格中显示该文件的内容(该文 件成为附加的选项卡)。在 Editor 窗格中,只需要单击适当的选项卡,就可以在任意已经显示出来的 文件之间即时切换。 Class View 选项卡显示项目内定义的类,还显示各个类的内容。在该应用程序中还没有任何类, 所以类视图为空。到讨论类的时候,就可以使用 Class View 选项卡来迅速、容易地移动与应用程序 中的所有类有关的代码。 在 View 菜单中选择 Property Manager,就可以显示该选项卡,它显示了为项目的 Debug 版本和 Release 版本设定的属性。稍后将在本章中解释这两种版本。通过右击某个属性,并从上下文菜单中 选择 Properties 项,就可以修改显示的任何属性。该操作将打开一个对话框,可以在该对话框中设 定项目属性。还可以随时按下 Alt+F7 组合键来显示 Property Pages 对话框。当介绍程序的 Debug 版 本和 Release 版本时,还将更详细地论述属性对话框。 从 View 菜单中选择相应的项或按下 Ctrl+Shift+E 组合键,可以显示 Resource View 选项卡。它 显示程序使用的对话框、图标、菜单、工具栏和其他资源。因为这是一个控制台程序,所以没有使 用任何资源。但是,当开始编写 Windows 应用程序时,将在这里看到大量内容。利用该选项卡,可 图 1-6 Visual C++ 2013 入门经典(第 7 版) 12 以编辑或添加项目的可用资源。 像 IDE 的大多数元素一样,当用户右击选项卡中显示的条目或者某些情况下右击选项卡的空白 区域时,Solution Explorer 选项卡和其他选项卡也提供了与上下文相关的弹出式菜单。如果觉得 Solution Explorer 窗格妨碍了我们编写代码,那么可以通过单击 Auto Hide 图标来隐藏该窗格。如果 要重新显示,则只要单击 IDE 窗口中左边的 Name 选项卡即可。 修改源代码 应用程序向导生成的是完整的、可以编译和执行的 Win32 控制台程序。但是,该程序运行时不 做任何事情,因此为了使之更有意义,需要对其进行修改。如果该程序在 Editor 窗格中还不可见, 则双击 Solution Explorer 窗格中的 Ex1_01.cpp。该文件是该程序的主源文件,如图 1-7 所示。 图 1-7 如果屏幕上没有显示行号,请从主菜单中选择 Tools | Options 菜单项,则会显示 Options 对话框。 如果在左边窗格中,展开 Text Editor 子树的 C/C++选项,并从展开的树中选择 General 选项,则可 以在 Options 对话框的右边窗格中选择 Line Numbers 选项。这里首先大致讲述一下图 1-7 中代码的 作用,后面将看到与之有关的更多讲解。 前两行只是注释。编译器将忽略一行内跟在“//”后面的任何内容。如果想在某行内添加说明 性注释,则应该在那些文本前面输入“//”。 第 4 行是#include 指令。该指令将 stdafx.h 文件的内容添加到这个文件中,以代替这条#include 指令。在 C++程序中,这是将.h 源文件的内容添加到.cpp 源文件的标准方法。 第 7 行是本文件中可执行代码的第一行,是函数_tmain()的开始。在 C++程序中,函数只是有名 称的可执行代码单元,每个 C++程序都至少由一个(通常是许多)函数组成。 第 8 行和第 10 行分别有左、右大括号,它们包围着函数_tmain()中所有的可执行代码。因此, 只有第 9 行是可执行代码,其唯一功能就是结束程序。 现在,可以在 Editor 窗口中添加下面两行代码: // Ex1_01.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include int _tmain(int argc, _TCHAR* argv[]) 第 1 章 使用 Visual C++编程 13 { std::cout << "Hello world!\n"; return 0; } 应该添加的新行以粗体显示,其他行是自动生成的。为了添加各个新行,我们将光标放在前一 行文本的最后,然后按下 Enter 键,这样即可产生用来输入新代码的空行。确保程序完全与前面例 子中显示的一样,否则该程序可能无法编译。 第一个新行是#include 指令,该指令在 Ex1_01.cpp 源文件中添加一个 C++标准库文件的内容。 iostream 库定义了基本的 I/O 操作功能。添加的第二行(将输出写到命令行)使用的 std::cout 是标准输 出流的名称,我们在这条语句中将字符串“Hello world!\n”写到第二个新加语句的 std::cout 后面。 那对双引号之间的内容将写到命令行上。 构建解决方案 要构建解决方案,按 F7 键或选择菜单项 Build | Build Solution。另外,还可以单击对应于该菜单 项的工具栏按钮。Build 菜单的工具栏按钮可能没有显示出来,但很容易改变这种情况,只需要在工 具栏区域右击,然后从列表中选择 Build 工具栏即可。之后,上述程序应能成功编译。如果出现错误, 则可能是输入新代码时产生的,所以请非常仔细地检查刚才输入的两行代码。 构建控制台应用程序时创建的文件 成功构建了上面的例子之后,使用 Windows Explorer 查看项目文件夹时,将看到解决方案文件 夹 Ex1_01 中出现了一个新的子文件夹 Debug。此文件夹是 Ex1_01\Debug,而不是 Ex1_01\Ex1_01\ Debug。该文件夹包含刚才构建项目时产生的输出。注意,这个文件夹包含 3 个文件。 除.exe 文件(可执行的程序)之外,关于这些文件的内容我们不必知道太多。不过,如果读者比较 好奇,就会发现链接器在构建项目时使用.ilk 文件。它使链接器能够将根据修改的源代码生成的目标 文件增量地链接到现有的.exe 文件,从而避免每次修改程序时都重新链接所有文件。而.pdb 文件包 含调试信息,在调试模式中执行程序时要使用该调试信息。在调试模式中,可以动态检查程序执行 过程中所生成的信息。 Ex1_01 项目文件夹也有一个 Debug 子目录。这包含在构建过程中生成的很多文件,从 Windows Explorer 的 Type 描述中可以看到它们包含何种信息。 2. 程序的 Debug 版本和 Release 版本 通过 Project | Ex1_01 Properties 菜单项,可以设定许多项目选项。这些选项决定了在编译和链接 阶段处理源代码的方式。产生具体的可执行程序版本所对应的选项集合称为配置。当创建新的项目 工作空间时,Visual C++自动创建可产生两种应用程序版本的配置。一种称作 Debug 版本,该版本 包括帮助用户调试程序的信息。使用程序的 Debug 版本,可以在出现问题时单步执行代码,以检查 程序中的数据值。另一种称作 Release 版本,它不包括调试信息,但打开了编译器的代码优化选项, 以提供最高效的可执行模块。就本书而言,这两种配置已经足够了。但如果需要为应用程序添加其 他配置,也能通过 Build | Configuration Manager 菜单项实现。注意,该菜单项在尚未加载任何项目 时不会出现。这显然不是什么问题,但当读者正好在浏览菜单想看看都有什么选项时,这一点可能 使人迷惑。 Visual C++ 2013 入门经典(第 7 版) 14 可以从工具栏的下拉列表中选择采用哪个程序配置。如果从下拉列表中选择 Configuration Manager 选项,则会显示 Configuration Manager 对话框。当解决方案包含多个项目时,使用 Configuration Manager 对话框。在这里,可以选择每个项目的配置,并可以选择想要构建哪些项目。 在使用调试配置测试过应用程序,且看起来可以正确工作之后,通常重新构建该程序作为 Release 版本,这样将产生没有调试和跟踪能力的优化代码,使程序运行得更快,而且占用更少的 内存。 执行程序 在成功编译过解决方案之后,可以按下 Ctrl+F5 组合键来执行程序,窗口应如图 1-8 所示。 可以看出,双引号之间的文本写到了命令行上。文本字符串最后的“\n”是表示换行符的特殊序 列—— 称作转义序列。转义序列用来表示文本串中不能直接从键盘输入的字符。最后一行提示,在 显示了控制台程序的输出后如何继续执行。按下回车键会关闭窗口。本书在显示程序的输出后,不 显示这一行。 图 1-8 试一试:创建空控制台项目 当学习简单的 C++语言示例时,前面的项目包含了一些我们不需要的累赘。默认选中的预编 译头文件选项使该项目创建 stdafx.h 文件。当程序中有大量文件时,这种机制可以使编译过程效 率更高,但在我们的许多例子中都是不必要的。在这些实例中,首先需要创建一个空项目,然后 在其中添加自己的源文件。在新的解决方案中为名为 Ex1_02 的 Win32 控制台程序创建新项目, 我们将看到创建空项目的整个过程。在输入项目名称并单击 OK 按钮之后,单击随后出现的对话 框左边的 Application Settings 选项。之后就可以从附加选项中选择 Empty Project 复选框,取消选 择 SDL。单击 Finish 按钮之后,还是像以前一样创建项目,但这次没有任何源文件。 接下来,向项目中添加新的源文件。右击 Solution Explorer 窗格,然后从上下文菜单中选择 Add | New Item 菜单项。这时会出现一个对话框,单击其左边窗格中的 Code 选项和右边窗格中的 C++ File(.cpp) 选项。然后输入文件名 Ex1_02。 单击对话框中的 Add 按钮之后,就在项目中添加了这个新文件,并显示在 Editor 窗口中。当然, 该文件是空的,因此不会显示任何内容。在 Editor 窗口中输入下面的代码: // Ex1_02.cpp A simple console program #include // Basic input and output library int main() { std::cout << "This is a simple program that outputs some text." << std::endl; std::cout << "You can output more lines of text" << std::endl; 第 1 章 使用 Visual C++编程 15 std::cout << "just by repeating the output statement like this." << std::endl; return 0; // Return to the operating system } 注意输入代码时发生的自动缩进。C++使用缩进使程序更加清晰、可读,编辑器基于前面一行 的内容,自动缩进用户输入的每一行代码。可以修改缩进,具体做法是选择 Tools | Options 菜单项, 显示 Options 对话框。在此对话框的左窗格中选择 Text Editor | C/C++ | Tabs 选项,会在右窗格中显 示缩进选项。默认情况下,编辑器插入制表符,但如果需要,也可以修改为插入空格。 我们还看到在输入时会用不同的颜色来突出显示语法。由于编辑器根据语言元素的种类自动给 它们分配颜色,因此程序的某些元素以不同的颜色显示。 前面的代码是完整的程序。读者可能注意到,该程序与前一个例子中由应用程序向导生成的代 码相比有两处不同。这里没有 stdafx.h 文件的#include 指令。因为这里不使用预编译的头文件功能, 所以没有使该文件成为项目的组成部分。这里的函数名称是 main,而前面的名称是_tmain。事 实 上 , 所有 ISO/IEC 标准 C++程序都是在 main()函数中开始执行的。当使用 Unicode 字符时,微软公司还 提供了相应的 wmain 函数。而名称_tmain 定义为 main 或 wmain(在 tchar.h 头文件中),这取决于程 序是否将使用 Unicode 字符。就前一个例子而言,名称_tmain 在后台被定义为 wmain,因为项目设 置是 Unicode。所有的 C++例子都使用标准名称 main,输出语句有些区别。main()中的第一条语 句是: std::cout << "This is a simple program that outputs some text." << std::endl; 这条语句中有两个<<运算符,二者都将后面跟着的内容发送到标准输出流 std::cout。首先是双 引号之间的字符串被发送到输出流,然后是 std::endl,而 std::endl 在标准程序库中定义为换行符。 在前面,使用转义序列\n 来表示双引号之间字符串内的换行符。因此前面的语句还可以写成下面的 形式: std::cout << "This is a simple program that outputs some text.\n"; 但这不同于使用 std::endl。使用 std::endl 会输出一个换行符,再刷新输出缓存。仅使用\n,不会 立即刷新缓存。 最后一个语句是 return,它结束 main()和程序。严格来说,这里不需要这个语句,可以省略它。 如果执行到 main()末尾,没有遇到 return 语句,就等价于执行 return 0。 现在,可以用与前一个例子相同的方式构建该项目。注意,构建项目时,Editor 窗格中任何打 开的源文件都将自动保存—— 如果还没有保存的话。在成功编译该程序之后,按下 Ctrl+F5 组合键执 行它。如果一切正常,将显示如下输出: This is a simple program that outputs some text. You can output more lines of text just by repeating the output statement like this. 注意,按下 Ctrl+F5 组合键时,如果项目不是最新的,就先构建项目,再执行它。 3. 处理错误 当然,如果没有正确地输入程序,则将报告错误。为了显示其工作原理,我们可以故意将错误 引入这个程序。如果已经有错误,那么可以使用那些错误来做这个练习。回到 Editor 窗格,删除大 Visual C++ 2013 入门经典(第 7 版) 16 括号之间倒数第二行(第 8 行)最后的分号,然后重新构建源文件。应用程序窗口底部的 Output 窗格 将出现下面这条错误消息: C2143: syntax error : missing ';' before 'return' 编译过程的所有错误消息都有能够在文档中查找到的错误编号。这里的问题是显而易见的。但 在某些更含糊的情况下,文档可以帮助我们弄清楚引起错误的原因。要得到关于错误的帮助文档, 在 Output 窗格中单击包含错误编号的那一行,然后按 F1 键。这时将出现一个新窗口,其中包含关 于该错误的更多信息。如果愿意,读者可以用上面那个简单的错误试一下。 当改正错误之后,就可以重新构建项目。构建操作将高效率地进行,因为项目定义会记录构成 该项目的那些文件的状态。在正常的构建过程中,Visual C++只重新编译那些自上次编译或构建程 序以来修改过的文件。这意味着如果某个项目有好几个源文件,而自上次构建该项目以来仅修改过 其中一个文件,就只重新编译该文件,然后链接、创建新的.exe 文件。如果修改了头文件,包含该 头文件的所有文件都会重新编译。 1.5.5 设置 Visual C++的选项 可以设置的选项有两组。可以设置应用于 Visual C++提供的工具的选项,这些选项将应用到每 个项目上下文中。还可以设置某个项目特有的、决定如何在编译和链接时处理项目代码的选项。从 主菜单上选择 Tools | Options 菜单项,将会显示 Options 对话框,通过该对话框可以设置应用到每个 项目的各种选项。之前曾使用此对话框改变过编辑器采用的代码缩进。Options 对话框如图 1-9 所示。 图 1-9 单击左边窗格中任意一项旁边的空心符号,将显示出子主题列表。图 1-9 显示了 Projects and Solutions 下面 General 子主题的选项。右边窗格显示对应于左边窗格选定主题的可设置选项。此时, 我们仅应当关心少数几个选项,但最好花点时间看看可用的选项。单击该对话框右上方的 Help 按钮 (带问号的按钮),将显示当前选项的解释。 第 1 章 使用 Visual C++编程 17 我们可能希望选择某个路径作为创建新项目时的默认路径,通过设置图 1-9 所示的第一个选项 就能做到这一点。只需要将该路径设定为希望用来存储项目和解决方案的那个位置即可。 在 Options 对话框的左边窗格中选择 Projects and Solutions | VC++ Project Settings 主题,可以设 置应用于所有 C++项目的选项。通过选择主菜单上的 Project | Ex1_02 Properties 菜单项,或者按下 Alt+F7 组合键,又可以设定当前项目所特有的选项。该菜单项的标签是定制的,以反映当前项目的 名称。 1.5.6 创建和执行 Windows 应用程序 为了演示创建和执行 Windows 应用程序不是一件难事,现在就创建一个。讲完必要的基础知识 后,才讨论生成这个程序的过程,确保读者能够在细节上理解它们。但是,这个过程十分简单。 1. 创建 MFC 应用程序 在开始时,如果某个现有项目处于活动状态—— Visual C++主窗口标题栏中出现的项目名称指出 了活动的项目,可以从 File 菜单上选择 Close Solution 菜单项。另一种方法是创建新项目,使当前解 决方案自动关闭。在 New Project 对话框中默认选择为解决方案创建目录。 要创建 Windows 程序,选择 File | New | Project 菜单项或者按 Ctrl+Shift+N 组合键,然后在左窗 格中选择项目类型 MFC,并选择 MFC Application 作为该项目的模板。之后,可以输入项目名称 Ex1_03。单击 OK 按钮,就会显示 MFC Application Wizard 对话框。该对话框包含许多选项,它们 决定应用程序将包括哪些功能。该对话框左边列表中的条目标识了这些选项。 单击 Application Type 显示这些选项。单击 Tabbed documents 选项以取消选中此选项。从右边的 下拉列表中选择 Windows Native/Default 选项。此时对话框应该如图 1-10 所示。 图 1-10 Visual C++ 2013 入门经典(第 7 版) 18 接下来单击 Advanced Features,取消除 Printing and Print Preview 和 Common Control Manifest 选 项之外的其他选项,此时的对话框如图 1-11 所示。注意选中和取消选中复选框时,对话框左上角的 小图像会改变。 图 1-11 最后,单击 Finish 按钮以创建项目。IDE 窗口中取消停靠 的 Solution Explorer 窗格,如图 1-12 所示。 图中列表显示了所创建的大量源文件和几个资源文件。扩 展名为.cpp 的文件包含可执行的 C++源代码,而.h 文件(称为 头文件)包含的 C++代码由可执行代码使用的定义组成。.ico 文 件包含图标。在 Solution Explorer 窗格中,为了方便访问,这 些文件在 Solution Explorer 窗格中分组为子文件夹。但这些不 是真正的文件夹,它们不会出现在磁盘的项目文件夹中。 如果现在使用 Windows Explorer 来查看 Ex1_03 解决方案 文件夹和子文件夹,那么将看到生成了许多文件。其中 4 个文 件(包括临时的.opensdf 文件)在解决方案文件夹中,一些文件在 项目文件夹中,其余文件在项目文件夹的 res 子文件夹中。子 文件夹 res 中的文件包含该程序使用的资源,如工具栏和图标。 仅仅输入一个希望指定给该项目的名称,就得到这么多文件。 由于自动创建的文件和文件名如此之多,因此各个项目最好使 用单独的目录。 项目目录 Ex1_03 中的文件 ReadMe.txt 解释了 MFC Application Wizard 生成的各个文件的用途。双击 Solution Explorer 窗格中的 ReadMe.txt,就可以在 Editor 窗口中查看它。 图 1-12 第 1 章 使用 Visual C++编程 19 2. 构建和执行 MFC 应用程序 在执行程序之前,必须构建项目—— 即编译 源代码并链接程序模块。这些操作与在控制台应 用程序例子中所做的完全相同。为节省时间,按 下 Ctrl+F5 组合键就能构建并执行项目。 在构建项目之后,Output 窗口指示没有任何 错误,可执行代码开始运行。所生成程序的窗口 如图 1-13 所示。 可以看出,该窗口包括菜单和工具栏。尽管 程序中没有任何具体的功能—— 需要添加代码才 能使之成为有用的程序,但所有菜单都可以工作。 读者可以试着用一下,甚至可以选择 File | New 菜单项来创建新窗口。 用 MFC Application Wizard 创建 Windows 程序并不困难。后面章节将要把这里的基本程序开 发成能够完成一些有趣事情的程序,那时将需要稍微多一点的时间,但也不会太难。当然,对许 多人来说,如果没有 Visual C++的帮助,用老式的方法编写一个重要的 Windows 程序,那么在着 手之前至少需要几个月的时间考虑很多因素。现在有了 Visual C++,那些全都成了过去。但是, 我们永远不知道在编程技术方面又有什么新事物即将来临。 1.6 小结 本章简要介绍了使用 Visual C++创建各种应用程序的基本过程。我们创建并执行了控制台程序, 并在应用程序向导的帮助下创建了基于 MFC 的 Windows 程序。 创建和执行项目是很容易的。 从下一章开始,所有说明 C++语言元素使用方法的例子都是用 Win32 控制台应用程序执行的。 一旦结束钻研 C++的奥妙,我们就回过头来使用应用程序向导创建基于 MFC 的程序。 1.7 本章主要内容 本章主要内容如表 1-1 所示。 表 1-1 主 题 概 念 C++ Visual C++支持遵循 C++ 11 语言标准的 C++语句,C++ 11 语言标准是在 ISO/IEC 14882:2011 文档中定义的。Visual C++实现了该标准定义的大多数语言特性,和下一个标准 C++ 14 中的一 些功能 解决方案 解决方案是一个或多个项目的容器,这些项目形成某种信息处理问题的解决方案 项目 项目是代码和资源元素的容器,代码和资源元素构成程序中的功能单元 Solution Explorer 窗格 Solution Explorer 窗格中的一个或多个选项卡显示了项目的不同方面。Solution Explorer 选项卡 显示了项目文件,Class View 选项卡显示了项目中的类,Resource View 选项卡显示了项目资源 图 1-13 Visual C++ 2013 入门经典(第 7 版) 20 ( 续表) 主 题 概 念 项目选项 从 Tools 菜单中选择 Options,显示 Options 对话框,就可以通过该对话框显示和修改应用 于所有 C++项目的选项 项目属性 从 Project 菜单中选择 Properties,显示 Properties 对话框,就可以通过该对话框为当前项目 设置属性值 控制台应用程序 控制台应用程序是没有 GUI 的基本 C++应用程序,一般,其输入来自于键盘,输出在命令 行中显示 main()函数 标准 C++程序的起点是 main()函数。New Project 对话框生成的控制台应用程序从_tmain() 函数开始 Unicode 如果希望在控制台程序中使用标准的 main()函数,就可以生成一个空的 Win32 项目,之后 添加 main()的源文件 Windows Store 应用程序 Windows Store 应用程序面向运行 Windows 8 操作系统的平板电脑和桌面 PC Windows 运行库 Windows 运行库 WinRT 为Windows Store 应用程序提供了与 Windows 8 及以后操作系统的 接口 Windows 桌面应用 程序 Windows 桌面应用程序有一个应用程序窗口和一个 GUI,GUI 用来集成菜单、工具栏和对 话框等控件。桌面应用程序通过 Win32 函数集与操作系统交互操作。桌面应用程序在 Windows 7 、Windows 8 和后续版本下执行 Microsoft Foundation Classes MFC 是一组封装了 Win32 函数的 C++类。MFC 简化了开发 Windows 桌面应用程序的过程 MFC 项目 在 New Project 对话框中选择 MFC,再选择 MFC Application,就可以创建 MFC 项目 数据、变量和计算 本章要点 ● C++程序结构 ● 名称空间 ● C++中的变量 ● 定义变量和常量 ● 基本的键盘输入和屏幕输出 ● 执行算术运算 ● 强制转换操作数 ● 变量作用域 ● auto 关键字的作用 ● 如何获取表达式的类型 本章源代码下载地址(wrox.com): 打开网页 http://www.wrox.com/go/beginningvisualc,单击 Download Code 选项卡即可下载本章源 代码。这些代码在 Chapter 2 文件夹中,文件都根据本章的内容单独进行了命名。 2.1 C++程序结构 控制台应用程序从命令行读取数据,然后将结果输出到命令行。用于了解 C++语言如何运行的 所有示例都是控制台程序,以避免创建和使用 Windows 应用程序的复杂性,之后详细探讨 Windows 应用程序的运行方式。这样读者能完全专注于 C++语言,在掌握了 C++语言以后,就可以创建和管 理 Windows 应用程序及其涉及的代码。我们首先了解控制台程序的构成。 C++程序由一个或多个函数组成。函数是一个具有唯一名称的自包含代码块,要想执行函数, 可以使用函数的名称标识函数。第 1 章中的 Win32 控制台程序示例只包含 main 函数,main 是函数 2 第 章 Visual C++ 2013 入门经典(第 7 版) 22 的名称。每个 C++程序都包含函数 main(),这是系统开始执行的地方。任何规模的程序都包含几个 函数—— main()函数,以及其他一些函数。 如第 1 章中所述,由 Application Wizard 生成的控制台程序具有一个名称为_tmain 的主函数。这 是一个 Microsoft 专用的编程结构,根据程序是否使用 Unicode 字符,它允许函数的名称是 main 或 wmain。wmain 或_tmain 是 Microsoft 专有的,不是标准的 C++名称。标准 C++中的主函数名称 是 main。在所有的控制台示例中都将使用名称 main,因为这是可移植性最好的选项。如果打算 只编译Visual C++代码,且希望对 main 使用Microsoft 专有的名称,就可以使用Application Wizard 生成的默认控制台应用程序。此时,只需要将控制台程序示例中 main 函数体的代码复制到_tmain 即可。 图 2-1 展示了典型的控制台程序的结构。所示程序的执行流始于函数 main()的开始处。然后执 行流由 main()传递到函数 input_names(),该函数将执行流返回到紧跟在 main()函数中调用它的位置 之后。然后从 main()函数调用 sort_names()函数,在控制权返回到 main()以后,将调用函数 output_names()。output_names()完成后,执行流再次返回到 main(),这时该程序结束。 函数在调用时,从其 开头开始执行 函数的执行结果返回 到调用它之后的下一行 main()函数的执行 结果返回到操作系统 程序从 main()函数开始执行 图 2-1 第 2 章 数据、变量和计算 23 当然,不同的程序可能有截然不同的函数结构,但是它们都从 main()的开始处开始执行。将一 个程序分成多个函数的主要优点是,可以分别编写和测试每个函数。另一个优点是,为执行特定任 务而编写的函数可以在其他程序中重用。C++附带的库提供了大量可以在程序中使用的标准函数, 它们可以为您省去大量的工作。 试一试:使用 main()的简单程序 这个示例演示了在 VC++控制台程序中使用 main()需要执行的操作。首先创建一个新项目—— 其 捷径是使用 Ctrl+Shift+N 组合键。当出现 New Project 对话框时,在项目类型中选择 Win32,在模板 中选择 Win32 Console Application。可以将这个项目命名为 Ex2_01。 如果单击 OK 按钮,则将出现如图 2-2 所示的对话框,它概述了 Application Wizard 将生成 什么。 图 2-2 现在,如果单击这个对话框左边的 Application Settings 选项,则将显示 Win32 应用程序的更多 选项,如图 2-3 所示。 第 5 章将详细介绍函数的创建和使用。 Visual C++ 2013 入门经典(第 7 版) 24 图 2-3 默认设置是 Console application,它包括一个包含函 数 main()默认版本_tmain()的文件,但因为要从最基本的 项目结构(它不包含源文件)开始,所以在选项集合中选 择 Empty project 选项,取消选中 SDL checks,然后单击 Finish 按钮。 从主窗口左边的 Solution Explorer 窗格可以看到这 个项目包含的内容,如图 2-4 所示。 首先要在这个项目中添加一个新的源文件,右击 Solution Explorer 窗格中的 Source Files,选择 Add | New Item 菜单项。这时将显示 Add New Item 对话框,显示 可用的选项。 选择左边窗格中的代码,单击中间窗格中的 C++ File (.cpp)模板,然后输入文件名 Ex2_01。这 个文件将自动被赋予扩展名.cpp,所以不必输入扩展名。该文件的名称可以和项目的名称相同。项 目文件的扩展名是.vcxproj,以便与源文件相区别。 单击 Add 按钮,创建这个文件。然后可以在 IDE 窗口的编辑器窗格中输入下列代码: // Ex2_01.cpp // A Simple Example of a Program #include using std::cout; using std::endl; int main() { int apples, oranges; // Declare two integer variables int fruit; // ...then another one 图 2-4 第 2 章 数据、变量和计算 25 apples = 5; oranges = 6; // Set initial values fruit = apples + oranges; // Get the total fruit cout << endl; // Start output on a new line cout << "Oranges are not the only fruit... " << endl << "- and we have " << fruit << " fruits in all."; cout << endl; // Output a new line character return 0; // Exit the program } 这演示了编写 C++语句的一些方法,它并不是一个良好的编程样式。输入代码时,编辑器将进 行检查。编辑器会在它认为不正确的代码下面标上红色的波浪线,所以输入代码时要留意它们。如 果看到这样的红色波浪线,通常意味着有拼写错误。把鼠标悬停在红色波浪线上,会显示一个消息, 指出错误所在。 源文件根据其扩展名标识为包含 C++代码的文件,因此编辑器识别出来的各种语言元素会上色, 以突出显示它们。本章后面将详细介绍彩色编码方式。 如果观察 Solution Explorer 窗格(按 Ctrl+Alt+L 组合键可以显示它),就将看到新建的源文件名。 Solution Explorer 窗格始终显示一个项目中的所有文件。从 View 菜单中选择 Class View 项,或者按 下 Ctrl+Shift+C 组合键,可以显示 Class View 窗格。它由两个窗格组成,上面的窗格显示这个项目 中的全局函数和宏(在着手创建一个包含类的项目时,这个窗格还将显示类),下面的窗格目前是空 的。如果在 Class View 上面的窗格中选择了 Global Functions and Variables 项,那么在下面的窗格中 将出现 main()函数,如图 2-5 所示。后面将详细讨论其含义,但是,全局函数和变量实质上是可以 从程序的任何地方访问的函数和变量。 从 View 菜单中选择相应的项,可以显示 Property Manager 窗格。如果单击空心箭头,展开树中 的条目,就会如图 2-6 所示。图 2-6 显示了可以构建的两种可能版本,用于测试的 Debug 版本和已经 测试过程序的 Release 版本。图 2-6 还显示了项目的各个版本的属性,双击任一属性将出现一个对话框, 显示 Property Pages,必要时可以在这个对话框中修改属性。 图 2-5 图 2-6 可以使用三种方法编译和链接这个程序:可以从 Build 菜单中选择 Build Ex2_01 菜单项,可以 按 F7 功能键,或者选择适当的工具栏按钮—— 把鼠标指针放在一个工具栏按钮上,就可以识别它的 功能。如果将鼠标放在上面时,没有显示工具提示 Build Ex2_01 的工具栏按钮,那么当前不显示 Build 工具栏。补救办法是:在工具栏的一个空白区域上右击,从显示的工具栏列表中选择 Build 选项。 这是一个非常长的列表,您可能会根据正在做的事情选择显示不同的工具栏组。 Visual C++ 2013 入门经典(第 7 版) 26 假设编译操作是成功的,那么通过按 Ctrl+F5 组合键,或者从 Debug 菜单中选择 Start Without Debugging 菜单项,就可以执行这个程序,并在命令行窗口中得到下列输出: Oranges are not the only fruit... - and we have 11 fruits in all. Press any key to continue . . . 前两行代码由这个程序生成,最后一行代码说明如何结束这个执行过程,并关闭该命令行窗口。 虽然在其他控制台示例中不会显示最后一行输出,但它一直是存在的。 1. 程序注释 上面这个程序中的前两行是注释。注释是所有程序的一个重要部分,但它们不是可执行代码, 它们只是为了增强程序的可读性。编译器将忽略所有注释。在任意一行代码中,如果出现两个不包 含在文本字符串(后面将介绍文本字符串)中的连续斜杠(//),则表示这一行的其余部分是一个注释。 这个程序的几个代码行包含注释。 注释的另一种形式是以/*和*/为界。例如,这个程序的第一行可以写成: /* Ex2_01.cpp */ 使用//的注释只包括这一行中两个连续斜杠后面的部分,而/*…*/这种形式将把包含在/*和*/之 间的所有内容定义为注释,并且可以跨越几行。例如,可以编写下列代码: /* Ex2_01.cpp A Simple Program Example */ 下面 4 行代码都是注释,编译器会忽略它们。如果想突出显示某些特定的注释行,则可 以利用一些框框修饰它们: /***************************** * Ex2-01.cpp * * A Simple Program Example * *****************************/ 通常,应总是给程序充分的注释。注释应当非常充分,以便于另一个程序员或者你自己以后了 解代码的目的及其运行方式。本书示例中的注释经常比你在实际程序中解释得更详细。 2. #include 指令—— 头文件 在注释之后有一个#include 指令: #include 之所以称其为指令,是因为它命令编译器完成某项任务—— 此处是在编译之前,在此程序的源 文件中插入文件 iostream 的内容,该文件名位于尖括号之间。iostream 文件称为头文件,因为它总 是插入到另一个源文件中。iostream 头文件是标准 C++库的一部分,它包含一些使用输入和输出语 句所需的定义。如果没有在此程序中包括 iostream 的内容,那么不能编译这个程序,因为在这个程 序中使用的输出语句依赖该文件中的一些定义。Visual C++提供了许多不同的标准头文件,它们具 第 2 章 数据、变量和计算 27 有各种各样的功能。在进一步学习语言工具时,将看到更多的头文件。 由#include 指令插入的文件的名称不一定写在尖括号之间。头文件名也可以写在双引号中。因 此上面的代码也可以写成: #include "iostream" 两者之间的唯一区别是编译器将在什么地方查找此文件。如果头文件名是用双引号引起来的, 则编译器先在包含此指令的源文件所在的目录中搜索头文件。如果头文件未找到,编译器再搜索存 储标准头文件的目录。 如果文件名是用尖括号括起来的,则编译器只搜索包含标准头文件的目录。因此,想在源文件 中包含标准头文件时,应该将文件名用尖括号括起来,因为这样的搜索速度更快。而要包含其他头 文件,一般是自己创建的头文件,则应该将文件名用双引号引起来;否则,根本找不到。 #include 语句是几个预处理器指令中的一个,本书后面将介绍其他预处理指令。编辑器会在编 辑窗口中用蓝色突出显示它们。预处理器指令是由编译的预处理阶段执行的命令,这个阶段在代码 编译成目标代码之前执行,在编译源代码之前,预处理器指令通常以某种方式作用于它们。预处理 器指令都以#字符开头。 3. 名称空间和 using 声明 如第 1 章所述,标准库是一个大型的例程集合,用于执行许多常见的任务,如处理输入和输出, 以及执行基本的数学计算。由于标准库中的这些例程以及其他具有名称的事物数量巨大,因此用户 使用的名称可能无意中与标准库中的名称雷同。名称空间是一种机制,它可以将无意中使用重名的 风险降至最低,其方法是将一组给定的名称(如标准库中的名称)与一种姓(family name)关联起来, 这种姓就是名称空间名称。 在名称空间的代码中定义的每个名称都有一个关联的名称空间名称。标准库工具定义在 std 名 称空间内,所以标准库中的每一项都有自己的名称,以及作为限定符的名称空间名称 std。标准 库中 cout 和 endl 的全名是 std::cout 和 std::endl,第 1 章介绍过这些名称。将名称空间名称和实体名分隔 开的两个冒号构成了称为“作用域解析运算符”的运算符,本书后面将介绍这种运算符的其他用途。 在程序中使用全名会使代码看起来有点混乱,所以最好使用不由名称空间名称 std 限定的简化名。 在前面的程序中,iostream 的#include 指令后面的两行代码使之得以实现: using std::cout; using std::endl; 这些是using 声明,它们告诉编译器,要在不指定名称空间名称的情况下使用名称空间 std 中的 名称 cout 和 endl。编译器假定,在第一个 using 声明之后,只要使用名称 cout,就表示 std::cout。名 称 cout 表示对应于命令行的标准输出流,名称 endl 表示换行符,并刷新输出缓存。本章后面会详细 介绍名称空间,包括如何自定义名称空间。 在头文件中使用 using 声明时要小心,尤其是它们包含在其他几个源文件中时。 在头文件中应避免把 using 声明放在全局作用域中,因为它们会应用于包含该头文件 的所有源文件。 Visual C++ 2013 入门经典(第 7 版) 28 2.1.1 main()函数 上述示例中的函数 main()包括将它定义为 main()的函数头,以及从第一个左大括号({)到对应 的右大括号(})之间的所有语句。这对括号将这个函数中的可执行语句包围起来,它们总称为函 数体。 所有函数都包括一个定义函数名称的头,然后是函数体,它由包括在大括号之间的一些程序语 句组成。函数体也可以不包含任何语句,这时它不做任何事情。 不做任何事情的函数似乎有些多余,但是在编写大程序时,一开始可以先勾画出函数中的完整 程序结构,而忽略许多函数的代码,使它们有一个空的或者最小的函数体。这意味着,可以随时编 译和执行包含所有函数的整个程序,并逐步给函数添加详细的代码。 2.1.2 程序语句 main()函数体的每个程序语句都以一个分号结束。分号表示语句的结束,而不是这一行的结束。 因此,为了使代码更易于理解,可以把一个语句扩展成几行,也可以把几个语句放在同一行中。程 序语句是定义程序功能的基本单元。这有点像文章中一个段落的句子,每个句子都独立地表达一个 行为或想法,和这个段落中的其他句子联系和组合起来,就表达出比较全面的想法。一个语句就是 计算机将要执行的一个行为的自包含定义,和其他语句组合起来, 就可以定义比较复杂的行为 或计算。 函数的行为始终由一些语句来表达,每个语句都以分号结束。看看刚才编写的示例中的语句, 大致了解它是如何运行的。本章后面将讨论每种类型的语句。 在 main()函数体中,第一个语句是: int apples, oranges; // Declare two integer variables 这个语句定义了两个变量 apples 和 oranges。变量是一段已命名的计算机内存,用于存储数据, 引入一个或多个变量名称的语句称为变量声明。关键字 int 表明 apples 和 oranges 变量将存储整数值。 每当把一个变量的名称引入程序时,都要指定它将存储的数据类型,这称为变量的类型。 接下来的这个语句声明了另一个整型变量 fruit: int fruit; // ...then another one 虽然可以在同一个语句中声明几个变量,如同前面的语句声明 apples 和 oranges 那样。但是, 一般最好用一个语句声明一个变量,独占一行,这样就可以单独注释,以解释它们的用途。 示例中的下一行代码是: apples = 5; oranges = 6; // Set initial values 这行代码包含两个语句,每个语句都以一个分号结束。这样做的目的是说明可以把多个语句放 在一行中。这不是强制的,但良好的编程习惯一般是一行只编写一个语句,使代码较容易理解。良 好的编程习惯是采用使代码易于理解、且使出错的可能性降至最低的编码方法。 这两个语句分别将数值 5 和 6 存储到变量 apples 和 oranges 中。这些语句称为赋值语句,因为 它们把新值赋给变量,=是赋值运算符。 下一个语句是: 第 2 章 数据、变量和计算 29 fruit = apples + oranges; // Get the total fruit 这也是一个赋值语句,但稍有不同,因为在赋值运算符的右边是一个算术表达式。这个语句 把存储在变量 apples 和 oranges 中的数值相加,然后在变量 fruit 中存储结果。 下面的 3 个语句是: cout << endl; // Start output on a new line cout << "Oranges are not the only fruit... " << endl << "- and we have " << fruit << " fruits in all."; cout << endl; // Output a new line character 它们都是输出语句。第一个语句把由 endl 表示的换行符发送到屏幕的命令行中。在 C++中,输 入的来源或输出的目的地称为流。名称 cout 指定“标准的”输出流,运算符<<表明,该运算符右边 的内容将发送到输出流 cout。<<运算符“指出”数据流动的方向,从这个运算符右边的变量、字符 串或表达式到左边的输出目的地。因此,在第一个语句中,由名称 endl 表示的值(即换行符)将发送 到由名称 cout 标识的流—— 传输到 cout 的数据会写入命令行。把 endl 发送到流,也会刷新流缓存, 然后把所有输出发送到命令行。 名称 cout 和运算符<<的含义定义在头文件 iostream 中,该文件利用#include 指令在程序代码中 添加。cout 是标准库中的名称,所以它在名称空间 std 内。如果不使用 using 指令,就不会识别 cout, 除非使用了其全限定名 std::cout。cout 定义为表示标准输出流,因此不应当把它用于其他目的,将 相同的名称用于不同的事情很可能引起混淆。 第 2 个输出语句扩展成如下两行: cout << "Oranges are not the only fruit... " << endl << "- and we have " << fruit << " fruits in all."; 如前所述,每个语句都可以扩展成许多行。语句的结束始终用分号表示,而不是一行的结束。 编译器将读取连续的行,并把它们组合成一个语句,直至发现定义该语句结束的分号。当然,这也 意味着,如果忘记在语句的结尾处放置分号,那么编译器将假定下一行是同一语句的一部分,并将 它们连接到一起。这通常会产生编译器无法理解的东西,所以您将得到一个错误消息。 这个语句将文本字符串"Oranges are not the only fruit…"发送到命令行,后跟另一个换行符(endl), 然后是另一个文本字符串"- and we have ",接着是存储在变量 fruit 中的值,最后是另一个文本字符 串"fruits in all."。可以按照这种方法把想输出的一系列内容串起来。这个语句从左向右执行,每一项 都依次发送到 cout。注意,发送到 cout 的每一项的前面都有自己的<<运算符。 第三个也就是最后一个输出语句把另一个换行符发送到屏幕,这 3 个语句将生成前述的输出。 这个程序中的最后一个语句是: return 0; // Exit the program 它将终止 main()函数的执行,从而停止这个程序的执行。控制权返回到操作系统,返回的代码 0 告诉操作系统:应用程序成功终止。忽略 main()的 return 语句,程序应能编译并执行。本章的后 面将详细地讨论所有这些语句。 程序中的语句按照编写它们的顺序执行,除非一个语句明确改变了这个自然顺序。第 3 章将讨 论改变执行顺序的语句。 Visual C++ 2013 入门经典(第 7 版) 30 2.1.3 空白 空白包含空格、制表符、换行符、换页符和注释的任意序列。空白将语句的各个部分分隔开, 从而使编译器能够识别语句中的一个元素(如 int)在何处结束,下一个元素在何处开始。否则,将忽 略空白,且不会产生任何影响。 以下面的语句为例: int fruit; // ...then another one 为了使编译器能够区分 int 和 fruit,它们之间至少有一个空白字符,但是如果添加更多的空白字 符,它们将被忽略。这一行分号后面的内容全部是空白,所以都被忽略。 另一方面,下面的语句: fruit = apples + oranges; // Get the total fruit fruit 和=之间,=和 apples 之间不需要空白字符,但也可以添加一些空白字符。这是因为=不是 字母或数字字符,所以编译器可以将它与其周围的内容区分开。类似地,符号+的两边也不需要有 空白字符,但是也可以添加一些。 除了在语句元素之间用作分隔符的空白之外,编译器将忽略其他空白(当然,在双引号之间的一 串字符中使用的空白除外)。可以添加一些空白,使程序更具可读性,记住,每当出现分号时,语 句就结束。 2.1.4 语句块 可以把几个语句括在一对大括号中,这时它们就变成了块或复合语句。函数体就是块的一个示 例。可以把这样的复合语句看成单个语句(在第 3 章讨论判断语句时,将看到这种情况)。只要是可 以放单个语句的地方,都可以放置一个用大括号括起来的语句块。因此,块可以置入其他块中。实 际上,块可以嵌套(一个块在另一个块内)至任意深度。 2.1.5 自动生成的控制台程序 上一个示例生成了一个没有源文件的空项目,然后添加了源文件。如果允许 Application Wizard 生成这个项目,如第 1 章所述,则这个项目将包含几个文件,下面深入分析它们的内容。创建一个 新的 Win32 控制台项目 Ex2_01A,这次允许 Application Wizard 完成这个项目,而不在 Application Settings 对话框中设置任何选项。这个项目有 4 个包含代码的文件:Ex2_01A.cpp 和 stdafx.cpp 源文 件、stdafx.h 头文件以及 targetver.h 文件,其中 targetver.h 文件指定能运行应用程序的 Windows 的最 早版本。这表示一个不执行任何任务的有效程序。从主菜单上选择 File | Close Solution 菜单项,可 以关闭打开的项目。在现有项目打开的情况下,可以创建一个新项目,这时将自动关闭旧项目,除 非选择在相同的解决方案中添加它。 Ex2_01A.cpp 的内容是: // Ex2_01A.cpp : Defines the entry point for the console application. 语句块对变量产生重要影响,本章后面讨论变量作用域时,将讨论它。 第 2 章 数据、变量和计算 31 // #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { return 0; } 这确实和前一个示例不同。其中有一个用于 stdafx.h 头文件的#include 指令,和程序执行的起始 函数_tmain(),而不是 main()。第 5 章将介绍函数头中圆括号的内容。 1. 预编译的头文件 Application Wizard 生成的 stdafx.h 头文件是该项目的一部分,观察一下其中的代码,将看到还 有 3 个#include 指令,分别用于前面提到的 targetver.h 头文件,以及标准库头文件 stdio.h 和 tchar.h。 stdio.h 是用于标准 I/O 的老式头文件,在 C++当前的标准出台之前使用,它的功能和 iostream 头文 件类似,但没有定义相同的名称。这个控制台示例使用 iostream 符号,所以需要包含它。tchar.h 是 Microsoft 特有的头文件,它定义文本函数。 stdafx.h 仅在修改代码时编译,而不是在每次编译程序时重新编译它。编译 stdafx.h 会得到一 个.pch 文件(预编译的头文件),只有没有对应的.pch 文件,或者.pch 文件的时间戳比 stdafx.h 文件的 时间戳早,编译器才重新编译 stdafx.h。一些标准库头文件非常大,所以这个功能可以显著减少编译 项目所需的时间。如果仅在 Ex2_01A.cpp 中给 iostream 包含#include 指令,则每次编译程序时,都 重新编译它。如果把它放在 stdafx.h 中,iostream 就只编译一次。因此,stdafx.h 应包含不常修改的 所有头文件的#include 指令。这包括项目的标准头文件和很少修改的所有自定义项目头文件。在学 习 C++时,不会使用出现在 stdafx.h 中的这两种头文件。 2. Main 函数名 如前所述,在编写使用 Unicode 字符的程序时,Visual C++支持 wmain()作为 main()的替代函数, wmain()是 main()的 Microsoft 特有定义,不是标准 C++的一部分。tchar.h 头文件定义了名称_tmain, 它一般由 main 取代,但是如果定义了符号_UNICODE,它就由 wmain 取代。为了把程序标识为使 用 Unicode,需要在 stdafx.h 头文件的开始处添加下列语句: #define _UNICODE #define UNICODE 为什么需要两个语句?定义符号_UNICODE,会让 Windows 头文件假定,Unicode 字符是默认 的。定义_UNICODE 会给 C++标准库附带的 C 例程头文件带来相同的效果。Ex2_01A 项目并不需 要这么做,因为 Character Set 项目属性默认设置为使用 Unicode 字符。前面详细解释了 main()函数, C++控制台示例坚持使用普通但成熟的 main()函数,它是标准 C++函数,因此是可移植性最好的编 码方法。 如果愿意,可以给本书中的控制台示例使用默认的控制台项目。此时只需要把示 例中的 main()函数体作为 _tmain()函数体,还应把标准库头文件的所有 #include 指令放 在 stdafx.h 中。 Visual C++ 2013 入门经典(第 7 版) 32 2.2 定义变量 在所有的计算机程序中,一个基本的目标是操作数据,获得结果。这个过程中的一个基本元素 是获得一段内存,可以称其为自己的内存,使用一个有意义的名称引用它,并在其中存储一条数据。 所指定的每段内存都称为一个变量。 我们知道,每种变量都存储一种特定的数据,在定义了变量后,它可以存储的数据类型就是固定 的。存储整数的变量,就不能存储小数。变量在某一时刻包含的值由程序中的语句确定,随着程序计 算的进展,它的值通常多次改变。 2.2.1 命名变量 变量的名称(其实是 C++中所有事物的名称)可以是任意字母和数字的序列,其中下划线_算作字 母,其他字符则不允许使用,如果在名称中使用了一些其他字符,编译程序时就会得到一个错误消 息。名称必须以字母或下划线开头,通常表明所存储的信息的种类。名称也称为标识符。 在 Visual C++中,变量名最长可以有 2048 个字符,因此给变量命名有相当大的灵活性。如果使 用长名称会使程序难以阅读,除非键盘技巧很高,否则长名称很难输入。更严重的问题是,并非所 有编译器都支持这么长的名称。事实上,很少需要使用 10 或 15 个字符以上的名称。 最好避免使用以下划线开头、且包含大写字母的名称,如_Upper 和_Lower,因为它们可能与相 同形式的标准库名称发生冲突。由于同样的原因,还应当避免使用以双下划线开头的名称。 下面是一些有效的变量名: price discount pShape value_ COUNT five NaCl sodiumChloride tax_rate 涉及两个或多个单词的有意义的名称可以用各种方式构建:第二个单词和以后各单词的首字母 大写,或者在各单词之间插入下划线。前面列表里有一些示例。本书的代码中给名称使用各种样式, 但最好在一个程序中只使用一种样式。 8_Ball、7Up 和 6_pack 不是合法的名称。Hash!或 Mary-Ann 也不是合法的名称。最后这个示 例是一个很常见的错误,但带有下划线的 Mary_Ann 是合法的。当然,Mary Ann 不是合法的名称, 因为变量名不允许有空白。名称 republican 和 Republican 是不同的,因为名称是区分大小写的。一 个常见的约定是类名以大写字母开头,变量名以小写字母开头,参见第 8 章。 2.2.2 关键字 关键字是 C++中的保留字,它们在该语言内有特殊的意义。关键字不能用作代码中的名称。编 辑器用特定的颜色突出显示关键字,默认为蓝色。如果没有突出显示关键字,那么说明输入了不正 确的关键字。如果不喜欢编辑器使用的默认颜色来标识各种语言元素,可以修改颜色:从 Tools 菜 单中选择 Options 菜单项,在出现的对话框左窗格中选择 Environment/Fonts and Colors 选项,就可以 进行修改了。 记住,关键字和名称一样,也是区分大小写的。例如,本章前面输入的程序包含关键字 int 和 return,如果把它们写成了 Int 或 Return,这些就不是关键字,不会被识别出来,也不会突出显示为 第 2 章 数据、变量和计算 33 蓝色。在学习本书的过程中,您将看到更多的关键字。 2.2.3 声明变量 如前所述,变量声明是一个语句,它指定变量的名称和类型。例如: int value; 这个语句声明了一个名称为 value 的变量,它可以存储整数。在变量 value 中可以存储的数据类 型由关键字 int 指定,所以只能使用 value 存储 int 类型的数据。 一个声明可以指定几个变量的名称: int cost, discount_percent, net_price; 不推荐这么做,比较好的方法一般是用一个语句声明一个变量,每行只写一个语句。本书有时 会违背这个规则,但这仅仅是为了不让代码占用太多篇幅。 为了存储数据,需要将一段计算机内存与变量名关联起来。这个过程称为变量定义。除了本书 提及的一些特殊情况之外,变量声明也是一个定义,它引入了变量名,并将它与适当容量的一段内 存联系起来。 下面的语句 int value; 既是一个声明,又是一个定义。已声明的变量名 value 用来访问与之关联的一段计算机内存, 这段内存可以存储一个 int 类型的值。这样区别声明和定义明显有一些学究气,因为将遇到的语句 是声明而不是定义。为了避免编译器报告错误消息,必须在第一次使用变量之前声明它。最好在接 近于首次使用变量的位置声明它们。 2.2.4 变量的初始值 在声明变量时,还可以给它赋予初始值。将初始值赋给变量的声明称为初始化。初始化有三种 语法形式,最后介绍推荐方式。下列语句给每个变量赋予一个初始值: int value = 0; int count = 10; int number = 5; value 的值是 0,cout 的值是 10,number 的值是 5。 初始化变量的第二种方式是使用函数表示法。这时不使用等号和数值,而是把数值写入变量名 后面的圆括号内。前面的声明可以重写为: int value(0); int count(10); int number(5); 第三种是推荐方式:初始化列表。前面三个语句可以写为: int value{}; int count{10}; int number{5}; Visual C++ 2013 入门经典(第 7 版) 34 初始值放在变量名后面的花括号中。如果花括号为空,如 value 的定义,就假定 value 的值是 0。 这种记号由 C++ 11 标准引入,前面两种方法仍是有效的,但目前第三种是推荐方式。因为这种记 号可以用在几乎每种情形下,使初始化统一起来。本书的后面就使用这种形式,并指出不能使用该 形式的一些情形。 如果没有提供初始值,变量就通常将包含前一个程序在该变量占用的内存中留下的无用信息(本 章后面将遇到一个例外情形)。应尽可能在定义变量时进行初始化。如果变量一开始就有已知值,在 出错时就比较容易解决所发生的问题。有一件事是确定的—— 一定会出现差错。 2.3 基本数据类型 变量可以保存的信息种类由其数据类型确定。程序中的所有数据和变量都必须是某种已定义的 类型。C++提供了一系列由特定关键字指定的基本数据类型。之所以称为基本数据类型,是因为它 们存储表示计算机中基本数据的值,特别是数值,字符也是数值,因为字符由数字字符代码表示。 前面介绍过整型变量的关键字 int。 基本类型分为 3 类: ● 存储整数的类型 ● 存储非整数值的类型,也称为浮点类型 ● 指定空的值集或者不指定任何类型的 void 类型 2.3.1 整型变量 整型变量只能包含整数值。足球队中球员的数量就是一个整数,至少在比赛开始时是这样。可以 使用关键字 int 来声明整型变量。int 型变量在内存中占用 4 个字节,可以存储正负整数值。int 型变量 的值的上下限对应于最大和最小的带符号二进制数字,它们可以表示为 32 位。int 型变量的上限是 231–1,即 2 147 483 647;下限是–(231),即–2 147 483 648。下面是定义 int 型变量的一个示例: int toeCount {10}; 关键字 short 可以定义占用 2 个字节的整型变量。关键字 short 等同于 short int,所以利用下列语 句可以定义两个 short 型变量: short feetPerPerson {2}; short int feetPerYard {3}; 这两个变量具有相同的类型,因为 short 的含义和 short int 完全相同。在此使用这两种形式的类 型名称是为了说明它们的用法,但最好只使用一种表示方法,short 用得更多一些。 Visual C++中的整数类型 long 占用 4 个字节,因此存储的值域与 int 类型相同。在其他一些 C++ 编译器中,long 类型占用 8 个字节。该类型也可以写作 long int。下面的语句声明了 long 型变量。 long bigNumber {1000000L}; long int largeValue {}; 这些语句声明了变量 bigNumber 和 largeValue,它们的初值分别是 1 000 000 和 0。字面值结尾处 附加的字母 L 指定它们是 long 类型。也可以将小写字母 l 用于相同的目的,但其缺点是,它容易和 第 2 章 数据、变量和计算 35 数字 1 混淆。没有附加 L 的整型字面值属于 int 型。 使用 long long 型变量可以存储更大的整数: long long huge {100000000LL}; long long 型变量占用 8 个字节,可存储的值的范围是–9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807。表明整数为 long long 类型的后缀是 LL 或 ll,最好避免使用小写形式, 因为它看起来像数字 11。 2.3.2 字符数据类型 数据类型 char 有双重用途。它指定 1 字节变量,可以存储给定值域内的整数,或者存储单个 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)字符的代码。可 以利用下列语句声明 char 型变量: char letter {'A'}; 这个语句声明了变量 letter,并把它初始化为常量'A'。指定的值是位于单引号之间的单个字符, 而不是前面在定义要显示的一串字符时使用的双引号。由于'A'是由 ASCII 码表示的十进制数值 65, 因此可以把上面的语句写成: char letter {65}; // Equivalent to A 这个语句产生的结果和前面那个语句相同。可以存储在 char 型变量中的整数的范围是 –128~127。 类型 wchar_t 的叫法源于它是宽字符类型,VC++中这种类型的变量存储 2 字节字符代码,值域 为 0~65 535。下面是定义 wchar_t 型变量的一个示例: wchar_t letter {L'Z'}; // A variable storing a 16-bit character code 这个语句定义了变量 letter,并用字母 Z 的 16 位代码初始化它。字符常量'Z'前面的字母 L 告诉 编译器,这是一个 16 位字符代码值。 也可以使用十六进制常量初始化整型变量,包括 char 型变量。十六进制数是利用十六进制数字 的标准表示法编写的:0~9,A~F(或者 a~f)——表示数字值 10~15。为了与十进制值相区分,它还以 0x(或者 0X)开头。因此,为了得到完全一致的结果,可以把上面的语句重写成: 在代码中编写数值时,不能包括逗号。在正文中可以把数字写成 12,345,但是在 代码中,必须把它写成 12345。 C++标准不要求 char 型变量必须表示有符号的 1 字节整数。 至于 char 型变量是表 示–128~127 的有符号整数,还是 0~255 的无符号整数, 是由编译器的实现方式决定 的。如果要将 C++代码移植到一个不同的环境,就需要考虑这一点。 Visual C++ 2013 入门经典(第 7 版) 36 wchar_t letter{0x5A}; // A variable storing a 16-bit character code Microsoft Windows 提供了一个字符映射(Character Map)实用程序,它能够根据可用的字体查找 字符。它将显示十六进制的字符代码,并指出输入这个字符时要使用的键。 2.3.3 整型修饰符 char、int、short、long 或 long long 整型变量存储有符号的整数值,所以可以使用这些类型存储 正值或负值。这是因为这些类型假定具有默认的类型修饰符 signed。因此,只要使用 int 或 long,都 可以把它们分别写成 signed int 或 signed long。 还可以单独使用关键字 signed 来指定变量的类型,这时它表示 signed int。例如: signed value {-5}; // Equivalent to signed int 这种用法不常见,笔者更喜欢使用 int,这会使含义比较明显。 可以存储在 char 型变量中的值的范围为–128~127,这和 signed char 型变量相同。尽管如此, char 型和 signed char 型仍然是不同的类型,因此不应当错误地认为它们是一样的。char 型是否带符 号一般由具体的实现方式来决定。Visual C++把它定义为 signed char,但其他编译器可能不是这样。 如果确信不需要存储负值(例如,如果记录一周内行驶的英里数),则可以把变量指定为 unsigned: unsigned long mileage {0UL}; 变量 mileage 可以存储的最小值是 0,最 大 值 是 4 294 967 295(即 232–1)。比较这个值域与 signed long 的–2 147 483 648~2 147 483 647(-231~231–1)。signed 变量中用于确定数值符号的位,在 unsigned 变量中是数值的一部分。因此,unsigned 变量可以存储更大的正值,但不能表示负值。注意要把 U(或 u)附加到 unsigned 常量上。在前面的示例中,还附加了 L,表明这个常量是 long 型。可以使用 U 和 L 的大写或小写字母,而且它们的顺序并不重要。但最好采用一致的方法。 也可以单独把 unsigned 作为变量的类型规范,这时指定的类型是 unsigned int。 2.3.4 布尔类型 布尔变量只能存储两个值:true(真)和 false(假)。布尔变量也称为逻辑变量,逻辑变量的类型 是 bool,这是以 George Boole 的名字命名的,他开发了布尔代数,类型 bool 被认为是整数类型。bool 型变量用来存储可以是 true 或 false 的测试结果,如两个值是否相等。 可以利用下列语句声明 bool 型变量: bool testResult; 不要写具有前置零的十进制整数值。 编译器将把这样的值翻译为八进制值 (基数为 8),所以写成 065 的值将等同于十进制表示法中的 53。 signed 和 unsigned 都是关键字,不能用作变量名。 第 2 章 数据、变量和计算 37 当然,也可以在声明 bool 变量时进行初始化: bool colorIsRed {true}; 2.3.5 浮点类型 不是整数的数值存储为浮点数。浮点数可以表示为 112.5 这样的小数值,或者具有指数的小数 值,如 1.125E2,其中小数部分与 E(代表指数)后面指定的 10 的幂相乘。因此,1.125E2 是 1.125× 102,即 112.5。 如下列语句所示,可以使用关键字 double 指定浮点型变量: double in_to_mm {25.4}; double 型变量占用 8 个字节的内存,它存储的值可以精确到大约 15 个小数位,所存储值的值域 则比 15 个数位精度表示的值域宽得多,从 1.7×10–308~1.7×10308,包括正数和负数。如果不需要 15 个数位的精度,也不需要 double 型变量提供的大值域,那么可以使用关键字 float 声明占用 4 个字 节的浮点型变量。例如: float pi {3.14159f}; 这个语句定义了初始值为 3.141 59 的变量 pi。常量结尾的 f 指定它属于 float 型。如果没有 f,这 个常量就是 double 型。float 型的变量有大约 7 个小数位的精度,值域为 3.4×10–38~ 3.4×1038,包 括正数和负数。 C++标准还定义了 long double 浮点类型,在 Visual C++中实现时,这种类型具有和 double 类型 相同的值域和精度。在某些编译器中,long double 浮点类型对应于 16 字节的浮点值,它的值域和精 度都比 double 类型的大得多。 2.3.6 C++中的基本类型 表 2-1 总结了 Visual C++支持的所有基本类型和值域。 TRUE 和 FALSE 这两个值广泛应用于数字类型的变量,特别是 int 型变量。这是 时代的遗留物,在 C++中实现 bool 类型之前,通常使用 int 型变量表示逻辑值。此时, 零被视为假,而非零被视为真。符号 TRUE 和 FALSE 仍然在 MFC 内使用,它们分别 表示非零整数值和 0。注意, TRUE 和 FALSE(写成大写字母 )不是关键字, 只是在 Win32 SDK 内部定义的符号。 TRUE 和 FALSE 不是合法的 bool 值,所以不能混淆 true 和 TRUE。 浮点常量必须包含一个小数点、一个指数或者两者都有。 如果数字值既没有小数 点也没有指数,那么它是一个整数。 Visual C++ 2013 入门经典(第 7 版) 38 表 2-1 类 型 字 节 数 值 域 bool 1 true 或 false char 1 默认情况下和 signed char 型一样:–128~127。另外,也可以使 char 型变量的 值域和 unsigned char 型一样 signed char 1 –128~127 unsigned char 1 0~255 wchar_t 2 0~65 535 short 2 –32 768~32 767 unsigned short 2 0~65 535 int 4 –2 147 483 648~2 147 483 647 unsigned int 4 0~4 294 967 295 long 4 –2 147 483 648~2 147 483 647 unsigned long 4 0~4 294 967 295 long long 8 –9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 unsigned long long 8 0 ~ 18 446 744 073 709 551 615 float 4 ±3.4×10±38,精度大约为 7 个数位 double 8 ±1.7×10±308,精度大约为 15 个数位 long double 8 ±1.7×10±308,精度大约为 15 个数位 2.3.7 字面值 前面使用大量的显式常数来初始化变量,所有种类的常数都称为字面值。字面值是特定类型的 值,所以 23、3.14159、9.5f 和 true 这些值分别是 int 型、double 型、float 型和 bool 型字面值的示例。 “Samuel Beckett”是一个字符串字面值的示例,第 4 章将讨论字符串的类型。表 2-2 总结了如何书 写各种类型的字面值。 表 2-2 类 型 字面值示例 char、signed char 或 unsigned char 'A'、'Z'、'8'、'*' wchar_t L'A'、L'Z'、L'8'、L'*' int –77、65、12 345、0x9FE unsigned int 10U、64000u long –77L、65L、12345l unsigned long 5UL、999999UL、25ul、35Ul long long –777LL、66LL、1234567ll 第 2 章 数据、变量和计算 39 (续表) 类 型 字面值示例 unsigned long long 55ULL、999999999ULL、885ull、445Ull float 3.14f、34.506F double 1.414、2.71828 long double 1.414L、2.71828l bool true、false 不能指定 short 型或 unsigned short 型字面值,但如果这些类型变量的初值是 int 型字面值,且在 int 型的值域内,那么编译器将接受这些初值。 计算时,经常需要使用字面值,例如,把 12 英尺转换成英寸,把 25.4 英寸转换成毫米。但是, 应当避免显式地使用含义不明显的数字字面值。2.54 是 1 英寸对应的厘米数,这未必对每个人都是 显而易见的。最好声明一个有固定值的变量—— 例如,可以命名一个值为 2.54 的变量 inchesToCentimeters。然后,只要在代码中使用 inchesToCentimeters,它的含义都非常明显。本章稍 后将讨论如何固定变量的值。 2.3.8 定义类型的别名 typedef 关键字能够为现有的类型定义自己的类型名称。例如,利用下列声明可以把名称 BigOnes 定义为标准 long int 类型的别名: typedef long int BigOnes; // Defining BigOnes as a type name 这个语句把 BigOnes 定义为 long int 的别名,因此可以利用下列声明把变量 mynum 声明为 long int 型: BigOnes mynum {}; // Define a long int variable 这个声明和使用内置类型名称的声明没有任何区别。也可以使用: long int mynum {}; // Define a long int variable 定义了类型别名如 BigOnes 后,在同一个程序内,就可以使用这两种类型说明符声明具有相同 类型的不同变量。 C++ 11 标准引入了另一个语法,它使用 using 关键字来定义类型别名。BigOnes 别名的定义可 以写为: using BigOnes = long int; 在=的左边写类型别名,在=的右边写原来的类型。这个语句的作用与使用 typedef 相同。后面 的示例将使用这种形式,但 typedef 也是可用的。 类型别名只是现有类型的同义词,所以它看来有点无关紧要,但事实并非如此。如后面所述, 类型别名能够发挥非常有益的作用。它可以为较复杂的类型规范定义简单的名称,简化了复杂的声 明,提高了代码的可读性。 Visual C++ 2013 入门经典(第 7 版) 40 2.4 基本的输入/输出操作 这里只介绍接着学习 C++所需的输入和输出操作。这并不困难—— 事实上正好相反—— 但是对 于 Windows 编程来说,根本不需要了解它们。C++输入/输出围绕着数据流这个概念,可以把数据插 入输出流,或者从输入流中提取数据。如前所述,到命令行的标准输出流称为 cout,来自键盘的互 补输入流称为 cin。当然,这两个流的名称都定义在 std 名称空间中。 2.4.1 从键盘输入 使用流的析取运算符>>,可以通过标准输入流 cin 从键盘获得输入。要从键盘把 2 个整数值读入 整型变量 num1 和 num2,可以编写下列语句: std::cin >> num1 >> num2; 析取运算符(>>)“指向”数据流动的方向—— 在这个例子中,数据从 cin 依次流动到这两个变量。 存储输入的变量类型确定需要的数据类型。跳过输入中所有前置的空白,所输入的第一个整数值被 读入 num1。这是因为该输入语句从左向右执行。忽略 num1 后面的所有空白,所输入的第二个整数 值被读入 num2。在连续的值之间必须有一些空白,以便区分它们。在按下 Enter 键时,流输入操作 结束,然后继续执行下一个语句。当然,如果输入了错误的数据,就会出现错误,但这里假定您始 终没犯错误! 读入浮点值的方法和整数完全相同,当然,可以混合使用这两种值。流输入和操作将自动处理 各种基本类型的变量和数据。例如: int num1 {}, num2 {}; double factor {}; std::cin >> num1 >> factor >> num2; 最后一行将把一个整数读入 num1,然后把一个浮点值读入 factor,最后把一个整数读入 num2。 2.4.2 到命令行的输出 前面提及到命令行的输出,这里再介绍一下。输出将按照输入的互补方式操作。标准输出流是 cout,使用插入运算符(<<)把数据传递到输出流。这种运算符也“指向”数据移动的方向。前面已经 使用这种运算符输出了文本字符串。下面利用一个简单的程序演示输出变量值的过程。 试一试:到命令行的输出 假设已经创建了一个新的空项目,即首先在这个项目中添加新的源文件,然后把源文件编译成 可执行文件。下面是在创建了项目 Ex2_02 后,需要放入源文件的代码: // Ex2_02.cpp // Exercising output #include using std::cout; using std::endl; int main() 第 2 章 数据、变量和计算 41 { int num1 {1234}, num2 {5678}; cout << endl; // Start on a new line cout << num1 << num2; // Output two values cout << endl; // End on a new line return 0; // Exit program } 示例说明 由于对 std::cout 和 std::endl 使用了 using 声明,因此可以在代码中使用非限定的流名称。main() 中的第一个语句声明并初始化两个整型变量 num1 和 num2。然后是三个输出语句,第一个输出语句 将屏幕光标移动到新行。由于输出语句从左向右执行,因此第二个输出语句显示 num1 的值,紧接 着显示 num2 的值。 在编译和执行这个程序后,将得到下列输出: 12345678 这个输出是正确的,但没有用。我们真正需要的是至少由一个空格分开的两个输出值。默认的 流输出只输出数字,不能把连续的输出值分隔开,以便区分它们。像现在这样,无法分辨第一个数 据结束的位置和第二个数字开始的位置。 2.4.3 格式化输出 只需要在两个数值之间输出一个空格,就可以轻松地解决这个问题。为此,只需要把原来程序 中的 cout << num1 << num2; // Output two values 替换成下列语句: cout << num1 << ' ' << num2; // Output two values 如果有几行输出,且想在列中对齐它们,就需要某种额外的操作,因为事先不知道每个数值中 有多少数字。利用操作符可以处理这种情况。操作符将修改数据输出到流(或者从流输入)的方式。 操作符在头文件 iomanip 中定义,因此需要添加这个头文件的#include 指令。要使用的操作符是 std::setw(n),它使下一个输出值在 n 个字符宽的字段中右对齐,所以 std::setw(6)使下一个输出值在 宽度为 6 个空格的字段中显示。下面看看它的使用情况。 试一试:使用操作符 为了获得更合适的输出,可以把上面的程序修改成: // Ex2_03.cpp // Exercising output #include #include using std::cout; using std::endl; using std::setw; Visual C++ 2013 入门经典(第 7 版) 42 int main() { int num1 {1234}, num2 {5678}; cout << endl; // Start on a new line cout << setw(6) << num1 << setw(6) << num2; // Output two values cout << endl; // Start on a new line return 0; // Exit program } 示例说明 对 Ex2_02.cpp 的修改有:添加了用于 iomanip 头文件的#include 指令,针对 std 名称空间中的名 称 setw 添加了一个 using 声明,在输出流中每个数值的前面插入了 setw()操作符。现在的输出非常 整齐: 1234 5678 注意,setw()操作符插入流中后,只应用于其后的单个输出值。后续的数值都以默认的方式输出。 对每个要在给定字段宽度内输出的数值,都必须在它们的前面插入这个操作符。 iomanip 头文件中的另一个有用的操作符是 std::setiosflags。它可以使输出在给定字段宽度内左 对齐,而不是默认的右对齐。其用法如下: cout << std::setiosflags(std::ios::left); std::ios::left 是该操作符设置的标记,它使输出在给定字段宽度内左对齐,也可以设置其他标记, 使用 std::setiosflags 来控制数字输出的格式,这值得探讨。 2.4.4 转义序列 在双引号之间编写单个字符或字符串时,要通过特殊的字符序列(称作转义序列)来指定一些 字符。之所以称为转义序列,是因为它们通过避开一些字符的默认解释,允许指定不能利用其他方 法表示的字符。不能在双引号之间包含的字符或指定为单个字符字面值的示例是换行符。按下回车 键来表示换行符,只会把光标移动到下一行——该字符本身不会输入到代码中。 转义序列以反斜杠字符(\)开头,反斜杠字符告诉编译器按照特殊的方法解释后面的字符。例如, 制表符写作\t,所以编译器将 t 理解为表示制表符,而不是字母 t。观察下面的两个语句: cout << endl << "This is output."; cout << endl << "\tThis is output after a tab."; 它们将输出下面这些行: This is output. This is output after a tab. 第二个语句中的\t 使输出缩进到第一个制表符位置。可以在每个字符串中使用换行字符的转义 序列\n,而不是 endl,所以前面的语句可以重写为: cout << "\nThis is output."; cout << "\n\tThis is output after a tab."; 输出是相同的,但注意\n 与 endl 不完全相同。endl 会输出一个新行,并刷新数据流,而\n 仅输 第 2 章 数据、变量和计算 43 出一个新行,但不刷新数据流。刷新输出流会使流缓存中的所有数据写入设备。 表 2-3 给出了一些非常有用的转义序列。 表 2-3 转 义 序 列 作 用 转 义 序 列 作 用 \a 发出蜂鸣声 \b 退格 \n 换行 \t 制表符 \' 单引号 \" 双引号 \\ 反斜杠 \? 问号 显然,如果希望在字符串中把反斜杠或双引号作为字符显示,就必须使用适当的转义序列来表 示它们。否则,反斜杠将被解释为另一个转义序列的开始,双引号将表示字符串的结束。同样,要 定义单引号字符字面值,必须使用'\''。 当然,在初始化 char 型变量时,可以使用转义序列。例如: char Tab {'\t'}; // Initialize with tab character 试一试:使用转义序列 下列程序使用了表 2-3 中的一些转义序列: // Ex2_04.cpp // Using escape sequences #include using std::cout; int main() { char newline {'\n'}; // Newline escape sequence cout << newline; // Start on a new line cout << "\"We\'ll make our escapes in sequence\", he said."; cout << "\n\tThe program\'s over, it\'s time to make a beep beep.\a\a"; cout << newline; // Start on a new line return 0; // Exit program } 如果编译和执行这个示例,那么将产生下列输出: "We'll make our escapes in sequence", he said. The program's over, it's time to make a beep beep. 可以把问号放在字符串或字符字面值中。 \?转义序列可以避免与“三联符序列” 冲突,三联符序列是一个 C 语言结构,它包含以 ??开头的三个字母,来定义不能用其 他方法表示的字符。三联符序列目前很少使用,但仍是语言标准的一部分。 Visual C++ 2013 入门经典(第 7 版) 44 示例说明 main()函数中的第一行把变量 newline 定义为 char 型,并利用换行符的转义序列来初始化它。 然后就可以使用 newline 代替标准库中的 endl。 把 newline 写入 cout 以后,输出了一个使用双引号(\")和单引号(\')转义序列的字符串。在这里 不必使用单引号的转义序列,因为这个字符串由双引号定界,编译器会识别单引号字符,不会把它 解释为定界符。但是,在这个字符串中必须使用双引号的转义序列。这个字符串以一个换行转义序 列开头,然后是一个制表符转义序列,所以输出行将按照制表符距离缩进。这个字符串还以蜂鸣声 转义序列的两个实例结束,所以您将听到从 PC 的扬声器中发出两个嘀嘀声。 2.5 C++中的计算 现在开始处理输入的数据。前面介绍了如何完成简单的输入和输出,下面要开始了解中间环节: 程序的“处理”部分。C++计算的几乎所有方面都相当直观,所以这一部分应当很容易学习。 2.5.1 赋值语句 前面介绍了赋值语句的一些示例。典型的赋值语句如下所示: whole = part1 + part2 + part3; 这个赋值语句计算等号右边的表达式的值,在这个例子中是 part1、part2 和 part3 的和,并把结 果存储在等号左边指定的变量中,在这个例子中是 whole 变量。在这个语句中,whole 是它的各个 部分的和。 还可以编写连续赋值的语句,如: a = b = 2; 这相当于把值 2 赋给 b,然后把 b 的值赋给 a,所以这两个变量最后都将存储值 2。 2.5.2 算术运算 基本算术运算符是加法、减法、乘法和除法,分别由符号+、–、* 和 /表示。除了除法在处理 整数时稍有偏差之外,它们的运算在一般情况下和预期的一样。可以编写如下所示的语句: netPay = hours * rate - deductions; 这里将计算 hours 和 rate 的乘积,然后从得到的值中减去 deductions。乘法和除法运算在加法和 减法运算之前执行。本章后面将更完整地讨论表达式中各种运算符的执行顺序。表达式 hours * rate – deductions 的计算结果将存储在变量 netPay 中。 上一个语句使用的减号有两个操作数—— 它从其左边操作数的值中减去其右边操作数的值。由 于涉及两个值,因此这称为二元运算符。减号也可以用于一个操作数,改变它所涉及的值的符号, 这时它称作一元减号。可以编写下列语句: int a {}; int b {-5}; a = -b; // Changes the sign of the operand so a is 5 第 2 章 数据、变量和计算 45 其中,a 被赋予值+5,因为右边表达式中的一元减号改变了操作数 b 的值的符号。 赋值语句不等同于高中代数中的方程。赋值语句指定一个要执行的动作,而不是声明一个事实。 在执行过程中,将对赋值运算符右边的表达式进行计算,然后把结果存储在左边指定的位置。 看下列语句: number = number + 1; 这个语句表示“给存储在 number 中的当前值加 1,然后把结果放回 number 中”。作为正常的代 数语句,它没有任何意义。但作为一个编程操作,显然是有意义的。 试一试:基本算术运算 通过计算给一个房间贴壁纸时需要多少卷标准壁纸,以练习一下基本算术运算。下面的示例可 以做到这一点: // Ex2_05.cpp // Calculating how many rolls of wallpaper are required for a room #include using std::cout; using std::cin; using std::endl; int main() { double height {}, width {}, length {}; // Room dimensions double perimeter {}; // Room perimeter const double rollWidth {21.0}; // Standard roll width const double rollLength {12.0*33.0}; // Standard roll length(33ft.) int strips_per_roll {}; // Number of strips in a roll int strips_reqd {}; // Number of strips needed int nrolls {}; // Total number of rolls cout << endl // Start a new line << "Enter the height of the room in inches: "; cin >> height; cout << endl // Start a new line << "Now enter the length and width in inches: "; cin >> length >> width; strips_per_roll = rollLength / height; // Get number of strips per roll perimeter = 2.0*(length + width); // Calculate room perimeter 一般情况下,赋值语句左边的表达式是一个变量名,但这不是必须的。它可以 是某种表达式,但是,如果它是一个表达式,则其计算结果必须是一个 lvalue。如后 面所述,lvalue 是一个持久的内存位置,赋值运算符右边的表达式的结果可以存储在 这里。 Visual C++ 2013 入门经典(第 7 版) 46 strips_reqd = perimeter / rollWidth; // Get total strips required nrolls = strips_reqd / strips_per_roll; // Calculate number of rolls cout << endl << "For your room you need " << nrolls << " rolls of wallpaper." << endl; return 0; } 除非打字比较熟练,否则在第一次编译这个程序时,可能会出现几个错误。一旦修正了输入错 误,它就能很好地编译和运行。编译器将产生两个警告消息。不用担心,编译器只是想确保你理解 正在发生的事情。下面将解释出现警告消息的原因。 示例说明 首先需要澄清一件事,如果由于使用这个程序而用完了壁纸,我将不承担任何责任!您将看到, 在估计需要的卷数时,出现所有错误的原因都在于 C++运行的方式以及贴壁纸时不可避免出现的浪 费—— 通常在 50%以上。 下面将按顺序解释这个程序中的语句,同时讲解一些有趣、新颖甚至令人兴奋的功能。现在你 已经熟悉了 main()函数体之前的语句,所以它们不成问题。 代码的布局有几个地方值得注意。首先,main()函数体中的语句是缩进的,以便于看到函数体 的范围。其次,各组语句由一个空行隔开,以表明它们是功能组。缩进语句是布置 C++代码时的一 个基本技巧。这么做一般是为了给出程序中各种逻辑块的可见提示。 1. const 修饰符 在 main()函数体一开始,有一个语句块,它们声明程序中使用的变量。这些语句相当常见,但 是其中两个语句包含新功能: const double rollWidth {21.0}; // Standard roll width const double rollLength {12.0*33.0}; // Standard roll length(33ft.) 它们都以一个新的关键字 const 开头。这是一种类型修饰符,表明变量不仅是 double 类型,而 且是常量。这告诉编译器,这些是常量,因此编译器将检查是否有试图修改这些变量值的语句,如 果发现了这样的语句,编译器将生成一个错误消息。可以对此进行检验,其方法是在声明 rollWidth 之后的任何地方添加如下语句: rollWidth = 0; 程序将不再编译,同时返回错误消息: 'error C3892: 'rollWidth' : you cannot assign to a variable that is const'. 把常量定义为 const 变量是非常有用的,特别是要在程序中多次使用同一个常量时。首先,与 遍布程序的、也许没有明显含义的零星字面值相比,这是一种更好的方法。数值 42 可能指的是生命 的价值、宇宙和任何东西,但是如果使用了一个名为 myAge、值为 42 的 const 变量,那么很明显, 所指的不是这些东西。其次,如果修改了 const 变量的初值,这个变化应用于源文件的所有地方。 如果使用显式的字面值,就必须逐个修改所有的字面值实例。 第 2 章 数据、变量和计算 47 2. 常量表达式 const 变量 rollLength 用算术表达式(12.0*33.0)初始化。使用常量表达式初始化变量,就不必亲 自计算数值。这种方法也更有意义,如在这个例子中,33 英尺乘以 12 英寸所表达的意思就比简单 地写成 396 清楚得多。编译器能准确地计算常量表达式,但是如果你亲自计算,则根据表达式的复 杂性和你处理数字的能力,这可能会出错。 在编译时,可以使用能够计算为常量的任何表达式,包括已经定义的 const 变量。例如,如果 这样做在这个程序中有用,那么可以把一标准卷壁纸的面积声明为: const double rollArea {rollWidth*rollLength}; 这个语句需要放在初始化 rollArea 时使用的两个 const 变量的声明后面,因为在出现常量表达式 的地方,编译器必须已经知道出现在常量表达式中的所有变量。 3. 程序输入 在声明了一些整型变量后,这个程序的下面 4 个语句将处理来自键盘的输入: cout << endl // Start a new line << "Enter the height of the room in inches: "; cin >> height; cout << endl // Start a new line << "Now enter the length and width in inches: "; cin >> length >> width; 其中,把文本写入 cout,提示需要的输入,然后使用标准输入流 cin 读取键盘的输入。首先得 到了 height 值,然后依次读取 length 和 width 的值。在实际的程序中,需要检查错误,也许还需要 确保输入值是切合实际的,但是你现在还没有足够的知识能做到这一点! 4. 计算结果 对于尺寸给定的房间,如下 4 个语句计算所需要的标准卷壁纸的数量: strips_per_roll = rollLength / height; // Get number of strips in a roll perimeter = 2.0*(length + width); // Calculate room perimeter strips_reqd = perimeter / rollWidth; // Get total strips required nrolls = strips_reqd / strips_per_roll; // Calculate number of rolls 利用长度对应于房间高度这个关系,通过用一个值除以另一个值,第一个语句将计算可以由一 个标准壁纸卷得到的壁纸条的数量。因此,如果房间高 8 英尺,那么用 96 除 396,得到浮点型结果 4.125。这里有一个微妙之处。存储这个结果的变量 strips_per_roll 是 int 型,所以它只能存储整数。 任何要存储为整数的浮点值都向下舍入为最接近的整数,在这个例子中是 4,这个值将被存储起来。 这实际上就是此处想要的结果,因为零碎的壁纸条可以贴在窗户下面或者门的上面,但是在估算时 最好忽略它们。 值从一种类型转换成另一种类型称为类型转换。上面这个示例是隐式类型转换,因为代码没有 显式地声明类型需要转换,所以编译器必须自己进行处理。在编译期间发出两个警告的原因是,插 入的隐式类型转换意味着信息可能会由于一种类型到另一种类型的转换而丢失。 Visual C++ 2013 入门经典(第 7 版) 48 在代码需要隐式类型转换时应当非常谨慎。编译器并不总是给隐式类型转换发出警告,如果 把一种类型的值赋给某种类型的变量,而这种类型的变量具有较小的值域,就有丢失信息的风险。 如果无意中把隐式类型转换包括到程序中,就可能出现难以查找的 bug。 在导致信息丢失的类型转换无法避免时,可以显式地指定转换。为此,要把赋值语句右边的值 显式类型转换或强制转换成 int,因此第一个语句将变成: strips_per_roll = static_cast (rollLength / height); // Get number // of strips in // a roll 在右边添加的 static_cast以及包围表达式的圆括号告诉编译器,要把圆括号内表达式的值 转换成尖括号中的类型 int。虽然仍将丢失这个值的小数部分,但是编译器假定你知道正在做什么, 不会发出警告。本章后面将详细地讨论 static_cast()和其他类型转换。 注意下一个语句计算房间的周长。为了将 length 与 width 的和同 2 相乘,计算这两个变量之和 的表达式放在圆括号内。这将确保首先执行加法运算,得到的结果再与 2.0 相乘,得到周长的值。 可以使用圆括号来保证计算按照要求的顺序执行,因为圆括号内的表达式始终先计算。在有嵌套圆 括号的地方,圆括号内的表达式按照从最里面到最外面的顺序计算。 第三个语句计算贴满整个房间需要多少壁纸条,它的作用与第一个语句一样:因为结果存储到 整型变量 strips_reqd 中,所以它向下舍入为最近的整数。这不是实际需要的结果。在估计时最好向 上舍入,不过您现在还没有足够的知识来做到这一点。在学习了第 3 章后,就可以回过头来进行 修改。 最后一个算术语句用所需壁纸条的数量(整数)除以一卷壁纸中壁纸条的数量(也是整数),计算需 要的壁纸卷的数量。由于用一个整数除以另一个整数,因此结果肯定是整数,忽略余数。如果变量 nrolls 是浮点型,那么这仍然是一个问题。由这个表达式得到的整数值在存储到 nrolls 中之前,将转 换成浮点形式。所得到的结果实际上与计算出浮点型结果然后向下舍入为最近的整数时相同。同样, 这不是您想要的结果,如果想使用这个结果,则需要修改它。 5. 显示结果 计算的结果由下列语句显示: cout << endl << "For your room you need " << nrolls << " rolls of wallpaper." << endl; 这是一个扩展成 3 行的输出语句。它首先输出一个换行符,然后输出文本字符串"For your room you need ",接着是变量 nrolls 的值,最后是文本字符串" rolls of wallpaper."。可以看到,输出语句非 常简单。换行符可以使用转义序列来表示,如下所示: cout << "\nFor your room you need " << nrolls << " rolls of wallpaper.\n"; 最后,当执行下列语句时,这个程序结束: return 0; 此处的 0 值是返回值,它将返回到操作系统。第 5 章将详细讨论返回值。 第 2 章 数据、变量和计算 49 2.5.3 计算余数 如前所述,用一个整数值除以另一个整数值,会得到一个忽略所有余数的整数值,所以 11 除以 4 的结果是 2。由于除法运算以后的余数可能非常重要,如在孩子们之间分甜饼时,因此 C++提供 了一种特殊的运算符%。下列语句处理分甜饼的问题: int residue {}, cookies {19}, children {5}; residue = cookies % children; 变量 residue 最后的值是 4,即 19 除以 5 之后剩余的数字。要计算每个孩子得到的甜饼数量, 只需要使用除法运算,如下列语句所示: each = cookies / children; 2.5.4 修改变量 经常需要修改变量的现有值,如增加它的值,或者使它的值增加一倍。可以使用下列语句增加 变量 count 的值: count = count + 5; 这个语句把 5 和 count 的当前值相加,然后把结果存储到 count 中,所以如果 count 开始的值是 10,那么它最后的值是 15。 这个语句还有一种简写方式: count += 5; 这条语句表示“取出 count 中的值,给它增加 5,然后把结果存储到 count 中”。还可以对其他 运算符使用这种表示法。例如: count *= 5; 这条语句把 count 的当前值和 5 相乘,然后把结果存储到 count 中。一般来说,可以编写下列形 式的语句: lhs op= rhs; lhs 代表这个语句左边的任何合法表达式,通常(但不一定)是一个变量名。rhs 代表这个语句右 边的任何合法表达式。其中,op 是下列运算符之一: + – * / % << >> & ^ | 前 5 个运算符已经介绍过,其他的是移位和逻辑运算符,本章后面将介绍它们。 这个语句的通用形式等同于: lhs = lhs op (rhs); 括住 rhs 的圆括号表示将首先求这个表达式的值,得到的结果将变成 op 的右操作数。 这意味着可以编写如下语句: Visual C++ 2013 入门经典(第 7 版) 50 a /= b + c; 这个语句的效果和下列语句相同: a = a/(b + c); 因此,a 的值将除以 b 与 c 的和,得到的结果将存储到 a 中,覆盖以前的值。 2.5.5 增量和减量运算符 现在介绍一些不寻常的算术运算符,称作增量和减量运算符,一旦对 C++的应用有了进一步的 兴趣,将发现它们非常有用。这些是一元运算符,用于给变量中存储的值增 1 或减 1。例如,假设 变量 count 是 int 型,则下面 3 个语句具有完全相同的效果: count = count + 1; count += 1; ++count; 它们都把变量 count 的值增加 1。最后一种使用增量运算符的形式无疑最简洁。 增量运算符不仅修改它涉及的变量的值,而且产生值。因此,使用增量运算符把变量的值增加 1,也可以作为较复杂表达式的一部分。表达式++count 增加变量的值,所得的结果是表达式的值。 例如,假设 count 的值是 5,把变量 total 定义为 int 型。假设编写了下列语句: total = ++count + 10; 这个语句首先把 count 的值增加到 6,表达式++count 的结果是 count 的最终值 6,然后将这个 结果和 10 相加,因此 total 的值是 16。 前面一直把增量运算符++写在它所涉及的变量的前面,这称为增量运算符的前缀形式。增量运 算符还有后缀形式,这时运算符写在所涉及变量的后面,其效果稍有不同。运算符所涉及变量的值 只有在上下文中使用过以后才增加。例如,把 count 的值重新设置为 5,并把前一个语句重写成: total = count++ + 10; 假设 count 的值是 5,则 total 的值是 15,因为 count 的值先用于计算表达式,再增加 1。前面的 语句等同于下面两个语句: total = count + 10; ++count; 在前面那个后缀形式的示例中,一群“+”号可能会导致混乱。一般说来,最好不要这样编写 增量运算符。写成下列语句则比较清楚: total = 6 + count++; 也可以给 count++加上括号。如果表达式是 a++ + b 甚至 a+++b,则它们的含义或者编译器如何 处理就不太明显。它们实际上是一样的,但是在第二种情况下,其本意可能是 a + ++b,这是一个不 同的表达式。它的计算结果比另外两个表达式大 1。 前述有关增量运算符的规则同样适用于减量运算符--。例如,如果 count 的初始值是 5,那么下 列语句 total = --count + 10; 第 2 章 数据、变量和计算 51 将把值 14 赋给 total,而 total = 10 + count--; 则把 total 的值设置为 15。这两种运算符通常用于整数,特别是用于循环的上下文中,第 3 章将对此 进行介绍,也可以把它们用于浮点变量。如后面各章所述,它们还可应用于其他数据类型,特别是 存储地址的变量。 试一试:逗号运算符 逗号运算符能够指定几个表达式,通常只有其中一个表达式可能出现。通过如下演示逗号运算 符的示例,能很好地理解这个概念。 // Ex2_06.cpp // Exercising the comma operator #include using std::cout; using std::endl; int main() { long num1 {}, num2 {}, num3 {}, num4 {}; num4 = (num1 = 10L, num2 = 20L, num3 = 30L); cout << endl << "The value of a series of expressions " << "is the value of the rightmost: " << num4; cout << endl; return 0; } 示例说明 如果编译和运行这个程序,将得到下列输出: The value of a series of expressions is the value of the rightmost: 30 这个输出相当容易理解,main()中的第一条语句创建了 4 个变量,即 num1 到 num4,并将它们 初始化为 0。变量 num4 接受 3 个赋值语句中最后一个赋值语句的值,该赋值语句的值就是赋给其 左边变量 num4 的值。在对 num4 赋值的语句中,圆括号是必须有的。可以尝试一下没有这对圆括 号时的结果。如果没有圆括号,那么在一系列由逗号分隔开的表达式中,第一个表达式将变成: num4 = num1 = 10L 这样,num4 的值是 10L。 当然,由逗号分隔开的表达式不一定是赋值语句。同样可以编写下列语句: long num1 {1L}, num2 {10L}, num3 {100L}, num4 {}; num4 = (++num1, ++num2, ++num3); 这个赋值语句的作用是把变量 num1、num2 和 num3 的值增加 1,然后把 num4 的值设置为最后 一个表达式的值,即 101L。这个示例旨在说明逗号运算符的作用,而不是说明如何编写良好的代码。 Visual C++ 2013 入门经典(第 7 版) 52 2.5.6 计算的顺序 前面没有讨论如何确定求表达式的值时所涉及的计算顺序。在处理基本的算术运算符时,这和 您在学校学过的一样,但是 C++还有许多其他的运算符。为了了解这些运算符,需要了解 C++中用 于确定这个顺序的机制。这种机制称为运算符优先顺序。 运算符优先顺序 运算符优先顺序按照优先级顺序排列运算符。在表达式中,具有最高优先顺序的运算符先执行, 然后执行具有次高优先顺序的运算符,依此类推,直至具有最低优先顺序的运算符。表 2-4 给出了 运算符的优先顺序。 表 2-4 顺 序 运算(操作)符 关 联 性 1 :: 无 2 () [] -> . 后缀 ++ 后缀 -- typeid const_cast dynamic_cast static_cast reinterpret_cast 从左向右 3 logical not ! one's complement ~ unary + unary - prefix ++ prefix -- address-of & indirection * type cast (type) sizeof decltype new new[] delete delete[] 从右向左 4 .* ->* 从左向右 5 * / % 从左向右 6 + - 从左向右 7 << >> 从左向右 8 == != 从左向右 9 & 从左向右 10 ^ 从左向右 11 | 从左向右 12 && 从左向右 13 || 从左向右 14 ?:(条件运算符) 从右向左 15 = *= /= %= += -= &= ^= |= <<= >>= 从右向左 16 throw 从右向左 17 , 从左向右 const_cast、static_cast、dynamic_cast、reinterpret_cast 和 typeid 没有包含在表中,这里有许多运 算符还没有介绍过,不过学完本书以后,您将全部认识它们。本书包括了所有的运算符,这样,如 果无法确定一个运算符相对于另一个运算符的优先顺序,可以查阅表 2-4。 第 2 章 数据、变量和计算 53 具有最高优先顺序的运算符位于表 2-4 的顶部。同一行中的所有运算符都具有相同的优先顺序。 如果表达式中没有圆括号,那么具有相同优先顺序的运算符将按照由其关联性确定的顺序执行。如 果关联性是“从左向右”,那么表达式中最左边的运算符先执行,然后向右一直执行到最右边的运算 符。这意味着,a+b+c+d 这样的表达式在执行时,可以写成(((a + b) + c) + d),因为二元运算符+是从 左向右关联的。 注意,如果一个运算符既有一元形式(处理一个操作数)又有二元形式(处理两个操作数),一 元 形 式始终具有较高的优先顺序,因而首先执行。 2.6 类型转换和类型强制转换 计算只能在相同类型的值之间进行。在编写的表达式涉及不同类型的变量或常量时,对于每个 二元运算,编译器都必须把其中一个操作数的类型转换成与另一个操作数相匹配的类型。这个转换 过程称为隐式类型转换。例如,如果要把一个 double 型的值与一个整型值相加,首先将整数值转换 成 double 型,然后执行加法运算。当然,包含被转换值的变量本身不会改变。编译器把转换后的值 存储在一个临时的内存位置,在计算完成以后,将丢弃这个值。 在任何运算中,都有确定转换哪个操作数的选择规则。任何表达式都分解成一系列两个操作数 之间的运算。例如,表达式 2*3–4+5 分解为“2×3 得 6,6–4 得 2,最后 2+5 得 7”这个序列。 因此,在必要时转换操作数类型的规则只需要根据一对操作数的类型来定义。因此,对于类型不同 的任何一对操作数,编译器根据表 2-5 所示的由高到低的类型排序,来确定将哪个操作数转换成另 一个操作数的类型。 表 2-5 1. long double 2. double 3. float 4. unsigned long long 5. long long 6. unsigned long 7. long 8. unsigned int 9. int 因此,如果运算的两个操作数分别是 long long 类型和 unsigned int 类型,则后者会转换成 long long 类型。任何 char、signed char、unsigned char、short 或 unsigned short 类型的操作数在操作之前 至少会转换成 int 类型。 隐式类型转换可能会产生意想不到的结果。例如,考虑下面这些语句: 始终可以使用圆括号重写运算符的优先顺序。由于运算符非常多,因此有时难以 确定谁的优先顺序高。最好插入圆括号来确保优先顺序。圆括号的另一个优点是它们 经常使代码更容易阅读。 Visual C++ 2013 入门经典(第 7 版) 54 unsigned int a {10u}; signed int b {20}; std::cout << a - b << std::endl; 你可能以为这段代码输出–10,其实不是。它输出 4294967286。这是因为 b 的值转换成 unsigned int 类型,以匹配 a 的类型,所以减法运算的结果是无符号整数值。这就意味着,如果整型操作涉及 不同类型的操作数,除非非常有把握,否则不应依赖隐式类型转换来生成想要的结果。 2.6.1 赋值语句中的类型转换 在示例 Ex2_05.cpp 中,赋值语句右端表达式的结果与左端的变量有不同的类型时,就要插入隐 式的类型转换。这可能导致丢失信息。例如,如果把一个值为 float 或 double 型的表达式赋给一个 类型为 int 或 long 的变量,将丢失 float 或 double 型的值的小数部分,而只存储整数部分(如果结果 超过了整数类型的值域,就可能丢失更多的信息)。 例如,在执行下列代码之后, int number {}; float decimal {2.5f}; number = decimal; number 的值将是 2。注意 2.5f 末尾的字母 f 告诉编译器,它是单精度浮点型。如果没有 f,则 它的类型将是 double。任何包含小数点的常量都是浮点型。如果不希望它们成为双精度浮点型,那 么需要附加字母 f。大写字母 F 具有相同的作用。 2.6.2 显式类型转换 对于涉及基本类型的混合表达式,编译器将在必要的地方自动安排类型转换,也可以使用显式 类型转换(也叫强制转换),强制进行从一种类型到另一种类型的转换。要把表达式的值强制转换成 给定的类型,可以编写下列形式的强制转换语句: static_cast<要转换成的类型>(表达式) 关键字 static_cast 表明将静态地检查类型强制转换,也就是说,在编译程序时检查。在执行程 序时,不再检查这种转换的应用是否安全。在后面介绍类时,会遇到 dynamic_cast,它将动态检查 转换,也就是说,在执行程序时检查。还有两种类型的强制转换,const_cast 用于删除表达式中的 const 属性,reinterpret_cast 是一种无条件的强制转换,这里不再介绍它们。 static_cast 运算的作用是将表达式求得的值转换成在尖括号内指定的类型。表达式没有限制,从 单个变量到包含许多嵌套圆括号的复杂表达式都可以。 下面是使用 static_cast< >( )的一个示例: double value1 {10.5}; double value2 {15.5}; int whole_number {}; whole_number = static_cast(value1) + static_cast(value2); 赋予变量 whole_number 的值是 value1 和 value2 的整数部分的和,因此它们都显式地强制转换 成类型 int,变量 whole_number 的值是 25。强制转换不影响存储在 value1 和 value2 中的值,它们仍 然分别是 10.5 和 15.5。由强制转换产生的数值 10 和 15 只是临时存储起来,供计算时使用,随后丢 第 2 章 数据、变量和计算 55 弃。虽然这两个强制转换在计算时都丢失了信息,但是编译器假定,在显式地指定强制转换时,你 了解自己正在做的事情。 可以对任何数字类型应用显式类型强制转换,但是应当意识到存在信息丢失的可能性。如果把 一个类型为 float 或 double 的值强制转换成整数类型,转换后将丢失这个值的小数部分,如果这个 值开始时小于 1.0,那么结果将是 0。如果把一个 double 值强制转换成类型 float,将损失精度,因 为 float 型变量只有 7 位精度,而 double 型变量有 15 位精度。根据所涉及的值,甚至在整数类型之 间进行强制转换,也有可能丢失数据。例如,一个类型为 long long 的整数值可能超过能在 int 型变 量中存储的最大值,所以从 long long 型数值到 int 型数值的强制转换就可能会丢失信息。 一般来说,应当尽可能地避免强制转换。如果发现程序需要大量的强制转换,就应看看程序的 结构,以及选择数据类型的方法,以了解是否可以消除,至少减少强制转换数量。 2.6.3 老式的类型强制转换 在 static_cast< >( )(以及本书后面将要讨论的其他类型强制转换 const_cast< >( )、dynamic_cast < >( )和 reinterpret_cast< >( ))引入 C++之前,显式强制转换写为: (要转换成的类型)表达式 表达式的结果强制转换成圆括号之间的类型。例如,计算 strips_per_roll 的语句可以写成: strips_per_roll = (int)(rollLength / height); //Get number of strips in a roll 有 4 种不同的强制转换,老式的强制转换语法涵盖了所有这些转换。由此,使用老式类型强制 转换的代码更容易出错—— 它们往往不能清楚地说明您的意图,可能得不到期望的结果。虽然老式 的类型强制转换仍在广泛地使用(它仍然是语言的一部分,由于历史的原因,在 MFC 代码中它仍然 存在),但强烈建议在代码中坚持只使用新式的类型强制转换。 2.7 auto 关键字 可以在定义语句中将 auto 关键字用作变量的类型,变量的类型根据提供的初始值来推断。下面 是几个例子: auto n = 16; // Type is int auto pi = 3.14159; // Type is double auto x = 3.5f; // Type is float auto found = false; // Type is bool 在每种情况下,分配给所定义变量的类型与用作初始值的字面值的类型是相同的。当然,以这 种方式使用 auto 关键字时,必须为变量提供初始值。 注意不应联合使用初始化列表和 auto 关键字,因为初始化列表有类型。编译器不从列表的项中, 而是根据列表推断出类型。假定有如下代码: auto n {16}; 则赋予 n 的类型不是 int,而是 std::initializer_list,这是这个初始化列表的类型。本书后面 介绍 std::initializer_list<>类型。 Visual C++ 2013 入门经典(第 7 版) 56 可以联合使用初始化的函数形式和 auto 关键字: auto n (16); 变量 n 的类型是 int。使用 auto 关键字定义的变量也可以指定为常量: const auto e = 2.71828L; // Type is const long double 当然,也可以使用函数式记数法: const auto dozen(12); // Type is const int 初始值也可以是一个表达式: auto factor(n*pi*pi); // Type is double 在此情况下,变量 n 和 pi 的定义必须在此语句之前。 使用 auto,就是让编译器推断出变量的类型。在这一点上,关键字 auto 似乎是 C++中一个微不 足道的特性,但在本书后面(尤其是第 10 章)会看到,这不但可以在确定复杂的变量类型时省去很多 工作,而且使代码更优美。建议仅在使用 auto 有好处时使用它,不要把它用于定义基本类型的变量。 2.8 类型的确定 typeid 操作符能确定表达式的类型。要获得表达式的类型,只需要编写 typeid(表达式),这会产 生一个类型为 type_info 的对象,该对象封装了表达式的类型。下面看一个示例。 假定变量 x 和 y 的类型分别是 int 和 double。表达式 typeid(x*y)产生一个 type_info 对象,此对 象表示 x*y 的类型,现在我们知道是 double 类型。因为 typeid 操作符的结果是一个对象,所以不能 直接将它写到标准输出流。但是,可以像下面这样输出表达式 x*y 的类型: cout << "The type of x*y is " << typeid(x*y).name() << endl; 这会产生如下输出: The type of x*y is double 第 7 章学习关于类和函数的更多内容后,就会更好地理解其工作过程。不需要经常使用 typeid 操作符,但需要使用时,它是极为有用的。 2.9 按位运算符 按位运算符把操作数看作是一系列单独的位,而不是一个数字值。它们只处理整型变量或整型 常量,所以只能用于数据类型 short、int、long、long long、signed char 和 char,以及这些类型的无 符号变体。按位运算符在编程硬件设备中非常有用,因为设备的状态经常表示为一系列单独的标志 (也就是说,字节的每个位可以表示设备各个方面的状态),在需要把一组开关标志装入单个变量中 时,按位运算符也非常有用。在详细分析输入/输出时,你将看到它们的作用,其中单个位用于控制 处理数据的选项。 第 2 章 数据、变量和计算 57 按位运算符有 6 个,如下所示: & 按位与(AND) | 按位或(OR) ^ 按位异或(EOR) ~ 按位取反(NOT) >> 右移 << 左移 下面几节将分析它们的作用。 2.9.1 按位 AND 运算符 按位 AND 运算符(&)是一个二元运算符,它将对应的位组合后,如果对应的位都是 1 位,那么 结果位是 1;如果任一个位或者两个位是 0,则结果位是 0。 二元运算符的作用可以利用真值表来表示。真值表给出操作数的各种可能组合的结果。&的真 值表如表 2-6 所示。 表 2-6 按位 AND 运算符 0 1 0 0 0 1 0 1 对于每个行和列的组合,&的结果就是该行和列交叉处的项。如下面的示例: char letter1 {'A'}, letter2 {'Z'}, result {}; result = letter1 & letter2; 为了了解所发生的情况,需要观察位模式。字母 A 和 Z 分别对应于十六进制值 0x41 和 0x5A。 图 2-7 给出了按位 AND 运算符对这两个值的操作方式。查看真值表中对应位如何利用&进行组合, 可以对此进一步证实。在赋值以后,result 就是 0x40,它对应于字符@。 图 2-7 由于任何一位是 0,&运算的结果就是 0,因此可以使用这个运算符把变量中特定的位设置成 0。 其方法是创建一个“掩码”,然后使用&将它与原变量组合起来。掩码的值是,在需要保留位的地方 为 1,而在需要把位设置成 0 的地方为 0。在屏蔽位是 0 的地方,掩码和变量“按位与”的结果将是 0 位,而在屏蔽位是 1 的地方,结果是变量中原始位的值。假设变量 letter 是 char 类型,要消除高阶 的 4 个位,而保留低阶的 4 个位。这很容易办到,如下所示,利用&将 0x0F 和 letter 的值组合起来: letter = letter & 0x0F; 或者写成更简洁的形式: Visual C++ 2013 入门经典(第 7 版) 58 letter & = 0x0F; 如果 letter 开始时是 0x41,那么经过以上任何一个语句的处理后,它最后的结果都将是 0x01。 这个操作如图 2-8 所示。掩码中的 0 位使 letter 中的对应位设置成 0,而掩码中的 1 位将使 letter 中 的对应位保持不变。 图 2-8 类似地,可以使用掩码 0xF0 保留 4 个高阶位,而把 4 个低阶位设置成 0。因此,下列语句 letter & = 0xF0; 使 letter 的值由 0x41 变成 0x40。 2.9.2 按位 OR 运算符 按位 OR 运算符( | )有时称为 OR(或)运算符,它将对应的位组合后,如果任一个操作数位是 1, 那么结果是 1;如果两个操作数位都是 0,那么结果是 0。按位 OR 运算符的真值表如表 2-7 所示。 表 2-7 按位 OR 运算符 0 1 0 0 1 1 1 1 可以通过一个示例练习一下,看看如何设置 int 型变量中的各个标志。假设 style 是 short 型变量, 它包含 16 个 1 位标志。另外假设想把 style 中的一些位设置为 1。为了设置最右边的位,可以定义 下列掩码: short vredraw {0x01}; 为了设置从右数的第二位,可以把变量 hredraw 定义为: short hredraw {0x02}; 下列语句把变量 style 最右边的两个位设置成 1: style = hredraw | vredraw; 这个语句的作用如图 2-9 所示。当然,要将 style 的第三位设置为 1,将使用 0x04。 第 2 章 数据、变量和计算 59 图 2-9 一个非常普遍的要求是,设置变量中的特定位。利用下面的语句很容易地实现: style |= hredraw | vredraw; 这个语句把变量 style 最右边的两个位设置成 1,其他位保持不变。 2.9.3 按位 XOR 运算符 之所以称其为“异或(XOR)”运算符(^),是因为它的运算方式和 OR 运算符类似,但是两个操 作数都是 1 时,它将产生 0。它的真值表如表 2-8 所示。 表 2-8 按位 XOR 运算符 0 1 0 0 1 1 1 0 利用介绍 AND 运算符时使用的变量值,可以观察下列语句的结果: result = letter1 ^ letter2; 这个运算是对如下二进制值进行 XOR 操作: letter1 0100 0001 letter2 0101 1010 得到: result 0001 1011 变量 result 设置成 0x1B,即十进制记数法的 27。 ^运算符有一个相当令人惊讶的特性。假设有两个 char 型变量,first 的值是‘A’,last 的值是‘Z’。 如果编写下列语句: first ^= last; // Result first is 0001 1011 Visual C++ 2013 入门经典(第 7 版) 60 last ^= first; // Result last is 0100 0001 first ^= last; // Result first is 0101 1010 其结果是,first 和 last 在不占用任何中间存储单元的情况下交换了值。这适用于所有整数值。 2.9.4 按位 NOT 运算符 按位 NOT 运算符~只需要一个操作数,并对操作数的位求反:1 变成 0,0 变成 1。下面是一个 示例 result = ~letter1; 如果 letter1 是 0100 0001,那么变量 result 的值是 1011 1110,即 0xBE,或者十进制值 190。 2.9.5 移位运算符 这些运算符将整型变量的值向左或向右移动指定的位数。运算符>>向右移动,而运算符<<向左 移动。“离开”变量任一端的位都将丢失。图 2-10 演示了向左和向右移动一个 2 字节变量的结果和 这个变量的初值。 十进制数 16,387 的二进制表示: 向左移动两位: 从右移入 0 这两位向右移出 边界,被舍弃 从左移入 0 向右移动两位: 这两位向左移出 边界,被舍弃 图 2-10 下列语句声明和初始化变量 number: unsigned short number {16387U}; 如前所述,无符号整数字面值要在数字的末尾附加 U 或 u。可以利用下列语句左移 number 变 量的内容: number <<= 2; // Shift left two bit positions 移位运算符的左操作数是要移位的值,这个值被移动的位数由右操作数指定。图 2-10 说明了这 一运算的结果。把值 16 387 向左移动两位将产生值 12。数值出现如此大的变化的原因是高阶位在移 出后丢失了。 也可以向右移动这个值。首先把 number 的值复位到初值 16 387。然后编写下列语句: number >>= 2; // Shift right two bit positions 第 2 章 数据、变量和计算 61 这把值 16 387 向右移动两位,最后存储的值是 4 096。向右移动两位实际上是把这个值除以 4(舍 弃余数)。图 2-10 也说明了这一运算。 只要位不丢失,向左移动 n 位相当于这个值和 2 相乘 n 次。换句话说,相当于乘以 2n。类 似 地 , 向右移动 n 位相当于除以 2n。但是要注意:正如在左移变量 number 时看到的那样,如果有效位丢 失,那么其结果将与您期望的大相径庭。但是,这和乘法运算没有关系。如果把 number 和 4 相乘, 那么将得到相同的结果,所以左移和乘法仍然是等效的。之所以出现精度问题,在于乘法运算的结 果超出了 2 字节整数的值域。 输入和输出运算符和移位运算符之间可能出现混乱。就编译器而言,它们的含义在上下文中始 终应当明确。否则,编译器将生成一条错误消息,但需要格外小心。例如,如果想输出变量 number 左移 2 位的结果,那么可以编写下列语句: cout << (number << 2); 其中的圆括号是必须有的。如果没有,则移位运算符将解释为流运算符,这样就得不到想要的 结果,这时的输出将是 number 的值,然后是数值 2。 右移运算类似于左移运算。例如,假设变量 number 的值是 24,执行下列语句: number >>= 2; 结果是 number 获得值 6,这实际上是将原值除以 4。但是,右移运算符以特殊的方法处理负整 数类型(也就是说,最左边的符号位是 1)。在这种情况下,符号位将向右传递。例如,声明一个 char 型变量 number,并初始化为十进制值–104: char number {-104}; // Binary representation is 1001 1000 现在可以利用下列运算将它向右移动 2 位: number >>= 2; // Result 1110 0110 因为符号位被复制下来,所以结果是十进制值–26。对无符号整数类型进行运算时,将不复制 符号位,而是出现 0。 2.10 lvalue 和 rvalue 每个表达式的结果都是 lvalue 或 rvalue。有时,它们也分别写作 l-value 和 r-value,但读音不变。 lvalue 指的是内存中持续存储数据的一个地址。而 rvalue 是临时存储的表达式结果。之所以称为 lvalue,是因为所有产生 lvalue 的表达式都可以出现在赋值语句中等号的左边。如果表达式结果不是 为什么移位运算符 <<和>>与标准流使用的运算符一样呢?因为 cin 和 cout 是流对 象,可以由运算符重载过程重新定义运算符在不同上下文中的意义。 >>运算符重定义 为输入流对象 (如 cin),因此能以前述方式使用。 <<运算符也重定义为输出流对象 (如 cout)中。第 8 章将介绍运算符重载。 Visual C++ 2013 入门经典(第 7 版) 62 lvalue,则它是 rvalue。 考虑下面的语句: int a {}, b {1}, c {2}; a = b + c; b = ++a; c = a++; 第一条语句声明类型为 int 的变量 a、b 和 c,并分别初始化为 0、1 和 2。在第二条语句中,求 表达式 b+c 的值,结果存储在变量 a 中。表达式 b+c 的结果是临时存储的,并将此值复制到 a。一 旦此语句执行完毕,则丢弃存储 b+c 结果的内存位置。因此,表达式 b+c 的结果是一个 rvalue。 在第三条语句中,表达式++a 是一个 lvalue,因为它的结果是递增之后的 a。第四条语句中的表 达式 a++是一个 rvalue,因为它临时将 a 的值存储为表达式的结果,然后递增 a。 只包含一个命名变量的表达式始终是 lvalue。 2.11 了解存储时间和作用域 所有变量都有一个有限的生存期。从声明它们之时开始存在,然后在某个时刻消失—— 最起码, 它们在程序终止时消失。特定变量的生存时间由一个称作存储时间的属性确定。变量可以有 3 种不 同的存储时间: ● 自动存储时间 ● 静态存储时间 ● 动态存储时间 变量具有哪种存储时间取决于它的创建方式。第 4 章将讨论动态存储时间,本章将讨论其他两 种存储时间的特性。 变量的另一种属性是作用域。变量的作用域是这个变量名在其中有效的那部分程序。在变量的 作用域内,可以合法地引用它、设置它的值,或者在表达式中使用它。在变量的作用域之外,不能 引用它的名称—— 任何尝试都将导致编译器错误。注意,变量仍然可以在它的作用域外存在,即使 不能引用它。稍后将看到这种情况的一些示例。 前面声明的所有变量都具有自动存储时间,因而称为自动变量。下面将对其进行详细的分析。 2.11.1 自动变量 前面声明的变量都是在一个代码块内声明的。也就是说,是在一对大括号的范围内声明的。这 些称为自动变量,它们具有局部作用域或者代码块作用域。自动变量“在作用域中”的时间从声明 它的那一刻开始,一直到包含其声明的代码块结束为止。自动变量占用的空间在一个称为栈的内存 区域中分配,栈是专门为此而留出的内存。栈的默认容量是 1MB,这对于大多数情况来说足够了, 这绝不是lvalue和rvalue的全部内容。 大多数时候, 都不需要太在意表达式是 lvalue 还是 rvalue。但有时,却必须在意。本书不时地还会出现 lvalue 和 rvalue,因此要牢记 这一概念。 第 2 章 数据、变量和计算 63 如果不够用,则可以把项目的/STACK 或/F 编译器命令行选项设置为所选的一个值,也可以把栈的 大小设置为一个链接器属性。 自动变量“出生”于它被定义之时,它占用的空间在栈上进行分配,在包含其定义的代码块结 束时自动消失。这个位置在与声明该变量之前的第一个左大括号对应的右大括号处。每次执行包含 自动变量声明的语句块时,就重新创建这个变量,如果指定了自动变量的初值,就每次重新初始化 它。当自动变量消失时,将释放它在栈上的内存,而由其他自动变量使用。下面的示例将具体说明 前述的作用域这个概念。 试一试:自动变量 下列示例演示了作用域对自动变量的影响。 // Ex2_07.cpp // Demonstrating variable scope #include using std::cout; using std::endl; int main() { // Function scope starts here int count1 {10}; int count3 {50}; cout << endl << "Value of outer count1 = " << count1 << endl; { // New scope starts here... int count1 {20}; // This hides the outer count1 int count2 {30}; cout << "Value of inner count1 = " << count1 << endl; count1 += 3; // This affects the inner count1 count3 += count2; } // ...and ends here cout << "Value of outer count1 = " << count1 << endl << "Value of outer count3 = " << count3 << endl; // cout << count2 << endl; // uncomment to get an error return 0; } // Function scope ends here 这个示例的输出是: Value of outer count1 = 10 Value of inner count1 = 20 Value of outer count1 = 10 Value of outer count3 = 80 Visual C++ 2013 入门经典(第 7 版) 64 示例说明 前两个语句声明、定义了 count1 和 count3,其初始值分别是 10 和 50。这两个变量从这个位置 开始存在,直到该程序结尾处的大括号结束。它们的作用域也延伸到 main()函数结尾处的右大括号。 记住,变量的生存期和作用域是两个不同的概念。千万不要混淆这两个概念。生存期是从创建 这个变量时开始,到销毁这个变量并释放其占用的内存时结束的一个期间。作用域是可以访问这个 变量的程序代码区域。 在变量定义的后面,将 count1 的值输出到 cout,产生如上所示的第一行输出。然后是第二个大 括号,它开始一个新的代码块。在这个代码块中定义了两个变量 count1 和 count2,它们的值分别是 20 和 30。此处的 count1 和第一个 count1 不同。虽然第一个 count1 仍然存在,但是它的名称被第二 个 count1 掩盖。在内部代码块中,在这个声明之后使用的名称 count1 指的都是在该代码块中声明的 count1。 之所以在这里使用相同的变量名 count1,是为了举例说明发生的情况。虽然这段代码是合法的, 但不是好的编程方法。在实际编程时,必将造成混淆,使用相同的名称很容易无意中隐藏定义在外 部作用域中的变量。 第二个输出行中显示的值表明,在内部代码块中,使用了内部作用域中的 count1—— 即最里面 的大括号内的 count1: cout << "Value of inner count1 = " << count1 << endl; 如果仍在使用外部的 count1,那么这个语句应该输出 10。下列语句将增加变量 count1 的值: count1 += 3; // This affects the inner count1 这个增量运算应用于内部作用域中的 count1,因为外部的 count1 仍然被隐藏着。但是,下一个 语句增加在外部作用域中定义的 count3 的值,没有任何问题: count3 += count2; 这表明,可以在内部作用域中访问在外部作用域开始处声明的变量。如果 count3 是在第二对内 部大括号之后声明的,那么它仍然在外部作用域内部,但不存在于内部作用域。 在结束内部作用域的大括号之后,count2 和内部的 count1 不再存在。变量 count1 和 count3 仍 然在外部作用域中,显示的值表明,的确在内部作用域中增加了 count3 的值。 如果解除下列代码行的注释: // cout << count2 << endl; // uncomment to get an error 程序将不再编译,因为它试图输出一个不存在的变量。得到如下错误消息: c:\microsoft visual studio\myprojects\Ex2_07\Ex2_07.cpp(29) : error C2065: 'count2' : undeclared identifier 这是因为 count2 这时在作用域之外。 第 2 章 数据、变量和计算 65 2.11.2 决定变量声明的位置 在决定变量定义的位置时,有很大的灵活性。要考虑的最重要的问题是变量需要有什么作用域。 除此之外,应把定义放在靠近第一次使用变量的地方。在编写程序时,要尽量使其他程序员容易理 解,在第一次使用变量的地方声明它有助于达到这个目的。 可以把变量的声明放在构成程序的所有函数之外。下一节将分析声明位置对变量的影响。 2.11.3 全局变量 在所有代码块和类(本书后面将讨论类)之外声明的变量称为全局变量,它们具有全局作用域(也 称作全局名称空间作用域或者文件作用域)。这意味着,在声明全局变量的位置之后,文件内的所有 函数都可以访问它们。如果在程序的顶部声明了全局变量,那么从文件内的所有位置都可以访问 它们。 全局变量默认具有静态存储时间。静态存储时间意味着,全局变量将从程序开始执行时存在, 直到程序执行结束时为止。如果没有指定全局变量的初始值,它就默认初始化为 0。全局变量的创 建和初始化发生在 main()函数执行之前,所以在变量作用域的任何代码内,始终可以使用它们。 图 2-11 给出了源文件 Example.cpp 的内容,箭头表示变量的作用域。 图 2-11 变量 value1 出现在文件开头,所以是在全局作用域内声明的,value4 也是如此,它出现在 main() Visual C++ 2013 入门经典(第 7 版) 66 函数的后面。每个全局变量的作用域都从定义它们的地方延伸到文件结尾处。尽管 value4 在执行开 始时就存在,但是在 main()函数内不能引用它,因为 main()函数不在这个变量的作用域内。要使 main() 函数使用 value4,它的定义必须移到 main()之前。value1 和 value4 默认初始化为 0,自动变量则不 是这样。function()中的局部变量 value1 隐藏了全局变量 value1。 只要程序正在运行,全局变量就一直存在,这可能会引发一个问题,“为什么不让所有变量都成 为全局变量,从而避免与局部变量混淆的情况呢?”这听起来很吸引人,但这样会产生严重的副作 用,完全是得不偿失的。 实际的程序一般由大量的语句、函数和变量组成。在全局作用域中声明所有变量将大大增加意 外错误修改变量的可能性,它们的命名工作也难以处理。它们还将在程序执行的整个期间内占用内 存。而将变量声明为函数或代码块的局部变量,可以确保它们几乎完全不受外部影响的干扰,只在 获得定义至所在代码块结束这段区间内存在和占用内存,整个开发过程也更加容易管理。这并不是 说不应在全局作用域内定义变量。有时,定义全局常量是非常方便的。 在 Class View 窗格中,观察前面创建的任一个示例,单击空心箭头,展开这个项目的类树,将 看到一个名为 Global Functions and Variables 的条目。单击这个条目,会显示一个列表,它包含所有 具有全局作用域的内容。这包括所有全局函数和所有全局变量。 试一试:作用域解析运算符 如前所述,全局变量可以被同名的局部变量隐藏。但是,使用作用域解析运算符(::)可以获得全 局变量。在第 1 章讨论名称空间时介绍过这种运算符。下面利用 Ex2_07.cpp 的修订版演示它: // Ex2_08.cpp // Demonstrating variable scope #include using std::cout; using std::endl; int count1 {100}; // Global version of count1 int main() { // Function scope starts here int count1 {10}; int count3 {50}; cout << endl << "Value of outer count1 = " << count1 << endl; cout << "Value of global count1 = " << ::count1 // From outer block << endl; { // New scope starts here... int count1 {20}; // This hides the outer count1 int count2 {30}; cout << "Value of inner count1 = " << count1 << endl; cout << "Value of global count1 = " << ::count1 // From inner block << endl; count1 += 3; // This affects the inner count1 count3 += count2; 第 2 章 数据、变量和计算 67 } // ...and ends here. cout << "Value of outer count1 = " << count1 << endl << "Value of outer count3 = " << count3 << endl; //cout << count2 << endl; // uncomment to get an error return 0; } // Function scope ends here 输出如下: Value of outer count1 = 10 Value of global count1 = 100 Value of inner count1 = 20 Value of global count1 = 100 Value of outer count1 = 10 Value of outer count3 = 80 示例说明 用粗体显示的代码是对前一个示例的修改,在此只讨论它们的作用。在定义函数 main()之前定 义的变量 count1 是全局的,因此原则上可以在函数 main()中的任何地方使用它。这个全局变量初始 化为 100: int count1 {100}; // Global version of count1 但是,在函数 main()中还定义了其他两个名称为 count1 的变量,所以在整个程序中,局部变量 count1 隐藏了全局变量 count1。第一个新的输出语句是: cout << "Value of global count1 = " << ::count1 // From outer block << endl; 这个语句使用作用域解析运算符(::)引用全局变量 count1,而不是局部变量 count1。从输出的值 中就可以看到这一点。 在内部代码块中,两个名称为 count1 的变量(内部的 count1 和外部的 count1)隐藏了全局变量 count1: 在内部代码块中,全局作用域解析运算符大显身手,从所添加的语句生成的输出中,也可以看到这一点: cout << "Value of global count1 = " << ::count1 // From inner block << endl; 和以前一样,这个语句将输出值 100 —— 以这种方式使用时,作用域解析运算符始终能够触及 全局变量。 如前所述,利用名称空间名 std 限定一个名称,如 std::cout 或者 std::endl,就可以引用名称空间 std 中的这个名称。如果作用域解析运算符的左操作数指定了一个名称,那么编译器就在此名称的名 称空间中,搜索指定为右操作数的名称。在前面的示例中,使用作用域解析运算符在全局名称空间 中搜索变量 count1。如果在运算符的前面没有指定名称空间名,编译器将在全局名称空间中搜索运 算符后面的名称。第 9 章讨论面向对象编程时,将详细讨论这种运算符,它在面向对象编程中使用 得非常广泛。 Visual C++ 2013 入门经典(第 7 版) 68 2.11.4 静态变量 可能需要这样的变量:可以对它进行局部定义和访问,但是在退出声明它的代码块后,它还继 续存在。换句话说,需要在代码块作用域内声明一个变量,但是它要有静态存储时间。static 说明符 提供了这样的方法,第 5 章开始处理函数时,这样做的必要性会比较明显。 即使静态变量是在一个代码块内声明,只能在这个代码块(或者它的子代码块)内部使用,但是 在程序的生存期内,它将持续存在。它的作用域仍然是这个代码块,但具有静态存储时间。要声明 一个静态整型变量 count,可以编写下列语句: static int count; 如果没有给静态变量提供初值,它将初始化为 0,且转换成适合于该变量的类型。注意自动变 量并非如此。 2.12 具有特定值集的变量 有时变量需要有限的可能值集,这些值可以通过标签来引用,例如星期、月份或扑克牌中的花 色。枚举提供了这个功能。枚举有两种,一种用 C++ 11 标准引入的语法来定义,另一种用旧语法 来定义。这里介绍这两种枚举,是因为旧语法使用得很广泛,但新语法有几个优点,所以应只使用 新语法。 2.12.1 旧枚举 用刚才的一个例子来说明——变量可以假定其值对应于星期。这个变量可以定义为: enum Weekdays{Mon, Tues, Wed, Thurs, Fri, Sat, Sun} today; 这个语句声明了一个枚举类型 Weekdays 和变量 today,该变量 today 是枚举类型 Weekdays 的一 个实例,它只能有大括号中指定的常量值。如果赋给 today 的值不属于指定的值集,就会出错。大 括号中列出的名称称为枚举器。每个星期名称都自动定义为表示一个固定的整数值,其类型默认为 int。列表中的第一个名称 Mon 的值是 0,第二个名称 Tues 的值是 1,依此类推。 可以把一个枚举常量赋给 today 变量,如下所示: today = Thurs; today 的值是 3,因为枚举器按顺序赋值,默认从 0 开始。后续的每个枚举器的值都比其前面的 枚举器大 1,但如果希望从另一个值开始赋值,就只需要编写如下代码: enum Weekdays {Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun} today; 现在,枚举常量的值是从 1 到 7。枚举器的值甚至不必是不同的,例如用下面的语句可以把 Mon 和 Tues 都定义为 1: enum Weekdays {Mon = 1, Tues = 1, Wed, Thurs, Fri, Sat, Sun} thisWeek; 因为 Weekdays 的枚举器是 int 类型,所以变量 today 存储了一个 int 类型的值,占据 4 个字节, Weekdays 类型的所有变量都是如此。枚举类型的枚举器和变量都会在需要时隐式转换为 int 类型。 第 2 章 数据、变量和计算 69 例如,可以将一个枚举器赋值给整数变量: int value {}; value = Wed; 如果愿意,也可以把特定的值赋予所有枚举器。例如,可以定义如下枚举: enum Punctuation {Comma = ',', Exclamation = '!', Question = '?'} things; 这个语句把 things 的可能值定义为对应字符的代码值。枚举器分别是十进制的 44、33 和 63。 可以看出,枚举器的值不一定按升序排列。没有指定值的枚举器会得到比前一个枚举器值大 1 的值。 也可以把枚举器的值写入标准输出流: cout << today << endl; 编译器会插入一个类型转换操作,把 today 的值转换为 int 类型。一般情况下,如果使用枚举类 型的枚举器或变量,编译器就会插入一个类型转换操作,把该值转换为表达式需要的数值类型。这 是一个不受欢迎的功能,因为这可能导致无意中把枚举类型的变量用作数值类型。 定义了枚举类型后,就可以定义另一个变量: enum Weekdays tomorrow; 这个语句把变量 tomorrow 定义为 Weekdays 类型,还可以省略 enum 关键字。前面的语句可以 替换为: Weekdays tomorrow; 可以像其他变量那样,声明并初始化枚举类型的变量,例如: Weekdays myBirthday {Tues}; Weekdays yourBirthday {Thurs}; 不需要用枚举名限定枚举常量,当然也可以这么做: Weekdays myBirthday {Weekdays::Tues}; enum 类型名称与枚举器名称用范围解析操作符分隔开。 如果以后不需要定义这个类型的其他变量,就可以省略枚举类型。例如: enum {Mon, Tues, Wed, Thurs, Fri, Sat, Sun} today, tomorrow, yesterday; 这里声明了 3 个变量,它们可以采用从 Mon 到 Sun 的任意值。因为没有指定枚举类型,所以不 能引用它。注意,因为不允许重复这个定义,所以不能定义这种枚举类型的其他变量。这样做就意 味着重新定义 Mon 到 Sun 的值。这是不允许的。 不一定要接受枚举器的默认类型,而可以把枚举器的类型显式指定为除 wchar_t 之外的其他整 数类型。下面是一个例子: enum Weekdays : unsigned long {Mon, Tues, Wed, Thurs, Fri} tomorrow; 现在 Weekdays 的枚举器是 unsigned long 类型。 枚举器名称默认导出到封闭的范围内,所以在引用它们时,不需要限定名称。但这会导致一个 Visual C++ 2013 入门经典(第 7 版) 70 问题。看看下面两个枚举: enum Suit {Clubs, Diamonds, Hearts, Spades}; enum Jewels {Diamonds, Emeralds, Opals, Rubies, Sapphires}; 如果把这些定义放在 main()中,它们不会编译。两组枚举器名称都在封闭的范围内使用,即在 main()的代码体中使用。但不能把 Jewels 枚举重定义为 Diamonds。 2.12.2 类型安全的枚举 C++ 11 引入了一种新的枚举,这些枚举称为类型安全的,因为不能把枚举器的值隐式转换为另 一种类型。在 enum 的后面使用 class 关键字,就可以指定新枚举类型。下面是一个例子: enum class Suit {Clubs, Diamonds, Hearts, Spades}; 有了这种形式的枚举,枚举器的名称就不会导出到封闭的范围内,而必须使用类型的名称来限 定它们,例如: Suit suit {Suit::Diamonds}; 上一节不能编译的两个枚举可以重写为类型安全的枚举: enum class Suit {Clubs, Diamonds, Hearts, Spades}; enum class Jewels {Diamonds, Emeralds, Opals, Rubies, Sapphires}; 这些语句是可以编译的。其中没有名称冲突,因为枚举器名称没有导出到封闭的范围内。枚举 器名称必须总是用枚举类型名称来限定,所以 Suit::Diamonds 总是与 Jewels::Diamonds 不同。 如果希望把一个枚举值转换为另一种类型,就可以使用显式类型转换。例如: Suit suit{Suit::Diamonds}; // Create and initialize suit int suitValue {static_cast(suit)}; // Convert suit to int 不把 suit 显式转换为 int 类型,这些语句就不会编译。 枚举器的值默认为 int 类型,但可以改变枚举器的类型: enum class Jewels : char {Diamonds, Emeralds, Opals, Rubies, Sapphires}; 这里把枚举器的值存储为 char 类型。下面的例子演示了两种枚举。 试一试:使用枚举 这个例子演示了两种枚举类型: // Ex2_09.cpp // Demonstrating type-safe and non-type-safe enumerations #include using std::cout; using std::endl; // You can define enumerations at global scope //enum Jewels {Diamonds, Emeralds, Rubies}; // Uncomment this for an error enum Suit : long {Clubs, Diamonds, Hearts, Spades}; 第 2 章 数据、变量和计算 71 int main() { // Using the old enumeration type... Suit suit {Clubs}; // You can use old enumerator names directly Suit another {Suit::Diamonds}; // or you can qualify them // Automatic conversion from enumeration type to integer cout << "suit value: " << suit << endl; cout << "Add 10 to another: " << another + 10 << endl; // Using type-safe enumerations... enum class Color : char {Red, Orange, Yellow, Green, Blue, Indigo, Violet}; Color skyColor{Color::Blue}; // You must qualify enumerator names // Color grassColor{Green}; // Uncomment for an error // No auto conversion to numeric type cout << endl << "Sky color value: " << static_cast(skyColor) << endl; //cout << skyColor + 10L << endl; // Uncomment for an error cout << "Incremented sky color: " << static_cast(skyColor) + 10L // OK with explicit cast << endl; return 0; } 如果编译并运行这个例子,结果就如下所示: suit value: 0 Add 10 to another: 11 Sky color value: 4 Incremented sky color: 14 示例说明 从这个例子可以看出,枚举可以定义为全局范围,此时它们可以在整个源文件中访问。在代码 块内部定义的局部枚举,其使用范围是从定义它们的位置开始到代码块末。可以把枚举类型的定义 放在自己创建的.h 文件中,接着使用#include 指令把该头文件包含到要使用该枚举的任意源文件中。 去掉 Jewels 枚举定义的注释符号,会产生一个编译错误,因为 Diamonds 重复定义了。如果 Suit 和 Jewels 类型都定义为类型安全的枚举,就不会出现名称冲突。 main()函数先使用旧样式的枚举,说明枚举器名称的类型限定是可选的。这些语句显示,在操 作时会自动进行类型转换: cout << "suit value: " << suit << endl; cout << "Add 10 to another: " << another + 10 << endl; 编译器允许把 suit 输出为一个整数,但它是 Suit 类型的。也可以在算术表达式中使用它。 Color 枚举是类型安全的: enum class Color : char {Red, Orange, Yellow, Green, Blue, Indigo, Violet}; Visual C++ 2013 入门经典(第 7 版) 72 如果尝试使用没有限定的枚举器名称,就会得到编译器发出的错误消息。编译器要求,如果希 望把 Color 值转换为另一种类型,就必须插入一个显式类型转换语句。 2.13 名称空间 前面多次提到名称空间,现在应该详细地了解它们的用途了。它们不是用在支持 MFC 的库 中,但标准库广泛地使用名称空间。 我们知道,标准库中的名称是在名称为 std 的名称空间中定义的。这意味着,这些名称都有一 个额外的限定名 std;例如,cout 其实是 std::cout。添加 using 声明,可以将一个名称从 std 名称空间 导入源文件中。例如: using std::cout; 这就允许使用名称 cout,且它被解释为 std:cout。 名称空间提供了一种将在程序某一部分使用的名称与在另一部分使用的名称分隔开的方法。如 果大型项目需要由多个程序员小组分别负责程序的不同部分,则这种方法是非常有用的。每个小组 都可以有自己的名称空间名,不必担心两个小组会将相同的名称用于不同的函数。 看看下面的代码: using namespace std; 这是一个 using 指令,它与 using 声明是不同的。其作用是将 std 名称空间的所有名称导入源文 件,这样就可以引用在该名称空间中定义的所有名称,而不必限定名称。因此,可以编写名称 cout 和 endl,而不必编写 std::cout 和 std::endl。这听起来是一个很大的优点,但这个 using 指令有一个缺 点:它实际上否定了使用名称空间的主要原因—— 即防止意外的名称冲突。有两种方法可以访问名 称空间中的名称而没有这个副作用,第一种方法是利用名称空间名显式地限定每个名称。但是,这 会使代码非常长,并降低代码的可读性。第二种方法是利用 using 声明引入要在代码中使用的名称, 例如,在前面的例子中看到的下列声明: using std::cout; // Allows cout usage without qualification using std::endl; // Allows endl usage without qualification 每个 using 声明都从指定的名称空间引入一个名称,并且在之后的程序代码中使用此名称时无 需限定符。这是从名称空间导入名称的更好方法,因为只导入实际使用的名称。但 using 声明的数 量很大。当然,可以利用所选择的名称来定义自己的名称空间,参见下一节。 总是使用类型安全的枚举,这会使代码更不容易出错。 在头文件中不应使用 using 指令,因为它们会应用于包含头文件的所有源文件。 在头文件的全局作用域中应避免使用 using 声明。 第 2 章 数据、变量和计算 73 2.13.1 声明名称空间 使用关键字 namespace 可以声明一个名称空间,例如: namespace myStuff { // Code that I want to have in the namespace myStuff... } 这段代码定义了一个名称为 myStuff 的名称空间。大括号之间的代码中的所有名称声明都在 myStuff 名称空间内定义,要从此名称空间外面的一个位置访问名称,则该名称必须由名称空间名 myStuff 限定,在 myStuff 名称空间内,只使用无限定的名称。 不能在函数内声明名称空间。它的使用方法正好相反。使用名称空间包含函数、全局变量和其 他有名称的实体,如类。但一定不能把函数 main()的定义放在名称空间内。函数 main()表明执行开 始,必须始终在全局作用域内,否则编译器不能识别它。 在一个示例中,可以在一个名称空间内定义变量 value,再使用它: // Ex2_10.cpp // Declaring a namespace #include namespace myStuff { int value {}; } int main() { std::cout << "enter an integer: "; std::cin >> myStuff::value; std::cout << "\nYou entered " << myStuff::value << std::endl; return 0; } myStuff 名称空间定义了一个作用域,在这个作用域内定义的所有名称都由该名称空间名限 定。要从名称空间的外面引用其中的一个名称,必须利用该名称空间名限定这个名称。在名称空 间作用域内,不用进行限定即可引用在其中声明的名称—— 它们都是同一个家庭的成员。必须利用 名称空间名 myStuff 限定名称 value,否则,这个程序将不编译。函数 main()引用两个不同名称空间 内的名称,一般说来,可以在程序中根据需要声明多个名称空间。添加 using 指令以后,就不需要 限定 value: // Ex2_11.cpp // Using a using directive #include namespace myStuff Visual C++ 2013 入门经典(第 7 版) 74 { int value {}; } using namespace myStuff; // Make all the names in myStuff available int main() { std::cout << "enter an integer: "; std::cin >> value; std::cout << "\nYou entered" << value << std::endl; return 0; } 也可以对 std 使用 using 指令。一般来说,如果使用名称空间,那么不应当在代码中给它们添加 using 指令,此外,也不要在一开始就干扰名称空间。本书某些示例仍然对 std 添加一个 using 指令, 以使代码不混乱,容易阅读。当开始使用新的编程语言时,应使代码简单易读,无论复杂的代码在 实践中是多么有用。 2.13.2 多个名称空间 实际的程序很可能包括多个名称空间。对于给定名称的名称空间,可以有多个声明,所有具有 给定名称的名称空间块的内容都可以在同一个名称空间内。例如,一个程序文件包括两个名称空间: namespace sortStuff { // Everything in here is within sortStuff namespace } namespace calculateStuff { // Everything in here is within calculateStuff namespace // To refer to names from sortStuff they must be qualified } namespace sortStuff { // This is a continuation of the namespace sortStuff // so from here you can refer to names in the first sortStuff namespace // without qualifying the names } 对给定名称的名称空间的第二个声明是第一个声明的继续,所以可以从第二个名称空间块引用 第一个名称空间块中的名称,而不必限定它们。它们都在相同的名称空间中。当然,通常不会故意 按照这种方法组织源文件,但是头文件常常会这么做。例如,下面这样的代码: 第 2 章 数据、变量和计算 75 #include // Contents are in namespace std #include "myheader.h" // Contents are in namespace myStuff #include // Contents are in namespace std // and so on... 其中,iostream 和 string 是标准库头文件,myheader.h 代表一个包含程序代码的头文件。这个名 称空间的情况和前面的例子相似。 以上介绍了名称空间的作用。虽然有关名称空间的知识还有很多,但是如果掌握了本节讨论的 内容,那么在需要时,就能毫不费力地找到更多有关名称空间的知识。 2.14 小结 本章讨论了计算的基本知识,学习了 C++提供的所有基本数据类型,以及直接操作这些类型的 所有运算符。 虽然讨论了所有的基本类型,但不要误认为只有这么多。还有一些基于基本集的复杂类型,而 且还可以创建自己的原始类型。 2.15 练习 1. 编写一个程序,要求用户输入一个数字,然后把它打印出来,这时要把整数作为局部变量。 2. 编写一个程序,将从键盘输入的一个整数值读取到一个 int 型变量中,使用一个按位运算符(不 能使用%运算符!)来确定这个值除以 8 时的正余数。例如,29 = (3×8)+5 和–14 = (–2×8)+2 除以 8 的正余数分别是 5 和 2。 3. 给下列表达式加上完整的括号,以显示优先顺序和关联性: 1 + 2 + 3 + 4 16 * 4 / 2 * 3 a > b? a: c > d? e: f a & b && c & d 4. 使用下列语句编写一个计算计算机屏幕高宽比的程序,宽度和高度值(单位为像素)已给定: int width {1280}; int height {1024}; double aspect {width / height}; 在输出结果后,得到的答案是什么?它令人满意吗?如果不能令人满意,那么如何在不添加变 量的情况下修改代码? Visual C++ 2013 入门经典(第 7 版) 76 5. (高级题)如果不运行下列代码,可以计算出它将输出什么值吗?为什么? unsigned s {555}; int i {static_cast ((s >> 4) & ~(~0 << 3))}; cout << i; 2.16 本章主要内容 本章主要内容如表 2-9 所示。 表 2-9 主 题 概 念 main()函数 C++程序包含一个或多个函数,且必须包含 main()函数,它是程序开始执行的位置 函数体 函数的可执行部分由包括在大括号之间的语句组成 语句 语句以分号结束,可以放在多个代码行上 名称 命名的对象,如变量或函数,可以定义一个由字母、下划线和数字序列组成的名称,其 中第一位是字母或下划线。大写和小写字母是有区别的 保留字 C++中的保留字称为关键字。关键字不能用作代码中的名称 基本类型 C++中的所有常量和变量都有给定的类型。基本类型是 char、signed char、unsigned char、 wchar_t、short、unsigned short、int、unsigned int、long、unsigned long、long long、unsigned long long、bool、float、double 以及 long double 声明 变量的名称和类型在以分号结束的声明语句中定义。变量也可以在声明中赋予初始值 const 修饰符 利用修饰符 const 可以保护变量的值。这将防止在程序内直接修改变量,尝试修改常量 值,编译器会报告错误 自动变量 默认情况下,变量是自动变量,这意味着它的生存期始于它被声明之时,止于定义它的 作用域终止之时,终止点由其声明之后对应的右大括号表示 static 变量 可以把一个变量声明为 static,在这种情况下,它将在程序的生存期内持续存在。只能 在定义它的作用域内访问它 全局变量 可以在一个程序的所有代码块之外声明变量,这时它们具有全局名称空间作用域。除名 称与全局变量相同的局部变量所在的部分以外,可以在整个程序内访问具有全局名称 空间作用域的变量。即使这样,使用作用域解析运算符仍然可以访问它们 枚举 枚举是用固定值集定义的一种类型。应使用类型安全的枚举,它使用关键字 enum class 来定义 第 2 章 数据、变量和计算 77 (续表) 主 题 概 念 名称空间 名称空间定义一个作用域,其中声明的每个名称都由该名称空间的名称限定。从名称空 间外部引用名称时,需要限定这些名称。使用名称空间名来限定对象名,就可以在名称 空间的外部访问该名称空间中的各个对象。另外,还可以给名称空间中要引用的每个名 称提供 using 声明 标准库 标准库包含可以在程序中使用的大量函数、运算符和常量,它们包含在名称空间 std 内 lvalue 和 rvalue C++中的每个表达式都是 lvalue 或 rvalue。lvalue 是一个持续存在的地址,可以出现在 赋值语句的左边。非 const 变量就是 lvalue 的一个示例。不是 lvalue 的结果都是 rvalue, 这表示,rvalue 不能用在赋值语句的左边
还剩95页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

BigSun

贡献于2016-01-13

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf