Skip to content

Commit b85d8e2

Browse files
committed
Updated to Chpter 7, Section 4
1 parent a441ee0 commit b85d8e2

File tree

9 files changed

+204
-54
lines changed

9 files changed

+204
-54
lines changed

generalized_parts/01_welcome_to_cpp/01_start_with_a_cpp_program.tex

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ \section{开始一个C++程序}
2323
\end{figure}
2424
实际上的过程会更复杂,比如在编译之前还会进行预处理(Preprocess)。但是我们现在还不需要操心这么多,因为在按下``编译''键之后,预处理和编译都会进行,所以不妨把它统称为一个过程。\par
2525
\subsection*{编译器的选择}
26-
C++的\textbf{编译器(Compiler)}非常丰富。\href{https://en.wikipedia.org/wiki/List_of_compilers#C++_compilers}{List of C++ Compilers-Wikipedia} 条目为我们提供了一个不完全的C++编译器列表。\par
26+
C++的\textbf{编译器(Compiler)}非常丰富。\href{https://en.wikipedia.org/wiki/List_of_compilers\#C++_compilers}{List of C++ Compilers - Wikipedia} 条目为我们提供了一个不完全的C++编译器列表。\par
2727
一般我们选择使用Clang、GCC或Visual C++(MSVC),它们都是跨系统的编译器,对语言标准的支持比较好\footnote{Clang和GCC支持C++17以前标准和C++20的大部分标准,而MSVC支持C++20的全部标准和C++23的部分标准。},许多IDE(如Dev\_C++, Code::Blocks,Visual C++等)也都默认提供这些编译器之一。另外,Coliru和Wandbox等在线编译器也都支持使用Clang和GCC。\par
2828
同种的编译器也会有版本上的差异。很多古老的版本(如GCC 4.9.4)并不支持新近的语法(如C++17\footnote{C++17是C++委员会制定的一个语言标准,其最终版发布于2017年12月。这也是本书采用的主要语言标准。})。新版本的编译器往往有很多好用的特性,比如对代码有更好的优化。\footnote{另请注意,最好选择稳定版本的编译器,而非试验性的测试版,以免出现问题带来的麻烦。}\par
2929
\subsection*{语言标准的选择}
Original file line numberDiff line numberDiff line change
@@ -1 +1,147 @@
11
\section{编码风格}
2+
国际上有一项有趣的比赛,名为国际C语言混乱代码大赛(International Obfuscated C Code Contest, IOCCC)。此项比赛的宗旨在于鼓励人们写出最有创意和最让人难以理解的C语言代码。图7.3就是一个这样的例子(代码部分),它的代码是一个飞行器的形状,而运行结果也是一个飞行模拟器。\par
3+
\begin{figure}[htbp]
4+
\centering
5+
\includegraphics[width=.8\textwidth]{../images/generalized_parts/07_international_obfuscated_c_code_contest_instance.png}
6+
\caption{飞行模拟器(1998年IOCCC获奖作品)的代码}
7+
\footnotesize{资料来源:Wikipedia}
8+
\end{figure}
9+
这份代码很复杂,而且很难懂,非常合乎``有创意''``难以理解''的宗旨。而这也正是我们在日常写代码时要规避的,否则无论是写代码时检查逻辑,还是写完代码复盘内容,抑或是交给别人学习,这样难懂的代码都会给人增加诸多不必要的麻烦。\par
10+
它难以理解的方面主要有以下几点:
11+
\begin{itemize}
12+
\item 排版混乱。虽然这个排版很有创意,但这也使得我们在阅读时难以理清代码之间的逻辑。
13+
\item 命名混乱。这段代码用了各种简短的名字,比如 \lstinline@L@, \lstinline@o@ 甚至 \lstinline@_@ 这样的名字!命名不清很容易导致人在阅读代码时不知所云。
14+
\item 语句臃肿。在很多地方,硬生生地把一个复杂功能用单一的语句表达出来,这样就会让代码变得十分难懂。
15+
\item 完全没有注释。很多人十分反感在代码中写注释,觉得很麻烦——我自己就是如此。但是有效的注释可以大大增加代码的可读性。很多时候,我们用一句话就可以表达清楚一个函数的作用,比把代码写得清楚还要有用。
16+
\end{itemize}
17+
那么我们就根据这个``反面教材'',来讲一下如何写一段令人赏心悦目的代码。\par
18+
\subsubsection*{代码排版}
19+
在排版方面,适当的空格、缩进和换行能让代码更易读。空格的作用很明显,它可以将逻辑上不同的事物区分开来(很多时候,仅靠运算符来进行区分还不够清晰)。以下是一个对比:
20+
\begin{lstlisting}
21+
//无空格
22+
for(i=0;i<10;++i)
23+
std::cout<<arr[i].member;
24+
//有空格
25+
for (i = 0; i < 10; ++i)
26+
std::cout << arr[i].member;
27+
\end{lstlisting}
28+
注意这里的 \lstinline@std::cout@ 和 \lstinline@arr[i].member@ 之间没有空格,其实也可以有。我的理解是:这里不需要空格,因为它们是用来表示同一个事物的,所以不需要用空格来区分开。读者当然也可以有自己的理解,并逐渐形成自己的风格,只要它易读就好了。\par
29+
对于缩进和换行来说,不同代码风格的习惯更是迵异——不过大体原则还是相似的,无非是在花括号是否换行、标签是否不缩进等细节上有出入。
30+
一般我们默认,在逻辑上有附属关系的语句之间最好加一层缩进。例如说 \lstinline@if@ 语句的作用域内,就要加一层缩进。
31+
\begin{lstlisting}
32+
if (a > 0)
33+
std::cout << "Positive";
34+
else if (a < 0)
35+
std::cout << "Negative";
36+
else
37+
std::cout << "Zero";
38+
\end{lstlisting}
39+
其它诸如 \lstinline@for@, \lstinline@while@, \lstinline@switch@ 等结构,以及函数定义、类定义等也是如此。至于花括号在什么位置,它要不要独占一行,又怎么缩进,那就是一个人一个风格了\footnote{\href{https://en.wikipedia.org/wiki/Indentation_style\#Brace_placement_in_compound_statements}{Brace placement in compound statements - Wikipedia} 列表中为我们展示了几种常见的花括号风格。读者可以挑选一种合自己口味的来用。}。\par
40+
再比如,在初始化数组的时候,假如初始化内容非常冗长,我们也可以换行加适当缩进。
41+
\begin{lstlisting}
42+
struct Data {
43+
int num;
44+
Data *next;
45+
};
46+
Data heads[3] {
47+
{0, nullptr},
48+
{0, nullptr},
49+
{0, nullptr}
50+
};
51+
\end{lstlisting}
52+
我们这样定义\footnote{补充:全局变量定义在Data段或BSS段。如果我们没有提供初始化的话,它会存储于BSS段,同时获得默认值。这个默认值对于整型来说是 \lstinline@0@,对于指针来说是 \lstinline@nullptr@,因此我们可以只定义 \lstinline@heads[3]@ 但不提供初始化,效果相同。这里只为讲解而如此。},肯定比下面这种写法要美观吧——
53+
\begin{lstlisting}
54+
Data heads[3]{{0, nullptr}, {0, nullptr}, {0, nullptr}};
55+
\end{lstlisting}\par
56+
这些都是常见的缩进原则\footnote{C/C++都是自由格式语言(Free-form language),自由格式语言对空格的要求不太,对缩进和换行则是几乎没有要求;非自由格式语言,如Python,写起来更简洁,但它对格式要求更严格,比如说缩进直接与逻辑挂钩,所以就不能像IOCCC示例那样乱写。}。至于换行,适当的换行配合缩进,也能让程序更加整洁。
57+
\begin{lstlisting}
58+
//写到同一行
59+
if (ch > 'a' && ch < 'z' || ch > 'A' && ch < 'Z' || ch > '0' && ch < '9') {
60+
//...
61+
}
62+
//适当换行+缩进
63+
if (ch > 'a' && ch < 'z'
64+
|| ch > 'A' && ch < 'Z'
65+
|| ch > '0' && ch < '9'){
66+
//...
67+
}
68+
\end{lstlisting}
69+
孰优孰劣,就不言而喻了吧。\par
70+
\subsubsection*{命名}
71+
在写一些小的函数时,我们常常会用一些简短的变量/函数或类名称,比如说
72+
\begin{lstlisting}
73+
template <typename T>
74+
T max(T a, T b) {
75+
return a > b ? a : b;
76+
}
77+
\end{lstlisting}
78+
但这种习惯最好不要带到大规模函数中,尤其是那种需要数据处理或交互的场合,很容易让人搞不懂一个变量名意味着什么。\par
79+
函数名和类名尤其应当如此。虽然名字是可以随便起的,但最好还是要做到让人顾名思义。标准库中的 \lstinline@max@ 和 \lstinline@strlen@ 函数,\lstinline@string@ 和 \lstinline@list@ 类等,都有这样顾名思义的优点。\par
80+
至于具体的命名,可以用简称(比如有人会用 \lstinline@siz@ 代替 \lstinline@size@,当然这样也可以避免与 \lstinline@std::size@ 撞名,所以也很受常用 \lstinline@using namespace std@ 人士的喜好)或全名。至于我自己写代码时,一般都使用全名(反正我不用 \lstinline@using namespace std@)。读者可以选择自己喜欢的命名风格。
81+
\subsubsection*{语句的处理}
82+
有些程序员对于``压行''有一种执念,能写在一句(一行)代码中的内容,就尽量写在同一句,而不是把它拆成多句(多行)。为了压行,他们可以使用各种手段,比如把嵌套的选择结构用条件运算符拼湊起来,比如:
83+
\begin{lstlisting}
84+
return exp1 ? exp2 ? opt1 : opt2 : exp2 ? opt3 : opt4; //甚至可以继续缝
85+
\end{lstlisting}\par
86+
这种习惯对于稍大规模的项目来说是不利的,因为稍大规模的项目不可避免地会有很多代码,这时最重要的并不是``行数少'',而是代码逻辑一目了然,容易让人(包括自己)看懂。否则一旦代码写出了问题,自己就要费很大事来从这一句冗长的代码当中抽丝剥茧,这是何苦呢?\par
87+
所以,除非这段代码的逻辑简单到可以写成 \lstinline@max@ 那样,否则我们还是老老实把代码的逻辑写得清楚一点,这样能免去很多麻烦。
88+
\begin{lstlisting}
89+
if (exp1) {
90+
if (exp2) {
91+
opt1;
92+
}
93+
else {
94+
opt2;
95+
}
96+
}
97+
else {
98+
if (exp2) {
99+
opt3;
100+
}
101+
else [
102+
opt4;
103+
]
104+
}
105+
\end{lstlisting}\par
106+
还有一些时候,我们需要表示一个很长的表达式——比如说一元二次方程的求根公式。
107+
\begin{lstlisting}
108+
#include <utility>
109+
#include <cmath>
110+
using std::sqrt;
111+
//把sqrt引入全局命名空间中,这样我们就不用再写std::sqrt了
112+
using pair = std::pair<double, double>;
113+
//pair是一个类模版,它的对象可以存储两个值,适合本例
114+
//这里使用using之后,我们可以直接用pair来代替std::pair<double,double>
115+
pair quadratic_solve(double a, double b, double c) {
116+
return pair(
117+
(-b - sqrt(b * b - 4 * a * c)) / 2 / a,
118+
(-b + sqrt(b * b - 4 * a * c)) / 2 / a
119+
); //这里用到了std::pair的构造函数,我们下一章再讲。
120+
}
121+
\end{lstlisting}
122+
这里我们就不管命名的问题了,因为我们在数学上就是用$a,b,c$的,而且这个函数的功能也没有复杂到需要用多少名字。但仅就表达式的形式来看,它也是非常冗长难看的。\par
123+
对于这种,乃至更冗长的表达式,我们最好是用中间变量的方式来简化我们的表达。我们在数学中是用中间变量$\Delta$来表式根式内的部分,而在编程中我们也可以这样做。
124+
\begin{lstlisting}
125+
pair quadratic_solve(double a, double b, double c) {
126+
const double Delta {b * b - 4 * a * c}; //常量中间变量Delta
127+
return pair(
128+
(-b - sqrt(Delta)) / 2 / a,
129+
(-b + sqrt(Delta)) / 2 / a
130+
); //这样写就显得简洁了一些
131+
}
132+
\end{lstlisting}\par
133+
\subsubsection*{注释}
134+
注释的好处,已经不再需要我多作说明了。本书示例代码中的注释详尽之致,想必读者都能感受得到。对于编译器来说,注释部分的内容会被完全忽略,所以注释主要是为了给人看的。\par
135+
良好的注释对于我们理解代码来说很重要,尤其是过了几天到几个月之后,如果你回看源代码,会感谢你当时写过的注释的。\par
136+
C++中允许我们使用行注释和块注释,我们在前文中的绝大部分注释都是行注释,它的作用范围就是从 \lstinline@//@ 开始到行尾的部分。如果我们要写多行注释的话,那就只能在每行都用一个 \lstinline@//@ 了\footnote{也可以在上一行的注释末尾加反斜线 \lstinline@\\@,不过我不是很推荐这样做。}。\par
137+
块注释以 \lstinline@/*@ 开始,以 \lstinline@*/@ 结尾,当中的所有部分,无论是否有换行,都会被当成注释内容来对待。所以它更适合我们写大段注释(毕竟,写注释也要适当换行嘛)。\par
138+
\begin{lstlisting}
139+
//这是行注释
140+
/*这
141+
142+
143+
144+
释*/ int main() { //块注释结束后,后续内容就不是注释了
145+
//...
146+
}
147+
\end{lstlisting}\par
Loading

main.aux

+22-21
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,20 @@
162162
\@writefile{lof}{\contentsline {figure}{\numberline {7.2}{\ignorespaces Windows文件资源管理器中的重名文件}}{156}{figure.7.2}\protected@file@percent }
163163
\@writefile{toc}{\contentsline {section}{\numberline {7.3}作用域、存储期和链接}{159}{section.7.3}\protected@file@percent }
164164
\@writefile{toc}{\contentsline {section}{\numberline {7.4}编码风格}{165}{section.7.4}\protected@file@percent }
165+
\@writefile{lof}{\contentsline {figure}{\numberline {7.3}{\ignorespaces 飞行模拟器(1998年IOCCC获奖作品)的代码}}{166}{figure.7.3}\protected@file@percent }
165166
\gdef \LT@ii {\LT@entry
166167
{1}{42.00002pt}\LT@entry
167168
{1}{136.19997pt}\LT@entry
168169
{1}{117.00002pt}\LT@entry
169170
{1}{52.00002pt}\LT@entry
170171
{1}{82.00002pt}}
171-
\@writefile{toc}{\contentsline {part}{精讲篇}{169}{part*.120}\protected@file@percent }
172-
\@writefile{toc}{\contentsline {chapter}{\numberline {附录 A\hspace {.3em}}C++运算符基本属性}{169}{appendix.A}\protected@file@percent }
172+
\@writefile{toc}{\contentsline {part}{精讲篇}{173}{part*.124}\protected@file@percent }
173+
\@writefile{toc}{\contentsline {chapter}{\numberline {附录 A\hspace {.3em}}C++运算符基本属性}{173}{appendix.A}\protected@file@percent }
173174
\@writefile{lof}{\addvspace {10.0pt}}
174175
\@writefile{lot}{\addvspace {10.0pt}}
175-
\newlabel{ch:appendix_A}{{A}{169}{C++运算符基本属性}{appendix.A}{}}
176-
\@writefile{lot}{\contentsline {table}{\numberline {A.1}{截至C++17的所有运算符}}{169}{table.A.1}\protected@file@percent }
177-
\newlabel{tab:A-1}{{A.1}{169}{截至C++17的所有运算符}{table.A.1}{}}
176+
\newlabel{ch:appendix_A}{{A}{173}{C++运算符基本属性}{appendix.A}{}}
177+
\@writefile{lot}{\contentsline {table}{\numberline {A.1}{截至C++17的所有运算符}}{173}{table.A.1}\protected@file@percent }
178+
\newlabel{tab:A-1}{{A.1}{173}{截至C++17的所有运算符}{table.A.1}{}}
178179
\gdef \LT@iii {\LT@entry
179180
{1}{33.67001pt}\LT@entry
180181
{1}{33.81001pt}\LT@entry
@@ -186,11 +187,11 @@
186187
{1}{26.72002pt}\LT@entry
187188
{1}{27.283pt}\LT@entry
188189
{1}{86.44002pt}}
189-
\@writefile{toc}{\contentsline {chapter}{\numberline {附录 B\hspace {.3em}}ASCII码表(0到127)}{171}{appendix.B}\protected@file@percent }
190+
\@writefile{toc}{\contentsline {chapter}{\numberline {附录 B\hspace {.3em}}ASCII码表(0到127)}{175}{appendix.B}\protected@file@percent }
190191
\@writefile{lof}{\addvspace {10.0pt}}
191192
\@writefile{lot}{\addvspace {10.0pt}}
192-
\@writefile{lot}{\contentsline {table}{\numberline {B.1}{33个ASCII控制字符}}{171}{table.B.1}\protected@file@percent }
193-
\newlabel{tab:B-1}{{B.1}{171}{33个ASCII控制字符}{table.B.1}{}}
193+
\@writefile{lot}{\contentsline {table}{\numberline {B.1}{33个ASCII控制字符}}{175}{table.B.1}\protected@file@percent }
194+
\newlabel{tab:B-1}{{B.1}{175}{33个ASCII控制字符}{table.B.1}{}}
194195
\gdef \LT@iv {\LT@entry
195196
{1}{33.67001pt}\LT@entry
196197
{1}{33.81001pt}\LT@entry
@@ -204,18 +205,18 @@
204205
{1}{33.67001pt}\LT@entry
205206
{1}{33.81001pt}\LT@entry
206207
{1}{26.72002pt}}
207-
\@writefile{lot}{\contentsline {table}{\numberline {B.2}{95个ASCII可打印字符}}{172}{table.B.2}\protected@file@percent }
208-
\newlabel{tab:B-2}{{B.2}{172}{95个ASCII可打印字符}{table.B.2}{}}
209-
\@writefile{toc}{\contentsline {chapter}{\numberline {附录 C\hspace {.3em}}相关数学知识}{173}{appendix.C}\protected@file@percent }
208+
\@writefile{lot}{\contentsline {table}{\numberline {B.2}{95个ASCII可打印字符}}{176}{table.B.2}\protected@file@percent }
209+
\newlabel{tab:B-2}{{B.2}{176}{95个ASCII可打印字符}{table.B.2}{}}
210+
\@writefile{toc}{\contentsline {chapter}{\numberline {附录 C\hspace {.3em}}相关数学知识}{177}{appendix.C}\protected@file@percent }
210211
\@writefile{lof}{\addvspace {10.0pt}}
211212
\@writefile{lot}{\addvspace {10.0pt}}
212-
\@writefile{toc}{\contentsline {section}{\numberline {C.1}数制转换}{173}{section.C.1}\protected@file@percent }
213-
\@writefile{lof}{\contentsline {figure}{\numberline {C.1}{\ignorespaces 一个简单的数字时钟}}{173}{figure.C.1}\protected@file@percent }
214-
\@writefile{lof}{\contentsline {figure}{\numberline {C.2}{\ignorespaces 从计数到乘方}}{174}{figure.C.2}\protected@file@percent }
215-
\@writefile{lof}{\contentsline {figure}{\numberline {C.3}{\ignorespaces 一个12进制乘法表}}{175}{figure.C.3}\protected@file@percent }
216-
\@writefile{lof}{\contentsline {figure}{\numberline {C.4}{\ignorespaces $(1a.c3)_{16}$的形式化表示}}{177}{figure.C.4}\protected@file@percent }
217-
\@writefile{lof}{\contentsline {figure}{\numberline {C.5}{\ignorespaces 通过短除法把十进制数转换成$R$进制}}{180}{figure.C.5}\protected@file@percent }
218-
\@writefile{lof}{\contentsline {figure}{\numberline {C.6}{\ignorespaces 二进制与八进制的转换}}{181}{figure.C.6}\protected@file@percent }
219-
\@writefile{lof}{\contentsline {figure}{\numberline {C.7}{\ignorespaces 二进制与十六进制的转换}}{182}{figure.C.7}\protected@file@percent }
220-
\@writefile{toc}{\contentsline {chapter}{跋}{183}{appendix*.128}\protected@file@percent }
221-
\gdef \@abspage@last{190}
213+
\@writefile{toc}{\contentsline {section}{\numberline {C.1}数制转换}{177}{section.C.1}\protected@file@percent }
214+
\@writefile{lof}{\contentsline {figure}{\numberline {C.1}{\ignorespaces 一个简单的数字时钟}}{177}{figure.C.1}\protected@file@percent }
215+
\@writefile{lof}{\contentsline {figure}{\numberline {C.2}{\ignorespaces 从计数到乘方}}{178}{figure.C.2}\protected@file@percent }
216+
\@writefile{lof}{\contentsline {figure}{\numberline {C.3}{\ignorespaces 一个12进制乘法表}}{179}{figure.C.3}\protected@file@percent }
217+
\@writefile{lof}{\contentsline {figure}{\numberline {C.4}{\ignorespaces $(1a.c3)_{16}$的形式化表示}}{181}{figure.C.4}\protected@file@percent }
218+
\@writefile{lof}{\contentsline {figure}{\numberline {C.5}{\ignorespaces 通过短除法把十进制数转换成$R$进制}}{184}{figure.C.5}\protected@file@percent }
219+
\@writefile{lof}{\contentsline {figure}{\numberline {C.6}{\ignorespaces 二进制与八进制的转换}}{185}{figure.C.6}\protected@file@percent }
220+
\@writefile{lof}{\contentsline {figure}{\numberline {C.7}{\ignorespaces 二进制与十六进制的转换}}{186}{figure.C.7}\protected@file@percent }
221+
\@writefile{toc}{\contentsline {chapter}{跋}{187}{appendix*.132}\protected@file@percent }
222+
\gdef \@abspage@last{194}

0 commit comments

Comments
 (0)