├── .gitignore
├── Expert-C++.tex
├── LICENSE
├── README.md
├── content
├── Assessments
│ ├── 1.tex
│ ├── 10.tex
│ ├── 11.tex
│ ├── 12.tex
│ ├── 13.tex
│ ├── 14.tex
│ ├── 15.tex
│ ├── 16.tex
│ ├── 2.tex
│ ├── 3.tex
│ ├── 4.tex
│ ├── 5.tex
│ ├── 6.tex
│ ├── 7.tex
│ ├── 8.tex
│ └── 9.tex
├── Dedication.tex
├── Preface.tex
├── Section-1
│ ├── Chapter-1
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ └── chapter1.tex
│ ├── Chapter-2
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter2.tex
│ ├── Chapter-3
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 15.png
│ │ ├── 16.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter3.tex
│ ├── Chapter-4
│ │ ├── 1.png
│ │ ├── 2.png
│ │ └── chapter4.tex
│ ├── Chapter-5
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 15.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter5.tex
│ └── summary.tex
├── Section-2
│ ├── Chapter-10
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter10.tex
│ ├── Chapter-11
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter11.tex
│ ├── Chapter-12
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter12.tex
│ ├── Chapter-13
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ └── chapter13.tex
│ ├── Chapter-14
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 15.png
│ │ ├── 16.png
│ │ ├── 17.png
│ │ ├── 18.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter14.tex
│ ├── Chapter-6
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 15.png
│ │ ├── 16.png
│ │ ├── 17.png
│ │ ├── 18.png
│ │ ├── 19.png
│ │ ├── 2.png
│ │ ├── 20.png
│ │ ├── 21.png
│ │ ├── 22.png
│ │ ├── 23.png
│ │ ├── 24.png
│ │ ├── 25.png
│ │ ├── 26.png
│ │ ├── 27.png
│ │ ├── 28.png
│ │ ├── 29.png
│ │ ├── 3.png
│ │ ├── 30.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter6.tex
│ ├── Chapter-7
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ └── chapter7.tex
│ ├── Chapter-8
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── 9.png
│ │ └── chapter8.tex
│ ├── Chapter-9
│ │ └── chapter9.tex
│ └── summary.tex
└── Section-3
│ ├── Chapter-15
│ ├── 1.png
│ ├── 10.png
│ ├── 11.png
│ ├── 12.png
│ ├── 13.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ ├── 9.png
│ └── chapter15.tex
│ ├── Chapter-16
│ ├── 1.png
│ ├── 10.png
│ ├── 11.png
│ ├── 12.png
│ ├── 13.png
│ ├── 14.png
│ ├── 15.png
│ ├── 16.png
│ ├── 17.png
│ ├── 18.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ ├── 9.png
│ └── chapter16.tex
│ └── summary.tex
└── images
├── cover.png
├── tip.png
└── warn.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.toc
2 | /Expert-C++.synctex.gz
3 | *.pdf
4 | *.out
5 | *.log
6 | *.aux
7 |
--------------------------------------------------------------------------------
/Expert-C++.tex:
--------------------------------------------------------------------------------
1 |
2 | \documentclass[11pt,a4paper,UTF8]{ctexart}
3 |
4 | \usepackage[T1]{fontenc}
5 | \usepackage[utf8]{inputenc}
6 | \usepackage{authblk}
7 |
8 | \title{Expert C++}
9 | \author{Vardan Grigoryan \& Shunguang Wu}
10 | \date{\today}
11 |
12 | \usepackage{ctex} %导入中文包
13 | \usepackage{tocvsec2}
14 |
15 | \usepackage{tabularx}
16 |
17 | \usepackage{graphicx}
18 | \usepackage{subfigure}
19 |
20 | \usepackage{subfiles} %使用多文件方式进行
21 |
22 | \usepackage{geometry} %设置页边距的包
23 | \geometry{left=2.5cm,right=2cm,top=2.54cm,bottom=2.54cm} %设置书籍的页边距
24 |
25 | \usepackage{hyperref} %制作pdf的目录
26 | \hypersetup{hidelinks, %去红框
27 | colorlinks=true,
28 | allcolors=black,
29 | pdfstartview=Fit,
30 | breaklinks=true
31 | }
32 |
33 | % 调整itemlist中的行间距
34 | \usepackage{enumitem}
35 | \setenumerate[1]{itemsep=0pt,partopsep=0pt,parsep=\parskip,topsep=5pt}
36 | \setitemize[1]{itemsep=0pt,partopsep=0pt,parsep=\parskip,topsep=5pt}
37 | \setdescription{itemsep=0pt,partopsep=0pt,parsep=\parskip,topsep=5pt}
38 |
39 | % 超链接样式设置
40 | \usepackage{hyperref}
41 | \hypersetup{
42 | colorlinks=true,
43 | linkcolor=blue,
44 | filecolor=blue,
45 | urlcolor=blue,
46 | citecolor=cyan,
47 | }
48 |
49 | \usepackage{indentfirst}
50 |
51 | %展示代码
52 | \usepackage{listings}
53 | \usepackage[usenames,dvipsnames]{xcolor}
54 |
55 | \definecolor{mygreen}{rgb}{0,0.6,0}
56 | \definecolor{mygray}{rgb}{0.5,0.5,0.5}
57 | \definecolor{mymauve}{rgb}{0.58,0,0.82}
58 | \lstset{
59 | backgroundcolor=\color{lightgray},
60 | basicstyle = \footnotesize,
61 | breakatwhitespace = false,
62 | breaklines = true,
63 | captionpos = b,
64 | commentstyle = \color{mygreen}\bfseries,
65 | extendedchars = false,
66 | frame =shadowbox,
67 | framerule=0.5pt,
68 | keepspaces=true,
69 | keywordstyle=\color{blue}\bfseries, % keyword style
70 | language = C++, % the language of code
71 | otherkeywords={string},
72 | numbers=left,
73 | numbersep=5pt,
74 | numberstyle=\tiny\color{mygray},
75 | rulecolor=\color{black},
76 | showspaces=false,
77 | showstringspaces=false,
78 | showtabs=false,
79 | stepnumber=1,
80 | stringstyle=\color{mymauve}, % string literal style
81 | tabsize=2,
82 | title=\lstname
83 | }
84 |
85 | \begin{document}
86 | %\maketitle
87 |
88 | \begin{center}
89 | \includegraphics[width=1\textwidth]{images/cover}
90 | \newpage
91 | \huge
92 | \textbf{Expert C++}
93 | \\[9pt]
94 | \normalsize
95 | Become a proficient programmer by learning coding best practices with C++17 and C++20's latest features
96 | \\[10pt]
97 | \normalsize
98 | 作者: Vardan Grigoryan 和 Shunguang Wu
99 | \\[8pt]
100 | \normalsize
101 | 译者;陈晓伟
102 | \end{center}
103 |
104 |
105 | \hspace*{\fill} \\ %插入空行
106 | \noindent\textbf{本书主旨}\
107 | \begin{itemize}
108 | \item 通过学习函数式编程、模板和网络等高级概念,设计专业的、可维护的应用。
109 | \item 应用设计模式和最佳实践来解决实际问题。
110 | \item 通过设计并发数据结构和算法提升应用性能。
111 | \end{itemize}
112 |
113 | \hspace*{\fill} \\ %插入空行
114 | \noindent\textbf{本书概述}\ \par
115 | C++经过多年的发展,目前的最新标准为C++20。自C++11以来,C++语言不断的增强特性集。在新标准中,您将了解到一系列新特性,如概念、模块、范围和协程。这本书将作为学习错综复杂的语言、技术、C++工具和C++ 20新特性的指南,同时也会帮助你了解,在构建软件时如何应用他们。\par
116 |
117 | 本书将从C++的最新特性开始,然后转向高级技术,如多线程、并发性、调试、监视和高性能编程。本书将深入探讨面向对象的编程原理和C++标准模板库,并展示如何创建自定义模板。之后,将学习不同的方法,比如测试驱动开发(TDD)、行为驱动开发(BDD)和领域驱动设计(DDD),然后看看构建专业级应用程序所必需的编码最佳实践和设计模式。本书的最后,有关于人工智能和机器学习的C++最新进展的内容。\par
118 |
119 | 这本书的末尾,还有实际应用程序开发方面的专业知识,包括设计复杂软件的过程。\par
120 |
121 | \hspace*{\fill} \\ %插入空行
122 | \noindent\textbf{将会学到}\ \par
123 | \begin{itemize}
124 | \item 了解内存管理和C++底层编程,编写安全稳定的应用程序。
125 | \item 了解C++20的新特性,如模块、概念、范围和协程。
126 | \item 熟悉调试和测试技术,减少程序中的问题。
127 | \item 使用Qt5设计带GUI的程序。
128 | \item 使用多线程和并发性可以使程序运行得更快。
129 | \item 使用C++的面向对象的功能开发高端游戏。
130 | \item 使用C++探索人工智能和机器学习。
131 | \end{itemize}
132 |
133 | \hspace*{\fill} \\ %插入空行
134 | \noindent\textbf{目标读者}\ \par
135 | 这本书是为有经验的C++开发人员准备的,能将他们现有的知识进行升级,并完善在构建专业级应用程序方面的技能。\par
136 |
137 | \hspace*{\fill} \\ %插入空行
138 | \noindent\textbf{作者简介}\ \par
139 | \textbf{Vardan Grigoryan}是一名高级后端工程师和C++开发者,拥有9年以上的开发经验。Vardan以C++开发人员的身份开始他的职业生涯,然后转到服务器端后端开发领域。在设计可伸缩的后端架构时,总是在耗时敏感的关键部分使用C++。Vardan喜欢在更深的层面上处理计算机系统和程序结构,通过对现有解决方案的详细分析和对复杂系统的精心设计,可以实现真正的卓越编程。\par
140 |
141 | \textbf{Shunguang Wu}是美国约翰霍普金斯大学应用物理实验室高级专业人员,分别在西北大学和美国莱特州立大学获得理论物理和电气工程博士学位。早期职业生涯中,在非线性动力学、统计信号处理和计算机视觉领域发表了大约50篇评论期刊论文。与C++的邂逅是在20世纪90年代末的本科教学中,从那时起,他就一直在学术和工业实验室使用C++设计和开发大量的研究和开发。这些项目都是跨平台项目,主要是Windows和Linux平台。\par
142 |
143 | \hspace*{\fill} \\ %插入空行
144 | \noindent\textbf{书评人简介}\ \par
145 | \textbf{Lou Mauget}在密歇根州立大学(Michigan State University)主修物理时,使用软件设计了回旋加速器。之后,在IBM工作了34年,目前是堪萨斯州利伍德的Keyhole软件公司的顾问。Lou会使用C++、Java、JavaScript、Python和新语言进行了编程,几乎是个语言通。其目前的关注的领域有,响应式函数编程、容器、Node.js、NoSQL、地理空间系统、移动端,以及任何新的语言或框架。与其他人合著了三本计算机相关的书籍。他编写了两个IBM DeveloperWorks XML教程,并与其他人合作为IBM编写了几个J2EE认证测试。并且,他还是Packt Publishing等公司的书评人。 \par
146 |
147 | \textbf{Scott Hutchinson}在加州奥克斯纳德领导着一个C++和F\#开发团队。做了几年VB/VBA开发人员之后,他在2002年开始使用.NET框架。2016年之后,他的大部分开发工作都用C++完成。他是\href{https://github.com/exercism/fsharp}{F\# track on Exercism}项目的导师,并在工作中作为团队教授F\#函数式编程。他的主要关注函数式编程和机器学习。并且,他在假期时,会常在南加州的山区进行徒步旅行。 \par
148 |
149 | \hspace*{\fill} \\ %插入空行
150 | \noindent\textbf{本书相关}\ \par
151 | \begin{itemize}
152 | \item github翻译地址:\href{https://github.com/xiaoweiChen/Expert-Cpp}{https://github.com/xiaoweiChen/Expert-Cpp}
153 | \item 英文原版PDF:\href{https://zh.1lib.us/book/5537006/2e05c8}{https://zh.1lib.us/book/5537006/2e05c8}
154 | \end{itemize}
155 | \newpage
156 |
157 | \tableofcontents
158 | \newpage
159 |
160 | %致谢
161 | \pagestyle{empty}
162 | \subfile{content/Dedication.tex}
163 | \newpage
164 |
165 | %前言
166 | \pagestyle{empty}
167 | \subfile{content/Preface.tex}
168 | \newpage
169 |
170 | \setsecnumdepth{section}
171 | \section{C++编程基础}
172 | \subfile{content/Section-1/summary.tex}
173 | \subsection{第1章:构建C++应用}
174 | \subfile{content/Section-1/Chapter-1/chapter1.tex}
175 | \subsection{第2章:C++底层编程}
176 | \subfile{content/Section-1/Chapter-2/chapter2.tex}
177 | \subsection{第3章:面向对象编程}
178 | \subfile{content/Section-1/Chapter-3/chapter3.tex}
179 | \subsection{第4章:了解并设计模板}
180 | \subfile{content/Section-1/Chapter-4/chapter4.tex}
181 | \subsection{第5章:内存管理和智能指针}
182 | \subfile{content/Section-1/Chapter-5/chapter5.tex}
183 | \section{设计健壮和高效的应用}
184 | \subfile{content/Section-2/summary.tex}
185 | \subsection{第6章:挖掘STL中的数据结构和算法}
186 | \subfile{content/Section-2/Chapter-6/chapter6.tex}
187 | \subsection{第7章:函数式编程}
188 | \subfile{content/Section-2/Chapter-7/chapter7.tex}
189 | \subsection{第8章:并发和多线程}
190 | \subfile{content/Section-2/Chapter-8/chapter8.tex}
191 | \subsection{第9章:设计并发式数据结构}
192 | \subfile{content/Section-2/Chapter-9/chapter9.tex}
193 | \subsection{第10章:设计实际程序}
194 | \subfile{content/Section-2/Chapter-10/chapter10.tex}
195 | \subsection{第11章:使用设计模式设计策略游戏}
196 | \subfile{content/Section-2/Chapter-11/chapter11.tex}
197 | \subsection{第12章:网络和安全}
198 | \subfile{content/Section-2/Chapter-12/chapter12.tex}
199 | \subsection{第13章:调试与测试}
200 | \subfile{content/Section-2/Chapter-13/chapter13.tex}
201 | \subsection{第14章:使用Qt开发图形界面}
202 | \subfile{content/Section-2/Chapter-14/chapter14.tex}
203 | \section{人工智能领域中的C++}
204 | \subfile{content/Section-3/summary.tex}
205 | \subsection{第15章:使用C++进行机器学习}
206 | \subfile{content/Section-3/Chapter-15/chapter15.tex}
207 | \subsection{第16章:实现一个交互式搜索引擎}
208 | \subfile{content/Section-3/Chapter-16/chapter16.tex}
209 | \section{评估}
210 | \subsection{第1章}
211 | \subfile{content/Assessments/1.tex}
212 | \subsection{第2章}
213 | \subfile{content/Assessments/2.tex}
214 | \subsection{第3章}
215 | \subfile{content/Assessments/3.tex}
216 | \subsection{第4章}
217 | \subfile{content/Assessments/4.tex}
218 | \subsection{第5章}
219 | \subfile{content/Assessments/5.tex}
220 | \subsection{第6章}
221 | \subfile{content/Assessments/6.tex}
222 | \subsection{第7章}
223 | \subfile{content/Assessments/7.tex}
224 | \subsection{第8章}
225 | \subfile{content/Assessments/8.tex}
226 | \subsection{第9章}
227 | \subfile{content/Assessments/9.tex}
228 | \subsection{第10章}
229 | \subfile{content/Assessments/10.tex}
230 | \subsection{第11章}
231 | \subfile{content/Assessments/11.tex}
232 | \subsection{第12章}
233 | \subfile{content/Assessments/12.tex}
234 | \subsection{第13章}
235 | \subfile{content/Assessments/13.tex}
236 | \subsection{第14章}
237 | \subfile{content/Assessments/14.tex}
238 | \subsection{第15章}
239 | \subfile{content/Assessments/15.tex}
240 | \subsection{第16章}
241 | \subfile{content/Assessments/16.tex}
242 | \end{document}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Expert C++
2 | *Become a proficient programmer by learning coding best practices with C++17 and C++20's latest features(各位程序员,来了解一下C++17和20标准的新特性)*
3 |
4 | * 作者:Vardan Grigoryan & Shunguang Wu
5 | * 译者:陈晓伟
6 | * 原文发布时间:2020年04月10日
7 |
8 | > 翻译是译者用自己的思想,换一种语言,对原作者想法的重新阐释。鉴于我的学识所限,误解和错译在所难免。如果你能买到本书的原版,且有能力阅读英文,请直接去读原文。因为与之相较,我的译文可能根本不值得一读。
9 | >
10 | >
— 云风,程序员修炼之道第2版译者
11 |
12 | ## 本书主旨
13 |
14 | - 通过学习函数式编程、模板和网络等高级概念,设计专业的、可维护的应用。
15 | - 应用设计模式和最佳实践来解决实际问题。
16 | - 通过设计并发数据结构和算法提升应用性能。
17 |
18 | ## 本书概述
19 |
20 | C++经过多年的发展,目前的最新标准为C++20。自C++11以来,C++语言不断的增强特性集。在新标准中,您将了解到一系列新特性,如概念、模块、范围和协程。这本书将作为学习错综复杂的语言、技术、C++工具和C++ 20新特性的指南,同时也会帮助你了解,在构建软件时如何应用他们。
21 |
22 | 本书将从C++的最新特性开始,然后转向高级技术,如多线程、并发性、调试、监视和高性能编程。本书将深入探讨面向对象的编程原理和C++标准模板库,并展示如何创建自定义模板。之后,将学习不同的方法,比如测试驱动开发(TDD)、行为驱动开发(BDD)和领域驱动设计(DDD),然后看看构建专业级应用程序所必需的编码最佳实践和设计模式。本书的最后,有关于人工智能和机器学习的C++最新进展的内容。
23 |
24 | 这本书的末尾,还有实际应用程序开发方面的专业知识,包括设计复杂软件的过程。
25 |
26 | ## 将会学到
27 |
28 | - 了解内存管理和C++底层编程,编写安全稳定的应用程序。
29 | - 了解C++20的新特性,如模块、概念、范围和协程。
30 | - 熟悉调试和测试技术,减少程序中的问题。
31 | - 使用Qt5设计带GUI的程序。
32 | - 使用多线程和并发性可以使程序运行得更快。
33 | - 使用C++的面向对象的功能开发高端游戏。
34 | - 使用C++探索人工智能和机器学习。
35 |
36 | ## 目标读者
37 |
38 | 这本书是为有经验的C++开发人员准备的,能将他们现有的知识进行升级,并完善在构建专业级应用程序方面的技能。
39 |
40 | ## 作者简介
41 |
42 | **Vardan Grigoryan** 是一名高级后端工程师和C++开发者,拥有9年以上的开发经验。Vardan以C++开发人员的身份开始他的职业生涯,然后转到服务器端后端开发领域。在设计可伸缩的后端架构时,总是在耗时敏感的关键部分使用C++。Vardan喜欢在更深的层面上处理计算机系统和程序结构,通过对现有解决方案的详细分析和对复杂系统的精心设计,可以实现真正的卓越编程。
43 |
44 | **Shunguang Wu** 是美国约翰霍普金斯大学应用物理实验室高级专业人员,分别在西北大学和美国莱特州立大学获得理论物理和电气工程博士学位。早期职业生涯中,在非线性动力学、统计信号处理和计算机视觉领域发表了大约50篇评论期刊论文。与C++的邂逅是在20世纪90年代末的本科教学中,从那时起,他就一直在学术和工业实验室使用C++设计和开发大量的研究和开发。这些项目都是跨平台项目,主要是Windows和Linux平台。
45 |
46 | ## 书评人简介
47 | **Lou Mauget** 在密歇根州立大学(Michigan State University)主修物理时,使用软件设计了回旋加速器。之后,在IBM工作了34年,目前是堪萨斯州利伍德的Keyhole软件公司的顾问。Lou会使用C++、Java、JavaScript、Python和新语言进行了编程,几乎是个语言通。其目前的关注的领域有,响应式函数编程、容器、Node.js、NoSQL、地理空间系统、移动端,以及任何新的语言或框架。与其他人合著了三本计算机相关的书籍。他编写了两个IBM DeveloperWorks XML教程,并与其他人合作为IBM编写了几个J2EE认证测试。并且,他还是Packt Publishing等公司的书评人。
48 |
49 | **Scott Hutchinson** 在加州奥克斯纳德领导着一个C++和F#开发团队。做了几年VB/VBA开发人员之后,他在2002年开始使用.NET框架。2016年之后,他的大部分开发工作都用C++完成。他是[F# track on Exercism](https://github.com/exercism/fsharp)项目的导师,并在工作中作为团队教授F#函数式编程。他的主要关注函数式编程和机器学习。并且,他在假期时,会常在南加州的山区进行徒步旅行。
50 |
51 | ## 本书相关
52 |
53 | * github翻译地址:https://github.com/xiaoweiChen/Expert-Cpp
54 | * 英文原版PDF:https://zh.1lib.us/book/5537006/2e05c8
--------------------------------------------------------------------------------
/content/Assessments/1.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 从源代码生成可执行文件的过程称为编译。编译C++程序是一项复杂的任务。通常,C++编译器解析和分析源代码,生成中间代码,优化它,最后在目标文件的文件中生成机器码。另一方面,解释器不产生机器代码,它会逐行执行源代码中的指令。
3 | \item 首先是预处理,然后编译器通过解析代码来编译代码,执行语法和语义分析,然后生成中间代码。优化生成的中间代码之后,编译器生成最终目标文件(包含机器码),然后可以将其与其他目标文件链接起来。
4 | \item 预处理器的目的是处理源文件,使它们为编译做好准备。预处理程序使用预处理程序指令,比如\#define和\#include。指令不代表程序语句,但它们是预处理器的命令,告诉它如何处理源文件的文本。编译器无法识别这些指令,因此无论何时在代码中使用预处理器指令,预处理器都会在代码开始实际编译之前解析它们。
5 | \item 编译器为每个编译单元输出一个目标文件。链接器的任务是将这些目标文件组合成单个目标文件。
6 | \item 库可以作为静态库或动态库与可执行文件链接。当为静态库时,将成为最终可执行文件的一部分。动态链接库会被操作系统加载到内存中,以便为您的程序提供使用相应的功能。
7 | \end{enumerate}
--------------------------------------------------------------------------------
/content/Assessments/10.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item TDD代表测试驱动开发,其目标是在项目的实际实现之前编写测试。这有助于更清楚地定义项目需求,并预先避免代码中的大多数错误。
3 | \item 交互图描绘了对象之间交流的准确过程。这允许开发人员对任何给定时刻的实际程序执行有一个高级视图。
4 | \item 在聚合的情况下,可以在没有聚合的情况下实例化包含一个或多个其他类实例的类。
5 | \item 简单地说,Liskov替换原则确保任何函数接受某种类型T的对象作为参数,如果K扩展了T,也将接受类型K的对象。
6 | \item 开闭原则表示类应该对扩展开放,对修改关闭。在上述例子中,Animal是可扩展的,因此它与从Animal继承monkey类的原则并不矛盾。
7 | \item 参考GitHub中这一章的源代码。
8 | \end{enumerate}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/content/Assessments/11.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 重写私有虚函数允许通过保持类的公共接口不变来修改类的行为。
3 | \item 它是一种行为设计模式,其中对象封装了一个操作以及执行该操作所需的所有信息。
4 | \item 通过尽可能多地与其他对象共享数据。当我们有很多具有相似结构的对象时,在对象之间共享重复的数据可以最小化内存的使用。
5 | \item 观察器将事件通知订阅者对象,而中介扮演互通对象之间的连接中心的角色。
6 | \item 将游戏循环设计成无限循环是合理的,因为从理论上讲,游戏可能永远不会结束,只有在玩家命令时才会结束。
7 | \end{enumerate}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/content/Assessments/12.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
3 | \item 端口号提供了一种区分在同一环境中运行的几个网络应用程序的方法。
4 | \item 套接字是一种抽象,为程序员提供了一种通过网络发送和接收数据的方法。
5 | \item 首先,我们需要创建和绑定一个IP地址的套接字。接下来,我们应该侦听传入的连接,如果有的话,我们应该接受连接以进一步处理数据通信。
6 | \item TCP是一种可靠的协议。它处理端点之间的强连接,并通过重新发送未被接收方接收到的包来处理包丢失。另一方面,UDP是不可靠的。几乎所有的处理工作都由程序员来完成。UDP的优势在于它的速度,因为它忽略了握手、检查和包丢失处理。
7 | \item 宏定义会导致代码中的逻辑错误,很难发现。使用const表达式总是比使用宏更好。
8 | \item 客户端应用程序必须具有惟一的标识符以及用于授权和/或验证它们的令牌(或密码)。
9 | \end{enumerate}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/content/Assessments/13.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 这是一个实验练习。
3 | \item 以下是Ubuntu 18.04在NVIDIA Jetson Nano上的输出:
4 | \begin{lstlisting}[caption={}]
5 | swu@swu-desktop:~/ch13$ g++ -c -Wall -Weffc++ -Wextra
6 | ch13_rca_compound.cpp
7 | ch13_rca_compound.cpp: In function ‘int main()’:
8 | ch13_rca_compound.cpp:11:17: warning: operation on ‘x’ may be
9 | undefined [-Wsequence-point]
10 | std::cout << f(++x, x) << std::endl; //bad,f(4,4) or f(4,3)?
11 | ^~~
12 | swu@swu-desktop:~/ch13$ g++ -c -Wall -Weffc++ -Wextra
13 | ch13_rca_mix_sign_unsigned.cpp
14 | nothing is detected
15 | swu@swu-desktop:~/ch13$ g++ -c -Wall -Weffc++ -Wextra
16 | ch13_rca_order_of_evaluation.cpp
17 | ch13_rca_order_of_evaluation.cpp: In constructor ‘A::A(int)’:
18 | ch13_rca_order_of_evaluation.cpp:14:14: warning: ‘A::v3’ will be
19 | initialized after [-Wreorder]
20 | int v1, v2, v3;
21 | ^~
22 | ch13_rca_order_of_evaluation.cpp:14:6: warning: ‘int A::v1’ [-
23 | Wreorder]
24 | int v1, v2, v3;
25 | ^~
26 | ch13_rca_order_of_evaluation.cpp:7:2: warning: when initialized
27 | here [-Wreorder]
28 | A(int x) : v2(v1), v3(v2), v1(x) {
29 | ^
30 | ch13_rca_order_of_evaluation.cpp: In constructor ‘B::B(float)’:
31 | ch13_rca_order_of_evaluation.cpp:32:6: warning: ‘B::v2’ will be
32 | initialized after [-Wreorder]
33 | int v2;
34 | ^~
35 | ch13_rca_order_of_evaluation.cpp:31:6: warning: ‘int B::v1’ [-
36 | Wreorder]
37 | int v1; //good, here the declaration order is clear
38 | ^~
39 | ch13_rca_order_of_evaluation.cpp:25:2: warning: when initialized
40 | here [-Wreorder]
41 | B(float x) : v2(x), v1(v2) {};
42 | ^
43 | swu@swu-desktop:~/ch13$ g++ -c -Wall -Weffc++ -Wextra
44 | ch13_rca_uninit_variable.cpp
45 | ch13_rca_uninit_variable.cpp: In function ‘int main()’:
46 | ch13_rca_uninit_variable.cpp:7:2: warning: ‘x’ is used
47 | uninitialized in this function [-Wuninitialized]
48 | if (x) {
49 | ^~
50 | \end{lstlisting}
51 | \item 因为静态分析工具从它们的模型中预测错误,而动态分析工具通过程序的执行来检测错误。
52 | \item 请参考示例代码ch13\underline{ }tdd\underline{ }v3.h、ch13\underline{ }tdd\underline{ }v3.cpp和ch13\underline{ }tdd\underline{ }Boost\underline{ }UTF3.cpp
53 | \\https://github.com/PacktPublishing/Mastering-Cpp-Programming./tree/master/Chapter-13
54 | \end{enumerate}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/content/Assessments/14.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item Qt的编译模型不用虚拟机。它使用元对象编译器(MOC)将其翻译成C++,然后将其编译成特定平台的机器码。
3 | \item QApplication::exec()是应用程序的起点。它开始Qt的事件循环。
4 | \item 通过调用setWindowTitle()
5 | \item m->index (2, 3) .
6 | \item wgt->resize (400, 450) .
7 | \item 继承QLayout时,应该提供addItem()、sizeHint()、setGeometry()、itemAt()、takeAt()和minimumsize()函数的实现。
8 | \item 通过使用connect()函数,该函数以源和目标对象以及信号和槽的名称作为参数。
9 | \end{enumerate}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/content/Assessments/15.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item ML代表机器学习,是一个研究算法和统计模型的领域,计算机系统使用这些算法和模型来执行特定任务,而不需要使用明确的指令,而是依赖模式和推理。
3 | \item 监督学习算法(也称为导师训练)从标记的数据集学习,每个记录都包含描述数据的附加信息。无监督学习算法甚至更加复杂——它们处理包含一组特征的数据集,然后试图找到这些特征的有用属性。
4 | \item ML应用包括机器翻译、自然语言处理、计算机视觉和电子邮件垃圾检测。
5 | \item 其中一种方法是对每个结果加一个权重,如果减法运算的权重大于其他运算的权重,则它将成为主导运算。
6 | \item 神经网络的目的是识别模式。
7 | \end{enumerate}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/content/Assessments/16.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 爬虫程序下载网页并存储其内容,以便搜索引擎建立索引。
3 | \item 我们称它为反向索引,因为它将单词映射回它们在文档中的位置。
4 | \item 在建立索引之前,标记化对单词进行规范化。
5 | \item 推荐引擎验证并推荐适合特定请求的最佳结果。
6 | \item 知识图谱是这样一种图,其中节点是主题(知识),边是主题之间的连接。
7 | \end{enumerate}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/content/Assessments/2.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 通常,main()函数有两个形参,argc和argv,其中argc是程序的输入参数数,argv构成这些输入参数。偶尔会看到一个得到广泛支持但没有标准化的第三个参数,最常见的名称是envp。envp的类型是一个char指针数组,它保存着系统的环境变量。
3 | \item constexpr说明符声明函数的值可以在编译时求值。同样的定义也适用于变量。名称由const和表达式组成。
4 | \item 递归会为函数调用分配额外的空间。与迭代解决方案相比,为函数和调用分配空间代价会很大。
5 | \item 堆栈可以自动存储对象,开发者不需要关心相应对象的构造和销毁。通常,堆栈用于函数参数和局部变量。另一方面,堆允许在程序执行期间分配新的内存。然而,适当的内存空间回收也是开发者的责任。
6 | \item 指针的大小不依赖于指针的类型,因为指针是表示内存中地址的值。地址的大小取决于系统。通常,它不是32位就是64位。因此,我们说指针的大小是4字节或8字节。
7 | \item 就项目位置而言,数组具有独特的结构。它们被连续地放置在内存中,第二项放在第一项的右边,第三项放在第二项的右边,以此类推。考虑到这个特性,以及数组由相同类型的元素组成的事实,访问任何位置的项都需要固定的时间。
8 | \item 如果在case语句中忘记break关键字,执行将传递给下一个case语句,而不检查其条件。
9 | \item 例如, \texttt{operations['+'] = [](int a, int b) { return a + b; } }
10 | \end{enumerate}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/content/Assessments/3.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 类型、状态和行为。
3 | \item 当移动对象而不是复制对象时,我们省略了临时变量。
4 | \item 在C++中,除了默认的访问修饰符之外,结构和类之间没有任何区别。这对于结构体是公共的,对于类是私有的。
5 | \item 在聚合的情况下,可以在没有聚合的情况下实例化包含一个或多个其他类实例的类。
6 | \item 私有继承从派生类的客户端代码中隐藏继承的成员。受保护的继承也执行同样的操作,但允许链中的派生类访问这些成员。
7 | \item 通常,虚函数的引入会导致使用指向虚函数表的扩充。通常,这加起来有4或8个字节的空间(根据指针的大小)指向类对象。
8 | \item 单例设计模式允许构造该类的单个实例。在许多项目中,我们需要确保类的实例数量限制在一个以内,这是很有帮助的。例如,如果一个数据库连接类作为一个单例来实现,它的工作效果最好。
9 | \end{enumerate}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/content/Assessments/4.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 如果使用得当,宏是强大的工具。然而,以下几个方面限制了宏的使用。(1)你不能调试宏;(2)宏观扩张会导致奇怪的副作用;(3)宏没有命名空间,所以如果你有一个宏与其他地方使用的名字冲突,你会得到你不想要的宏替换,这通常会导致奇怪的错误消息;(4)宏可能会影响你不知道的事情。有关详情,请浏览 https://stackoverflow.com/questions/14041453 .
3 | \item 类/函数模板是指一种用来生成模板类/函数的模板。它只是一个模板,而不是一个类/函数,因此编译器不会为它生成任何目标代码。模板类/函数是类/函数模板的一个实例。因为它是一个类/函数,相应的目标代码由编译器生成。
4 | \item 定义类/函数模板时,在关键字template后面有一个<>符号,其中必须给出一个或多个类型形参。<>内部的类型形参称为模板形参列表。当实例化一个类/函数模板时,所有模板形参都必须替换为它们对应的模板实参,这就是模板实参列表。 \par
5 | 隐式实例化按需发生。但是,当提供库文件(.lib)时,您不知道用户将来将使用哪种类型的参数列表,因此,需要显式实例化所有可能的类型。
6 | \item 多态性意味着某些东西以不同的形式存在。具体来说,在编程语言中,多态性意味着一些函数、操作或对象在不同的上下文中具有几种不同的行为。在C++中,有两种多态性:动态多态性和静态多态性。动态多态性允许用户确定要在运行时执行的实际函数方法,而静态多态性意味着要调用的实际函数(通常是要运行的实际代码)在编译时是已知的。 \par
7 | 函数重载意味着函数用相同的名称定义,但参数集合不同(不同的签名)。 \par
8 | 函数重写是指子类重写父类中定义的虚方法的能力。 \par
9 | \item 类型特征是一种用于收集有关类型信息的技术。在它的帮助下,我们可以做出更智能的决策,开发高质量的泛型编程优化算法。类型特征可以通过部分或全部模板特化来实现。
10 | \item 我们可以在g()中写一条错误语句,然后构建代码。如果一个未使用的函数被实例化,编译器将报告错误,否则它将被成功构建。您可以在以下文件ch4\underline{ }5\underline{ }class\underline{ }template\underline{ }implicit\underline{ }inst\underline{ }v2.h和ch4\underline{ }5\underline{ }class\underline{ }template\underline{ }implicit\underline{ }inst\underline{ }B\underline{ }v2.cpp和ch4\underline{ }q7.cpp中找到示例代码,网址是\\https://github.com/PacktPublishing/Mastering- Cpp-Programming./tree/master/Chapter-4
11 | \item 这是一个开放练习,不需要标准答案。
12 | \end{enumerate}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/content/Assessments/5.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 计算机内存可以被描述为一个单一的概念-动态RAM(DRAM),或作为计算机包含的所有内存单元的组合,从寄存器和高速缓存开始,到硬盘驱动器结束。从程序员的角度来看,DRAM是最有趣的,因为它保存着计算机中运行的程序的指令和数据。
3 | \item 虚拟内存是一种有效管理计算机物理内存的方法。通常,操作系统集成了虚拟内存来处理来自程序的内存访问,并有效地将内存块分配给特定的程序。
4 | \item 在C++中,我们使用new和delete操作符来分配和释放内存空间。
5 | \item delete释放为单个对象分配的空间,而delete[]用于动态数组,释放堆上数组的所有元素。
6 | \item 垃圾收集器是一种工具或一组工具和机制,在堆上提供自动资源回收。对于垃圾收集器,需要环境支持,比如虚拟机。C++可以编译生在没有支持环境下运行的机器码。
7 | \end{enumerate}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/content/Assessments/6.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 当向vector对象插入新元素时,它将被放置在vector对象中已经分配的空闲槽位上。如果vector的大小与容量相等,则意味着vector没有存放新元素的空闲槽位。在这些(罕见的)情况下,vector会自动调整自身的大小,这涉及到分配新的内存空间并将现有元素复制到新的更大的空间。
3 | \item 当在链表的前面插入元素时,我们只创建新元素并更新链表指针,以有效地将新元素放入链表中。在vector的前端插入新元素需要将所有vector元素向右移动,从而为该元素腾出一个槽位。
4 | \item 选择排序搜索最大(或最小)元素,并用该最大(或最小)元素替换当前元素。插入排序将集合分成两个部分,遍历未排序的部分,并将其每个元素放置在已排序部分的适当槽中。
5 | \item 参考GitHub中这一章的源代码。
6 | \end{enumerate}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/content/Assessments/7.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item C++中的ranges库允许处理元素的范围,使用视图适配器来操作它们,这要高效得多,因为它们不将整个集合作为适配器结果存储。
3 | \item 如果一个函数不修改状态,并且为相同的输入产生相同的结果,那么它就是纯函数。
4 | \item 纯虚函数是没有实现的函数的特征。纯虚函数用于描述派生类的接口函数。函数式编程中的纯函数是那些不修改状态的函数。
5 | \item 折叠表达式(或简化)是将一组值组合在一起以生成较少数量的结果的过程。
6 | \item 尾递归允许编译器通过忽略为每个递归调用分配新的内存空间来优化递归调用。
7 | \end{enumerate}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/content/Assessments/8.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 如果两个操作的开始时间和结束时间在任意点交叉,那么它们将并发运行。
3 | \item 并行性意味着任务同时运行,而并发性并不强制任务同时运行。
4 | \item 过程是程序的映像。它是程序指令和装入计算机内存的数据的组合。
5 | \item 线程是进程范围内的一段代码,可以由操作系统调度器调度,而进程是正在运行的程序的镜像。
6 | \item 参考本章中的例子。
7 | \item 通过使用双重检查锁定。
8 | \item 参考GitHub中这一章的源代码。
9 | \item C++20引入协程作为对经典异步函数的补充。协程将代码的后台执行移动到下一个级别,允许一个函数在必要时被暂停和恢复。co\underline{ }await是一个告诉代码等待异步执行代码的构造。这意味着函数可以在此时挂起,并在结果就绪时继续执行。
10 | \end{enumerate}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/content/Assessments/9.tex:
--------------------------------------------------------------------------------
1 | \begin{enumerate}
2 | \item 双重检查锁定是使单例模式在多线程环境中完美工作的一种方法。
3 | \item 这是一种确保在复制另一个堆栈的基础数据时不会被修改的方法。
4 | \item 原子操作是一种不可分割的操作,原子类型利用较低级别的机制来确保指令的独立和原子执行。
5 | \item load()和store()利用低级机制来确保写和读操作以原子的方式完成。
6 | \item 除了load()和store()之外,还有诸如exchange()、wait()和notify\underline{ }one()等操作
7 | \end{enumerate}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/content/Dedication.tex:
--------------------------------------------------------------------------------
1 | 感谢我的母亲卡琳,还有我的小公主莱娅,感谢他们的鼓励和支持。\par
2 | \begin{flushright}
3 | – Vardan Grigoryan
4 | \end{flushright}
5 |
6 | 致敬我的妻子文,以及我的儿子贾斯汀和扎卡里。\par
7 | \begin{flushright}
8 | - Shunguang Wu
9 | \end{flushright}
10 |
11 |
12 | \newpage
--------------------------------------------------------------------------------
/content/Preface.tex:
--------------------------------------------------------------------------------
1 | %\begin{flushright}
2 | % \Huge\textbf{前言} \\
3 | %\zihao{0} 前言
4 | %\end{flushright}
5 |
6 | 这本书将为提供关于C++17和C++20标准的细节,以及如何编译、链接和执行。还会介绍内存管理是如何工作的,关于内存管理问题的最佳实践,以及相关的类是如何实现的。还有,编译器如何优化代码,以及编译器在支持类继承、虚函数和模板方面的方法。 \par
7 | 并告诉读者如何使用内存管理、面向对象编程、并发和设计模式来创建实际的应用。 \par
8 | 读者将了解数据结构和算法的内部细节,了解如何衡量和比较它们,并针对问题选择最适合特定的方法。\par
9 | 本书将帮助读者将系统设计和设计模式融入到C++应用中。 \par
10 | 另外,还介绍了人工智能,包括使用C++编程语言进行机器学习的基础知识。 \par
11 | 最后,读者应该能使用高效的数据结构和算法,设计实际的架构、可扩展的C++应用程序。 \par
12 |
13 | \hspace*{\fill} \\ %插入空行
14 | \noindent\textbf{目标读者}\ \par
15 | 本书适合于探究语言和程序结构相关细节的C++开发人员,或者尝试深入研究程序的本质来提高自己专业知识体系结构的读者。还有,那些愿意使用C++17和C++20的新特性(高效数据结构和算法)的开发人员。 \par
16 |
17 | \hspace*{\fill} \\ %插入空行
18 | \noindent\textbf{章节概要}\ \par
19 | \textsf{第1章},\textit{构建C++应用},
20 | 包括对C++的介绍,应用程序,以及最新的语言标准。本章还对C++涉及的主题进行了很好的概述,并介绍了代码编译、链接和执行。
21 |
22 | \noindent\textbf{}\ \par
23 | \textsf{第2章},\textit{C++底层编程},重点讨论C++的数据类型、数组、指针、指针寻址和操作,以及条件、循环、函数、函数指针和结构的底层细节。本章还介绍了结构体(struct)。
24 |
25 | \noindent\textbf{}\ \par
26 | \textsf{第3章},\textit{面向对象编程},深入研究类和对象的结构,以及编译器如何实现对象生存周期。在本章最后,读者将了解继承函数和虚函数的实现细节,以及C++中面向对象的内部细节。
27 |
28 | \noindent\textbf{}\ \par
29 | \textsf{第4章},\textit{了解并设计模板},介绍C++模板、模板函数示例、模板类、模板特化和模板元编程。特性和元编程将为C++带来魔法般的效果。
30 |
31 | \noindent\textbf{}\ \par
32 | \textsf{第5章},\textit{内存管理和智能指针}, 深入研究内存的相关内容,包括分配和管理的细节,以及使用智能指针来避免内存泄漏。
33 |
34 | \noindent\textbf{}\ \par
35 | \textsf{第6章},\textit{挖掘STL中的数据结构和算法},介绍数据结构以及其STL实现。本章还包括数据结构的比较和与对其实现例程的讨论。
36 |
37 | \noindent\textbf{}\ \par
38 | \textsf{第7章},\textit{函数式编程},着重于函数式编程,这是一种不同的编程范式,允许读者关注代码的“函数式”结构,而不是“物理”结构。掌握函数式编程为开发人员提供了一种新技能,有助于提供更好的问题解决方案。
39 |
40 | \noindent\textbf{}\ \par
41 | \textsf{第8章},\textit{并发和多线程},如何利用并发性使程序运行得更快。当算法的高效实现遇到性能瓶颈时,并发就会有用武之地。
42 |
43 | \noindent\textbf{}\ \par
44 | \textsf{第9章},\textit{设计并发式数据结构},重点介绍如何利用数据结构和并发性,来设计基于锁和无锁的并发数据结构。
45 |
46 | \noindent\textbf{}\ \par
47 | \textsf{第10章}, \textit{设计实际程序}, 通过使用设计模式,将前面章节中获得的知识整合到设计健壮的实际应用程序中。本章还包括,通过设计Amazon的克隆版来理解和应用领域驱动设计。
48 |
49 | \noindent\textbf{}\ \par
50 | \textsf{第11章},\textit{使用设计模式设计策略游戏},通过使用设计模式和最佳实践,将前面章节中获得的知识整合到策略游戏中。
51 |
52 | \noindent\textbf{}\ \par
53 | \textsf{第12章},\textit{网络和安全},在C++中的网络编程和如何利用网络编程技能建立一个dropbox后端副本。本章还讨论了如何进行最佳实践。
54 |
55 | \noindent\textbf{}\ \par
56 | \textsf{第13章},\textit{调试与测试},着重于调试C++应用程序和最佳实践,以避免代码中的错误,应用静态代码分析减少程序中的问题,引入测试驱动开发和行为驱动开发。本章还讨论了行为驱动开发和TDD用例之间的区别。
57 |
58 | \noindent\textbf{}\ \par
59 | \textsf{第14章},\textit{使用Qt开发图形界面},介绍Qt库及其主要组件。本章还包括对Qt跨平台特性的理解进行了介绍,并通过构建一个简单的桌面客户端继续dropbox的例子。
60 |
61 | \noindent\textbf{}\ \par
62 | \textsf{第15章},\textit{使用C++进行机器学习},简要介绍了人工智能的概念和该领域的最新发展。本章还介绍了机器学习的相关知识,如回归分析和聚类,以及如何建立一个简单的神经网络。
63 |
64 | \noindent\textbf{}\ \par
65 | \textsf{第16章},\textit{实现一个交互式搜索引擎},应用前面所有章节的知识,设计一个高效的基于对话框的搜索引擎,可以通过询问(和学习)用户的相应问题来找到正确的文档。
66 |
67 | \hspace*{\fill} \\ %插入空行
68 | \noindent\textbf{书中程序的应用环境}\ \par
69 | 基本的C++经验包括熟悉内存管理、面向对象编程、基本的数据结构和算法,要能了解这些就最好了。如果你想要了解复杂程序是如何工作的,并且渴望理解编程概念的细节和c++应用程序设计的最佳实践,那么你绝对应该要阅读本书。 \par
70 |
71 | \hspace*{\fill} \\ %插入空行
72 | \begin{tabular}{|l|c|r|} %l(left)居左显示 r(right)居右显示 c居中显示
73 | \hline
74 | \textbf{本书所需要的软件和硬件}&\textbf{所需操作系统}\\
75 | \hline
76 | g++编译器&Ubuntu Linux最好\\
77 | \hline
78 | \end{tabular}
79 |
80 | \noindent\textbf{}\ \par
81 | 您还需要在您的计算机上安装Qt。详情见相关章节。\par
82 |
83 | 写这本书的时候,并不是所有的C++编译器都支持C++20的新特性,可以考虑使用最新版本的编译器来测试本书介绍的新特性。 \par
84 |
85 | \hspace*{\fill} \\ %插入空行
86 | \noindent\textbf{下载示例源码} \par
87 | 您可以从您的帐户下载本书的示例代码文件\textsf{www.packt.com}. 如果在别处买到这本书,可以访问\textsf{www.packt.com/support},进行注册后,文件会通过电子邮件直接发给你。 \par
88 |
89 | 你可以通过以下步骤下载代码文件: \par
90 |
91 | \noindent\textbf{}\ \par
92 | \begin{enumerate}
93 | \item 在\textsf{www.packt.com}登录或注册账号。
94 | \item 选择\textbf{SUPPORT}页面。
95 | \item 点击\textbf{Code Downloads \& Errata}。
96 | \item 在\textbf{Search}框中输入书名后,根据屏幕上的指示进行操作。
97 | \end{enumerate}
98 |
99 | \noindent\textbf{}\ \par
100 | 下载文件后,请确保您使用最新版本的解压包: \par
101 |
102 | \begin{itemize}
103 | \item Windows下WinRAR/7-Zip
104 | \item Mac下Zipeg/iZip/UnRarX
105 | \item Linux下7-Zip/PeaZip
106 | \end{itemize}
107 |
108 | \hspace*{\fill} \\ %插入空行
109 | \noindent\textbf{下载彩图}\ \par
110 | 我们还提供了一个PDF文件,其中有本书中使用的屏幕截图/图表的彩色图像。下载地址:
111 | https://static.packt-cdn.com/downloads/9781838552657\_ColorImages.pdf \par
112 |
113 | \hspace*{\fill} \\ %插入空行
114 | \noindent\textbf{约定惯例}\ \par
115 | 本书中有许多文本约定。 \par
116 | \textsf{CodeInText}: 表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟url、用户输入和Twitter句柄。下面是一个例子:“前面的代码声明了两个带有预赋值的\textsf{readonly}属性。” \par
117 |
118 | \noindent\textbf{}\ \par
119 | \noindent 代码块样式如下: \par
120 | \textsf{Range book = 1..4}; \par
121 | \textsf{var res = Books[book];} \par
122 | \textsf{Console.WriteLine(\$\"\textbackslash tElement of array using Range: Books[{book}] => {Books[book]}");} \par
123 |
124 | \noindent\textbf{}\ \par
125 | \noindent 当我们希望提请您注意代码块的特定部分时,相关的行或项将以粗体显示: \par
126 | \textsf{private static \textbf{readonly} int num1=5;} \par
127 | \textsf{private static \textbf{readonly} int num2=6;} \par
128 |
129 | \noindent\textbf{}\ \par
130 | \noindent 任何命令行输入或输出都是这样写的:\par
131 | \textsf{\textbf{dotnet --info}} \par
132 |
133 | \noindent\textbf{}\ \par
134 | \textbf{Bold}: 指示一个新的术语,一个重要的词,或你在屏幕上看到的词。例如,菜单或对话框中的单词出现在这样的文本中。这里有一个例子:“\textbf{Select System info} from \textbf{Administration} panel”。
135 |
136 | \includegraphics[width=0.05\textwidth]{images/warn}
137 | 表示警告或重要提示。
138 |
139 | \includegraphics[width=0.05\textwidth]{images/tip}
140 | 表示提示和技巧。
141 |
142 | \newpage
143 |
--------------------------------------------------------------------------------
/content/Section-1/Chapter-1/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-1/1.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-1/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-1/2.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-1/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-1/3.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-1/chapter1.tex:
--------------------------------------------------------------------------------
1 | 不同编程语言的执行模型也不同,最常见的便是解释语言和编译语言。编译器将源代码翻译成机器代码,计算机可以在没有中间系统支持的环境下运行。另外,解释性语言代码需要支持系统、解释器和虚拟环境才能工作。 \par
2 | C++是编译语言,所以会比解释型程序运行得更快。但C++程序需要针对每个平台进行编译,但解释型程序可以跨平台运行。 \par
3 | 我们将讨论程序构建的细节,从源代码阶段开始——由编译器完成——到可执行文件(编译器的输出)结束。还会去了解,为什么为一个平台构建的程序不能在另一个平台上运行。 \par
4 | 本章将讨论以下主题: \par
5 |
6 | \begin{itemize}
7 | \item 介绍C++20。
8 | \item C++预处理的细节。
9 | \item 源代码的(底层)编译。
10 | \item 了解连接器及其功能。
11 | \item 加载和运行可执行文件的过程。
12 | \end{itemize}
13 |
14 | \noindent\textbf{}\ \par
15 | \textbf{编译器要求} \ \par
16 | g++编译器需要添加编译选项\texttt{-std=c++2a} 来编译本章的代码。可以从这里获取本章的源码文件:https://github.com/PacktPublishing/Expert-CPP \par
17 |
18 | \noindent\textbf{}\ \par
19 | \textbf{介绍C++20} \ \par
20 | C++经过多年的发展,目前发展到C++ 20。自C++ 11以来,C++标准已经对语言的特性集进行了极大地扩展。现在,让我们来看看C++ 20标准中哪些值得关注的特性。 \par
21 |
22 | \noindent\textbf{}\ \par
23 | \textbf{概念(Concepts):}\ \par
24 | 概念是C++ 20的主要特性之一,它为类型提供了一组需求。概念的基本思想是模板参数的编译时进行验证,例如:要指定模板实参必须有默认构造函数,可以使用\textbf{default\underline{ }constructible}概念,方法如下: \par
25 | \noindent\textbf{}\ \par
26 |
27 | %template <\textbf{default\underline{ }constructible} T> \par
28 | %void make\underline{ }T() { return T(); } \par
29 |
30 | \begin{lstlisting}[caption={}]
31 | template
32 | void make_T() { return T(); }
33 | \end{lstlisting}
34 |
35 | \noindent\textbf{}\ \par
36 | 上面的代码中,我们忽略了typename关键字,设置一个概念来描述模板函数的形参T。\par
37 |
38 | 可以说概念是描述其他类型的类型——可称为元类型。允许在编译时验证模板参数,以及基于类型属性的函数调用。我们将在第3章和第4章中详细讨论这些概念。 \par
39 |
40 | \noindent\textbf{}\ \par
41 | \textbf{协程(Coroutines):}\ \par
42 | 协程是能够在执行点停止,并在稍后恢复的特殊函数。协程用以下关键字进行扩展:\par
43 |
44 | \begin{enumerate}
45 | \item \texttt{co\underline{ }await} 暂停协程的执行。
46 | \item \texttt{co\underline{ }yield} 暂停协程的执行,同时返回一个值。
47 | \item \texttt{co\underline{ }return} 类似于return,完成协程时返回一个值。举个栗子:
48 | \end{enumerate}
49 |
50 | % generator step\underline{ }by\underline{ }step(int n = 0) \{ \par
51 | % \quad while (true) \{ \par
52 | % \quad \quad \textbf{co\underline{ }yield} n++; \par
53 | % \quad \} \par
54 | % \} \par
55 |
56 | \begin{lstlisting}[caption={}]
57 | generator step_by_step(int n = 0) {
58 | while (true) {
59 | co_yield n++;
60 | }
61 | }
62 | \end{lstlisting}
63 |
64 | \noindent\textbf{}\ \par
65 | 协程与promise对象相关联,promise可以存储协程的状态并发出警报。我们将在第8章中更深入地研究协程。 \par
66 |
67 | \noindent\textbf{}\ \par
68 | \textbf{范围(Ranges):}\ \par
69 | 范围库提供了一种处理元素范围的新方法。要使用它们,首先要包含头文件。来看一个例子,范围是一个有开始和结束的元素vector,提供了一个begin迭代器和一个end哨兵:\par
70 |
71 | \begin{lstlisting}[caption={}]
72 | import
73 | int main()
74 | {
75 | std::vector elements{0, 1, 2, 3, 4, 5, 6};
76 | }
77 | \end{lstlisting}
78 |
79 | 带有范围适配器(\texttt{|}操作符)的范围支持处理一系列元素的功能。看下代码: \par
80 |
81 | \begin{lstlisting}[caption={}]
82 | import
83 | import
84 | int main()
85 | {
86 | std::vector elements{0, 1, 2, 3, 4, 5, 6};
87 | for (int current : elements | ranges::view::filter([](int e) { return
88 | e % 2 == 0; }))
89 | {
90 | std::cout << current << " ";
91 | }
92 | }
93 | \end{lstlisting}
94 |
95 | 前面的代码中,使用\texttt{ranges::view::filter()}过滤偶数。请注意\texttt{|}可以在不同的vector间使用。我们将在第7章中讨论范围及其强大的特性。 \par
96 |
97 | \noindent\textbf{}\ \par
98 | \textbf{更多C++20的特性}\ \par
99 | C++ 20是一个大版本,它包含了许多更加复杂和灵活的特性。概念、范围和协程是本书讨论的众多特性中部分。 \par
100 | 最受期待的特性(之一)是模块,它提供了声明模块,以及在这些模块中导出类型和值的能力。可以将模块视为带有包含保护头文件,也就是头文件的改进版本。本章将介绍C++20中模块的特性。 \par
101 | 除了C++ 20中添加的一些特性外,我们还将在本书中讨论其他一些特性: \par
102 |
103 | \begin{itemize}
104 | \item 超级操作符: \texttt{operator<=>()}。 现在可以利用\texttt{<=>()}来控制操作符重载的冗长程度。
105 | \item \texttt{constexpr}在这门语言中越来越常见。C++ 20现在添加了\texttt{consteval}函数、\texttt{constexpr std::vector}和\texttt{std::string}等类型。
106 | \item 数学常量,如\texttt{std::number::pi}和\texttt{std::number::log2e}。
107 | \item 线程库的更新,包括停止令牌和加入线程。
108 | \item 概念迭代器。
109 | \item 仅移动视图和其他功能。
110 | \end{itemize}
111 |
112 | 为了更好地理解一些新特性并深入到该语言的本质,我们将从以前的版本开始介绍该语言的核心。这将有助于我们找到新特性相对于旧特性的更好用法,也将有助于支持历史遗留的C++代码。现在让我们从理解C++应用程序的构建开始。 \par
113 |
114 | \noindent\textbf{}\ \par
115 | \textbf{构建并运行程序}\ \par
116 | 可以使用任何文本编辑器来编写代码,因为代码就是文本。可以在简单的文本编辑器(如Vim)和高级集成开发环境(IDE)(如MS Visual Studio)之间自由选择。情书和源代码的唯一区别是后者可能由一种称为编译器的特殊程序解释(虽然情书不能被编译成程序,但它可能会让你紧张不安)。\par
117 | 为了区分文本文件和源代码,使用文件扩展名对二者进行区分。C++源码文件扩展为.cpp和.h(可能偶尔也会遇到.cxx和.hpp)。在讨论细节之前,请将编译器视为一种将源代码转换为可运行程序(即可执行文件)的工具,而源代码生成可执行文件的过程称为编译。编译C++程序是生成机器码的一系列复杂任务,机器码是计算机可以看得懂的语言。 \par
118 | 通常,C++编译器会解析和分析源码,然后生成中间码,对其进行优化,最后在目标文件中生成机器码。读者们可能见过目标文件了,它们有各自的扩展名,Linux中的.o和Windows中的.obj。所创建的目标文件不仅包含计算机可以运行的机器码,编译通常涉及几个源文件,编译每个源文件会生成一个目标文件。然后,这些目标文件通过链接器链接在一起,形成一个的可执行文件。链接器使用存储在目标文件中的附加信息,来正确地链接它们(链接将在本章后面讨论)。 \par
119 | 下面的图表描述了程序构建各个阶段: \par
120 |
121 | \begin{center}
122 | \includegraphics[width=0.3\textwidth]{content/Section-1/Chapter-1/1}
123 | \end{center}
124 |
125 | C++应用程序构建过程包括三个主要步骤:预处理、编译和链接。这些步骤使用不同的工具完成,但是编译器将它们封装在一个工具中,从而为程序员提供了更直接的接口。\par
126 | 生成的可执行文件保存在计算机的硬盘驱动器上,运行时会复制到主存RAM中,复制是由另一个名为加载器的工具完成。加载器是操作系统的一部分,它知道应该从可执行文件的内容中复制什么内容,以及在哪里复制。并且,加载的可执行文件不会从硬盘上删除。 \par
127 | 程序的加载和运行由操作系统(OS)完成,操作系统管理程序的执行,先执行优先级高的程序,完成后卸载程序等工作。程序的运行副本称为进程,进程是可执行文件的实例。 \par
128 |
129 | \noindent\textbf{}\ \par
130 | \textbf{预处理}\ \par
131 | 预处理器对源文件进行处理,使它们为编译做好准备。预处理器使用预处理器指令,比如\#define、\#include等等。指令不代表程序语句,但它们是预处理命令,告诉预处理器如何处理源文件的文本。编译器无法识别这些指令,因此无论何时在代码中使用预处理器指令,预处理器都会在代码开始实际编译之前解析它们。例如,以下代码将在编译器开始编译之前修改:\par
132 |
133 | \begin{lstlisting}[caption={}]
134 | #define NUMBER 41
135 | int main() {
136 | int a = NUMBER + 1;
137 | return 0;
138 | }
139 | \end{lstlisting}
140 |
141 | 使用\#define指令是的定义称为宏。经过预处理后,编译器得到转换后的源文件如下: \par
142 |
143 | \begin{lstlisting}[caption={}]
144 | int main() {
145 | int a = 41 + 1;
146 | return 0;
147 | }
148 | \end{lstlisting}
149 |
150 | 预处理程序只处理文本,不关心语言规则或语法。使用预处理器指令,特别是宏定义,如前面的例子中,\#define NUMBER 41很容易出错,除非预处理器只是简单地将NUMBER替换为41,而没有将41解释为整数。对于预处理器,以下两行都是有效的:\par
151 |
152 | \begin{lstlisting}[caption={}]
153 | int b = NUMBER + 1;
154 | struct T {}; // user-defined type
155 | T t = NUMBER; // preprocessed successfully, but compile error
156 | \end{lstlisting}
157 |
158 | 预处理后的代码: \par
159 |
160 | \begin{lstlisting}[caption={}]
161 | int b = 41 + 1
162 | struct T {};
163 | T t = 41; // error line
164 | \end{lstlisting}
165 |
166 | 编译器开始编译时,发现t = 41是错误的,因为没有从'int'到' T'的转换。\par
167 | 使用语法正确,但有逻辑错误的宏非常危险: \par
168 |
169 | \begin{lstlisting}[caption={}]
170 | #define DOUBLE_IT(arg) (arg * arg)
171 | \end{lstlisting}
172 |
173 | 预处理器将用(arg * arg)替换任何出现的DOUBLE\underline{}IT(arg),因此下面的代码将输出16: \par
174 |
175 | \begin{lstlisting}[caption={}]
176 | int st = DOUBLE_IT(4);
177 | std::cout << st;
178 | \end{lstlisting}
179 |
180 | 编译器接收到的代码如下所示: \par
181 |
182 | \begin{lstlisting}[caption={}]
183 | int st = (4 * 4);
184 | std::cout << st;
185 | \end{lstlisting}
186 |
187 | 当使用复杂表达式作为宏的参数时,会出现问题: \par
188 |
189 | \begin{lstlisting}[caption={}]
190 | int bad_result = DOUBLE_IT(4 + 1);
191 | std::cout << bad_result;
192 | \end{lstlisting}
193 |
194 | 这段代码期望输出25,但预处理程序除了文本处理之外什么都不做,所以会像这样替换宏:\par
195 |
196 | \begin{lstlisting}[caption={}]
197 | int bad_result = (4 + 1 * 4 + 1);
198 | std::cout << bad_result;
199 | \end{lstlisting}
200 |
201 | 输出是 9 ,而不是期望的25。 \par
202 | 对宏定义进行修正,需要在宏参数周围加上括号:\par
203 |
204 | \begin{lstlisting}[caption={}]
205 | #define DOUBLE_IT(arg) ((arg) * (arg))
206 | \end{lstlisting}
207 |
208 | 现在预处理后的代码如下: \par
209 |
210 | \begin{lstlisting}[caption={}]
211 | int bad_result = ((4 + 1) * (4 + 1));
212 | \end{lstlisting}
213 |
214 | 强烈建议在合适的情况下使用const声明,而非宏定义。 \par
215 |
216 | \hspace*{\fill} \\ %插入空行
217 | \includegraphics[width=0.05\textwidth]{images/tip}
218 | 经验法则:避免使用宏定义。宏易于出错,C++提供的构造方式可以不使用宏。 \par
219 |
220 | \noindent\textbf{}\ \par
221 | 如果使用constexpr函数,则会在编译时检查类型并处理,使用上例: \par
222 |
223 | \begin{lstlisting}[caption={}]
224 | constexpr int double_it(int arg) { return arg * arg; }
225 | int bad_result = double_it(4 + 1);
226 | \end{lstlisting}
227 |
228 | 使用constexpr说明符可以在编译时计算函数的返回值(或变量的值)。有数字定义的例子最好使用const变量: \par
229 |
230 | \begin{lstlisting}[caption={}]
231 | const int NUMBER = 41;
232 | \end{lstlisting}
233 |
234 | \noindent\textbf{}\ \par
235 | \textbf{头文件}\ \par
236 | 预处理器最常见的用法是\#include指令,用于在源代码中包含头文件。头文件包含函数、类等定义: \par
237 |
238 | \begin{lstlisting}[caption={}]
239 | // file: main.cpp
240 | #include
241 | #include "rect.h"
242 | int main() {
243 | Rect r(3.1, 4.05)
244 | std::cout << r.get_area() << std::endl;
245 | }
246 | \end{lstlisting}
247 |
248 | 假设头文件rect.h的定义如下: \par
249 |
250 | \begin{lstlisting}[caption={}]
251 | // file: rect.h
252 | struct Rect
253 | {
254 | private:
255 | double side1_;
256 | double side2_;
257 | public:
258 | Rect(double s1, double s2);
259 | const double get_area() const;
260 | };
261 | \end{lstlisting}
262 |
263 | 包含在rect.cpp中: \par
264 |
265 | \begin{lstlisting}[caption={}]
266 | // file: rect.cpp
267 | #include "rect.h"
268 | Rect::Rect(double s1, double s2)
269 | : side1_(s1), side2_(s2)
270 | {}
271 | const double Rect::get_area() const {
272 | return side1_ * side2_;
273 | }
274 | \end{lstlisting}
275 |
276 | 预处理器检查main.cpp和rect.cpp之后,其会将\#include替换为相应的iostream头文件中的内容,并将rect.h的内容替换到main.cpp和rect.cpp中。C++17 引入了\underline{~~}has\underline{ }include 预处理常量表达式。 \underline{~~}has\underline{ }include如果找到指定名称的文件,则计算结果为1,否则为0: \par
277 |
278 | \begin{lstlisting}[caption={}]
279 | #if __has_include("custom_io_stream.h")
280 | #include "custom_io_stream.h"
281 | #else
282 | #include
283 | #endif
284 | \end{lstlisting}
285 |
286 | 声明头文件时,强烈建议使用包含保护(include-guards) (\#ifndef, \#define, \#endif)方式,以避免多重声明。同样,这些也是预处理器指令,以避免以下情况:\par
287 |
288 | \begin{lstlisting}[caption={}]
289 | // file: square.h
290 | #include "rect.h"
291 | struct Square : Rect {
292 | Square(double s);
293 | };
294 | \end{lstlisting}
295 |
296 | 在main.cpp中同时包含square.h和rect.h会导致包含rect.h两次: \par
297 |
298 | \begin{lstlisting}[caption={}]
299 | // file: main.cpp
300 | #include
301 | #include "rect.h"
302 | #include "square.h"
303 | /*
304 | preprocessor replaces the following with the contents of square.h
305 | */
306 | // code omitted for brevity
307 | \end{lstlisting}
308 |
309 | 预处理后,编译器将接收到如下的main.cpp: \par
310 |
311 | \begin{lstlisting}[caption={}]
312 | // contents of the iostream file omitted for brevity
313 | struct Rect {
314 | // code omitted for brevity
315 | };
316 | struct Rect {
317 | // code omitted for brevity
318 | };
319 | struct Square : Rect {
320 | // code omitted for brevity
321 | };
322 | int main() {
323 | // code omitted for brevity
324 | }
325 | \end{lstlisting}
326 |
327 | 然后,编译器将报出一个错误,因为它遇到了两个Rect类型的声明。头文件应该通过以下方式使用包含保护来防止多重包含: \par
328 |
329 | \begin{lstlisting}[caption={}]
330 | #ifndef RECT_H
331 | #define RECT_H
332 | struct Rect { ... }; // code omitted for brevity
333 | #endif // RECT_H
334 | \end{lstlisting}
335 |
336 | 当预处理器第一次遇到头文件时,RECT\underline{ }H没有定义,在\#ifndef和\#endif之间的语句都会进行处理,包括RECT\underline{ }H的定义。当预处理器第二次在同一源文件中包含同一头文件时,因为RECT\underline{ }H已经定义,所以会省略其中的内容。 \par
337 | 包含保护是控制源文件部分编译的指令的一部分。所有的条件编译指令为\#if、\#ifdef、\#ifndef、\#else、\#elif和\#endif。\par
338 | 条件编译在许多情况下非常有用,可以在调试模式下记录函数调用。在发布程序之前,建议对程序进行调试,并针对逻辑缺陷进行测试。你可能想看看调用某个函数后代码中会发生什么,例如: \par
339 |
340 | \begin{lstlisting}[caption={}]
341 | void foo() {
342 | log("foo() called");
343 | // do some useful job
344 | }
345 | void start() {
346 | log("start() called");
347 | foo();
348 | // do some useful job
349 | }
350 | \end{lstlisting}
351 |
352 | 每个函数会调用log()函数,其实现如下: \par
353 |
354 | \begin{lstlisting}[caption={}]
355 | void log(const std::string& msg) {
356 | #if DEBUG
357 | std::cout << msg << std::endl;
358 | #endif
359 | }
360 | \end{lstlisting}
361 |
362 | 如果定义了DEBUG, log()函数将打印msg。如果你编译的项目启用了DEBUG(使用编译器标记,例如\texttt{g++}中的\texttt{-D}),那么log()函数将打印传递给它的字符串,否则什么都不做。 \par
363 |
364 | \noindent\textbf{}\ \par
365 | \textbf{C++20中的模块}\ \par
366 | 模块避免了头文件中恼人的包含保护问题,现在可以摆脱预处理宏。模块包含有两个相关的关键字,import和export。要使用一个模块,我们需要import。要声明带有导出属性的模块,我们使用export。在列出模块的好处前,先看一个简单的使用示例。下面的代码声明了一个模块: \par
367 |
368 | \begin{lstlisting}[caption={}]
369 | export module test;
370 | export int twice(int a) { return a * a; }
371 | \end{lstlisting}
372 |
373 | 第一行声明了名为test的模块。接下,声明twice()函数,并将其设置为export。这意味着可以也有未导出的函数和其他实例,这些未导出的部分是模块私有的。通过导出,可以将其设置为模块的公共部分。要使用模块,可参照下面的代码: \par
374 |
375 | \begin{lstlisting}[caption={}]
376 | import test;
377 | int main()
378 | {
379 | twice(21);
380 | }
381 | \end{lstlisting}
382 |
383 | 模块是C++中一个期待已久的特性,它在编译和维护方面提供了更好的性能。以下特性使模块比常规头文件的表现得更好:\par
384 |
385 | \begin{itemize}
386 | \item 一个模块只导入一次,类似于自定义语言实现所支持的预编译头文件。这大大减少了编译时间。未导出的元素对导入模块的单元没有影响。
387 | \item 模块允许选择哪些单元导出,哪些不导出,从而表达代码的逻辑。模块可以绑定到更大的模块中。
388 | \item 摆脱前面描述的包含安全之类的工作区。可以以任何顺序导入模块,不再需要考虑宏的重新定义。
389 | \end{itemize}
390 |
391 | \noindent\textbf{}\ \par
392 | 模块可以与头文件一起使用。我们可以在同一个文件中导入和包含头文件,如下面的例子所示: \par
393 |
394 | \begin{lstlisting}[caption={}]
395 | import ;
396 | #include
397 | int main()
398 | {
399 | std::vector vec{1, 2, 3};
400 | for (int elem : vec) std::cout << elem;
401 | }
402 | \end{lstlisting}
403 |
404 | 创建模块时,可以自由地导出模块接口文件中的元素,并将实现移动到其他文件中。逻辑与管理的方式.h和.cpp文件相同。 \par
405 |
406 | \noindent\textbf{}\ \par
407 | \textbf{编译阶段}\ \par
408 | C++编译过程包括几个阶段。其中一些阶段用于分析源代码,其他阶段用于生成和优化目标机器代码。下面的图表显示了编译的阶段:\par
409 |
410 | \begin{center}
411 | \includegraphics[width=0.3\textwidth]{content/Section-1/Chapter-1/2}
412 | \end{center}
413 |
414 | 让我们详细地来了解每一个阶段。 \par
415 |
416 | \noindent\textbf{}\ \par
417 | \textbf{符号化}\ \par
418 | 编译器的分析阶段旨在将源代码分割成可符号化的小单元。一个符号可以是一个单词,也可以只是一个操作符,比如=(等号)。符号是源代码中的原子单元,为编译器带来有意义的值,例如:表达式\texttt{int a = 42;}将被分为符号\texttt{int, a, =, 42,;}。表达式不用空格分隔,下面的表达式也会分割成相同的标记(但建议不要忘记操作数之间的空格): \par
419 |
420 | \begin{lstlisting}[caption={}]
421 | int a=42;
422 | \end{lstlisting}
423 |
424 | 使用正则表达式的复杂方法,将源码分割成符号的,这个过程称为“词法分析”,或“符号化”(分为符号)。对于编译器来说,使用符号化的输入,是构建用于分析代码语法的内部数据结构的更好方法。 \par
425 |
426 | \noindent\textbf{}\ \par
427 | \textbf{语法分析}\ \par
428 | 当谈到编程语言的编译时,我们通常区分两个术语:语法和语义。语法是代码的结构,定义了符号组合结构的规则,例如:\texttt{day nice}在英语中是一个语法正确的短语,因为它的任何一个标记都不包含错误。另外,语义关注的是代码的实际意义,也就是说\texttt{day nice}在语义上是不正确的,应该改为\texttt{a nice day}。\par
429 | 语法分析是源码分析的关键部分,即使符号符合一般语法规则,也要对其进行语法和语义分析。以下代码为例: \par
430 |
431 | \begin{lstlisting}[caption={}]
432 | int b = a + 0;
433 | \end{lstlisting}
434 |
435 | 这对我们来说可能没有意义,因为向变量添加0不会改变它的值,但编译器并不寻找逻辑意义——它寻找代码的语法正确性(缺少分号、缺少闭括号等)。编译的语法分析阶段检查代码的语法正确性。词法分析将代码分成符号,语法分析检查符号语法的正确性。如果我们遗漏了一个分号,上述表达式将产生语法错误: \par
436 |
437 | \begin{lstlisting}[caption={}]
438 | int b = a + 0
439 | \end{lstlisting}
440 |
441 | g++将会报出一个错误:\texttt{expected ';' at end of declaration}。 \par
442 |
443 | \noindent\textbf{}\ \par
444 | \textbf{语义分析}\ \par
445 | 如果前面的表达式是这样的\texttt{b = a + 0;}时,编译器将把它分成标记为it、b、=和其他。我们知道有些是未知的,但对编译器来说,这是没问题的。不过,这将导致在g++中的编译错误\texttt{ unknown type name "it" }。寻找表达式背后的含义,才是语义分析(解析)的任务。 \par
446 |
447 | \noindent\textbf{}\ \par
448 | \textbf{生成中间码}\ \par
449 | 所有的分析完成后,编译器会生成中间码,这是一个阉割的C++,主要是由C语言构成。一个简单的例子如下: \par
450 |
451 | \begin{lstlisting}[caption={}]
452 | class A {
453 | public:
454 | int get_member() { return mem_; }
455 | private:
456 | int mem_;
457 | };
458 | \end{lstlisting}
459 |
460 | 对代码进行分析之后,将生成中间码(这是一个抽象的例子,意在展示中间代码生成的思想,编译器可能在实现上有所不同):\par
461 |
462 | \begin{lstlisting}[caption={}]
463 | struct A {
464 | int mem_;
465 | };
466 | int A_get_member(A* this) { return this->mem_; }
467 | \end{lstlisting}
468 |
469 | \noindent\textbf{}\ \par
470 | \textbf{优化}\ \par
471 | 生成中间码有助于编译器在代码中进行优化,编译器可以优化代码。优化是在多次转换中完成的,例如下面的代码: \par
472 |
473 | \begin{lstlisting}[caption={}]
474 | int a = 41;
475 | int b = a + 1;
476 | \end{lstlisting}
477 |
478 | 在编译过程中,将被优化为: \par
479 |
480 | \begin{lstlisting}[caption={}]
481 | int a = 41;
482 | int b = 41 + 1;
483 | \end{lstlisting}
484 |
485 | 再次优化为: \par
486 |
487 | \begin{lstlisting}[caption={}]
488 | int a = 41;
489 | int b = 42;
490 | \end{lstlisting}
491 |
492 | 一些程序中,如今的编译器比程序员代码写得更好。 \par
493 |
494 | \noindent\textbf{}\ \par
495 | \textbf{生成机器码}\ \par
496 | 编译器优化是在中间码和生成的机器码中完成的。当我们编译这个项目的时候是什么样子的呢?之前,当我们讨论源代码的预处理时,看了一个包含几个源文件的简单结构,包括两个头文件,rect.h和square.h,每个头文件都有它的.cpp文件,以及main.cpp包含程序入口点(main()函数)。预处理之后,下面的单元是编译器的输入为:main.cpp, rect.cpp和square.cpp,如下图所示: \par
497 |
498 | \begin{center}
499 | \includegraphics[width=0.3\textwidth]{content/Section-1/Chapter-1/3}
500 | \end{center}
501 |
502 | 编译器将分别编译。编译单元,也称为源文件,在某种程度上彼此独立。当编译器编译在Rect中调用get\underline{ }area()函数的main.cpp时,不包含main.cpp中的get\underline{ }area()实现。相反,它只能确定该功能是在项目的某个地方实现的。当编译器到达rect.cpp时,编译器并不知道get\underline{ }area()函数在哪里使用。\par
503 | 下面是编译器在main.cpp通过预处理阶段后得到的结果: \par
504 |
505 | \begin{lstlisting}[caption={}]
506 | // contents of the iostream
507 | struct Rect {
508 | private:
509 | double side1_;
510 | double side2_;
511 | public:
512 | Rect(double s1, double s2);
513 | const double get_area() const;
514 | };
515 | struct Square : Rect {
516 | Square(double s);
517 | };
518 | int main() {
519 | Rect r(3.1, 4.05);
520 | std::cout << r.get_area() << std::endl;
521 | return 0;
522 | }
523 | \end{lstlisting}
524 |
525 | 分析main.cpp之后,编译器生成如下中间码(为了简单地表达编译的思想,省略了很多细节): \par
526 |
527 | \begin{lstlisting}[caption={}]
528 | struct Rect {
529 | double side1_;
530 | double side2_;
531 | };
532 | void _Rect_init_(Rect* this, double s1, double s2);
533 | double _Rect_get_area_(Rect* this);
534 | struct Square {
535 | Rect _subobject_;
536 | };
537 | void _Square_init_(Square* this, double s);
538 | int main() {
539 | Rect r;
540 | _Rect_init_(&r, 3.1, 4.05);
541 | printf("%d\n", _Rect_get_area(&r));
542 | // we've intentionally replace cout with printf for brevity and
543 | // supposing the compiler generates a C intermediate code
544 | return 0;
545 | }
546 | \end{lstlisting}
547 |
548 | 编译器在优化代码时将删除Square结构体,及其构造函数(我们将其命名为\underline{ }Square\underline{ }init\underline{ }),因为它从未在源代码中使用过。 \par
549 | 此时,编译器只操作main.cpp,因此看到调用了\underline{ }Rect\underline{ }init\underline{ }和\underline{ }Rect\underline{ }get\underline{ }area\underline{ }函数的地方,但实现没有在同一个文件中提供。然而,由于事先提供了声明,所以编译器相信这些函数是在其他编译单元中实现的。基于这种信任和最小信息函数签名(其返回类型、名称和参数)的数量和类型,编译器生成一个对象文件,其中包含main.cpp工作代码。而后续的解析工作,是由链接器完成的。 \par
550 | 下面的例子中,有生成的目标文件的简化版本,它包含两个部分——代码和信息。代码部分有每个指令的地址(十六进制值): \par
551 |
552 | \begin{lstlisting}[caption={}]
553 | code:
554 | 0x00 main
555 | 0x01 Rect r;
556 | 0x02 _Rect_init_(&r, 3.1, 4.05);
557 | 0x03 printf("%d\n", _Rect_get_area(&r));
558 | information:
559 | main: 0x00
560 | _Rect_init_: ????
561 | printf: ????
562 | _Rect_get_area_: ????
563 | \end{lstlisting}
564 |
565 | 先来看信息部分。编译器标记了代码部分中使用的所有函数,而这些函数在????的同一个编译单元中找不到。这些问号将被链接器在其他单元中找到的函数的实际地址所取代。main.cpp结束编译后,编译器开始编译rect.cpp文件: \par
566 |
567 | \begin{lstlisting}[caption={}]
568 | // file: rect.cpp
569 | struct Rect {
570 | // #include "rect.h" replaced with the contents
571 | // of the rect.h file in the preprocessing phase
572 | // code omitted for brevity
573 | };
574 | Rect::Rect(double s1, double s2)
575 | : side1_(s1), side2_(s2)
576 | {}
577 | const double Rect::get_area() const {
578 | return side1_ * side2_;
579 | }
580 | \end{lstlisting}
581 |
582 | 按照同样的逻辑,编译这个单元会产生以下输出(仍然提供抽象的例子): \par
583 |
584 | \begin{lstlisting}[caption={}]
585 | code:
586 | 0x00 _Rect_init_
587 | 0x01 side1_ = s1
588 | 0x02 side2_ = s2
589 | 0x03 return
590 | 0x04 _Rect_get_area_
591 | 0x05 register = side1_
592 | 0x06 reg_multiply side2_
593 | 0x07 return
594 | information:
595 | _Rect_init_: 0x00
596 | _Rect_get_area_: 0x04
597 | \end{lstlisting}
598 |
599 | 这个输出中包含了所有函数的地址,因此不需要等待稍后的解析。\par
600 |
601 | \noindent\textbf{}\ \par
602 | \textbf{平台和对象文件}\ \par
603 | 我们刚才看到的抽象输出,与编译器在编译后产生的实际对象文件结构有些相似。对象文件的结构取决于平台,例如:在Linux中,它以ELF格式表示(ELF代表可执行和可链接格式)。平台是程序执行的环境,这里所说的平台是指计算机体系结构(更具体地说,是指令集体系结构)和操作系统的结合。硬件和操作系统是由不同的团队和公司设计和创建的。它们有不同的设计问题解决方案,这导致了平台之间的差异。平台在许多方面存在差异,这些差异也会投射到可执行文件的格式和结构上,例如:Windows系统中的可执行文件格式是可移植可执行文件(PE),它与Linux中的ELF格式有不同的结构、编号和序列。 \par
604 | 一个目标文件可以分为几个部分。最重要的是代码部分(.text)和数据部分(.data)。.text部分保存程序指令,.data部分保存指令使用的数据。数据本身可分割成几个部分,比如初始化的、未初始化的和只读数据。 \par
605 | 除了.text和.data部分之外,对象文件的一个重要部分是符号表。符号表存储了字符串(符号)到目标文件中的位置的映射。前面的例子中,编译器生成的输出有两个部分,第二部分被标记为\texttt{information:},它保存了代码中使用的函数名称和相对地址。\texttt{information:}是目标文件实际符号表的抽象版本,符号表包含代码中定义的符号和需要解析的代码中使用的符号。然后,链接器使用这些信息将目标文件链接在一起,形成最终的可执行文件。 \par
606 |
607 | \noindent\textbf{}\ \par
608 | \textbf{连接阶段}\ \par
609 | 编译器为每个编译单元输出一个对象文件。在前面的示例中,我们有三个.cpp文件,编译器生成了三个目标文件。链接器的任务是将这些目标文件组合成一个单一的目标文件。合并文件会导致相对地址的改变,例如:如果链接器将rect.o文件放在main.o文件之后rect.o的起始地址变成0x04,而不是以前的值0x00:\par
610 |
611 | \begin{lstlisting}[caption={}]
612 | code:
613 | 0x00 main
614 | 0x01 Rect r;
615 | 0x02 _Rect_init_(&r, 3.1, 4.05);
616 | 0x03 printf("%d\n", _Rect_get_area(&r));
617 | 0x04 _Rect_init_
618 | 0x05 side1_ = s1
619 | 0x06 side2_ = s2
620 | 0x07 return
621 | 0x08 _Rect_get_area_
622 | 0x09 register = side1_
623 | 0x0A reg_multiply side2_
624 | 0x0B return
625 | information (symbol table):
626 | main: 0x00
627 | _Rect_init_: 0x04
628 | printf: ????
629 | _Rect_get_area_: 0x08
630 | _Rect_init_: 0x04
631 | _Rect_get_area_: 0x08
632 | \end{lstlisting}
633 |
634 | 链接器相应地更新符号表地址(例子中的信息部分)。正如前面提到的,每个对象文件都有符号表,它将符号的字符串名称映射到文件中的相对位置(地址)。链接的下一步是解析目标文件中所有未解析的符号。\par
635 | 现在连接器将main.o和rect.o合并,因为它们现在位于同一个文件中,就需要知道未解析符号的相对位置。printf符号将以同样的方式解析,只是这一次是把对象文件与标准库链接起来。在所有的目标文件组合在一起后(省略了方块的链接),所有的地址都进行更新,所有的符号都可解析,链接器输出一个最终的目标文件,这个目标文件可以在操作系统中执行。正如本章前面所述,在可执行文件执行前,操作系统会使用加载器将可执行文件的内容加载到内存中。 \par
636 |
637 | \noindent\textbf{}\ \par
638 | \textbf{链接库}\ \par
639 | 库类似于可执行文件,但有一个主要区别:没有main()函数,它不能作为常规程序调用。库用于组合多个程序中重用的代码,例如:通过包含头文件将程序与标准库链接起来了。 \par
640 | 库可以作为静态库或动态库与可执行文件链接。将它们链接为静态库时,将成为最终可执行文件的一部分。一个动态链接的库也由操作系统加载到内存中,以便为您的程序提供调用其函数的能力。假设我们想求平方根:\par
641 |
642 | \begin{lstlisting}[caption={}]
643 | int main() {
644 | double result = sqrt(49.0);
645 | }
646 | \end{lstlisting}
647 |
648 | C++标准库提供了sqrt()函数,它返回参数的平方根。如果编译前面的示例,它将产生一个错误,提示sqrt函数没有声明。我们知道,要使用标准库函数,必须包含相应的头文件。但是头文件不包含函数的实现,只是声明了函数(在std命名空间中)。我们先包含必要的头文件在源文件中: \par
649 |
650 | \begin{lstlisting}[caption={}]
651 | #include
652 | int main() {
653 | double result = std::sqrt(49.0);
654 | }
655 | \end{lstlisting}
656 |
657 | 编译器将sqrt符号的地址标记为未知的,链接器应该在链接阶段解析它。如果源文件没有链接到标准库实现(包含库函数的目标文件),连接器将无法解析这个问题。 \par
658 | 如果链接是静态的,那么链接器生成的最终可执行文件将包含我们的程序和标准库。另一方面,如果链接是动态库,链接器会在运行时标记查找sqrt符号。 \par
659 | 当我们运行程序时,加载器加载动态链接到程序的库。它也将标准库的内容加载到内存中,然后解析sqrt()函数在内存中的实际位置。已经加载到内存中的库,也可以供其他程序使用。 \par
660 |
661 | \noindent\textbf{}\ \par
662 | \textbf{总结}\ \par
663 | 本章中,我们讨论了C++20的一些新特性,现在我们准备深入研究这门语言。我们讨论了构建C++应用程序的过程及其编译阶段。这包括分析代码,检测语法错误,生成中间代码以进行优化,最后生成目标文件,该目标文件将与其他生成的目标文件链接在一起,形成最终的可执行文件。 \par
664 | 下一章中,我们将学习C++数据类型、数组和指针。还将了解指针是什么,并查看条件语句的底层细节。\par
665 |
666 | \noindent\textbf{}\ \par
667 | \textbf{问题}\ \par
668 | \begin{enumerate}
669 | \item 编译器和解释器的区别是什么?
670 | \item 列出程序编译阶段。
671 | \item 预处理器做什么?
672 | \item 链接器做了什么?
673 | \item 链接静态库和动态库之间有什么区别?
674 | \end{enumerate}
675 |
676 | \noindent\textbf{}\ \par
677 | \textbf{扩展阅读}\ \par
678 | 更多信息,请参阅高级C和C++编译网站: https://www.amazon.com/Advanced-C-Compiling-Milan-Stevanovic/dp/1430266678/\par
679 | LLVM的信息, https://www.packtpub.com/application-development/llvm-essentials \par
680 | \newpage
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/1.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/10.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/11.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/12.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/13.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/14.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/2.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/3.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/4.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/5.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/6.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/7.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/8.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-2/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-2/9.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/1.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/10.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/11.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/12.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/13.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/14.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/15.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/16.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/2.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/3.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/4.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/5.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/6.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/7.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/8.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-3/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-3/9.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-4/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-4/1.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-4/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-4/2.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/1.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/10.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/11.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/12.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/13.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/14.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/15.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/2.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/3.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/4.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/5.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/6.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/7.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/8.png
--------------------------------------------------------------------------------
/content/Section-1/Chapter-5/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-1/Chapter-5/9.png
--------------------------------------------------------------------------------
/content/Section-1/summary.tex:
--------------------------------------------------------------------------------
1 | 本节将学习C++编译和链接的细节,并深入了解面向对象编程(OOP)、模板和内存管理的细节。 \par
2 |
3 | 本节包括以下章节: \par
4 |
5 | \begin{itemize}
6 | \item 第1章,构建C++应用
7 | \item 第2章,C++底层编程
8 | \item 第3章,面向对象编程
9 | \item 第4章,了解并设计模板
10 | \item 第5章,内存管理和智能指针
11 | \end{itemize}
12 |
13 | \newpage
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/10.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/11.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/12.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/5.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/6.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/7.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/8.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-10/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-10/9.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/10.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/11.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/12.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/5.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/6.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/7.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/8.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-11/9.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-11/chapter11.tex:
--------------------------------------------------------------------------------
1 | 游戏开发是软件工程中最有趣的话题之一。C++因其高效而广泛应用于游戏开发中,但由于C++没有GUI组件,所以目前只能在后端使用。本章中,我们将学习如何在后端设计策略游戏。我们将把前面章节中学到的所有东西都结合起来,包括设计模式和多线程。 \par
2 | 我们要设计的游戏是一款名为《读者与扰乱者》的策略游戏。玩家创造了能够建造图书馆和其他建筑的单位,也就是所谓的“读者”,以及守卫这些建筑不受敌人攻击的士兵。 \par
3 | 本章中,我们将了解以下内容: \par
4 |
5 | \begin{itemize}
6 | \item 游戏制作入门
7 | \item 深入研究游戏设计过程
8 | \item 使用设计模式
9 | \item 设计游戏循环
10 | \end{itemize}
11 |
12 | \noindent\textbf{}\ \par
13 | \textbf{编译器要求} \ \par
14 | g++编译器需要添加编译选项 \texttt{-std=c++2a} 来编译本章的代码。可以从这里获取本章的源码文件:https://github.com/PacktPublishing/Expert-CPP \par
15 |
16 | \noindent\textbf{}\ \par
17 | \textbf{游戏制作入门} \ \par
18 | 我们将设计一款策略游戏的后端,玩家可以创建单位(工人,士兵),建造建筑,并与敌人战斗。设计一款游戏,无论是战略游戏还是第一人称射击游戏,有一些基本元素是相同的,比如:游戏物理元素,能够让玩家觉得游戏更真实,更有沉浸感。 \par
19 | 游戏设计中有些组件会在所有游戏中重复出现,如:碰撞检测机制、音频系统、图像渲染等。设计游戏时,我们既可以区分引擎和游戏,也可以开发一个紧密结合的应用程序,将引擎和游戏作为单独的成果。单独设计的游戏引擎,需要允许为进一步开发进行扩展,甚至用于其他游戏。毕竟,游戏都有相同的机制和流程。他们的不同之处在于情节主线。 \par
20 | 设计游戏引擎时,应该仔细规划使用该引擎的游戏类型。独立于游戏类型之外,3D射击游戏和策略游戏也有区别。策略游戏中,玩家在一个大的游戏场地上,有策略地部署单位。游戏世界是以自上而下的视角呈现的。 \par
21 |
22 | \noindent\textbf{}\ \par
23 | \textbf{了解《读者与扰乱者》游戏} \ \par
24 | 这款游戏很简单:玩家拥有有限的资源。这些资源可以用来为游戏角色创造建筑。我们将角色单位命名为读者和士兵。读者是建造图书馆和其他建筑的人物。每个构建的库最多可以容纳10个读者。如果玩家将10个读者移进图书馆,在一段特定时间后,图书馆将产生1个教授。教授是一个强大的单位,可以摧毁三个敌人。教授可以为士兵创造更好的武器。 \par
25 | 游戏一开始有一座已经建好的房子,两个士兵和三个读者。一所房子每5分钟生产一个新的读者。读者可以建造新的房子,这样就能产生更多的读者。他们也可以建造兵营来生产士兵。 \par
26 | 玩家的目标是建立5个库,每个库至少有一个教授。玩家必须在游戏中保护他/她的建筑和读者不受敌人攻击。敌人称为干扰者,因为他们的目标是干扰读者在图书馆里学习。 \par
27 |
28 | \noindent\textbf{}\ \par
29 | \textbf{战略游戏组件} \ \par
30 | 我们的策略游戏将包含基本组件——读者和士兵(称为单位)、建筑和地图。游戏地图包含游戏中每个对象的坐标。我们将讨论一个更轻版本的游戏地图。现在,让我们利用设计技能分解游戏本身. \par
31 | 游戏由以下角色单位组成: \par
32 |
33 | \begin{itemize}
34 | \item 读者
35 | \item 士兵
36 | \item 教授
37 | \end{itemize}
38 |
39 | 它还包括以下建筑物: \par
40 |
41 | \begin{itemize}
42 | \item 图书馆
43 | \item 房子
44 | \item 兵营
45 | \end{itemize}
46 |
47 | 现在,让我们讨论游戏中每个组件的属性。游戏角色有以下属性: \par
48 |
49 | \begin{itemize}
50 | \item 生命值(整数,每次敌人攻击后会减少)
51 | \item 攻击力(整数,可以对敌人单位造成的伤害)
52 | \item 角色(读者,士兵,教授)
53 | \end{itemize}
54 |
55 | 生命值应该有一个基于单位类型的初始值。例如,读者的初始生命值是10,而士兵的初始生命值是12。当在游戏中,所有的单位都可能受到敌人单位的攻击。每次攻击描述为生命点的减少。我们将减少的生命点数是基于攻击者的攻击力。例如,士兵的攻击力设置为3,这意味着士兵的每一次攻击都会让生命值减少3点。当受攻击的单位生命值小于等于0时,单位将死亡。 \par
56 | 建筑也是如此。建筑物有一个建造周期,一个建筑物也有生命值,任何敌人对建筑造成的伤害都会降低这些生命值。以下是建筑物业的完整列表: \par
57 |
58 | \begin{itemize}
59 | \item 生命值
60 | \item 建筑类型
61 | \item 建造时间
62 | \item 生产时间
63 | \end{itemize}
64 |
65 | 生产单位持续时间是指,生产一个新角色单位所需要的时间。例如,一个兵营每3分钟产生一个士兵,一个房子每5分钟产生一个读者,一个图书馆在10个读者进入图书馆时立即产生一个教授。 \par
66 | 现在定义了游戏组件,让我们来讨论它们之间的交互作用。 \par
67 |
68 | \noindent\textbf{}\ \par
69 | \textbf{组件之间的相互作用} \ \par
70 | 游戏设计的下一个重要内容是角色之间的互动。我们已经提到,读者可以建造建筑物。游戏中,这一过程应该得到重视,因为每种类型的建筑都有其建造时间。因此,如果读者正忙于构建过程,我们应该计算时间,以确保构建将在指定的时间后完成。然而,为了让游戏变得更好,我们应该考虑到不止一个读者可以参与到构建过程中。这应该会使建造楼房的速度加快。例如,如果一个兵营是一个读者需要5分钟建造的,那么两个读者就应该在2.5分钟完成,以此类推。这是游戏中复杂互动的一个例子,可以用下图来描述: \par
71 |
72 | \begin{center}
73 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/1}
74 | \end{center}
75 |
76 | 接下来是处理攻击。当一个单位遭受攻击时,我们应该减少单位的生命值。单位可以进行反攻(保护自己)。当有多个攻击者或防御者时,我们应该应该对每次攻击都进行处理。还定义单位每次命中的持续时间,一个单位不应该快速攻击另一个单位。为了让事情更自然,我们会在每次点击之间引入1秒或2秒的暂停。下图描述了一个简单的攻击交互: \par
77 |
78 | \begin{center}
79 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/2}
80 | \end{center}
81 |
82 | 更多的互动发生在游戏中。游戏中有两组,其中一组由玩家控制。另一个是由电脑控制。这意味着作为游戏设计师必须定义敌人。游戏将自动创建读者,并分配给他们创建图书馆、兵营和房屋的任务。每个士兵都应该承担保卫建筑物和读者(人民)的责任。有时,士兵们应该聚在一起,执行攻击任务。 \par
83 | 我们将设计一个平台,让玩家创建一个帝国,而游戏也应该创造敌人来完善游戏。玩家将经常面临敌人的攻击,敌人将通过建造更多建筑和生产更多单位而进化。总的来说,我们可以用下图来描述交互: \par
84 |
85 | \begin{center}
86 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/3}
87 | \end{center}
88 |
89 | 我们将在设计游戏时参考上述图表。 \par
90 |
91 | \noindent\textbf{}\ \par
92 | \textbf{设计游戏} \ \par
93 | 虽然游戏并不是一款典型的软件,但它的设计却与常规应用设计并无太大区别。我们将从主要实体开始,并将它们进一步分解为类的关系。 \par
94 | 在前一节中,我们讨论了游戏组件及其交互作用。我们根据项目开发生命周期进行了需求分析和收集。现在,我们开始设计游戏。 \par
95 |
96 | \noindent\textbf{}\ \par
97 | \textbf{设计角色} \ \par
98 | 下面的类图代表了一个读者: \par
99 |
100 | \begin{center}
101 | \includegraphics[width=0.6\textwidth]{content/Section-2/Chapter-11/4}
102 | \end{center}
103 |
104 | 当我们浏览其他角色单位时,我们将为每个角色单位想出一个基类。每个特定的单元将从这个基类继承,并添加特定的属性(如果有的话)。以下是角色单位的完整类图: \par
105 |
106 | \begin{center}
107 | \includegraphics[width=0.6\textwidth]{content/Section-2/Chapter-11/5}
108 | \end{center}
109 |
110 | 请注意基类——它是一个接口,而不是一个常规类。它定义了在派生类中实现的纯虚函数。以下是CharacterUnit接口在代码中的声明: \par
111 |
112 | \begin{lstlisting}[caption={}]
113 | class CharacterUnit
114 | {
115 | public:
116 | virtual void attack(const CharacterUnit&) = 0;
117 | virtual void destroy() = 0;
118 | virtual int get_power() const = 0;
119 | virtual int get_life_points() const = 0;
120 | };
121 | \end{lstlisting}
122 |
123 | attack()会降低角色的生命值,destroy()则会摧毁角色。摧毁不仅意味着将角色从场景中移除,还意味着停止该单位正在进行的所有互动(如建造建筑、自卫等等)。 \par
124 | 派生类提供了CharacterUnit接口类的纯虚函数的实现。让我们来看看Reader字符单元的代码: \par
125 |
126 | \begin{lstlisting}[caption={}]
127 | class Reader : public CharacterUnit
128 | {
129 | public:
130 | Reader();
131 | Reader(const Reader&) = delete;
132 | Reader& operator=(const Reader&) = delete;
133 |
134 | public:
135 | void attack(const CharacterUnit& attacker) override {
136 | decrease_life_points_by_(attacker.get_power());
137 | }
138 |
139 | void destroy() override {
140 | // we will leave this empty for now
141 | }
142 |
143 | int get_life_points() const override {
144 | return life_points_;
145 | }
146 |
147 | int get_power() const override {
148 | return power_;
149 | }
150 |
151 | private:
152 | void decrease_life_points_(int num) {
153 | life_points_ -= num;
154 | if (life_points_ <= 0) {
155 | destroy();
156 | }
157 | }
158 |
159 | private:
160 | int life_points_;
161 | int power_;
162 | };
163 | \end{lstlisting}
164 |
165 | 现在,我们可以通过以下任何一种方式声明Reader: \par
166 |
167 | \begin{lstlisting}[caption={}]
168 | Reader reader;
169 | Reader* pr = new Reader();
170 | CharacterUnit* cu = new Reader();
171 | \end{lstlisting}
172 |
173 | 主要通过基接口类来引用角色单位。 \par
174 |
175 | \hspace*{\fill} \\ %插入空行
176 | \includegraphics[width=0.05\textwidth]{images/tip}
177 | 请注意复制构造函数和赋值操作符。我们故意把它们标记为删除,因为我们不想通过复制其他单位来创建单位。我们将为该行为使用原型模式。 \par
178 | \noindent\textbf{}\ \par
179 |
180 | 我们应该为不同类型的单位做同样的事情的场景中,拥有CharacterUnit接口非常重要,例如:我们必须计算两名士兵、一名读者和一名教授对一座建筑造成的全部损害。我们不必保留三个不同的引用来引用三种不同类型的单元,而是将它们都称为CharacterUnits。方法如下: \par
181 |
182 | \begin{lstlisting}[caption={}]
183 | int calculate_damage(const std::vector& units)
184 | {
185 | return std::reduce(units.begin(), units.end(), 0,
186 | [](CharacterUnit& u1, CharacterUnit& u2) {
187 | return u1.get_power() + u2.get_power();
188 | }
189 | );
190 | }
191 | \end{lstlisting}
192 |
193 | calculate\underline{ }damage()函数对单元类型进行抽象,它不关心读者或士兵。它只调用CharacterUnit接口的get\underline{ }power()方法,该方法保证特定对象的实现。\par
194 | 我们将更新角色单位类,让我们继续为建筑设计类。 \par
195 |
196 | \noindent\textbf{}\ \par
197 | \textbf{设计建筑} \ \par
198 | 建筑类型与角色单位的接口相似。例如,可以定义建筑的类: \par
199 |
200 | \begin{lstlisting}[caption={}]
201 | class House
202 | {
203 | public:
204 | House();
205 | // copying will be covered by a Prototype
206 | House(const House&) = delete;
207 | House& operator=(const House&) = delete;
208 |
209 | public:
210 | void attack(const CharacterUnit&);
211 | void destroy();
212 | void build(const CharacterUnit&);
213 | // ...
214 |
215 | private:
216 | int life_points_;
217 | int capacity_;
218 | std::chrono::duration construction_duration_;
219 | };
220 | \end{lstlisting}
221 |
222 | 我们使用std::chrono::duration来统计房屋建造的时长,它在头文件中定义为一个刻度数和一个刻度周期,其中刻度周期是从一个刻度到下一个刻度的秒数。 \par
223 | House类需要更多的细节,需要为所有的建筑提供一个基接口(甚至是一个抽象类)。本章中描述的建筑都有相同的行为。建筑接口如下: \par
224 |
225 | \begin{lstlisting}[caption={}]
226 | class IBuilding
227 | {
228 | public:
229 | virtual void attack(const CharacterUnit&) = 0;
230 | virtual void destroy() = 0;
231 | virtual void build(CharacterUnit*) = 0;
232 | virtual int get_life_points() const = 0;
233 | };
234 | \end{lstlisting}
235 |
236 | 注意建筑物前面的I前缀。许多开发人员建议为接口类使用前缀或后缀,以提高可读性。例如,Building可能被命名为IBuilding或BuildingInterface。我们将对前面描述的CharacterUnit使用相同的命名技术。 \par
237 | House、Barrack和Library类实现了IBuilding接口,并且必须提供纯虚方法的实现。例如,Barrack类如下所示: \par
238 |
239 | \begin{lstlisting}[caption={}]
240 | class Barrack : public IBuilding
241 | {
242 | public:
243 | void attack(const ICharacterUnit& attacker) override {
244 | decrease_life_points_(attacker.get_power());
245 | }
246 |
247 | void destroy() override {
248 | // we will leave this empty for now
249 | }
250 |
251 | void build(ICharacterUnit* builder) override {
252 | // construction of the building
253 | }
254 |
255 | int get_life_points() const override {
256 | return life_points_;
257 | }
258 |
259 | private:
260 | int life_points_;
261 | int capacity_;
262 | std::chrono::duration construction_duration_;
263 | };
264 | \end{lstlisting}
265 |
266 | 让我们更详细地讨论构建时长的实现,std::chrono::duration可以告诉我们构建花费的时间。另外,类的最终设计可能在本章的行文过程中发生变化。现在,让我们看看如何让游戏的组件彼此互动。 \par
267 |
268 | \noindent\textbf{}\ \par
269 | \textbf{设计游戏控制器} \ \par
270 | 为角色单位和建筑设计类型只是设计游戏的第一步。游戏中最重要的组件间的互动。我们需要仔细分析和设计案例,比如:两个或两个以上的读者建造一座建筑。我们已经介绍了建筑的建造时间,但我们没有考虑到一个建筑可能由多个读者建造(可以建造建筑的角色单位)。 \par
271 | 两个读者建一座大楼的速度应该比一个读者建的快两倍。如果另一个读者加入了,我们应该重新计算持续时间。然而,我们应该限制能建造同一建筑的读者数量。 \par
272 | 如果读者被敌人攻击,这会打断读者建造,他们会进行自卫。当读者停止在建筑上工作时,我们应该重新计算施工时间。当角色受到攻击时,它应该反击来保护自己。每次命中都会减少角色的生命值。一个角色可能同时受到多个敌人角色的攻击。这将更快地降低他们的生命值。 \par
273 | 建筑有一个计时器,它会周期性地产生角色。设计最重要的是游戏动态,也就是循环。在每个特定的时间框架,游戏中都会发生一些事情。这可能是敌人士兵靠近,角色单位建造东西,或其他任何东西。一个操作的执行与另一个不相关的操作的完成没有严格的联系。这意味着建筑的建造与角色的创造同时发生。与大多数应用程序不同,游戏应该保持移动,即使用户不提供任何输入。如果玩家未能执行某个行动,游戏也不会停止。角色单位可能会等待一个命令,但是建筑会继续工作——产生新的角色。同样地,敌人玩家(自动玩家)也会为胜利不停的奋斗。 \par
274 |
275 | \noindent\textbf{}\ \par
276 | \textbf{并发行为} \ \par
277 | 游戏中的许多行动是同时发生的。就像我们之前所讨论的,建筑的建造不应该因为未参与建造的单位或受到敌人的攻击而停止。这意味着我们应该为游戏中的许多对象设计并发行为。 \par
278 | C++中实现并发的最佳方法是使用线程。我们可以重新设计单位和建筑,以便它们在基类中有可重写的动作,该动作将在单独的线程中执行。让我们重新设计IBuilding,使它成为一个抽象类,它有一个额外的run()虚函数: \par
279 |
280 | \begin{lstlisting}[caption={}]
281 | class Building
282 | {
283 | public:
284 | virtual void attack(const ICharacterUnit&) = 0;
285 | virtual void destroy() = 0;
286 | virtual void build(ICharacterUnit*) = 0;
287 | virtual int get_life_points() const = 0;
288 |
289 | public:
290 | void run() {
291 | std::jthread{Building::background_action_, this};
292 | }
293 |
294 | private:
295 | virtual void background_action_() {
296 | // no or default implementation in the base class
297 | }
298 | };
299 | \end{lstlisting}
300 |
301 | 注意background\underline{ }action\underline{ }()函数,它是私有的,但却是虚的,我们可以在派生类中重写它。run()函数不是虚函数,它在线程中运行私有实现。派生类可能会提供background\underline{ }action\underline{ }()的实现。当一个单元来建造建筑时,就会调用build()虚函数。build()函数将计算构造时间的工作委托给run()函数。 \par
302 |
303 | \noindent\textbf{}\ \par
304 | \textbf{游戏事件循环} \ \par
305 | 解决这个问题最简单的方法是定义一个事件循环。事件循环如下所示: \par
306 |
307 | \begin{lstlisting}[caption={}]
308 | while (true)
309 | {
310 | processUserActions();
311 | updateGame();
312 | }
313 | \end{lstlisting}
314 |
315 | 即使用户(玩家)没有采取任何行动,游戏仍然通过调用updateGame()函数继续运行。请注意,前面的代码只是对事件循环的介绍,它会无限循环并在每次迭代中处理和更新游戏。 \par
316 | 每次循环迭代都会推进游戏状态。如果用户操作处理需要很长时间,它可能会阻塞循环,游戏会暂停一会儿。我们通常以每秒帧数(FPS)来衡量游戏速度。值越高,游戏就越流畅。 \par
317 | 我们需要设计在游戏过程中持续运行的游戏循环。重要的是要将其设计成用户操作处理不会阻塞循环的方式。 \par
318 | 游戏循环负责处理游戏中发生的一切,包括AI。关于AI,指的是之前讨论过的电脑玩家。除此之外,游戏循环还会处理角色的行动,并相应地更新游戏状态。 \par
319 | 在深入研究游戏循环设计之前,让我们先了解一些能够帮助我们完成这一复杂任务的设计模式。毕竟,游戏循环是另一种设计模式! \par
320 |
321 | \noindent\textbf{}\ \par
322 | \textbf{使用设计模式} \ \par
323 | 使用面向对象编程(OOP)范式设计游戏是很香的,游戏代表的是物体之间紧密互动的组合。在我们的策略游戏中,我们的建筑由单位建造。单位防御敌人单位等。这种内部交流导致了复杂性的增长。随着项目的发展和更多特性的增加,维护将变得更加困难。很明显,设计是建筑项目中最重要的部分之一。合并设计模式将大大改善设计过程和项目支持。 \par
324 | 让我们来看看一些在游戏开发中有用的设计模式。我们将从经典模式开始,然后讨论更多特定于游戏的模式。 \par
325 |
326 | \noindent\textbf{}\ \par
327 | \textbf{命令模式} \ \par
328 | 开发人员将设计模式分为创建的、结构的和行为的三种类别。命令模式是一种行为设计模式。行为设计模式主要关注在对象之间的通信中提供灵活性。在此上下文中,命令模式将操作封装在一个对象中,该对象包含必要的信息以及操作本身。这样,命令模式的行为就像一个智能函数。在C++中实现它的最简单方法是重载类的函数操作(),如下所示: \par
329 |
330 | \begin{lstlisting}[caption={}]
331 | class Command
332 | {
333 | public:
334 | void operator()() { std::cout << "I'm a smart function!"; }
335 | };
336 | \end{lstlisting}
337 |
338 | 带有重载函数操作符()的类有时称为仿函数。以上代码与下面的常规函数声明相同: \par
339 |
340 | \begin{lstlisting}[caption={}]
341 | void myFunction() { std::cout << "I'm not so smart!"; }
342 | \end{lstlisting}
343 |
344 | 调用常规函数和Command类的对象看起来类似,如下所示: \par
345 |
346 | \begin{lstlisting}[caption={}]
347 | myFunction();
348 | Command myCommand;
349 | myCommand();
350 | \end{lstlisting}
351 |
352 | 当我们需要为函数使用状态时,这两者之间的区别很明显。为了存储常规函数的状态,我们使用静态变量。为了在对象中存储状态,我们使用对象本身。下面是如何跟踪重载函数操作符的调用次数: \par
353 |
354 | \begin{lstlisting}[caption={}]
355 | class Command
356 | {
357 | public:
358 | Command() : called_(0) {}
359 |
360 | void operator()() {
361 | ++called_;
362 | std::cout << "I'm a smart function." << std::endl;
363 | std::cout << "I've been called" << called_ << " times." << std::endl;
364 | }
365 | private:
366 | int called_;
367 | };
368 | \end{lstlisting}
369 |
370 | 调用的数量对于命令类的每个实例是唯一的。下面的代码声明了两个Command实例,并分别调用它们两次和三次: \par
371 |
372 | \begin{lstlisting}[caption={}]
373 | Command c1;
374 | Command c2;
375 | c1();
376 | c1();
377 | c2();
378 | c2();
379 | c2();
380 | // at this point, c1.called_ equals 2, c2.called_ equals 3
381 | \end{lstlisting}
382 |
383 | 现在,让我们尝试将这个模式应用到我们的策略游戏中。游戏的最终版本有一个图形界面,允许用户使用各种按钮和鼠标点击来控制游戏。例如,要让一个角色单位建造一座房子,而不是一个兵营,我们应该在游戏面板上选择相应的图标。让我们想象一个带有游戏地图和一些控制游戏动态的按钮的游戏面板。 \par
384 | 游戏向玩家提供以下命令: \par
385 |
386 | \begin{itemize}
387 | \item 将角色单位从A点移动到B点
388 | \item 攻击敌人
389 | \item 建造建筑
390 | \item 攻击建筑
391 | \end{itemize}
392 |
393 | 游戏命令的设计如下: \par
394 |
395 | \begin{center}
396 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/6}
397 | \end{center}
398 |
399 | 每个类都封装了操作逻辑。客户端代码与处理操作无关。它使用命令指针进行操作,每个命令指针都指向具体的命令(如前面的图像所示)。注意,我们只描述了玩家将执行的命令。游戏本身使用命令在模块之间进行通信,自动命令的例子包括Run、Defend、Die和Create。下面是一个更广泛的图表,展示了游戏中的命令: \par
400 |
401 | \begin{center}
402 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/7}
403 | \end{center}
404 |
405 | 上述命令执行游戏玩法中出现的任何事件。为了监听这些事件,我们应该考虑使用观察者模式。 \par
406 |
407 | \noindent\textbf{}\ \par
408 | \textbf{观察者模式} \ \par
409 | 观察者模式是一种体系结构机制,允许我们关注对象状态更改,我们观察物体的变化。观察者模式也是一种行为设计模式。 \par
410 | 大多数策略游戏都包含资源的概念。可能是石材、金币、木材等,例如:在建造一座建筑时,玩家需要花费20个木材,40个石材和10个金币。最终,玩家将耗尽资源并需要收集这些资源。这个玩家创造了更多的角色单位,并让他们收集资源——几乎就像在现实生活一样。 \par
411 | 现在,我们假设游戏中也有类似的资源收集。当玩家让单位收集资源时,他们应该在每次收集到固定数量的资源时通知我们。玩家是资源收集事件的关注者。 \par
412 | 建筑也是如此。建筑物产生一个角色-玩家会收到通知。一个角色单位完成建筑建造-玩家会收到通知。我们更新玩家仪表板以保持玩家的游戏状态更新,玩家在玩游戏时可以了解拥有多少资源、多少单位和多少建筑。 \par
413 | 观察者涉及到实现一个类,该类存储其关注者并在事件上调用指定的函数。它由两个实体组成:关注者和执行者。如下图所示,用户数量不限于1个: \par
414 |
415 | \begin{center}
416 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/8}
417 | \end{center}
418 |
419 | 例如,当一个角色单位去建造一座建筑时,它会不断的去建造,除非被阻止。造成这种情况的原因有很多: \par
420 |
421 | \begin{itemize}
422 | \item 玩家决定取消建造建筑的过程。
423 | \item 角色单位必须防御敌人的攻击,并暂停建造过程。
424 | \item 建筑已经完成,所以角色单位停止了工作。
425 | \end{itemize}
426 |
427 | 玩家还希望在建筑完成时得到通知,因为他们可能计划在完成建筑后让角色单位执行其他任务。我们可以设计构建流程,使其在事件完成时通知其侦听器(关注者)。下面的类图还涉及到一个操作接口。将其视为命令模式的实现: \par
428 |
429 | \begin{center}
430 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/9}
431 | \end{center}
432 |
433 | 针对观察者开发类会让我们意识到,游戏中几乎所有的实体都是关注者、执行者或两者兼有。如果您遇到类似的场景,您可以考虑使用中介——另一种行为模式。对象通过中介对象相互通信。触发事件的对象可以让中介知道该事件。然后,中介将消息传递给“订阅”到对象状态的任何相关对象。下图是中介集成的简化版本: \par
434 |
435 | \begin{center}
436 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/10}
437 | \end{center}
438 |
439 | 每个对象都包含一个中介,用于将更改通知关注者。中介对象通常包含所有相互通信的对象。对于事件,每个对象通过中介通知相关方,例如:当构建构建完成时,会触发中介,中介会通知所有关注方。要接收这些通知,每个对象都应该事先关注中介。 \par
440 |
441 | \noindent\textbf{}\ \par
442 | \textbf{享元模式} \ \par
443 | 享元是一种结构设计模式。结构模式负责将对象和类组装成更大、更灵活的结构。享元允许我们通过共享对象的公共部分来缓存对象。 \par
444 | 在策略游戏中,我们要处理许多呈现在屏幕上的对象。游戏过程中,对象的数量会增加。玩家玩游戏的时间越长,创造的角色单位和建筑就越多(敌人也是如此)。游戏中的每个单位代表一个包含数据的独立对象。字符单元至少占用16个字节的内存(对于它的两个整数数据成员和虚拟表指针)。 \par
445 | 当我们为了在屏幕上呈现单位而添加额外字段时,情况就变得更糟了。例如,高度、宽度和精灵(代表渲染单元的图像)。除了角色单位之外,游戏还应该添加一些辅助道具,例如:树、石头等装饰性道具,以提升用户体验。某种程度上,我们在屏幕上渲染了大量对象,每个对象几乎代表相同的对象,但它们的状态有微小的差异。享元模式在这里起到了很大的作用。对于角色单位,它的高度、宽度和精灵在所有单位中存储了几乎相同的数据。 \par
446 | 享元模式建议将一个重物体分解成两个: \par
447 |
448 | \begin{itemize}
449 | \item 一个不可变的对象,它为相同类型的每个对象包含相同的数据
450 | \item 一个可变对象,将自己与其他对象区分开来
451 | \end{itemize}
452 |
453 | 例如,一个移动的角色单位有它自己的高度、长度和精灵,所有这些都在所有的角色单位中重复。因此,我们可以将这些属性表示为单个不可变对象,并为所有对象的属性提供相同的值。然而,角色单位在屏幕上的位置可能与其他单位不同,当玩家命令该单位移动到其他地方或开始建造建筑时,该单位的位置会不断变化,直到终点。在每一步中,单元都应该在屏幕上重新绘制。通过这样做,我们得到了如下设计: \par
454 |
455 | \begin{center}
456 | \includegraphics[width=1.0\textwidth]{content/Section-2/Chapter-11/11}
457 | \end{center}
458 |
459 | 左边是修改前的CharacterUnit,而右边表示使用享元模式进行的最近的修改。游戏现在可以处理一堆CharacterUnit对象,而每一个都将存储一些UnitData对象的引用。这样,我们节省了很多内存。我们将每个单元唯一的值存储在CharacterUnit对象中。这些价值观会随着时间而改变。尺寸和精灵是恒定的,所以我们可以保持一个具有这些值的对象。这种不可变的数据称为内在状态,而对象的可变部分(CharacterUnit)称为外在状态。 \par
460 | 我们有意地将数据成员移动到CharacterUnit,从而将其从接口重新设计为抽象类。正如我们在第3章中所讨论的,抽象类几乎与可能包含实现的接口相同。move()方法是所有类型单元的默认实现的。因为所有的单元都有共同的属性,派生类只提供必要的行为就好,比如:生命点和能量。 \par
461 | 优化内存使用之后,我们应该处理复制对象。这款游戏涉及大量创造新对象。每个建筑都产生一个特定的角色单位,角色单位建造建筑,游戏世界本身呈现装饰元素(树木、岩石等)。现在,让我们尝试通过合并克隆功能来改进CharacterUnit。在本章的前面,我们故意删除了复制构造函数和赋值操作符。现在,是时候提供一种从现有对象创建新对象的机制了。 \par
462 |
463 | \noindent\textbf{}\ \par
464 | \textbf{原型模式} \ \par
465 | 该模式允许我们独立于对象类型创建对象的副本。下面的代码代表了关于我们最近修改的CharacterUnit类的最终版本。我们还将添加新的clone()成员函数,以合并原型模式: \par
466 |
467 | \begin{lstlisting}[caption={}]
468 | class CharacterUnit
469 | {
470 | public:
471 | CharacterUnit() {}
472 | CharacterUnit& operator=(const CharacterUnit&) = delete;
473 | virtual ~Character() {}
474 | virtual CharacterUnit* clone() = 0;
475 |
476 | public:
477 | void move(const Point& to) {
478 | // the graphics-specific implementation
479 | }
480 | virtual void attack(const CharacterUnit&) = 0;
481 | virtual void destroy() = 0;
482 | int get_power() const { return power_; }
483 | int get_life_points() const { return life_points_; }
484 |
485 | private:
486 | CharacterUnit(const CharacterUnit& other) {
487 | life_points_ = other.life_points_;
488 | power_ = other.power_;
489 | }
490 |
491 | private:
492 | int life_points_;
493 | int power_;
494 | };
495 | \end{lstlisting}
496 |
497 | 我们删除了赋值操作符,并将复制构造函数移动到private部分。派生类覆盖clone()成员函数,如下所示: \par
498 |
499 | \begin{lstlisting}[caption={}]
500 | class Reader : public CharacterUnit
501 | {
502 | public:
503 | Reader* clone() override {
504 | return new Reader(*this);
505 | }
506 |
507 | // code omitted for brevity
508 | };
509 | \end{lstlisting}
510 |
511 | 原型模式将复制委托给对象。公共接口允许我们将客户端代码与对象的类解耦。现在,我们可以在不知道它是读者或士兵的情况下,复制一个角色单位。请看下面的例子: \par
512 |
513 | \begin{lstlisting}[caption={}]
514 | // The unit can have any of the CharacterUnit derived types
515 | CharacterUnit* new_unit = unit->clone();
516 | \end{lstlisting}
517 |
518 | 当需要将对象转换为特定类型时,要强制将工作动态转换为良好。 \par
519 | 在本节中,我们讨论了许多有用的设计模式。如果你不熟悉这些模式,这似乎有点难以应付,正确使用它们可以让我们设计灵活和可维护的项目。最后让我们回到我们之前介绍的游戏循环。 \par
520 |
521 | \noindent\textbf{}\ \par
522 | \textbf{设计游戏循环} \ \par
523 | 策略游戏的玩法变化最大。在每一个时间点上,许多动作同时发生。读者完成了他们的建筑,兵营生士兵,士兵攻击敌人,玩家命令单位移动、建造、攻击或奔跑等。游戏循环处理一切。通常,游戏引擎提供设计良好的游戏循环。 \par
524 | 游戏循环在我们玩游戏时运行。正如我们已经提到的,循环处理玩家的动作,更新游戏状态,并呈现游戏(让玩家可以看到状态变化)。它在每次迭代中都这样做。循环还应该控制游戏玩法的速率,也就是FPS。例如,如果你设计一款以60帧每秒运行的游戏,这意味着每帧大约需要16毫秒。 \par
525 | 以下代码是在本章前面的简单游戏循环中使用的: \par
526 |
527 | \begin{lstlisting}[caption={}]
528 | while (true)
529 | {
530 | processUserActions();
531 | updateGame();
532 | }
533 | \end{lstlisting}
534 |
535 | 如果没有需要处理的用户操作,上述代码将快速运行。它在速度较快的机器上运行得更快(目标是16毫秒每帧)。这可能需要我们在处理动作和更新游戏状态后等待一段时间,如下图所示: \par
536 |
537 | \begin{center}
538 | \includegraphics[width=0.4\textwidth]{content/Section-2/Chapter-11/12}
539 | \end{center}
540 |
541 | 每次更新都将游戏时间提前一个固定的时间,这需要在现实世界中处理一个固定的时间。另一方面,如果一个帧的处理时间超过了指定的毫秒,游戏就会变慢。 \par
542 | 如上图所示,游戏中发生的所有事情都包含在游戏的更新部分中。大多数时候,更新可能需要一次执行多个操作。此外,我们必须为游戏后台发生的一些操作保留计时器,这主要取决于游戏的细节。例如,建造一座建筑可以表现为两种状态:初始状态和最终状态。 \par
543 | 在平面设计上,这两种状态应该代表两种不同的图像。第一张图片包含了建筑的一些基本部分,可能还包括一些围绕它的岩石,就好像它正在准备被建造一样。下一个图像代表了最终建成的建筑。当角色单位开始建造建筑时,我们便会向玩家呈现第一张图像(注:即围绕着一些岩石的基座)。当建筑完成后,我们用包含最终建筑的图像替换第一张图像。为了使这个过程更自然(更真实),我们人为地延长了时间。这意味着我们在图像的两个状态之间保持一个持续30秒或更多的计时器。 \par
544 | 我们用最少的细节描述了最简单的情况。如果我们需要让游戏变得更加详细,例如:通过渲染建筑在建造过程中的每个变化,我们就应该在代表建筑每个步骤的图像之间保留大量计时器。在更新游戏后,我们等待N毫秒。等待更多毫秒会让游戏流程更接近现实生活。如果更新时间太长,导致玩家体验滞后怎么办?在这种情况下,我们需要优化游戏,使其符合用户的最佳体验。现在,假设更新游戏需要执行数百次操作,玩家实现了一个繁荣的帝国,现在正在建造许多建筑物,并且用许多士兵攻击敌人。 \par
545 | 一个角色单位的每个行动,如从一个点移动到另一个点,攻击一个敌人单位,建造一座建筑等,都会在屏幕上及时呈现。现在,如果我们一次在屏幕上渲染数百个单位的状态会怎样?这就是我们使用多线程方法的地方。每个行动都涉及独立修改对象(注:对象是游戏中的任何单位,包括静态建筑)的状态。 \par
546 |
547 | \noindent\textbf{}\ \par
548 | \textbf{总结} \ \par
549 | 设计游戏是一项复杂的任务。我们可以将游戏开发视为一个独立的编程领域。游戏有不同的类型,其中之一就是策略游戏。策略游戏设计包括设计单位和建筑等游戏组件。通常情况下,策略游戏包括收集资源、建立帝国和与敌人战斗。游戏玩法包括游戏组件之间的动态交流,如角色单位建造建筑和收集资源,士兵防御敌人的土地等。 \par
550 | 为了恰当地设计一款策略游戏,我们结合了OOP设计技巧和设计模式。设计模式在设计整个游戏及其组件的交互过程中扮演着重要角色。在本章中,我们讨论了命令模式,它将动作封装在对象中;观察者模式,用于关注对象事件;以及中介模式,该模式用于将观察者推进到组件之间复杂交互的级别。 \par
551 | 游戏最重要的部分是它的循环。游戏循环控制渲染、游戏状态的及时更新和其他子系统。设计它涉及到使用事件队列和计时器。现代游戏使用网络,允许多个玩家通过互联网一起玩游戏。 \par
552 | 在下一章中,我们将介绍C++的网络编程,这样你就能将网络整合到你的游戏中。 \par
553 |
554 | \noindent\textbf{}\ \par
555 | \textbf{问题} \ \par
556 | \begin{enumerate}
557 | \item 重写一个私有虚拟函数的目的是什么?
558 | \item 描述命令设计模式。
559 | \item 享元模式如何节省内存使用?
560 | \item 观察者模式和中介模式之间有什么区别?
561 | \item 为什么我们要将游戏循环设计成一个无限循环?
562 | \end{enumerate}
563 |
564 | \noindent\textbf{}\ \par
565 | \textbf{扩展阅读} \ \par
566 | \begin{itemize}
567 | \item Game Development Patterns and Best Practices: Better games, less hassle by John P.Doran, Matt Casanova: https://www.amazon.com/Game-Development-Patterns- Best-Practices/dp/1787127834/ .
568 | \end{itemize}
569 |
570 | \newpage
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/10.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/11.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/12.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/13.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/5.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/6.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/7.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/8.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-12/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-12/9.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-13/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-13/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-13/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-13/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-13/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-13/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/10.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/11.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/12.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/13.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/14.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/15.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/16.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/17.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/18.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/5.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/6.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/7.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/8.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-14/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-14/9.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/10.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/11.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/12.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/13.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/14.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/15.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/16.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/17.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/18.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/19.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/20.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/21.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/22.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/23.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/24.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/25.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/26.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/27.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/28.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/29.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/30.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/5.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/6.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/7.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/8.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-6/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-6/9.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-7/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-7/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-7/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-7/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-7/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-7/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-7/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-7/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/1.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/10.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/11.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/12.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/13.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/2.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/3.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/4.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/5.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/6.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/7.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/8.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-8/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-2/Chapter-8/9.png
--------------------------------------------------------------------------------
/content/Section-2/Chapter-9/chapter9.tex:
--------------------------------------------------------------------------------
1 | 前一章中,讨论了C++中的并发和多线程的基础知识。并发代码设计中最大的挑战是正确处理数据竞争。线程同步和调度也不是一个容易掌握的知识点。我们可以在任何怀疑有数据竞争的地方使用同步原语,比如:互斥锁,但这并不是最佳方式。 \par
2 | 设计并发代码的更好方法是不惜一切代价不使用锁。这不仅会提高应用程序的性能,而且会使它比以前更安全。说起来容易做起来难——无锁编程是本章的一个具有挑战性的主题。特别是,我们将进一步深入设计无锁算法和数据结构的基础知识。这是许多优秀开发者不断研究的一个课题。我们将接触无锁编程的基础知识,这将使您了解如何以一种有效的方式构造代码。阅读完本章后,读者能够更好地理解数据竞争的问题,并获得设计并发算法和数据结构所需的基本知识。这些可能对构建容错系统的设计也有帮助。 \par
3 | 本章中,我们将了解以下内容: \par
4 |
5 | \begin{itemize}
6 | \item 理解数据竞争和基于锁的解决方案
7 | \item 在C++中使用原子操作
8 | \item 设计无锁的数据结构
9 | \end{itemize}
10 |
11 | \noindent\textbf{}\ \par
12 | \textbf{编译器要求} \ \par
13 | g++编译器需要添加编译选项 \texttt{-std=c++2a} 来编译本章的代码。可以从这里获取本章的源码文件:https://github.com/PacktPublishing/Expert-CPP \par
14 |
15 | \noindent\textbf{}\ \par
16 | \textbf{近距离观察数据竞争} \ \par
17 | 如前所述,数据竞争是开发者需要不惜一切代价试图避免的情况。前一章中,我们讨论了死锁以及避免它的方法。我们在前一章中使用的最后一个例子,是创建线程安全的单例模式。假设使用一个类来创建数据库连接(一个经典的例子)。 \par
18 | 下面是数据库连接模式的一个简单实现。每次访问数据库时,保持一个单独的连接并不是一个好做法。所以,我们重用现有的连接从程序不同的部分查询数据库: \par
19 |
20 | \begin{lstlisting}[caption={}]
21 | namespace Db {
22 | class ConnectionManager
23 | {
24 | public:
25 | static std::shared_ptr get_instance()
26 | {
27 | if (instance_ == nullptr) {
28 | instance_.reset(new ConnectionManager());
29 | }
30 | return instance_;
31 | }
32 | // Database connection related code omitted
33 | private:
34 | static std::shared_ptr instance_{nullptr};
35 | };
36 | }
37 | \end{lstlisting}
38 |
39 | 让我们更详细地讨论这个示例。前一章中,我们加入了锁来保护get\underline{ }instance()函数不受数据竞争的影响。让我们详细说明一下这样做的原因。为了简化这个例子,下面是我们感兴趣部分的四行伪码: \par
40 |
41 | \begin{lstlisting}[caption={}]
42 | get_instance()
43 | if (_instance == nullptr)
44 | instance_.reset(new)
45 | return instance_;
46 | \end{lstlisting}
47 |
48 | 现在,假设我们运行一个访问get\underline{ }instance()函数的线程。我们将其命名为Thread A,它执行的第一行是条件语句,如下所示: \par
49 |
50 | \begin{lstlisting}[caption={}]
51 | get_instance()
52 | if (_instance == nullptr) <--- Thread A
53 | instance_.reset(new)
54 | return instance_;
55 | \end{lstlisting}
56 |
57 | 它将逐行执行指令。更让我们感兴趣的是第二个线程(标记为Thread B),它开始执行与Thread A并发的函数。函数的并发执行过程中可能会出现以下情况: \par
58 |
59 | \begin{lstlisting}[caption={}]
60 | get_instance()
61 | if (_instance == nullptr) <--- Thread B (checking)
62 | instance_.reset(new) <--- Thread A (already checked)
63 | return instance_;
64 | \end{lstlisting}
65 |
66 | Thread B在比较instance\underline{ }和nullptr时为真。Thread A传递了相同的检查并将instance\underline{ }设置为一个新对象。虽然从Thread A的角度来看,一切看起来都很好,但它只是传递了条件检查,重置实例,并将转到下一行返回instance\underline{ }。然而,Thread B在instance\underline{ }的值改变之前比较了它。因此,Thread B也会继续设置instance\underline{ }的值: \par
67 |
68 | \begin{lstlisting}[caption={}]
69 | get_instance()
70 | if (_instance == nullptr)
71 | instance_.reset(new) <--- Thread B (already checked)
72 | return instance_; <--- Thread A (returns)
73 | \end{lstlisting}
74 |
75 | 前面的问题是,Thread B在instance\underline{ }设置好之后,又重新设置了它。它由几个指令组成,每个指令都由一个线程按顺序执行。为了使两个线程不相互干扰,操作不应该包含一个以上的指令。 \par
76 | 我们关心数据竞争的原因是前面代码块中的间隙,行与行之间的间隙允许线程相互干扰。因为这个解决方案可能不是正确的,所以使用同步原语(比如互斥锁)的解决方案时,应该想象所有的空隙。下面的修改使用了互斥锁和双重检查锁的模式: \par
77 |
78 | \begin{lstlisting}[caption={}]
79 | static std::shared_ptr get_instance()
80 | {
81 | if (instance_ == nullptr) {
82 | // mutex_ is declared in the private section
83 | std::lock_guard lg{mutex_};
84 | if (instance_ == nullptr) { // double-checking
85 | instance_.reset(new ConnectionManager());
86 | }
87 | }
88 | return instance_;
89 | }
90 | \end{lstlisting}
91 |
92 | 下面是两个线程试图访问instance\underline{ }对象时会发生的事情: \par
93 |
94 | \begin{lstlisting}[caption={}]
95 | get_instance()
96 | if (instance_ == nullptr) <--- Thread B
97 | lock mutex <--- Thread A (locks the mutex)
98 | if (instance_ == nullptr)
99 | instance_.reset(new)
100 | unlock mutex
101 | return instance_
102 | \end{lstlisting}
103 |
104 | 现在,即使两个线程都通过了第一次检查,其中一个线程也会锁定互斥锁。当一个线程试图锁定互斥锁时,另一个线程将重置实例。为了确保它没有被设置,我们使用了第二个检查(这就是为什么它被称为双重检查): \par
105 |
106 | \begin{lstlisting}[caption={}]
107 | get_instance()
108 | if (instance_ == nullptr)
109 | lock mutex <--- Thread B (tries to lock, waits)
110 | if (instance_ == nullptr) <--- Thread A (double check)
111 | instance_.reset(new)
112 | unlock mutex
113 | return instance_
114 | \end{lstlisting}
115 |
116 | 当Thread A完成了instance\underline{ }的设置后,就会解锁互斥锁,这样Thread B就可以继续锁定并重置instance\underline{ }: \par
117 |
118 | \begin{lstlisting}[caption={}]
119 | get_instance()
120 | if (instance_ == nullptr)
121 | lock mutex <--- Thread B (finally locks the mutex)
122 | if (instance_ == nullptr) <--- Thread B (check is not passed)
123 | instance_.reset(new)
124 | unlock mutex <--- Thread A (unlocked the mutex)
125 | return instance_ <--- Thread A (returns)
126 | \end{lstlisting}
127 |
128 | 作为经验法则,应该始终在代码的行与行之间寻找线索。两个语句之间总是有一个间隙,这个间隙会使两个或多个线程相互干扰。下一节将详细讨论数字递增的示例。 \par
129 |
130 | \noindent\textbf{}\ \par
131 | \textbf{添加同步机制} \ \par
132 | 几乎每一本涉及线程同步的书,都会使用了一个增加数字作为数据竞争示例。本书也不例外,示例如下: \par
133 |
134 | \begin{lstlisting}[caption={}]
135 | #include
136 |
137 | int counter = 0;
138 |
139 | void foo()
140 | {
141 | counter++;
142 | }
143 |
144 | int main()
145 | {
146 | std::jthread A{foo};
147 | std::jthread B{foo};
148 | std::jthread C{[]{foo();}};
149 | std::jthread D{
150 | []{
151 | for (int ix = 0; ix < 10; ++ix) { foo(); }
152 | }
153 | };
154 | }
155 | \end{lstlisting}
156 |
157 | 我们添加了两个线程,使示例更加复杂。前面的代码只是使用四个不同的线程递增计数器变量。乍一看,在任何时间点,只有一个线程增量计数器。然而,正如我们在前一节中提到的,我们应该注意并寻找代码中的漏洞。foo()函数似乎缺少一个。自增操作符的行为方式如下(伪代码): \par
158 |
159 | \begin{lstlisting}[caption={}]
160 | auto res = counter;
161 | counter = counter + 1;
162 | return res;
163 | \end{lstlisting}
164 |
165 | 现在,我们发现了本不应该存在的问题。在任何时候,只有一个线程执行前面三条指令中的一条。也就是说,可能出现如下情况: \par
166 |
167 | \begin{lstlisting}[caption={}]
168 | auto res = counter; <--- thread A
169 | counter = counter + 1; <--- thread B
170 | return res; <--- thread C
171 | \end{lstlisting}
172 |
173 | 例如:thread B可能修改counter的值,而thread A读取了修改之前的值。这意味着thread A将在thread B已经完成该操作时,给counter分配一个新的增量值。非常混乱,我们的大脑迟早会因试图理解操作的顺序而爆炸。作为一个经典示例,我们将使用线程锁的机制来解决它。这里有一个解决方案: \par
174 |
175 | \begin{lstlisting}[caption={}]
176 | #include
177 | #include
178 |
179 | int counter = 0;
180 | std::mutex m;
181 |
182 | void foo()
183 | {
184 | std::lock_guard g{m};
185 | counter++;
186 | }
187 |
188 | int main()
189 | {
190 | // code omitted for brevity
191 | }
192 | \end{lstlisting}
193 |
194 | 无论哪个线程先到达lock\underline{ }guard,都会首先锁定mutex,如下所示: \par
195 |
196 | \begin{lstlisting}[caption={}]
197 | lock mutex; <--- thread A, B, D wait for the locked mutex
198 | auto res = counter; <--- thread C has locked the mutex
199 | counter = counter + 1;
200 | unlock mutex; <--- A, B, D are blocked until C reaches here
201 | return res;
202 | \end{lstlisting}
203 |
204 | 使用锁的问题是性能。理论上,我们使用线程来加速程序的执行,更确切地说是数据处理。在大数据集合的情况下,使用多线程可能会大大提高程序的性能。但是,在多线程环境中,我们首先要注意并发访问,因为使用多个线程访问集合可能会导致数据的损坏。让我们来看看线程安全的堆栈实现。 \par
205 |
206 | \noindent\textbf{}\ \par
207 | \textbf{实现一个线程安全的栈} \ \par
208 | 回顾第6章中的堆栈数据结构适配器,我们将使用锁来实现线程安全的堆栈版本。栈有两个基本操作,push和pop。它们都修改容器的状态。堆栈本身不是容器,它是一种适配器,其包装了一个容器,并提供了一个适合访问的接口。合并线程安全,我们把std::stack封装在一个新类中。除了构造函数和销毁函数外,std::stack还提供了以下函数: \par
209 |
210 | \begin{itemize}
211 | \item top() : 访问堆栈的顶部元素
212 | \item empty() : 如果堆栈为空,则返回true
213 | \item size() : 返回堆栈的当前大小
214 | \item push() : 在堆栈(顶部)中插入一个新项
215 | \item emplace() : 在堆栈的顶部构造一个元素
216 | \item pop() : 删除堆栈的顶部元素
217 | \item swap() : 将内容与另一个栈交换
218 | \end{itemize}
219 |
220 | 我们将保持它的简单性,并将重点放在线程安全上。这里主要关注的是修改底层数据结构的函数。我们感兴趣的是push()和pop()函数。如果多个线程相互干扰,这些函数可能会破坏数据结构。因此,下面的声明是表示线程安全堆栈的类: \par
221 |
222 | \begin{lstlisting}[caption={}]
223 | template
224 | class safe_stack
225 | {
226 | public:
227 | safe_stack();
228 | safe_stack(const safe_stack& other);
229 | void push(T value); // we will std::move it instead of copy-referencing
230 | void pop();
231 | T& top();
232 | bool empty() const;
233 | private:
234 | std::stack wrappee_;
235 | mutable std::mutex mutex_;
236 | }
237 | \end{lstlisting}
238 |
239 | 注意,我们将mutex\underline{ }声明为可变的,因为我们将它锁定在const函数empty()中。与删除empty()的常量相比,这可以说是一个更好的设计选择。然而,对数据成员使用“可变”表示我们做出了糟糕的设计选择。无论如何,safe\underline{ }stack的客户端代码不会太关心实现的内部细节,甚至不知道堆栈使用互斥锁来同步并发访问。 \par
240 | 现在让我们看看它成员函数的实现和一个简短的描述。让我们从复制构造函数开始: \par
241 |
242 | \begin{lstlisting}[caption={}]
243 | safe_stack::safe_stack(const safe_stack& other)
244 | {
245 | std::lock_guard lock(other.mutex_);
246 | wrappee_ = other.wrappee_;
247 | }
248 | \end{lstlisting}
249 |
250 | 注意,我们锁定了另一个堆栈的互斥锁。尽管看起来不公平,但我们需要确保在复制另一个栈的基础数据时,数据不会被修改。 \par
251 | 接下来,让我们看看push()函数的实现。我们锁定互斥锁并将数据推入底层堆栈: \par
252 |
253 | \begin{lstlisting}[caption={}]
254 | void safe_stack::push(T value)
255 | {
256 | std::lock_guard lock(mutex_);
257 | // note how we std::move the value
258 | wrappee_.push(std::move(value));
259 | }
260 | \end{lstlisting}
261 |
262 | 几乎所有的函数都以相同的方式合并线程同步:锁定互斥锁、执行任务和解锁互斥锁。这确保了在任何时候只有一个线程在访问数据。也就是说,为了保护数据不受竞争条件的影响,我们必须确保函数不变量没有被破坏。 \par
263 |
264 | \hspace*{\fill} \\ %插入空行
265 | \includegraphics[width=0.05\textwidth]{images/tip}
266 | 如果你不喜欢输入很长的C++类型名,比如:std::lock\underline{ }guard,可以使用using关键字为类型创建简短的别名,例如,使用\texttt{locker = std::guard;}。 \par
267 | \noindent\textbf{}\ \par
268 |
269 | 现在,转到pop()函数,我们可以修改类声明,使pop()直接返回堆栈顶部的值。我们这样做的主要原因是不希望访问栈顶(使用引用),然后从另一个线程中弹出数据。因此,我们将修改pop()函数,使其成为一个共享对象,然后返回堆栈元素: \par
270 |
271 | \begin{lstlisting}[caption={}]
272 | std::shared_ptr pop()
273 | {
274 | std::lock_guard lock(mutex_);
275 | if (wrappee_.empty()) {
276 | throw std::exception("The stack is empty");
277 | }
278 | std::shared_ptr
279 | top_element{std::make_shared(std::move(wrappee_.top()))};
280 | wrappee_.pop();
281 | return top_element;
282 | }
283 | \end{lstlisting}
284 |
285 | 注意,safe\underline{ }stack类的声明也应该根据pop()函数的改变而改变。此外,我们不再需要top()。 \par
286 |
287 | \noindent\textbf{}\ \par
288 | \textbf{设计无锁的数据结构} \ \par
289 | 如果有一个线程可以保证进程进展,那么就可称其是一个无锁函数。与基于锁的函数(其中一个线程可以阻塞另一个线程,都可能在取得进展之前等待某些条件)相比,无锁状态确保有一个线程取得进展。使用数据同步原语的算法和数据结构正在阻塞,一个线程被挂起,直到另一个线程执行一个操作。这意味着在块移除(通常是为互斥锁解锁)之前,线程不能继续运行。我们的关注点在于不使用阻塞函数的数据结构和算法。我们称其中一些为无锁算法,尽管还应该区分非阻塞算法和数据结构的类型。 \par
290 |
291 | \noindent\textbf{}\ \par
292 | \textbf{使用原子类型} \ \par
293 | 前面,我们介绍了作为数据争用的原因的源代码行之间的间隙。当有一个包含不止一条指令的操作时,大脑就会提醒你可能出现的问题。然而,无论你如何努力使操作独立和单一,在大多数情况下,如果不将操作分解为涉及多个指令的步骤,将无法实现。C++通过提供原子类型来解决这个问题。 \par
294 | 首先,为什么使用原子这个词。一般来说,我们理解原子是指不能分解成更小部分的东西。也就是说,原子操作是一种不能做一半的操作:要么做了,要么没做。原子操作的例子可能是对整数的简单赋值: \par
295 |
296 | \begin{lstlisting}[caption={}]
297 | num = 37;
298 | \end{lstlisting}
299 |
300 | 如果两个线程访问这行代码,它们都不会遇到半途而废的代码。换句话说,任务之间没有间隙。当然,如果num表示带有用户定义的赋值操作符的复杂对象,同样的语句可能会有很多间隙。 \par
301 |
302 | \hspace*{\fill} \\ %插入空行
303 | \includegraphics[width=0.05\textwidth]{images/warn}
304 | 原子操作是一种不可分割的操作。 \par
305 | \noindent\textbf{}\ \par
306 |
307 | 另一方面,非原子操作可以视为完成了一半,例子是我们前面讨论的自增操作。在C++中,所有关于原子类型的操作也是原子类型。这意味着我们可以通过使用原子类型来避免行之间的间隙。在使用原子之前,我们可以通过使用互斥来创建原子操作。例如,我们可以认为下面的函数是原子的: \par
308 |
309 | \begin{lstlisting}[caption={}]
310 | void foo()
311 | {
312 | mutex.lock();
313 | int a{41};
314 | int b{a + 1};
315 | mutex.unlock();
316 | }
317 | \end{lstlisting}
318 |
319 | 真正的原子操作和上面假原子操作之间的区别是,原子操作不需要锁。这实际上是一个很大的区别,因为互斥等同步机制合并了开销和性能损失。更准确地说,原子类型利用低级机制来确保指令的独立性和原子性。标准原子类型定义在头文件中,标准原子类型也可以使用内部锁定。为了确保它们不使用内部锁定,标准库中的所有原子类型都is\underline{ }lock\underline{ }free()函数。 \par
320 |
321 | \hspace*{\fill} \\ %插入空行
322 | \includegraphics[width=0.05\textwidth]{images/warn}
323 | 唯一没有is\underline{ }lock\underline{ }free()成员函数的原子类型是std::atomic\underline{ }flag。此类型上的操作必须是无锁的。它是一个布尔标志,大多数时候它用作实现其他无锁类型的基值。 \par
324 | \noindent\textbf{}\ \par
325 |
326 | 也就是说,如果直接使用原子指令对obj进行操作,obj.is\underline{ }lock\underline{ }free()将返回true。如果返回false,则表示使用了内部锁定。如果原子类型对于所有支持的硬件都是无锁的,则静态constexpr函数is\underline{ }always\underline{ }lock\underline{ }free()返回true。由于该函数是constexpr,允许我们定义该类型在编译时是否无锁。这是一个很大的进步,会以一种良好的方式影响代码的结构和执行。例如,std::atomic::is\underline{ }always\underline{ }lock\underline{ }free()返回true,因为std::atomic很可能总是无锁的。 \par
327 |
328 | \hspace*{\fill} \\ %插入空行
329 | \includegraphics[width=0.05\textwidth]{images/tip}
330 | 在希腊语中,a的意思是不,tomo的意思是切。原子这个词来自希腊语atomos,翻译过来就是不可切割的。也就是说,我们认为原子是不可分割的最小单位。我们使用原子类型和操作来避免指令之间的间隙。 \par
331 | \noindent\textbf{}\ \par
332 |
333 | 我们对原子类型使用特化,例如:\texttt{std::atomic;}。可以参考下表来获得更方便的原子类型名称。表的左列包含原子类型,右列包含它的特化: \par
334 |
335 | \begin{table}[h]
336 | \begin{tabularx}{\textwidth}{|X|X|}
337 | \hline 原子类型 & 特化版 \\
338 | \hline atomic\underline{ }bool & std::atomic \\
339 | \hline atomic\underline{ }char & std::atomic \\
340 | \hline atomic\underline{ }schar & std::atomic \\
341 | \hline atomic\underline{ }uchar & std::atomic \\
342 | \hline atomic\underline{ }int & std::atomic \\
343 | \hline atomic\underline{ }uint & std::atomic \\
344 | \hline atomic\underline{ }short & std::atomic \\
345 | \hline atomic\underline{ }ushort & std::atomic \\
346 | \hline atomic\underline{ }long & std::atomic \\
347 | \hline atomic\underline{ }ulong & std::atomic \\
348 | \hline atomic\underline{ }llong & std::atomic \\
349 | \hline atomic\underline{ }ullong & std::atomic \\
350 | \hline atomic\underline{ }char16\underline{ }t & std::atomic \\
351 | \hline atomic\underline{ }char32\underline{ }t & std::atomic \\
352 | \hline atomic\underline{ }wchar\underline{ }t & std::atomic \\
353 | \hline
354 | \end{tabularx}
355 | \end{table}
356 |
357 | 上表表示基本原子类型。正则类型和原子类型之间的根本区别是,我们可以对它们进行操作。现在让我们更详细地讨论原子操作。 \par
358 |
359 | \noindent\textbf{}\ \par
360 | \textbf{操作原子的类型} \ \par
361 | 回想一下我们在前一节中讨论的间隙。原子类型的目标是消除指令之间的间隙,或者提供一些操作,这些操作负责将几个指令包装成一条指令组合在一起。以下是对原子类型的操作: \par
362 |
363 | \begin{itemize}
364 | \item load()
365 | \item store()
366 | \item exchange()
367 | \item compare\underline{ }exchange\underline{ }weak()
368 | \item compare\underline{ }exchange\underline{ }strong()
369 | \item wait()
370 | \item notify\underline{ }one()
371 | \item notify\underline{ }all()
372 | \end{itemize}
373 |
374 | load()操作自动加载并返回原子变量的值。store()用提供的非原子参数替换原子变量的值。 \par
375 | load()和store()都类似于对非原子变量的读取和分配操作。每当我们访问一个对象的值时,我们执行一个read指令。例如,下面的代码打印了double变量的内容: \par
376 |
377 | \begin{lstlisting}[caption={}]
378 | double d{4.2}; // "store" 4.2 into "d"
379 | std::cout << d; // "read" the contents of "d"
380 | \end{lstlisting}
381 |
382 | 原子类型的情况下,类似的读操作转换为: \par
383 |
384 | \begin{lstlisting}[caption={}]
385 | atomic_int m;
386 | m.store(42); // atomically "store" the value
387 | std::cout << m.load(); // atomically "read" the contents
388 | \end{lstlisting}
389 |
390 | 虽然前面的代码没有任何意义,但包含了这个示例来表示处理原子类型时的区别。访问原子变量应该通过原子操作来完成。下面的代码表示load()、store()和exchange()函数的定义: \par
391 |
392 | \begin{lstlisting}[caption={}]
393 | T load(std::memory_order order = std::memory_order_seq_cst) const noexcept;
394 | void store(T value, std::memory_order order =
395 | std::memory_order_seq_cst) noexcept;
396 | T exchange(T value, std::memory_order order =
397 | std::memory_order_seq_cst) noexcept;
398 | \end{lstlisting}
399 |
400 | 有一个类型为std::memory\underline{ }order的额外参数。exchange()函数由store()和load()函数组成,以原子的方式用提供的参数替换值,并原子地获取前一个值。 \par
401 | compare\underline{ }exchange\underline{ }weak()和compare\underline{ }exchange\underline{ }strong()函数的原理类似。下面是它们的定义: \par
402 |
403 | \begin{lstlisting}[caption={}]
404 | bool compare_exchange_weak(T& expected_value, T target_value,
405 | std::memory_order order =
406 | std::memory_order_seq_cst) noexcept;
407 | bool compare_exchange_strong(T& expected_value, T target_value,
408 | std::memory_order order =
409 | std::memory_order_seq_cst) noexcept;
410 | \end{lstlisting}
411 |
412 | 比较第一个参数(expected\underline{ }value)与原子变量,如果它们相等,则用第二个参数(target\underline{ }value)替换该变量。否则,它们会自动地将值加载到第一个参数中(这就是为什么它是通过引用传递的)。弱交换和强交换之间的区别是,compare\underline{ }exchange\underline{ }weak()允许错误地失败(称为假失败),也就是说,即使expected\underline{ }value等于基值,函数也认为它们不相等。这样做是因为在某些平台上,可以提升性能。 \par
413 | wait()、notify\underline{ }one()和notify\underline{ }all()函数从C++20开始添加。wait()函数阻塞线程,直到修改原子对象的值。它接受一个参数来与原子对象的值进行比较。如果两个值相等,则阻塞线程。要手动解除线程阻塞,可以调用notify\underline{ }one()或notify\underline{ }all()。它们之间的区别是,notify\underline{ }one()至少解除一个被阻塞的操作的阻塞,而notify\underline{ }all()解除所有这些操作的阻塞。 \par
414 | 现在,让我们讨论一下在前面声明的原子类型成员函数时遇到的内存序。memory\underline{ }order定义了原子操作前后内存访问的顺序。当多个线程同时读写变量时,一个线程读取变量的顺序与另一个线程存储变量的顺序不同。原子操作的默认顺序是连续一致的顺序——这就是std::memory\underline{ }order\underline{ }seq\underline{ }cst的作用。有几种类型的内存序,包括memory\underline{ }order\underline{ }relax、memory\underline{ }order\underline{ }consume、memory\underline{ }order\underline{ }acquire、memory\underline{ }order\underline{ }release、memory\underline{ }order\underline{ }acq\underline{ }rel和memory\underline{ }order\underline{ }seq\underline{ }cst。下一节中,我们将设计一个使用默认内存顺序的原子类型的无锁堆栈。 \par
415 |
416 | \noindent\textbf{}\ \par
417 | \textbf{设计一个无锁的栈} \ \par
418 | 在设计堆栈时,要记住的关键事情是确保推送的值可以安全地从另一个线程返回。同样重要的是确保只有一个线程返回值。 \par
419 | 前面几节中,我们实现了一个包装std::stack的基于锁的堆栈。我们知道堆栈不是一个真正的数据结构,而是一个适配器。通常,在实现堆栈时,我们选择向量或链表作为其底层数据结构。让我们看一个基于链表的无锁堆栈示例。将新元素推入堆栈涉及到创建一个新的列表节点,将它的next指针设置为当前的头节点,然后将头节点设置为指向新插入的节点。 \par
420 |
421 | \hspace*{\fill} \\ %插入空行
422 | \includegraphics[width=0.05\textwidth]{images/warn}
423 | 如果你对“头”或“next指针”这两个术语感到困惑,请回顾第6章,我们在其中详细讨论了链表。 \par
424 | \noindent\textbf{}\ \par
425 |
426 | 单线程上下文中,所描述的步骤是正确的。但是,如果有多个线程在修改堆栈,我们就应该担心了。让我们找出push()操作的缺陷。当一个新元素被推入堆栈时,下面是三个主要步骤: \par
427 |
428 | \begin{enumerate}
429 | \item \texttt{node* new\underline{ }elem = new node(data); }
430 | \item \texttt{new\underline{ }elem->next = head\underline{ }; }
431 | \item \texttt{head\underline{ } = new\underline{ }elem; }
432 | \end{enumerate}
433 |
434 | 第1步中,我们将新节点插入到底层链表中。第2步描述我们将它插入到列表的前面——这就是为什么新节点的next指针指向head\underline{ }。最后,由于head\underline{ }指针代表列表的起点,我们应该将其值重置为指向新添加的节点,就像第3步中那样。 \par
435 | 节点类型是我们在堆栈中用来表示列表节点的内部结构。下面是它的定义: \par
436 |
437 | \begin{lstlisting}[caption={}]
438 | template
439 | class lock_free_stack
440 | {
441 | private:
442 | struct node {
443 | T data;
444 | node* next;
445 | node(const T& d) : data(d) {}
446 | }
447 | node* head_;
448 | // the rest of the body is omitted for brevity
449 | };
450 | \end{lstlisting}
451 |
452 | 我们建议您做的第一件事是在当前代码中寻找问题——不是在前面的代码中,而是在我们描述的将新元素放入堆栈的步骤中。假设两个线程同时在添加节点。第2步中的一个线程将新元素的next指针设置为指向head\underline{ }。另一个线程使head\underline{ }指针指向另一个新元素。很明显,这可能会导致数据损坏。对于一个线程来说,步骤2和步骤3有相同的head\underline{ }是至关重要的。为了解决步骤2和步骤3之间的竞争条件,我们应该使用原子比较/交换操作来保证在之前读取head\underline{ }的值时head\underline{ }没有修改。由于需要原子地访问head指针,下面是如何修改lock\underline{ }free\underline{ }stack类中的head\underline{ }成员: \par
453 |
454 | \begin{lstlisting}[caption={}]
455 | template
456 | class lock_free_stack
457 | {
458 | private:
459 | // code omitted for brevity
460 | std::atomic head_;
461 | // code omitted for brevity
462 | };
463 | \end{lstlisting}
464 |
465 | 下面是我们如何实现原子head\underline{}指针的无锁push(): \par
466 |
467 | \begin{lstlisting}[caption={}]
468 | void push(const T& data)
469 | {
470 | node* new_elem = new node(data);
471 | new_elem->next = head_.load();
472 | while (!head_.compare_exchange_weak(new_elem->next, new_elem));
473 | }
474 | \end{lstlisting}
475 |
476 | 我们使用compare\underline{ }exchange\underline{ }weak()来确保head\underline{ }指针与我们在new\underline{ }elem->next中存储的值相同。如果是,则将其设置为new\underline{ }elem。当compare\underline{ }exchange\underline{ }weak()执行成功,就可以确定节点已经成功插入到列表中。 \par
477 | 看看我们如何使用原子操作访问节点。类型为T - std::atomic -的指针的原子形式提供了相同的接口。除此之外,std::atomic提供了指向算术操作fetch\underline{ }add()和fetch\underline{ }sub()的指针。它们对存储的地址进行原子加法和减法运算。这里有一个例子: \par
478 |
479 | \begin{lstlisting}[caption={}]
480 | struct some_struct {};
481 | any arr[10];
482 | std::atomic ap(arr);
483 | some_struct* old = ap.fetch_add(2);
484 | // now old is equal to arr
485 | // ap.load() is equal to &arr[2]
486 | \end{lstlisting}
487 |
488 | 我们故意将该指针命名为old,因为fetch\underline{ }add()将该数字添加到指针的地址中,并返回旧值。这就是为什么old和arr指向同一个地址。 \par
489 | 下一节中,我们将介绍有关原子类型的更多操作。现在,让我们回到我们的无锁堆栈。要pop()一个元素,即移除一个节点,我们需要读取head\underline{ }并将其设置为head\underline{ }的next元素,如下所示:\par
490 |
491 | \begin{lstlisting}[caption={}]
492 | void pop(T& popped_element)
493 | {
494 | node* old_head = head_;
495 | popped_element = old_head->data;
496 | head_ = head_->next;
497 | delete old_head;
498 | }
499 | \end{lstlisting}
500 |
501 | 现在,假设有几个线程并发地执行它。如果两个线程从栈中移除项,读取了相同的head\underline{ }值,该怎么办?这个和其他一些竞态条件产生了下面的实现: \par
502 |
503 | \begin{lstlisting}[caption={}]
504 | void pop(T& popped_element)
505 | {
506 | node* old_head = head_.load();
507 | while (!head_.compare_exchange_weak(old_head, old_head->next));
508 | popped_element = old_head->data;
509 | }
510 | \end{lstlisting}
511 |
512 | 我们在前面的代码中应用了与push()函数几乎相同的逻辑。前面的代码并不完美,应该得到加强。我们建议您努力修改它,以消除内存泄漏。 \par
513 | 我们已经看到,无锁实现严重依赖于原子类型和操作。我们在上一节中讨论的操作并不是终点。现在让我们来发现更多的原子操作。 \par
514 |
515 | \noindent\textbf{}\ \par
516 | \textbf{原子类型的更多操作方式} \ \par
517 | 在上一节中,我们在指向用户定义类型的指针上使用了std::atomic<>。也就是说,我们为list节点声明了以下结构: \par
518 |
519 | \begin{lstlisting}[caption={}]
520 | // the node struct is internal to
521 | // the lock_free_stack class defined above
522 | struct node
523 | {
524 | T data;
525 | node* next;
526 | };
527 | \end{lstlisting}
528 |
529 | 节点结构是一个用户定义的类型。在上一节中我们实例化了std::atomic,但是我们可以用同样的方式,为几乎任何用户定义的类型实例化std::atomic<>,即std::atomic。但是,应该注意到std::atomic的接口仅限于以下函数: \par
530 |
531 | \begin{itemize}
532 | \item load()
533 | \item store()
534 | \item exchange()
535 | \item compare\underline{ }exchange\underline{ }weak()
536 | \item compare\underline{ }exchange\underline{ }strong()
537 | \item wait()
538 | \item notify\underline{ }one()
539 | \item notify\underline{ }all()
540 | \end{itemize}
541 |
542 | 现在,让我们根据底层类型的细节,来查看原子类型上可用的操作的完整列表。 \par
543 | std::atomic<>实例化为整型(例如整数或指针),具有以下操作以及我们前面列出的操作: \par
544 |
545 | \begin{itemize}
546 | \item fetch\underline{ }add()
547 | \item fetch\underline{ }sub()
548 | \item fetch\underline{ }or()
549 | \item fetch\underline{ }and()
550 | \item fetch\underline{ }xor()
551 | \end{itemize}
552 |
553 | 此外,除了自增(++)和自减(--)之外,还可以使用以下操作符:+=、-=、|=、\&=和\^=。 \par
554 | 最后,一个特殊的原子类型叫做atomic\underline{ }flag,它有两个可用的操作: \par
555 |
556 | \begin{itemize}
557 | \item clear()
558 | \item test\underline{ }and\underline{ }set()
559 | \end{itemize}
560 |
561 | 对std::atomic\underline{ }flag使用原子操作时,需要使用clear()函数清除它,而test\underline{ }and\underline{ }set()将值更改为true,并返回前一个值。 \par
562 |
563 | \noindent\textbf{}\ \par
564 | \textbf{总结} \ \par
565 | 应该考虑一下使用原子操作的std::atomic\underline{ }flag。本章的测试中,我们介绍了一个相当简单的堆栈例子,当然还有更复杂的例子需要研究和遵循。当我们讨论设计并发栈时,我们考虑了两个版本,其中一个表示无锁堆栈。与基于锁的解决方案相比,无锁的数据结构和算法是开发者的最终目标,因为它们提供了避免数据竞争的机制,甚至不需要同步资源。 \par
566 | 还介绍了可以在项目中使用的原子类型和操作,以确保指令是不可分割的。如果一条指令是原子的,就不需要担心它的同步。我们强烈建议读者继续研究这个主题,并构建更健壮和复杂的无锁数据结构。下一章中,我们将看到如何设计实际使用的应用程序。 \par
567 |
568 | \noindent\textbf{}\ \par
569 | \textbf{问题} \ \par
570 | \begin{enumerate}
571 | \item 为什么要在多线程的单例实现中检查两次实例?
572 | \item 在实现基于锁的堆栈的复制构造函数时,我们锁定了另一个堆栈的互斥锁。为什么?
573 | \item 什么是原子类型和原子操作?
574 | \item 为什么我们对原子类型使用load()和store() ?
575 | \item std::atomic支持哪些操作?
576 | \end{enumerate}
577 |
578 | \noindent\textbf{}\ \par
579 | \textbf{扩展阅读} \ \par
580 | \begin{itemize}
581 | \item Concurrent Patterns and Best Practices by Atul Khot, at https://www.packtpub.com/application-development/concurrent-patterns-and-best-practices
582 | \item Mastering C++ Multithreading by Maya Posch, at https://www.packtpub.com/application-development/mastering-c-multithreading
583 | \end{itemize}
584 |
585 | \newpage
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
--------------------------------------------------------------------------------
/content/Section-2/summary.tex:
--------------------------------------------------------------------------------
1 | 本节将集中讨论数据结构、算法和并发工具数据处理的效率,并且还将介绍基本的设计模式和最佳实践。 \par
2 |
3 | 本节包括以下章节: \par
4 |
5 | \begin{itemize}
6 | \item 第6章,挖掘STL中的数据结构和算法
7 | \item 第7章,函数式编程
8 | \item 第8章,并发和多线程
9 | \item 第9章,设计并发式数据结构
10 | \item 第10章,设计实际程序
11 | \item 第11章,使用设计模式设计策略游戏
12 | \item 第12章,网络和安全
13 | \item 第13章,调试与测试
14 | \item 第14章,使用Qt开发图形界面
15 | \end{itemize}
16 |
17 | \newpage
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/1.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/10.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/11.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/12.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/13.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/2.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/3.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/4.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/5.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/6.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/7.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/8.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-15/9.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-15/chapter15.tex:
--------------------------------------------------------------------------------
1 | 近年来,人工智能(AI)和机器学习(ML)越来越受到人们的青睐。从简单的送餐网站,到复杂的工业机器人,人工智能已成为软件和硬件的主要功能之一。虽然在大多数情况下,这些术语都是为了让产品看起来更严肃,但一些公司正在深入研究并将人工智能融入自己的系统。 \par
2 | 在进一步讨论之前,请考虑以下事实:本章从C++开发者的角度对ML进行了介绍。要了解更全面的文献,请参阅本章末尾的书单。在这一章中,我们将介绍AI和ML的概念。虽然我们更倾向于有数学背景,但在这一章中我们几乎不使用任何数学。如果你正计划扩大你的技能集和潜入ML,必须首先考虑学习数学。 \par
3 | 除了介绍概念,本章还提供了ML任务的例子。我们将实现它们,并给你一个基本的想法,你应该如何研究和前进,以解决更复杂的任务。 \par
4 |
5 | 本章中,我们将了解以下内容: \par
6 |
7 | \begin{itemize}
8 | \item 介绍AI和ML
9 | \item ML的分类和应用
10 | \item 设计用于计算的C++类
11 | \item 神经网络的结构与实现
12 | \item 回归分析与聚类
13 | \end{itemize}
14 |
15 | \noindent\textbf{}\ \par
16 | \textbf{编译器要求} \ \par
17 | g++编译器需要添加编译选项 \texttt{-std=c++2a} 来编译本章的代码。可以从这里获取本章的源码件:https://github.com/PacktPublishing/Expert-CPP \par
18 |
19 | \noindent\textbf{}\ \par
20 | \textbf{人工智能导论} \ \par
21 | 人工智能最简单的定义是机器人像人类一样行动,是由机器表现出来的智能。下面是关于智力定义的讨论。我们如何为机器定义它?我们应该与什么水平上的智能机器打交道? \par
22 | 如果您不熟悉太多的机器智能测试,那么你一定听说过是图灵测试。这个想法是让审讯者向两个人提问,其中一个是机器,另一个是人类。如果审讯者不能明确区分这两者,那么这台机器就应该认为是智能的。 \par
23 |
24 | \hspace*{\fill} \\ %插入空行
25 | \includegraphics[width=0.05\textwidth]{images/warn}
26 | 图灵测试是以艾伦·图灵的名字命名的。他在1950年的论文《计算机与智能》中引入了这个测试。他提议使用模仿游戏来确定机器是否像人类一样思考。 \par
27 | \noindent\textbf{}\ \par
28 |
29 | 被审讯的人在一堵墙后面,这样审讯者就看不见他们了。然后审讯者向两名参与者问几个问题。下图展示了审问者是如何与人类和机器沟通的,但却看不到他们: \par
30 |
31 | \begin{center}
32 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-15/1}
33 | \end{center}
34 |
35 | 当你开始深入人工智能领域时,智能的定义就变得越来越模糊。可以以任何形式向机器提问:文本、音频、可视形式等。有许多东西可能永远无法在机器中实现,比如:面部表情。有时候人们通过对方的表情来理解对方的心情。你不能确定机器人是否能够理解甚至能够模仿脸上的情绪。没人教过我们在生气的时候要表现出生气的样子,没人教过我们要有情感,它们就在那里。很难说是否有一天,类似的东西是否也会出现在机器上。 \par
36 | 说到人工智能,大多数时候我们都认为它是一个说话和行为与人类相似的机器人。但是当你作为一个程序员去分析它的时候,你会遇到很多子领域,每一个都需要花很多时间去理解。许多领域都有很多任务在进行中或处于早期研究阶段。以下是一些你可能会在职业生涯中感兴趣的人工智能子领域: \par
37 |
38 | \begin{itemize}
39 | \item 计算机视觉:设计视觉物体识别的算法,并通过分析物体的视觉表现来理解物体。人类很容易在人群中发现一张熟悉的面孔,但为机器实现类似的功能可能需要花费很多时间来获得与人类相同的精度。
40 | \item 自然语言处理(NPL):机器对文本的语言分析。在很多领域都有应用,比如机器翻译。想象一下,计算机完全理解人类书写的文本,这样我们就可以告诉它该做什么,而不是花几个月的时间学习编程语言。
41 | \item 知识推理:这似乎是机器智能行为的目标。知识推理是关于使机器进行推理,并根据它们所拥有的信息提供解决方案,例如:通过检查医疗状况提供诊断。
42 | \item ML:一个研究算法和统计模型的领域,利用机器在没有明确指令的情况下执行任务。ML算法依赖于模式和推理,而不是直接指令。也就是说,ML允许机器自己完成工作,无需人类参与。
43 | \end{itemize}
44 |
45 | \noindent\textbf{}\ \par
46 | \textbf{计算机视觉} \ \par
47 | 计算机视觉是一个综合性的研究领域,有很多正在进行的研究项目。它几乎涉及所有与视觉数据处理相关的内容。它在各个领域有着广泛的应用,例如:人脸识别软件处理来自城市各个摄像头的数据,以寻找和确定犯罪嫌疑人,或者光学字符识别软件从包含该数据的图像中生成文本。结合一些增强现实(AR)技术,该软件能够将图像中的文本翻译成用户熟悉的语言。 \par
48 | 这一领域的研究正日益取得进展。与人工智能系统相结合,计算机视觉是一个让机器像我们一样感知世界的领域。然而,对我们来说,一个简单的任务,在计算机视觉方面却具有挑战性,例如:当我们在图像中看到一个物体时,我们很容易看出它的尺寸。例如,下面的图片代表了一辆自行车的前视图: \par
49 |
50 | \begin{center}
51 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-15/2}
52 | \end{center}
53 |
54 | 即使我们没有提到这是一辆自行车,人类也不难判断出来。很明显,底部中间的黑线是自行车的前轮。很难让电脑明白这是一个轮子。电脑看到的都是像素的集合,其中一些有相同的颜色: \par
55 |
56 | \begin{center}
57 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-15/3}
58 | \end{center}
59 |
60 | 除了理解自行车的车轮之外,还应该推断出这辆自行车一定有另一个在图像中看不到的车轮。同样,我们可能会猜测自行车的大致大小,而计算机从图像中确定它是一个综合性的任务。也就是说,在我们看来,这个简单的事情可能会成为对计算机视觉的真正挑战。 \par
61 |
62 | \hspace*{\fill} \\ %插入空行
63 | \includegraphics[width=0.05\textwidth]{images/warn}
64 | 我们建议在计算机视觉任务中使用OpenCV库。这是一个用C和C++编写的跨平台库。OpenCV代表了一组针对实时计算机视觉的功能,包括但不限于面部识别、手势识别、运动理解、运动跟踪等特征。 \par
65 | \noindent\textbf{}\ \par
66 |
67 | 计算机视觉的典型任务包括物体识别、识别和检测。物体识别是理解物体是前一幅图像中的一辆车。识别是对一个对象的单个实例的识别,例如:前面图像中自行车的轮子。目标检测任务可能包括从自行车的图像中发现损坏的区域。所有这些任务与ML算法结合起来,可能组成一个全面的软件,它可以像人类一样了解周围环境。 \par
68 |
69 | \noindent\textbf{}\ \par
70 | \textbf{NLP} \ \par
71 | 另一个有趣的研究领域是自然语言处理。NLP致力于让计算机理解人类语言,更普遍的方法是自动语音识别和自然语言理解,这是目前虚拟助手的一个关键特性。今天,使用语言控制手机要求它在网络上搜索东西已经不再是魔术了。所有的过程都是由复杂的算法在语音和文本分析。下图显示了会话代理背后发生的流程的高级视图: \par
72 |
73 | \begin{center}
74 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-15/4}
75 | \end{center}
76 |
77 | 许多语言处理任务都与Web有关。搜索引擎处理用户输入,以便在Web上的数百万文档中进行搜索,这是自然语言处理最热门的应用之一。搜索引擎设计的主要关注点之一是处理文本数据。搜索引擎不能仅仅存储所有的网站并对用户的第一个匹配的查询作出响应。在NLP中有许多具有复杂实现的任务。假设我们正在设计一个程序,它被输入一个文本文档,我们应该输出文档中的句子。识别一个句子的开头和结尾是一项复杂的任务。下面的句子是一个简单的例子: \par
78 |
79 | \texttt{I love studying C++. It's hard, but interesting.} \par
80 |
81 | 程序将输出两个句子: \par
82 |
83 | \texttt{I love studying C++.} \par
84 | \texttt{It's hard, but interesting.} \par
85 |
86 | 就编码任务而言,我们只搜索.(点)字符,并确保第一个单词以大写字母开头。如果一个句子具有以下形式,程序将如何表现? \par
87 |
88 | \texttt{I love studying C++!} \par
89 |
90 | 由于在句子末尾有一个感叹号,我们应该重新访问我们的程序,添加另一个识别句子结尾的规则。如果句子是这样结束的呢? \par
91 |
92 | \texttt{It's hard, but interesting...} \par
93 |
94 | 越来越多的规则和定义引入到具有完整功能的句子提取器中。在解决NLP任务时,利用ML将我们引向一个更明智的方向。 \par
95 | 另一个与语言相关的任务是机器翻译,它将文档从一种语言自动翻译成另一种语言。此外,需要注意的是,建立一个全面的NLP系统将有利于其他领域的研究,如知识推理。 \par
96 |
97 | \noindent\textbf{}\ \par
98 | \textbf{知识推理} \ \par
99 | 知识推理使计算机以与人类相似的方式思考和推理。想象一下和机器对话,开头是这样的: \par
100 | \par
101 | \texttt{[Human] 你好!} \par
102 | \texttt{[Machine] 你好!} \par
103 | \par
104 | 我们可以通过编程让机器回答特定的问题或理解用户输入的复杂文本,但要根据以往的经验来判断机器的原因要困难得多。例如,以下推理是这项研究的目标之一: \par
105 | \par
106 | \texttt{[Human] 我昨天走路的时候下着雨。} \par
107 | \texttt{[Machine] 雨中漫步很不错哦。} \par
108 | \texttt{[Human] 下次我应该穿得暖和点。} \par
109 | \texttt{[Machine] 你说的对。} \par
110 | \texttt{[Human] 我好像发烧了。} \par
111 | \textbf{[Machine] 你昨天感冒了吗?} \par
112 | \texttt{[Human] 我想是的。} \par
113 | \par
114 | 虽然人类很容易就能发现感冒和下雨之间的联系,但这个程序很难推导出这一点。它一定会把下雨和寒冷联系在一起,把发烧和感冒联系在一起。它还应该记住之前的输入,以便智能地保存。 \par
115 | 前面提到的所有研究领域都是开发者可以深入研究的领域。最后,ML通常是所有其他领域的基础,为每个特定的应用设计算法和模型。 \par
116 |
117 | \noindent\textbf{}\ \par
118 | \textbf{机器学习} \ \par
119 | ML将我们带到了一个全新的水平,让机器像人类一样执行任务,甚至可能做得更好。与我们前面介绍的领域相比,ML的目标是构建不需要特定指令就能完成任务的系统。在开发人工智能机器的过程中,我们应该仔细看看人类的智能。出生时,孩子不会表现出聪明的行为,但开始慢慢地熟悉周围的世界。没有记录证据表明有任何一个1个月大的孩子解微分方程或作曲。就像孩子学习和发现世界一样,ML关心的是建立直接执行任务的基础模型,而不是能够学习如何去做。这就是让系统执行预先设定好的指令和让它自己去做的根本区别。 \par
120 | 当一个孩子开始走路、拿东西、说话、问问题的时候,他们就是在一步步地获得关于世界的知识。她或他拿了一本书,尝了尝它的味道,迟早会停止把书当作可食用的东西来咀嚼。年复一年,孩子打开书页,在书中寻找图像和组成文字的小图形。又过了几年,孩子开始读这些书。多年来,大脑变得越来越复杂,神经元之间产生了越来越多的连接,成为一个有智慧的人。 \par
121 | 想象一个系统,它有一些神奇的算法和模型。把一堆数据喂给它,它的理解能力就会越来越强,就像孩子通过视觉(眼睛看)、嗅觉、味道等形式对输入数据进行处理来认识世界一样。后来,通过发展一种提问的方式,孩子能够理解词语,并将这些词语与现实世界中的物体,甚至是无形的概念联系起来。ML系统的作用方式几乎相同。它们对输入数据进行处理,并产生一些符合我们预期结果的输出。下图说明了这个想法: \par
122 |
123 | \begin{center}
124 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-15/5}
125 | \end{center}
126 |
127 | 现在让我们更深入地研究ML。和往常一样,理解新事物的最好方法是先尝试实现它。 \par
128 |
129 | \noindent\textbf{}\ \par
130 | \textbf{理解机器学习} \ \par
131 | ML是一个大的研究领域,有很多研究正在进行中,并且正在迅速扩展。要了解ML,首先要了解学习的本质。思考和推理是使我们人类与众不同的关键概念。ML的核心是使系统学习和使用知识来执行任务。您可能还记得学习编程的第一步,必须学习新的概念,构建抽象,使大脑理解程序执行背后发生的事情。之后,你应该使用那些小组件构建复杂系统,这些组件包括关键词,指示,条件语句,函数,类等。 \par
132 | 然而,ML程序不同于我们通常创建的程序。看看下面的代码: \par
133 |
134 | \begin{lstlisting}[caption={}]
135 | int calculate()
136 | {
137 | int a{14};
138 | int b{27};
139 | int c{a + b};
140 | return c;
141 | }
142 | \end{lstlisting}
143 |
144 | 简单的先例程序做我们指示它做的事情。它包含了几条简单的指令,指向表示a和b的和的变量c。我们可以修改函数,以接受用户输入如下: \par
145 |
146 | \begin{lstlisting}[caption={}]
147 | int calculate(int a, int b)
148 | {
149 | int c{a + b};
150 | return c;
151 | }
152 | \end{lstlisting}
153 |
154 | 前面的函数不会获得任何智能。无论我们调用了多少次calculate()函数都没有关系。输入的数字也无关紧要。该函数表示指令的集合。我们甚至可以说是硬编码指令的集合,函数永远不会修改自己的指令,使其根据输入表现出不同的行为。然而,我们可以引入一些逻辑——让它在每次接收到负数时返回0: \par
155 |
156 | \begin{lstlisting}[caption={}]
157 | int calculate(int a, int b)
158 | {
159 | if (a < 0 && b < 0) {
160 | return 0;
161 | }
162 | int c{a + b};
163 | return c;
164 | }
165 | \end{lstlisting}
166 |
167 | 条件语句引入了最简单的决策形式,该函数根据其输入做出决策。我们可以添加越来越多的条件,这样函数就会增长,并有一个复杂的实现。然而,再多的条件语句也不能使它变得聪明,因为这不是代码自己提出的东西。这就是我们在处理程序时所面临的限制。他们会按照我们设定的程序行动。我们决定他们的行为。他们总是服从,只要我们不引入漏洞。 \par
168 | 现在,想象一个ML算法在运行。假设calculate()函数具有某种魔力,因此它根据输入返回一个值。假设它有以下形式: \par
169 |
170 | \begin{lstlisting}[caption={}]
171 | int calculate(int a, int b)
172 | {
173 | // some magic
174 | // return value
175 | }
176 | \end{lstlisting}
177 |
178 | 现在,假设我们正在调用calculate(),并将2和4作为它的参数传递,希望它将计算它们的和并返回6。另外,想象一下我们可以通过某种方式告诉它结果是否符合我们的预期。一段时间后,函数的行为方式是它理解如何使用这些输入值并返回它们的和。下面我们要构建的类代表了我们理解ML的第一步。 \par
179 |
180 | \noindent\textbf{}\ \par
181 | \textbf{设计一个可以学习的算法} \ \par
182 | 下面的类代表了一台计算机器。它包含四个算术运算,并期望我们提供如何计算输入值的示例: \par
183 |
184 | \begin{lstlisting}[caption={}]
185 | struct Example
186 | {
187 | int input1;
188 | int input2;
189 | int output;
190 | };
191 | class CalculationMachine
192 | {
193 | public:
194 | using Examples = std::vector;
195 | // pass calculation examples through the setExamples()
196 | void setExamples(const Examples& examples);
197 |
198 | // the main function of interest
199 | // returns the result of the calculation
200 | int calculate(int a, int b);
201 |
202 | private:
203 | // this function pointer will point to
204 | // one of the arithmetic functions below
205 | int (*fptr_)(int, int) = nullptr;
206 |
207 | private:
208 | // set of arithmetic functions
209 | static int sum(int, int);
210 | static int subtract(int, int);
211 | static int multiply(int, int);
212 | static int divide(int, int);
213 | };
214 | \end{lstlisting}
215 |
216 | 在使用calculate()函数之前,我们应该提供一个setExamples()函数的示例列表。下面是我们提供给CalculationMachine的一个例子: \par
217 |
218 | \begin{lstlisting}[caption={}]
219 | 3 4 7
220 | 2 2 4
221 | 5 5 10
222 | 4 5 9
223 | \end{lstlisting}
224 |
225 | 每行中的前两个数字表示输入参数,第三个数字是运算的结果。setExamples()函数是计算机器如何学习使用正确的算术函数。同样的方法,我们可以从前面的例子中猜测发生了什么,同样的方法,CalculationMachine试图找到最适合它的操作。它遍历示例并定义了当调用calculate()时应该使用哪些函数。实现类似如下: \par
226 |
227 | \begin{lstlisting}[caption={}]
228 | void CalculationMachine::setExamples(const Examples& examples)
229 | {
230 | int sum_count{0};
231 | int sub_count{0};
232 | int mul_count{0};
233 | int div_count{0};
234 | for (const auto& example : Examples) {
235 | if (CalculationMachine.sum(example.input1, example.input2) ==
236 | example.output) {
237 | ++sum_count;
238 | }
239 | if (CalculationMachine.subtract(example.input1, example.input2) ==
240 | example.output) {
241 | ++sub_count;
242 | }
243 | // the same for multiply() and divide()
244 | }
245 |
246 | // the function that has the maximum number of correct output results
247 | // becomes the main function for called by calculate()
248 | // fptr_ is assigned the winner arithmetic function
249 | }
250 | \end{lstlisting}
251 |
252 | 从前面的示例中可以看到,该函数调用所有算术函数,并将它们的返回值与示例输出进行比较。每次结果正确时,就会增加特定函数正确答案的数量。最后,将正确答案最多的函数赋值给calculate()函数使用的fptr\underline{ },如下所示: \par
253 |
254 | \begin{lstlisting}[caption={}]
255 | int CalculationMachine::calculate(int a, int b)
256 | {
257 | // fptr_ points to the sum() function
258 | return fptr_(a, b);
259 | }
260 | \end{lstlisting}
261 |
262 | 我们设计了一个简单的学习算法。setExamples()函数可以重命名为setDataSet()或trainwithexample()或类似的东西。关于CalculationMachine的例子的要点是,我们定义了一个模型和使用它的算法,我们可以称之为ML。它从数据中学习,能从经验中学习。我们提供给CalculationMachine的例子向量中的每一个记录都可以视为经验,同时计算的性能随经验而提高。也就是说,我们提供的示例越多,它就越有信心选择正确的函数来执行任务。任务是根据两个输入参数计算值。学习的过程本身并不是任务,但学习是完成任务的关键。任务通常被描述为系统应该如何处理一个示例,而示例是功能的集合。尽管在ML术语中,其中每个条目都是另一个特征,矢量数据结构的选择只是一个巧合。ML算法的基本原理之一是对系统进行训练,它可以分为监督算法和无监督算法。让我们了解一下它们的差异,然后再建立ML系统的各种应用。 \par
263 |
264 | \noindent\textbf{}\ \par
265 | \textbf{机器学习的类别} \ \par
266 | 下面的图表说明了机器学习的分类: \par
267 |
268 | \begin{center}
269 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/6}
270 | \end{center}
271 |
272 | ML算法的分类取决于它们在学习过程中所拥有的经验。我们通常称示例集合为数据集。一些书也使用术语数据点。数据集基本上是表示对目标系统有用的任何数据的集合。可能包括一段时间内的天气测量、某个或多个公司的股票价格列表,或任何其他数据集。虽然数据集可能是未处理的或所谓的原始数据集,但也有针对每个包含的经验的附加信息的数据集。CalculationMachine示例中,我们使用了一个原始数据集,我们已经编程让系统识别出前两个值是操作的操作数,第三个值是操作的结果。如前所述,我们将ML算法分为有监督的和无监督的。 \par
273 | 监督学习算法从标记数据集学习,每个记录都包含描述数据的附加信息。calculationmachine是一个监督学习算法的例子。监督学习也称为导师培训。类似讲师使用数据集来教授知识的系统。 \par
274 | 有监督的学习算法将能够在学习经验后标记新的未知数据。下面的图表最好地描述了它: \par
275 |
276 | \begin{center}
277 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/7}
278 | \end{center}
279 |
280 | 监督学习算法应用的一个很好的例子是电子邮件应用中的垃圾邮件过滤器。用户将电子邮件标记为垃圾邮件或非垃圾邮件,然后系统试图在新收到的电子邮件中发现模式,以检测潜在的垃圾邮件。 \par
281 | CalculationMachine的例子是监督学习的另一个例子。我们提供了以下数据集: \par
282 |
283 | \begin{lstlisting}[caption={}]
284 | 3 4 7
285 | 2 2 4
286 | 5 5 10
287 | 4 5 9
288 | \end{lstlisting}
289 |
290 | 我们为CalculationMachine编写了程序,将前两个数字作为输入参数读取,将第三个数字作为应用于输入的函数产生的输出。通过这种方式,我们提供了有关系统应该得到的结果的必要信息。 \par
291 | 无监督学习算法甚至更复杂——处理包含一系列特征的数据集,然后找到这些特征的有用属性。无监督学习算法大多是自己定义数据集中的内容。在智能方面,无监督学习方法比监督学习算法更符合智能生物的描述。相比之下,监督学习算法试图预测哪些输入值映射到输出值,而非监督学习算法执行几个操作来发现数据集中的模式。下面的图描述了一种无监督学习算法: \par
292 |
293 | \begin{center}
294 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/8}
295 | \end{center}
296 |
297 | 无监督学习算法应用的例子是推荐系统。我们将在下一章中讨论,在那里我们设计了一个网络搜索引擎。推荐系统通过分析用户活动来推荐类似的数据,例如电影推荐。 \par
298 | 正如你在前面的例子中看到的,还有强化学习——从错误中学习的算法。在学习系统和经验之间有一个反馈循环,因此强化学习算法与环境相互作用。可能一开始就犯很多错,然后在处理反馈后,进行自我修正以改进算法。学习过程成为任务执行的一部分。想象一下,计算机只接收输入的数字,而不接收计算的结果。对于每一次体验,它将通过应用其中一个算术运算产生一个结果,然后收到反馈。假设它减去这些数字,然后根据反馈进行自我修改,计算出总和。 \par
299 |
300 | \noindent\textbf{}\ \par
301 | \textbf{机器学习的应用} \ \par
302 | 理解ML的分类有助于更好地将其应用于各种任务。有大量的任务可以用ML解决。我们已经提到分类作为ML算法解决的任务之一。基本上,分类就是对输入进行过滤和排序,以指定输入所属的类别的过程。用ML解决分类问题通常意味着它产生一个将输入映射到特定输出的函数。输出类的概率分布也是一种分类任务。分类任务最好的例子之一是物体识别。输入是一组像素值(换句话说,是一幅图像),输出是标识图像中的对象的值。想象一下,一个机器人可以识别不同种类的工具,并按命令将它们交付给工人,也就是说,一个在车库工作的机械师有一个助手机器人,能够识别螺丝刀并按命令进行工作。 \par
303 | 更有挑战性的是在缺少输入的情况下进行分类。前面的例子中,这类似于让机器人来拧紧螺栓。当一些输入缺失时,学习算法必须与多个函数一起操作才能获得成功的结果。例如,机器人助手可能会先拿出钳子,然后再拿出螺丝刀作为正确的解决方案。 \par
304 | 与分类类似的是回归,系统被要求预测给定输入的一个数值。不同之处在于输出的格式。回归任务的一个例子是预测股票的未来价格。ML的这些和其他应用使它作为一个研究领域迅速发展。学习算法并不像一开始感觉的那样只是一系列条件语句。它们是基于更全面的构造,模仿人类大脑神经元及其连接。这将引导我们进入下一节,人工神经网络(ANN)的研究。 \par
305 |
306 | \noindent\textbf{}\ \par
307 | \textbf{神经网络} \ \par
308 | 神经网络是用来识别模式的。它们是模仿人类大脑的,或者说是大脑的神经元和它们的人工对等物——人工神经元。下面的图展示了人脑中的一个神经元: \par
309 |
310 | \begin{center}
311 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/9}
312 | \end{center}
313 |
314 | 一个神经元通过突触与其他神经元交流。神经元的基本功能是处理一部分数据并根据这些数据产生信号,神经元接受一组输入并产生输出。 \par
315 | 下面的图表清楚地说明了为什么人工神经元与人类大脑神经元的结构相似: \par
316 |
317 | \begin{center}
318 | \includegraphics[width=0.5\textwidth]{content/Section-3/Chapter-15/10}
319 | \end{center}
320 |
321 | 神经网络是自然神经网络的一种简化模型。它代表一组相互连接的节点,每个节点代表一个神经元后的模型。每个节点连接都可以传递类似于生物大脑神经元突触的信号。神经网络是一组有助于聚类和分类的算法。从上图中可以看出,神经网络由三层组成: \par
322 |
323 | \begin{itemize}
324 | \item 输入层
325 | \item 隐藏层
326 | \item 输出层
327 | \end{itemize}
328 |
329 | 输入层表示问题的初始输入数据,例如:图像、音频或文本文件。输出层是完成任务的结果,例如:对文本内容或图像中已识别的对象进行分类。隐藏层使网络产生合理的结果。输入到输出的转换要经过隐藏层,该层要进行大量的分析、处理和修改,以产生输出。 \par
330 | 考虑上面的图表,表明神经元可以有多个输入和输出连接。通常,每个连接都有一个权重来指定连接的重要性。上图中的神经元层告诉我们,每一层的神经元都与前一层和后一层的神经元相连。应该注意,在输入层和输出层之间可能有几个隐藏层。输入输出层的主要目的是读取外部数据并返回计算(或推导)的输出,而隐含层的目的是通过学习来适应。学习还包括调整连接和权重,以提高输出精度(这就是ML发挥作用的地方)。所以,如果我们创建一个包含几个隐藏层的复杂神经网络,准备学习和改进,我们就得到了一个人工智能系统。例如,我们来看一下聚类问题,然后再转向回归分析。 \par
331 |
332 | \noindent\textbf{}\ \par
333 | \textbf{聚类} \ \par
334 | 聚类是指对一组对象进行分组,将它们分布在类似对象的分组中,也称为聚类分析。它是一组技术和算法,目的是将相似的对象分组在一起,产生聚类。最简单的说就是将一组有颜色的物体分成由相同颜色的物体组成的不同组,如下所示: \par
335 |
336 | \begin{center}
337 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/11}
338 | \end{center}
339 |
340 | 虽然本章中讨论的是人工智能任务,但我们建议你先尝试用目前拥有的知识库来解决问题,让想想我们自己如何通过相似性来给物体分类。首先,我们应该有一个物体的基本概念。在前面的例子中,对象的形状、颜色、尺寸(2D对象的宽度和高度)等。不需要深入了解,一个基本的对象表示可能是这样的: \par
341 |
342 | \begin{lstlisting}[caption={}]
343 | struct Object
344 | {
345 | int color;
346 | int shape;
347 | int width;
348 | int height;
349 | };
350 | \end{lstlisting}
351 |
352 | 我们考虑这样一个情况,即颜色和形状的值在一定范围内是已定义的。我们可以使用枚举来提高可读性。聚类分析包括对对象进行分析,并对其进行分类。首先想到的是拥有一个接受对象列表的函数。让我们来定义一个: \par
353 |
354 | \begin{lstlisting}[caption={}]
355 | using objects_list = std::vector;
356 | using categorized_table = std::unordered_map;
357 | categorized_table clusterize(const objects_list& objects)
358 | {
359 | // categorization logic
360 | }
361 | \end{lstlisting}
362 |
363 | 考虑一下实现细节。需要定义聚类点,有可能是颜色,也可能是形状的类型。挑战性在于,它可能是未知的。也就是说,为了防止万一,我们将每个属性的对象分类如下: \par
364 |
365 | \begin{lstlisting}[caption={}]
366 | categorized_table clusterize(const objects_list& objects)
367 | {
368 | categorized_table result;
369 | for (const auto& obj : objects) {
370 | result[obj.color].push_back(obj);
371 | result[obj.shape].push_back(obj);
372 | }
373 | return result;
374 | }
375 | \end{lstlisting}
376 |
377 | 具有相似颜色或形状的对象分组在一个散列表中。虽然前面的代码相当简单,但它承载了根据某种相似标准,对对象进行分组的基本思想。我们在上一个示例中所做的可以描述为硬集群。一个对象要么属于一个集群,要么不属于。相反,软聚类(也称为模糊聚类)可以在一定程度上描述对象所属的聚类。 \par
378 | 例如,shape属性的对象相似性可以通过应用于对象的函数的结果来定义。也就是说,如果对象A的形状是正方形,而对象B的形状是菱形,那么该函数定义对象A和对象B是否具有相似的形状。这意味着我们应该更新前面示例中的逻辑,将对象与多个值进行比较,并将它们的形状定义为一个组。通过进一步发展这一思想,我们会得到不同的聚类策略和算法,例如K-means聚类。 \par
379 |
380 | \noindent\textbf{}\ \par
381 | \textbf{回归分析} \ \par
382 | 回归分析关心的是找出一个值与另一个值的偏差。最简单的理解回归分析的方法是通过数学中的函数图。你可能还记得函数$ f(x) = y $的图像: \par
383 |
384 | \begin{center}
385 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/12}
386 | \end{center}
387 |
388 | 对于每一个x的值,函数的结果是y的一个固定值。回归分析有点类似于前面的图表,因为它关注的是找出变量之间的关系。更具体地说,它估计一个因变量和几个自变量之间的关系。因变量也称为结果,自变量也称为特征。功能的数量可能是一个。 \par
389 | 最常见的回归分析形式是线性回归。它看起来与前面的图表相似。下面的例子代表了花费在测试程序上的时间和在发布版本中发现的bug数量之间的关系: \par
390 |
391 | \begin{center}
392 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-15/13}
393 | \end{center}
394 |
395 | 有两种类型的回归:负回归是如上图所示的,随着自变量的减少而因变量的增加。另一方面,正回归对自变量的值递增。 \par
396 | ML中的回归分析是一种预测方法。可以开发一个程序,根据因变量的值来预测结果。ML是一个大领域,涉及的主题非常广泛。尽管开发者倾向于尽可能少地使用数学,但在ML领域是不行的。你仍然需要掌握一些数学科目,以充分利用ML。回归分析强烈依赖于统计学。 \par
397 |
398 | \noindent\textbf{}\ \par
399 | \textbf{C++和机器学习} \ \par
400 | ML更多的是关于数学而不是编程,这已经不再是一个秘密了。计算机科学起源于数学,在早期,计算机科学家首先是数学家。你可能熟悉几个著名的科学家,包括艾伦·图灵,约翰·冯·诺伊曼,克劳德·香农,诺伯特·维纳,尼克劳斯·沃斯,唐纳德·克努特还有很多人。他们都是数学家,对技术有着特殊的热爱。在计算机的发展过程中,编程成为了一个对新来者更友好的领域。过去的二三十年里,计算机开发者在开发有用的程序之前不再被迫要学习数学。语言发展成为越来越多的高级工具,几乎每个人都可以编写代码。 \par
401 | 有很多框架可以让程序员的工作变得更容易。现在,掌握一些框架或高级编程语言并创建一个新程序需要几周的时间。然而,程序往往会重复。现在构建东西并不难,因为有很多模式和最佳实践在这一过程中帮助我们。数学的作用已经不再是必须,越来越多的人成为程序员,甚至不需要使用数学。这其实不是问题,这更像是技术发展的自然过程。最终,科技的目的是让人类生活得更舒适,工程师也是如此。虽然在20世纪60年代,NASA的工程师们使用计算机进行计算,但那不是我们今天所知道的“计算机”。他们都是真实存在的人,他们有一种特殊的能力叫做计算机,“计算机”意味着他们在数学和解方程方面要比其他人快得多。 \par
402 | 现在我们是计算机科学新时代的一部分,数学又回来了。ML工程师现在使用数学的方式,就像数学家在70年代或80年代使用编程语言一样。现在,仅仅了解一种编程语言或框架来设计新的算法或将ML合并到应用程序中是不够的。你也应该至少在一些数学的子领域,如线性代数,统计和概率论。 \par
403 | 几乎同样的逻辑也适用于C++。现代语言提供了广泛的开箱即用的功能,而C++开发人员仍在努力设计具有手动内存管理的完美程序。如果你对ML领域做一些快速研究,你会发现大多数库或示例都在使用Python。首先,这可能被视为ML任务中使用的默认语言。然而,ML的工程师们开始触及进化的一个新门槛——性能。这并不新鲜,许多工具仍然在性能敏感的部分使用C++。游戏开发、操作系统、关键任务系统和许多其他基本领域都使用C++(和C)作为标准。现在是C++征服新领域的时候了。我们给读者的最好建议是同时学习ML和C++,因为对于ML工程师来说,可以结合C++以获得最好的性能在实际应用中变得越来越关键。 \par
404 |
405 | \noindent\textbf{}\ \par
406 | \textbf{总结} \ \par
407 | 介绍了ML的分类和应用。它是一个快速发展的研究领域,在构建智能系统方面有着众多的应用。我们将ML分为有监督、无监督和强化学习算法。每个类别都有自己的应用,如:分类,聚类,回归,和机器翻译。 \par
408 | 我们实现了一个简单的学习算法,它基于作为输入的经验定义了一个计算函数。我们称它为用来训练系统的数据集。使用数据集(称为经验)训练是ML系统的关键属性之一。 \par
409 | 最后,我们介绍并讨论了人工神经网络在模式识别中的应用。ML和神经网络在解决问题时是密切相关的。本章为您提供了该领域的必要介绍,以及几个任务示例,以便您可以花一些时间深入该主题。这将帮助您了解AI和ML的一般概念,因为在现实世界的应用程序开发中,工程师越来越需要AI和ML。在下一章,我们将学习如何实现一个基于对话框的搜索引擎。 \par
410 |
411 | \noindent\textbf{}\ \par
412 | \textbf{问题} \ \par
413 | \begin{enumerate}
414 | \item ML是什么?
415 | \item 有监督和无监督学习算法之间有什么区别?
416 | \item 给出一些ML应用的例子。
417 | \item 在用一组不同的经验训练CalculationMachine类后,您将如何修改它来改变它的行为?
418 | \item 神经网络的目的是什么?
419 | \end{enumerate}
420 |
421 | \noindent\textbf{}\ \par
422 | \textbf{扩展阅读} \ \par
423 | \begin{itemize}
424 | \item Artificial Intelligence and Machine Learning Fundamentals, at https://www.packtpub.com/big-data-and-business-intelligence/artificial-intelligence-and-machine-learning-fundamentals
425 | \item Machine Learning Fundamentals, at https://www.packtpub.com/big-data-and-business-intelligence/machine-learning-fundamentals
426 | \item Hands-On Machine Learning for Algorithmic Trading, at https://www.packtpub.com/big-data-and-business-intelligence/hands-machine-learning-algorithmic-trading
427 | \end{itemize}
428 |
429 | \newpage
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/1.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/10.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/11.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/12.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/13.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/14.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/15.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/16.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/17.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/18.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/2.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/3.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/4.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/5.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/6.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/7.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/8.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/content/Section-3/Chapter-16/9.png
--------------------------------------------------------------------------------
/content/Section-3/Chapter-16/chapter16.tex:
--------------------------------------------------------------------------------
1 | 已经到了这本书的最后一个章节了!我们学习了C++应用程序开发的基础知识,并讨论了架构和设计实际的应用程序。我们还深入研究了数据结构和算法,这是高效编程的核心。现在是时候利用所有这些技能来设计复杂的软件了——搜索引擎。 \par
2 | 随着互联网的普及,搜索引擎已经成为最受欢迎的产品。大多数用户通过搜索引擎开始他们的网络旅程。各种Web搜索服务,如谷歌、百度、Yandex等,接收大量的流量,每天服务数万亿的请求。搜索引擎处理每个请求的时间不到一秒。尽管它们维护了数千台服务器来处理负载,但它们高效处理的核心是数据结构和算法、数据架构策略和缓存。 \par
3 | 设计一个高效的搜索系统的问题不仅仅出现在网络搜索引擎中。本地数据库、客户关系管理(CRM)系统、会计软件等都需要健壮的搜索功能。在本章中,我们将发现搜索引擎的基本原理,并讨论用于构建快速搜索引擎的算法和数据结构。您将了解Web搜索引擎通常如何工作,并满足在需要高处理能力的项目中使用的新数据结构。你也将建立建立自己的搜索引擎,与现有大佬们竞争。 \par
4 |
5 | 本章中,我们将了解以下内容: \par
6 |
7 | \begin{itemize}
8 | \item 了解搜索引擎的结构
9 | \item 理解并设计用于将关键字映射到搜索引擎中的反向索引
10 | \item 为搜索平台的用户设计和构建一个推荐引擎
11 | \item 利用知识图谱设计一个基于对话框的搜索引擎
12 | \end{itemize}
13 |
14 | \noindent\textbf{}\ \par
15 | \textbf{编译器要求} \ \par
16 | g++编译器需要添加编译选项 \texttt{-std=c++2a} 来编译本章的代码。可以从这里获取本章的源码件:https://github.com/PacktPublishing/Expert-CPP \par
17 |
18 | \noindent\textbf{}\ \par
19 | \textbf{了解搜索引擎的结构} \ \par
20 | 想象一下,世界上有数十亿个网页。在搜索引擎界面中输入一个单词或短语,不到一秒钟就会返回一长串搜索结果。搜索引擎处理这么多网页的速度是不可思议的。它怎么能这么快找到正确的文档?为了回答这个问题,我们将做一个程序员能做的最明智的事情,设计一个我们自己的引擎。 \par
21 | 下图展示了搜索引擎背后的基本思想: \par
22 |
23 | \begin{center}
24 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/1}
25 | \end{center}
26 |
27 | 用户使用搜索引擎的用户界面输入单词。搜索引擎扫描所有文档,筛选它们,根据相关性对它们进行排序,并尽可能快地响应用户。我们的主要兴趣在于Web搜索引擎的实现。要想找到某样东西,就需要在数十亿份文件中搜索。 \par
28 | 让我们尝试设计一种方法来从数十亿的文档中(为了简洁起见,我们将Web页面称为文档)找到\texttt{Hello,world!}。扫描每一份文件寻找这个短语会花费大量的时间。如果我们认为每个文档至少有500个单词,那么搜索一个特定的单词或单词的组合将花费大量时间。事先扫描所有文件会更实际些。这个扫描过程包括为文档中出现的每个单词建立索引,并将信息存储在数据库中,这也称为索引文档。当用户输入一个短语时,搜索引擎将在其数据库中查找该词,并以满足查询的文档链接作出回应。 \par
29 | 搜索文档之前,引擎应该验证用户的输入。用户在短语中出现拼写错误并不罕见。除了拼写错误,如果引擎自动完成单词和短语,用户体验会更好。例如,当用户输入\texttt{hello}时,引擎可能会建议搜索短语\texttt{hello, world!}。一些搜索引擎跟踪用户,存储有关他们最近搜索的信息、他们用来发出请求的设备的详细信息等,例如:如果搜索引擎知道用户的操作系统,那么用户搜索如何重启计算机将得到更好的结果。如果是Linux发行版,搜索引擎将对搜索结果进行排序,使描述基于Linux的计算机重新启动的文档首先出现。 \par
30 | 我们还应该注意网络上经常出现的新文件。后台工作可能会不断地分析网络以找到新的内容,我们称这个工作为爬虫,因为它爬虫网络和索引文档。爬虫程序下载文档以解析其内容并构建索引。已经建立索引的文档可能会更新,甚至删除。因此,另一项后台工作应该负责定期更新现有文档。您可能会遇到术语“蜘蛛”,它表示在Web上搜索和解析文档的任务。 \par
31 | 下面的图表更详细地说明了搜索引擎的结构: \par
32 |
33 | \begin{center}
34 | \includegraphics[width=1.0\textwidth]{content/Section-3/Chapter-16/2}
35 | \end{center}
36 |
37 | 搜索应用广泛。想象一下最简单的搜索形式——在数组中查找单词: \par
38 |
39 | \begin{lstlisting}[caption={}]
40 | using words = std::vector;
41 | words list = get_list_of_words(); // suppose the function is implemented
42 |
43 | auto find_in_words(const std::string& term)
44 | {
45 | return std::find(list.begin(), list.end(), term);
46 | }
47 | \end{lstlisting}
48 |
49 | 虽然前面的例子适用于最简单的搜索引擎,但真正的问题是设计一个可伸缩的搜索引擎。我们不希望通过搜索字符串数组来满足用户请求。相反,应该努力实现一个可扩展的搜索引擎,能够搜索数百万个文档。这需要大量的思考和设计,因为从数据结构的正确选择到数据处理的高效算法,一切都很重要。现在让我们更详细地讨论搜索引擎的组件。我们将结合前面章节学到的所有技巧来设计一个好的搜索引擎。 \par
50 |
51 | \noindent\textbf{}\ \par
52 | \textbf{提供方便的用户界面} \ \par
53 | 投入时间和资源来构建一个细粒度的用户界面是至关重要的,它将提供惊人的用户体验。界面越简单,使用效果越好。我们将以占据市场主导地位的谷歌为例。它在页面中心有一个简单的输入字段。用户在字段中输入他们的请求,引擎会给出一些短语: \par
54 |
55 | \begin{center}
56 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/3}
57 | \end{center}
58 |
59 | 我们并不认为用户是懒惰的人,但是提供一个建议列表是很有帮助的,因为有时候用户并不知道他们想要的确切术语。让我们专注于建议列表的结构和实施。毕竟,我们感兴趣的是解决问题,而不是设计良好的用户界面。本章不讨论用户界面设计,而是专注于搜索引擎的后端。然而,在继续之前,还有一件事我们应该考虑,这里实现的搜索引擎是基于对话框的。用户查询引擎,并可以从几个答案中进行选择,以缩小结果列表。例如,假设用户查询一台计算机,搜索引擎询问一台台式机或笔记本电脑?这大大减少了搜索结果,为用户提供了更好的结果。我们将使用决策树来实现这一点。在此之前,让我们先了解一下搜索引擎的复杂性。 \par
60 | 首先是输入标记化的问题。这与文档解析和搜索短语分析有关。您可能会构建一个很好的查询解析器,它会因为用户在查询中犯了错误而中断。让我们看看处理模糊查询的两种方法。 \par
61 |
62 | \noindent\textbf{}\ \par
63 | \textbf{处理查询中的拼写错误} \ \par
64 | 用户在输入时出现拼写错误的情况并不少见。虽然这看起来是一件简单的事情,但对于搜索引擎设计者来说却是一个真正的问题。如果用户输入的是\texttt{helo world}而不是\texttt{hello world},那么搜索数百万个文档可能会出现意想不到的错误结果。您可能熟悉搜索引擎提供的自动建议,例如:当我们输入错误时,谷歌的搜索界面是这样的: \par
65 |
66 | \begin{center}
67 | \includegraphics[width=1.0\textwidth]{content/Section-3/Chapter-16/4}
68 | \end{center}
69 |
70 | 请注意截图底部的两行。其中之一是显示\texttt{hello world}的结果,这表明搜索引擎假设用户输入的查询有拼写错误,并主动显示正确的查询结果。然而,仍然有可能用户确实想要搜索他们输入的确切单词。因此,用户体验提供了下一行作为搜索\texttt{helo world}的结果。 \par
71 | 因此,在构建搜索引擎时,我们需要解决几个问题,首先是用户请求。首先,我们需要为用户提供一个简单的输入文本的界面。界面还应该与它们交互,以提供更好的结果,这包括基于部分输入的单词提供建议。使搜索引擎与用户交互是我们将在本章讨论的用户界面的另一个改进。 \par
72 | 接下来是检查拼写错误或不完整的单词,这不是一项容易的任务。在字典中保存所有单词的列表并比较用户输入的单词可能需要一段时间。为了解决这个问题,必须使用特定的数据结构和算法。例如,在检查用户查询中的拼写错误时,查找单词之间的Levenshtein距离可能会有帮助。Levenshtein距离是一个单词中应该添加、删除或替换的字符数,以使它等于另一个字符。例如,单词world和world之间的Levenshtein距离是1,因为从world中删除字母d或将字母d添加到world中会使这两个单词相等。单词编码和坐下之间的距离是4,因为以下四次编辑将一个单词变为另一个: \par
73 |
74 | \begin{itemize}
75 | \item coding -> cod\textbf{t}ing (在中间插入t)
76 | \item co\textbf{d}ting -> co\textbf{t}ting (用t替换d)
77 | \item c\textbf{o}tting -> c\textbf{i}tting (用i替换o)
78 | \item \textbf{c}itting -> \textbf{s}itting (用s替换c)
79 | \end{itemize}
80 |
81 | 如果我们将每个用户的输入与数万个单词进行比较,找出最接近的单词,那么处理过程将需要多长时间。另一种方法是使用一个大的try(数据结构)预先发现可能的拼写错误。一个try是一个有序的搜索树,其中键是字符串。下面的图表代表了一次尝试: \par
82 |
83 | \begin{center}
84 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-16/5}
85 | \end{center}
86 |
87 | 每个路径代表一个有效的字。例如,a节点指向n和r节点。注意n后面的\#。它告诉我们,到这个节点的路径代表一个单词an。但是,它继续指向d, d后面跟着另一个\#,这意味着到这个节点的路径表示另一个单词和。同样的逻辑也适用于其余的试验。例如,想象一下world这个单词的部分: \par
88 |
89 | \begin{center}
90 | \includegraphics[width=0.6\textwidth]{content/Section-3/Chapter-16/6}
91 | \end{center}
92 |
93 | 当引擎遇到world时,它将经历之前的尝试。w很好,o也很好,直到单词l中的倒数第二个字符为止,其他的一切都很好。在前面的图中,l之后没有终端节点,只有d。这意味着我们确定不存在world这样的单词,所以它可能是world。为了提供好的建议和检查拼写错误,我们应该有一个完整的用户语言词汇词典。当您计划支持多种语言时,这将变得更加困难。然而,虽然收集和存储字典是一项容易的任务,但更难的任务是收集Web上的所有文档并相应地存储它们以执行快速搜索。收集和解析网站以构建搜索引擎数据库(如前所述)的搜索引擎的工具、程序或模块称为爬虫程序。在深入了解存储这些网站页面的方式之前,让我们先快速了解一下爬虫程序的功能。 \par
94 |
95 | \noindent\textbf{}\ \par
96 | \textbf{爬虫} \ \par
97 | 每次用户输入查询时搜索数百万个文档是不现实的。想象一个搜索引擎,在用户点击系统UI上的搜索按钮之后,它解析网站以搜索用户查询。那将永远无法完成。搜索引擎对网站的每个请求都需要一些时间。即使它小于1毫秒(0.001秒),在用户等待查询完成时,分析和解析所有网站也需要很长的时间。为了让事情更清楚,让我们假设访问和搜索一个网站大约需要0.5毫秒(即使这样,那也是不合理的快)。这意味着搜索100万个网站大约需要8分钟。现在想象一下你打开一个谷歌搜索并进行查询——你会等待8分钟吗? \par
98 | 正确的方法是将所有信息存储在数据库中,以便搜索引擎有效地访问。爬虫程序下载网站页面并将其存储为临时文档,直到进行解析和建立索引。复杂的爬虫程序还会对文档进行解析,以使它们保持对索引器更方便的格式。这里重要的一点是,下载一个网页不是一个只发生一次的操作。Web页面的内容可能会更新。同时,在此期间可能会出现新的页面。因此,搜索引擎必须保持其数据库的更新。为了实现这一点,它安排爬虫程序定期下载页面。聪明的爬虫程序可能会在将内容传递给索引器之前比较内容的不同之处。 \par
99 | 通常,爬虫程序作为多线程应用程序工作。开发人员应该注意使爬行尽可能快,因为保持数十亿文档的最新不是一项容易的任务。正如我们已经提到的,搜索引擎并不直接搜索文档。它在所谓的索引文件中执行搜索。虽然爬行是一项有趣的编码任务,但在本章中,我们将关注于索引。下一节将介绍搜索引擎中的索引。 \par
100 |
101 | \noindent\textbf{}\ \par
102 | \textbf{索引文件} \ \par
103 | 搜索引擎的关键功能是索引。下面的图表显示了爬虫程序如何处理下载的文档来构建索引文件: \par
104 |
105 | \begin{center}
106 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/7}
107 | \end{center}
108 |
109 | 上面的图表中,索引显示为反向索引,用户查询被定向到反向索引。虽然我们在本章中交替使用索引和倒序索引,但反向索引是一个更准确的名称。首先,让我们看看搜索引擎的索引是什么。建立文档索引的全部原因是为了提供快速搜索功能。其思想很简单:每次爬虫程序下载文档时,搜索引擎都会处理它的内容,将其划分为引用该文档的单词。这个过程称为标记化。假设我们从维基百科下载了一个包含以下文本的文档(为了简洁起见,我们只取一部分作为例子): \par
110 |
111 | \begin{lstlisting}[caption={}]
112 | In 1979, Bjarne Stroustrup, a Danish computer scientist, began work on "C with Classes", the predecessor to C++. The motivation for creating a new language originated from Stroustrup's experience in programming for his PhD thesis. Stroustrup found that Simula had features that were very helpful for large software development...
113 | \end{lstlisting}
114 |
115 | 搜索引擎将前面的文档分成几个单独的单词,如下所示(为了简洁起见,这里只显示前几个单词): \par
116 |
117 | \begin{lstlisting}[caption={}]
118 | In
119 | 1979
120 | Bjarne
121 | Stroustrup
122 | a
123 | Danish
124 | computer
125 | scientist
126 | began
127 | work
128 | ...
129 | \end{lstlisting}
130 |
131 | 在将文档划分为单词之后,引擎将为文档中的每个单词分配一个标识符(ID)。假设前面文档的ID为1,下表显示了单词引用(出现在)ID为1的文档中: \par
132 |
133 | \begin{table}[h]
134 | \begin{tabularx}{\textwidth}{|X|X|}
135 | \hline
136 | In & 1 \\
137 | \hline
138 | 1979 & 1 \\
139 | \hline
140 | Bjarne & 1 \\
141 | \hline
142 | Stroustrup & 1 \\
143 | \hline
144 | a & 1 \\
145 | \hline
146 | Danish & 1 \\
147 | \hline
148 | computer & 1 \\
149 | \hline
150 | scientist & 1 \\
151 | \hline
152 | ... & \\
153 | \hline
154 | \end{tabularx}
155 | \end{table}
156 |
157 | 可能有几个文档包含相同的单词,所以上面的表可能看起来更像下面的表: \par
158 |
159 | \begin{table}[h]
160 | \begin{tabularx}{\textwidth}{|X|X|}
161 | \hline
162 | In & 1,4,14,22 \\
163 | \hline
164 | 1979 & 1,99,455 \\
165 | \hline
166 | Bjarne & 1,202,1314 \\
167 | \hline
168 | Stroustrup & 1,1314 \\
169 | \hline
170 | a & 1,2,3,4,5,6,7,8,9,10,11,... \\
171 | \hline
172 | Danish & 1,99,102,103 \\
173 | \hline
174 | computer & 1,4,5,6,24,38,... \\
175 | \hline
176 | scientist & 1,38, 101, 3958, ... \\
177 | \hline
178 | \end{tabularx}
179 | \end{table}
180 |
181 | 下面的表表示反向索引。它将单词与爬虫程序下载的文档的id映射在一起。现在查找包含用户作为查询输入的单词的文档变得更快了。现在,当用户通过键入computer来查询引擎时,结果将基于从索引中检索到的ID生成,即$ 1,4,5,6,24,38,… $在上面的例子中。索引还有助于为更复杂的查询找到结果。例如,“计算机科学家”匹配以下文件: \par
182 |
183 | \begin{table}[h]
184 | \begin{tabularx}{\textwidth}{|X|X|}
185 | \hline
186 | computer & \textbf{1},4,5,6,24,\textbf{38},... \\
187 | \hline
188 | scientist & \textbf{1},\textbf{38}, 101, 3958, ... \\
189 | \hline
190 | \end{tabularx}
191 | \end{table}
192 |
193 | 要用包含两个术语的文档响应用户,我们应该找到引用文档的交集(参见上表中的粗体数字),例如1和38。 \par
194 | 注意,用户查询在与索引匹配之前也需要标记化。标记化通常涉及到词的规范化。如果没有规范化,计算机科学家查询将不会给出任何结果(注意查询中的大写字母)。让我们进一步了解一下。 \par
195 |
196 | \noindent\textbf{}\ \par
197 | \textbf{分词文件} \ \par
198 | 您可能还记得第1章中标记化的概念,其中我们讨论了编译器如何通过将源文件标记为更小的、不可分割的单元(称为标记)来解析源文件。搜索引擎以类似的方式解析和标记文档。 \par
199 | 我们不会过多地讨论这个问题,但是应该考虑到,文档的处理方式意味着标记(在搜索引擎上下文中具有意义的不可分割的术语)是规范化的。例如,我们正在查看的所有单词都是小写的。因此,索引表应该如下所示: \par
200 |
201 | \begin{table}[h]
202 | \begin{tabularx}{\textwidth}{|X|X|}
203 | \hline
204 | in & 1,4,14,22 \\
205 | \hline
206 | 1979 & 1,99,455 \\
207 | \hline
208 | bjarne & 1,202,1314 \\
209 | \hline
210 | stroustrup & 1,1314 \\
211 | \hline
212 | a & 1,2,3,4,5,6,7,8,9,10,11,... \\
213 | \hline
214 | danish & 1,99,102,103 \\
215 | \hline
216 | computer & 1,4,5,6,24,38,... \\
217 | \hline
218 | scientist & 1,38, 101, 3958, ... \\
219 | \hline
220 | \end{tabularx}
221 | \end{table}
222 |
223 | 作为C++程序员,看到小写的bjarne或stroustrup可能会感到不舒服。然而,当我们用倒排的索引键匹配用户输入时,我们应该考虑到用户输入可能没有我们期望的形式。因此,我们需要对用户输入应用相同的规则,使其与倒排索引匹配。 \par
224 | 接下来,注意a,毫不夸张地说,这是每个文档中都会出现的一个词。其他的例子还有the, an, in等,我们把它们称为停止词。通常,搜索引擎会忽略它们,所以倒排索引会更新为以下形式: \par
225 |
226 | \begin{table}[h]
227 | \begin{tabularx}{\textwidth}{|X|X|}
228 | \hline
229 | 1979 & 1,99,455 \\
230 | \hline
231 | bjarne & 1,202,1314 \\
232 | \hline
233 | stroustrup & 1,1314 \\
234 | \hline
235 | danish & 1,99,102,103 \\
236 | \hline
237 | computer & 1,4,5,6,24,38,... \\
238 | \hline
239 | scientist & 1,38, 101, 3958, ... \\
240 | \hline
241 | \end{tabularx}
242 | \end{table}
243 |
244 | 您应该注意,规范化不仅仅是使单词小写。它还包括将单词变为正常形式。 \par
245 |
246 | \hspace*{\fill} \\ %插入空行
247 | \includegraphics[width=0.05\textwidth]{images/warn}
248 | 将单词规范化为词根形式(或词干)也称为词干提取。 \par
249 | \noindent\textbf{}\ \par
250 |
251 | 看看我们在本节开始时所使用的文件中的以下句子: \par
252 |
253 | \begin{lstlisting}[caption={}]
254 | The motivation for creating a new language originated from Stroustrup's experience in programming for his PhD thesis.
255 | \end{lstlisting}
256 |
257 | creation、originated和Stroustrup都是规范化的,所以反向索引的形式如下: \par
258 |
259 | \begin{table}[h]
260 | \begin{tabularx}{\textwidth}{|X|X|}
261 | \hline
262 | motivation & 1 \\
263 | \hline
264 | create & 1 \\
265 | \hline
266 | new & 1 \\
267 | \hline
268 | language & 1 \\
269 | \hline
270 | originate & 1 \\
271 | \hline
272 | stroustrup & 1 \\
273 | \hline
274 | experience & 1 \\
275 | \hline
276 | programming & 1 \\
277 | \hline
278 | phd & 1 \\
279 | \hline
280 | thesis & 1 \\
281 | \hline
282 | \end{tabularx}
283 | \end{table}
284 |
285 | 请注意,我们忽略了停止词,也没有在前面的表中包含。 \par
286 | 标记化是创建索引的第一步。除此之外,我们可以自由地以任何使搜索更好的方式处理输入。 \par
287 |
288 | \noindent\textbf{}\ \par
289 | \textbf{结果排序} \ \par
290 | 相关性是搜索引擎最重要的特性之一。仅响应与用户输入匹配的文档是不够的。我们应该对它们进行排序,使最相关的文档排在前面。 \par
291 | 一种策略是记录每个单词在文档中出现的次数。例如,描述计算机的文档可能包含单词computer的多次出现,如果用户搜索计算机,结果将首先显示包含最多的computer的文档。下面是一个索引表的例子: \par
292 |
293 | \begin{table}[h]
294 | \begin{tabularx}{\textwidth}{|X|X|}
295 | \hline
296 | computer & 1\{18\}, 4\{13\}, 899\{3\} \\
297 | \hline
298 | map & 4\{9\}, 1342\{4\}, 1343\{2\} \\
299 | \hline
300 | world & 12\{1\} \\
301 | \hline
302 | \end{tabularx}
303 | \end{table}
304 |
305 | 花括号中的值定义了文档中每个单词出现的次数。 \par
306 | 向用户展示搜索结果时,我们可以考虑许多因素。一些搜索引擎存储用户相关信息,以响应个性化的结果。甚至用户用于访问搜索引擎(通常是Web浏览器)的程序也可能改变搜索平台的结果,例如:在Linux操作系统上搜索重新安装操作系统的用户会得到列表顶部包含重新安装Ubuntu的结果,因为浏览器向搜索引擎提供了操作系统类型和版本信息。然而,考虑到隐私问题,有些搜索引擎完全不使用个性化用户数据。 \par
307 | 文档的另一个属性是更新日期,新鲜内容总是具有更高的优先级。因此,当向用户返回一个文档列表时,我们还可以按照其内容更新的顺序对它们重新排序。考虑到文档的相关排名,我们将进入下一节,在这里我们将讨论推荐引擎。 \par
308 |
309 | \noindent\textbf{}\ \par
310 | \textbf{构建推荐引擎} \ \par
311 | 在前一章中,我们介绍了人工智能(AI)和机器学习(ML)。推荐引擎可以视为人工智能驱动的解决方案或简单的条件语句集合。构建一个接受用户数据并返回最满足输入的选项的系统是一项复杂的任务。将ML合并到这样的任务中,可能很合理。 \par
312 | 但是,您应该考虑这样一个事实:推荐引擎可能包含一组规则,在将数据输出给最终用户之前,根据这些规则对数据进行处理。推荐引擎可以在预期和意外的地方运行。例如,在Amazon上浏览产品时,推荐引擎会根据我们当前浏览的产品向我们推荐产品。电影数据库会根据我们之前看过的电影或分级的电影来推荐新的电影。这对许多人来说可能是意想不到的,但推荐引擎也运行在搜索引擎之后。 \par
313 | 你可能熟悉一些电子商务平台推荐产品的方式。大多数时候,建议面板的标题是类似于购买了这个的客户,也购买了....回想一下我们在前一章介绍过的聚类分析。现在,如果我们试图理解这些建议是如何在幕后工作的,我们很可能会发现一些聚类算法。 \par
314 | 让我们简单地看一看,并尝试设计一些推荐机制。让我们以一个书店网站为例。John买了一本名为《精通Qt5》的书,所以让我们把这些信息放在下面的表格中: \par
315 |
316 | \begin{table}[h]
317 | \begin{tabularx}{\textwidth}{|X|X|}
318 | \hline
319 | & Mastering Qt5 \\
320 | \hline
321 | John & yes \\
322 | \hline
323 | \end{tabularx}
324 | \end{table}
325 |
326 | 接下来,约翰决定买一本C++书《精通c++编程》。莱娅买了一本名为《设计模式》的书。卡尔买了三本书,分别是《学习Python》、《掌握机器学习》和《用Python学习机器》。表格会进行更新,现在看起来像这样: \par
327 |
328 | \begin{table}[h]
329 | \begin{tabularx}{\textwidth}{|X|X|X|X|X|X|X|}
330 | \hline
331 | & Mastering Qt5 & Mastering C++ Programming & Design Patterns & Learning Python & Mastering Machine Learning & Machine Learning with Python \\
332 | \hline
333 | John & yes & yes & no & no & no & no \\
334 | \hline
335 | Leia & no & no & yes & no & no & no \\
336 | \hline
337 | Karl & no & no & no & yes & yes & yes \\
338 | \hline
339 | \end{tabularx}
340 | \end{table}
341 |
342 | 现在,让我们想象一下Harut访问了网站并购买了前面列出的两本书,《学习Python》和《用Python学习机器》。那么向他推荐《精通Qt5》这本书合理吗?我们不这么认为。但是我们知道他买了什么书,我们也知道另外一个用户Karl买了三本书,其中两本和Harut买的书是一样的。所以,向Harut推荐精通机器学习可能是合理的,告诉他买了其他两本书的顾客也买了这本书。这是一个简单的例子,说明了从高层次的角度推荐引擎是如何工作的。 \par
343 |
344 | \noindent\textbf{}\ \par
345 | \textbf{使用知识图谱} \ \par
346 | 现在,让我们回到搜索引擎。一个用户正在搜索一位著名的计算机科学家——比如Donald Knuth。他们在搜索字段中输入名称,然后从所有的Web中获得排序的结果,以提供最好的结果。我们再来看看谷歌搜索。为了充分利用用户界面,谷歌向我们展示了一些关于搜索主题的简要信息。在本例中,它在结果页面的右侧显示了这位伟大科学家的几张图片和一些关于他的信息。这部分看起来是这样的: \par
347 |
348 | \begin{center}
349 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/8}
350 | \end{center}
351 |
352 | 通过这种方式,搜索引擎试图满足用户的基本需求,让他们在不访问任何网站的情况下更快地找到信息。在这种情况下,我们最感兴趣的是放在前面信息框下面的建议框。它的标题是“人们也会搜索”,它是这样的: \par
353 |
354 | \begin{center}
355 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/9}
356 | \end{center}
357 |
358 | 这些是基于用户搜索Alan·Turing的推荐,就在他们搜Donald·Knuth之后。这促使推荐引擎提出了一个建议,如果有人在搜索Donald·Knuth,他们可能也对Alan·Turing感兴趣。 \par
359 | 我们可以通过谷歌称之为知识图谱的东西来组织类似的建议机制。这是一个由节点组成的图,每个节点表示某个主题、人物、电影或其他可搜索的内容。图数据结构是节点和连接这些节点的边的集合,如下图所示: \par
360 |
361 | \begin{center}
362 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/10}
363 | \end{center}
364 |
365 | 在知识图谱中,每个节点代表一个单独的实体。所谓实体,我们指的是一座城市,一个人,一只宠物,一本书,或者几乎任何你能想象到的东西。现在,图中的边表示实体之间的连接。每个节点可以通过多个节点连接到另一个节点。例如,看看这两个节点: \par
366 |
367 | \begin{center}
368 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/11}
369 | \end{center}
370 |
371 | 这两个节点只包含文本。我们可能会认为Donald·Knuth是一个名字,而《计算机编程的艺术》是某种艺术。构建知识图的本质是,我们可以将每个节点与表示其类型的另一个节点关联起来。下面的图是对前一图的扩展: \par
372 |
373 | \begin{center}
374 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/12}
375 | \end{center}
376 |
377 | 看看我们添加的两个新节点。其中一个代表一个人,而另一个代表一本书。更令人兴奋的是我们用一条边把Donald·Knuth节点和person节点连接起来并将其标记为'是'的关系。同样地,我们已经将《计算机编程艺术》节点与书籍节点连接起来,所以我们可以说计算机编程艺术是一本书。现在让我们把Donald·Knuth和他写的书联系起来: \par
378 |
379 | \begin{center}
380 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/13}
381 | \end{center}
382 |
383 | 所以,现在我们有了一个完整的关系因为我们知道Donald·Knuth是《计算机编程艺术》的作者。\par
384 | 让我们再添加几个节点来表示人。下面的图表显示了我们是如何添加Alan·Turing和Peter·Weyland节点的: \par
385 |
386 | \begin{center}
387 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/14}
388 | \end{center}
389 |
390 | Alan·Turing和Peter·Weyland都是人。现在,如果这是搜索引擎知识库的一部分,那么它给了我们一个很好的洞察用户的搜索意图。当我们看到Donald·Knuth的结果时,我们知道这是关于一个人的。如果有必要,可以建议用户看看在知识图谱中已经积累的其他人。建议搜索Donald·Knuth的用户也看看Alan·Turing和Peter·Weyland的页面是否合理?好了,棘手的部分来了:尽管他们都是人,但他们之间的联系并不紧密。所以,我们需要一些额外的东西来定义两个不同的人之间的联系的相关性。看看图表中添加的以下内容: \par
391 |
392 | \begin{center}
393 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/15}
394 | \end{center}
395 |
396 | 现在很清楚的是,Donald·Knuth和Alan·Turing的活动是相同的,作为计算机科学节点,代表了一个研究领域,而Peter·Weyland原来是一个虚构的人物。Peter·Weyland和Donald·Knuth唯一的联系就是他们都是人。看看我们放在从人节点到计算机科学节点的边缘上的数字。假设我们从0到100对这种关系进行评级,后者意味着这种关系是最强的。所以,我们给Alan·Turing和Donald·Knuth都加了99。我们应该忽略从Peter·Weyland到计算机科学的连线,而不是输入0,但我们这样做是为了显示对比。这些数字就是权重。我们在边缘添加权重以强调连接性因素,Donald·Knuth和Alan·Turing有相同的东西,而且彼此有很强的联系。如果我们把Steve·Jobs作为一个新人加入到知识图谱中,图谱会是这样的: \par
397 |
398 | \begin{center}
399 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/16}
400 | \end{center}
401 |
402 | 看看这些边的权值。Steve·Jobs与计算机科学有某种联系,但他主要与商人和有影响力的节点有关。同样,我们现在可以看到,Peter·Weyland与Steve·Jobs的共同点多于Donald·Knuth。现在,推荐引擎建议搜索Donald Knuth的用户也应该看看Alan·Turing,因为他们都是人和计算机科学相关,权重相等或接近相等。这是一个将这样的图表整合到搜索引擎中的很好的例子。接下来,我们将向您介绍如何使用类似的知识图来构建一个更智能的框架,以提供相关的搜索结果。我们称之为基于对话框的搜索。 \par
403 |
404 | \noindent\textbf{}\ \par
405 | \textbf{实现基于对话框的搜索引擎} \ \par
406 | 最后,让我们着手设计搜索引擎的一部分,它将为我们提供细粒度的用户界面。正如我们在本章开头提到的,基于对话框的搜索引擎涉及到构建一个用户界面,向用户询问与他们的查询相关的问题。这种方法最适用于结果不明确的情况。例如,搜索Donald的用户可能想到以下情况之一: \par
407 |
408 | \begin{itemize}
409 | \item \textit{Donald Knuth}, 伟大的计算机科学家
410 | \item \textit{Donald Duck}, 卡通人物
411 | \item \textit{Donald Dunn}, 真实姓名是杰瑞德·邓恩,虚构人物
412 | \item \textit{Donald Trump}, 商人和美国第45任总统
413 | \end{itemize}
414 |
415 | 前面的列表只是Donald搜索词潜在结果的一个小示例。那么,缺少基于对话的搜索引擎会做什么呢?它们为用户输入提供最佳匹配的相关结果列表。例如,在我写这本书的时候,搜索Donald的结果是一系列与Donald Trump有关的网站,尽管我脑子里想的是Donald Knuth。这里,我们可以看到用户的最佳匹配和最佳匹配之间的关系。 \par
416 | 搜索引擎收集了大量的数据用于个性化的搜索结果。如果用户在网站开发领域工作,他们的大多数搜索请求将以某种方式与该特定领域相关。这对于为用户提供更好的搜索结果非常有帮助,例如:用户有一个很大的搜索历史,主要包括与网站开发相关的请求,当搜索zepelin时,会得到更好、更集中的结果。理想的搜索引擎将提供到Zeplin应用程序的网站链接,以构建Web UI,而对于其他用户,该引擎将提供关于名为Led Zeppelin的摇滚乐队的信息。 \par
417 | 设计一个基于对话框的搜索引擎是为用户提供更好界面的下一步。现在,如果我们已经有了一个强大的知识库,构建它就足够简单了。我们将使用上一节中描述的知识图的概念。让我们假设当用户输入一个搜索词时,我们从知识图中获取所有匹配的主题,并得到用户的潜在搜索列表,如下图所示: \par
418 |
419 | \begin{center}
420 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/17}
421 | \end{center}
422 |
423 | 现在用户可以更容易地选择一个主题,并节省回忆全名的时间。当用户输入查询时,来自知识图的信息可以(对于某些搜索引擎来说是)并入自动建议中。此外,我们将处理搜索引擎的主要组成部分。显然,本章不能涵盖实现的每个方面,但我们将讨论的基本组件足以让你跳进自己的搜索引擎的设计和实现。 \par
424 | 我们不考虑搜索引擎的UI部分。我们最关心的是后台。当谈到应用程序的后端时,我们通常指的是用户不可见的部分。更具体地说,来看看下面的图表: \par
425 |
426 | \begin{center}
427 | \includegraphics[width=0.8\textwidth]{content/Section-3/Chapter-16/18}
428 | \end{center}
429 |
430 | 大部分引擎位于后端。虽然UI看起来很简单,但它是整个搜索系统的重要组成部分。这是用户开始他们的旅程的地方,用户界面越多的被设计来提供最好的可能的体验,当用户搜索时,不舒服的体验就越少。我们将专注于后端,下面是我们将要讨论的几个主要模块: \par
431 |
432 | \begin{itemize}
433 | \item 查询解析器:分析用户查询、规范化单词,并收集查询中每个词汇的信息,以便稍后传递给查询处理器。
434 | \item 查询处理器:使用索引和补充数据库检索与查询关联的数据,并进行响应。
435 | \item 对话生成器:提供更多的选项供用户在搜索时选择。对话框生成器是一个补充模块。发出请求的用户可以省略这个对话框,或者使用它进一步缩小搜索结果的范围。
436 | \end{itemize}
437 |
438 | 我们跳过了一些在搜索引擎中很常见的组件(比如爬虫),我们将把重点放在那些与基于对话框的搜索引擎密切相关的组件上。现在让我们从查询解析器开始。 \par
439 |
440 | \noindent\textbf{}\ \par
441 | \textbf{实现查询解析器} \ \par
442 | 查询解析器如其名称所示:解析查询。作为查询解析器的基本任务,我们应该用空格分隔单词。例如,用户查询zeplin best album会分为以下几个术语:zeplin、best和album。下面的类表示基本的查询解析器: \par
443 |
444 | \begin{lstlisting}[caption={}]
445 | // The Query and Token will be defined in the next snippet
446 | class QueryParser
447 | {
448 | public:
449 | static Query parse(const std::string& query_string) {
450 | auto tokens = QueryParser::tokenize(query_string);
451 | // construct the Query object and return
452 | // see next snippet for details
453 | }
454 |
455 | private:
456 | static std::vector tokenize(const std::string& raw_query) {
457 | // return tokenized query string
458 | }
459 | };
460 | \end{lstlisting}
461 |
462 | 看一下前面的parse()函数。它是这个类中唯一的公共函数。我们将添加从parse()函数调用的更多私有函数,以完全解析查询并作为查询对象获取结果。Query表示一个简单的结构体,包含关于查询的信息,如下所示: \par
463 |
464 | \begin{lstlisting}[caption={}]
465 | struct Query
466 | {
467 | std::string raw_query;
468 | std::string normalized_query;
469 | std::vector tokens;
470 | std::string dialog_id; // we will use this in Dialog Generator
471 | };
472 | \end{lstlisting}
473 |
474 | raw\underline{ }query是用户输入的查询的文本表示,normalized\underline{ }query是规范化后的相同查询。例如,如果用户输入了\texttt{good books, a programmer should read.}, \underline{ }query是精确的文本,normalized\underline{ }query是\texttt{good books programmer should read}。在下面的代码片段中,我们不使用normalized\underline{ }query,但在完成实现后,您将需要它。我们还将符号存储在Token的vector中,Token是一个结构体,如下所示: \par
475 |
476 | \begin{lstlisting}[caption={}]
477 | struct Token
478 | {
479 | using Word = std::string;
480 | using Weight = int;
481 | Word value;
482 | std::unordered_map related;
483 | };
484 | \end{lstlisting}
485 |
486 | 相关属性表示与该标记语义相关的单词列表。如果两个词在概念上表达的意思相似,我们称之为语义相关的词。例如,“best”和“good”或者“album”和“collection”可以认为是语义相关的。您可能已经猜到了哈希表值中权重的用途,我们用它来存储相似度的权重。\par
487 |
488 | \hspace*{\fill} \\ %插入空行
489 | \includegraphics[width=0.05\textwidth]{images/warn}
490 | 权重的范围应该在使用搜索引擎时进行配置。假设我们选择的范围是0到99。best和good两个词的相似度权重可以用接近90的数字表示,而album和collection两个词的相似度权重可以在40 ~ 70之间偏离。选择这些数字是很棘手的,它们应该在引擎的开发和开发过程中进行调整。 \par
491 | \noindent\textbf{}\ \par
492 |
493 | 最后,如果用户选择了生成器建议的路径,查询结构的dialog\underline{ }id表示生成的对话框的ID。我们很快就会讲到这个问题。现在让我们继续结束parse()函数。 \par
494 | 在QueryParser类添加的以下内容: \par
495 |
496 | \begin{lstlisting}[caption={}]
497 | class QueryParser
498 | {
499 | public:
500 | static Query parse(const std::string& query_string,
501 | const std::string& dialog_id = "")
502 | {
503 | Query qr;
504 | qr.raw_query = query_string;
505 | qr.dialog_id = dialog_id;
506 | qr.tokens = QueryParser::tokenize(query_string);
507 | QueryParser::retrieve_word_relations(qr.tokens);
508 | return qr;
509 | }
510 | private:
511 | static std::vector tokenize(const std::string& raw_string) {
512 | // 1. split raw_string by space
513 | // 2. construct for each word a Token
514 | // 3. return the list of tokens
515 | }
516 | static void retrieve_word_relations(std::vector& tokens) {
517 | // for each token, request the Knowledge Base
518 | // to retrieve relations and update tokens list
519 | }
520 | };
521 | \end{lstlisting}
522 |
523 | 虽然在前面的代码片段中没有实现两个私有函数(tokenize和retrieve\underline{ }word\underline{ }relations),但基本思想是它们对搜索查询进行规范化和收集信息。在实现查询处理器之前,先看一下前面的代码。 \par
524 |
525 | \noindent\textbf{}\ \par
526 | \textbf{实现查询处理器} \ \par
527 | 查询处理器完成搜索引擎的主要工作,从搜索索引中检索结果,并根据搜索查询响应一个相关的文档列表。在本节中,我们还将介绍对话框的生成。 \par
528 | 正如在前一节中看到的,查询解析器构造了一个包含令牌和dialog\underline{ }id的查询对象。我们将在这里的查询处理程序中使用这两种方法。 \par
529 |
530 | \hspace*{\fill} \\ %插入空行
531 | \includegraphics[width=0.05\textwidth]{images/tip}
532 | 考虑到可伸缩性,建议为对话框生成器使用单独的组件。出于学习目的,我们将保持实现的简洁,但您可以自由地重新设计基于对话框的搜索引擎,并完成实现以及爬虫和其他补充模块。 \par
533 | \noindent\textbf{}\ \par
534 |
535 | 查询对象中的标记用于向搜索索引发出请求,以便检索与每个单词相关联的一组文档。下面是相应的QueryProcessor类的样子: \par
536 |
537 | \begin{lstlisting}[caption={}]
538 | struct Document {
539 | // consider this
540 | };
541 | class QueryProcessor
542 | {
543 | public:
544 | using Documents = std::vector;
545 | static Documents process_query(const Query& query) {
546 | if (!query.dialog_id.empty()) {
547 | // request the knowledge graph for new terms
548 | }
549 | // retrieve documents from the index
550 | // sort and return documents
551 | }
552 | };
553 | \end{lstlisting}
554 |
555 | 请将前面的代码片段视为对实现的介绍。我们想表达QueryProcessor类的基本思想。它有process\underline{ }query()函数,该函数根据query参数中的标记从索引中检索文档。这里的关键角色是搜索索引。就进行快速查询而言,定义其构造的方式和存储文档的方式是必不可少的。同时,作为附加参数提供的对话框ID允许process\underline{ }query()函数请求知识库(或知识图谱)来检索与查询相关的更多相关标记。 \par
556 | 同样重要的是,考虑到QueryProcessor还负责生成对话框(即定义一组路径,为用户提供查询的可能场景)。生成的对话框被发送给用户,当用户进行另一个查询时,使用的对话框将通过我们已经看到的对话框ID与该查询关联。 \par
557 | 虽然前面的实现主要是介绍性的(因为代码太大,不适合本章),但它进一步设计和实现引擎的基础。 \par
558 |
559 | \noindent\textbf{}\ \par
560 | \textbf{总结} \ \par
561 | 对于经验丰富的程序员来说,从零开始构建搜索引擎也是一项艰巨的任务。我们在这本书中涉及了很多话题,并在本章中通过设计一个搜索引擎将它们中的大部分结合起来。 \par
562 | 我们已经了解到,Web搜索引擎是由几个组件组成的复杂系统,如爬虫、索引器和用户界面。爬虫程序负责定期检查网页,下载网页给搜索引擎索引。索引导致了被称为倒排索引的大数据结构的产生。倒排索引,或者仅仅是索引,是一种数据结构,它将单词映射到它们所在的文档中。 \par
563 | 接下来,我们定义了什么是推荐引擎,并尝试为我们的搜索引擎设计一个简单的推荐引擎。推荐引擎与本章讨论的基于对话框的搜索引擎特性相连接。基于对话框的搜索引擎旨在向用户提供有针对性的问题,以了解更多关于用户实际想要搜索的内容。 \par
564 | 本书的最后,我们从C++的角度讨论了计算机科学的各种主题。从C++程序的细节开始,然后简要地讨论了使用数据结构和算法有效地解决问题。掌握一门编程语言并不足以在编程方面取得成功。而且,需要解决在数据结构、算法、多线程等方面需要大量技能的编码问题。此外,处理不同的编程范式可以改变对计算机科学的看法,并允许以全新的视角来解决问题。本书中,我们已经接触了几种编程范式,如函数式编程。 \par
565 | 最后,软件开发不仅仅局限于编码。构建和设计项目是成功应用程序开发的关键步骤之一。第10章到第16章,主要是关于设计现实世界应用程序的方法和策略。让这本书成为你以C++开发人员的角度了解编程世界的入门指南。通过开发更复杂的应用程序来提高你的技能,并与同事和那些刚刚开始职业生涯的人分享知识。 \par
566 |
567 | \noindent\textbf{}\ \par
568 | \textbf{问题} \ \par
569 | \begin{enumerate}
570 | \item 爬虫程序在搜索引擎中的角色是什么?
571 | \item 为什么我们称搜索索引为反向索引?
572 | \item 在索引单词之前对单词进行标记的主要规则是什么?
573 | \item 推荐引擎的作用是什么?
574 | \item 什么是知识图谱?
575 | \end{enumerate}
576 |
577 | \noindent\textbf{}\ \par
578 | \textbf{扩展阅读} \ \par
579 | 更多信息,请参阅以下书籍: \par
580 | \begin{itemize}
581 | \item Introduction to Information Retrieval, Christopher Manning, et al., \\ https://www.amazon.com/Introduction-Information-Retrieval-Christopher-Manning/dp/0521865719/
582 | \end{itemize}
583 |
584 | \newpage
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
--------------------------------------------------------------------------------
/content/Section-3/summary.tex:
--------------------------------------------------------------------------------
1 | 本节概述人工智能和机器学习的最新进展。我们将使用C++进行机器学习,并设计一个基于对话框的搜索引擎。 \par
2 |
3 | 本节包括以下章节: \par
4 |
5 | \begin{itemize}
6 | \item 第15章,使用C++进行机器学习
7 | \item 第16章,实现一个交互式搜索引擎
8 | \end{itemize}
9 |
10 | \newpage
--------------------------------------------------------------------------------
/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/images/cover.png
--------------------------------------------------------------------------------
/images/tip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/images/tip.png
--------------------------------------------------------------------------------
/images/warn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Expert-Cpp/3eaef32a458d063f84bea0e963642483d40915db/images/warn.png
--------------------------------------------------------------------------------