├── 1 ├── 1-1.md ├── 1-4.md ├── 1-5.md ├── 2-1.md ├── 2-2.md ├── 2-3.md ├── 2-4.md ├── 2-5.md ├── 3-1.md ├── 3-2.md ├── 3-3.md ├── 3-4.md ├── 4-1.md ├── 4-2.md ├── 4-3.md ├── 4-4.md └── 5-1.md ├── LICENSE ├── README.md ├── SUMMARY.md ├── about.md ├── first.md └── notes.md /1/1-1.md: -------------------------------------------------------------------------------- 1 | # 刷新认知 - 编写第一道程序 2 | 3 | 计算机是个很神奇的玩意,因为它是由我们的“意识”创造出来的另一种截然不同的“意识”。数以兆计的微弱电流在一片片不到半个指甲盖大小的芯片上流动,居然就这么支撑起现在的信息时代,这在古人看来确实是匪夷所思的。 4 | 5 | 现在的计算机十分强大,看视频、上网、玩游戏都已不足为奇。不过,在计算机尚未发展成为家喻户晓的电子产品之前,人们面对的都是黑底白字的屏幕——也就是所谓的命令行界面(终端)。 6 | 7 | C++ 便是在这个年代出现的,亦因此它最先支持的与人们交互的方式也便是使用命令行界面。 8 | 9 | 我们便从这里开始,迈出第一步。 10 | 11 | ------ 12 | 13 | 我们即将看到的是本书的第一个 C++ 程序——不用担心,不必看懂,瞅一下就好了: 14 | 15 | ```cpp 16 | #include 17 | // 第一个程序——在屏幕上显示 "Hi, C++ World!" 18 | int main() 19 | { 20 | /* 21 | 这里是一切的开始,你接下来要让计算机做的事情都是从这里开始执行的。 22 | 被执行的内容以 int main() {...} 包裹,它们用于表达这是一个“主函数”。 23 | “函数”可以被理解为是一段可以被执行的过程。 24 | */ 25 | // 接下来我们要在屏幕上打印文字了,使用了来自标准库的 cout 与 endl 26 | std::cout << "Hi, C++ World!" << std::endl; 27 | // 在最后,我们要作收尾工作——向运行这个程序的操作系统报告没有异常,并结束程序的运行。 28 | return 0; 29 | } 30 | ``` 31 | 32 | 其中,以```//```开头或以```/*```与```*/```包裹的内容称作**注释**。它并不是写给计算机看的,而是写给我们人类看的——例如这段程序的用途与原理,或者,其实写什么都可以: 33 | 34 | ```cpp 35 | // 这是一段注释,可以写在除了以 # 开头的任意一行行尾。 36 | // 从 "//" 开始,到这一行的结束,这部分都将会被当作注释。 37 | // 请注意,注释不是程序本身的一部分,在 C++ 编译这个程序的时候会直接跳过注释。 38 | /* 39 | 这是一块注释,可以换行写。 40 | 从 "/*" 开始,到最近的一个 "* /" 结束为止(中间无空格),均为注释。 41 | */ 42 | ``` 43 | 44 | 刚开头的一句```#include```用于向这个程序导入支持库,在此程序中导入的是名为```iostream```的一个库。这个库用于支持```std::cout```与```std::endl```这两个**对象**。如果不先包含这个库,```std::cout```与```std::endl```便也无法使用。 45 | 46 | 像这样以 # 开头的语句称作**预编译语句**。它并不是程序本身的一部分,但它却对程序能否正确**编译**至关重要。 47 | 48 | ```int main () {```与```}```之间的内容是主程序,一切都是从这里开始的。其中的每一条**语句**都会按从头到尾的顺序执行。```main```用于声明一个**主函数**,而```int main()```后紧跟的一对花括号便是主函数中的内容。 49 | 50 | 在这个程序的主函数中,便有两条语句: 51 | 52 | ```cpp 53 | std::cout << "Hi, C++ World!" << std::endl; 54 | return 0; 55 | ``` 56 | 57 | 每一条语句的末尾应当写一个分号(```;```),以表示这个语句的结束,就像汉语中的句号(。)与英语中的句点(.)一样。 58 | 59 | ```cout```与```endl```分别是 Console Out (控制台输出) 与 End Line (结束一行) 的缩写,分别用于输出内容到屏幕与换行。从```cout```开始,要想输出什么内容,需要先写```<<```,然后再写想输出的内容。```"Hi, C++ World"```是一个**字符串**,用一对双引号包裹起来,其中写上我们想要输出的内容。换行用的```endl```是一个**操纵符**,用于控制```cout```。使其**刷新并换下一行输出**。 60 | 61 | 在我们的程序中,```cout```与```endl```都被加上了```std::```这个前缀。STD 是**标准库**(**St**andar**d** Library)的缩写,它是 C++ 语言体系中的重要组成部分。```iostream```这个库便是由标准库提供的,它提供的诸如```cout```与```endl```这样的内容,都被打包进了一个名为```std```的**命名空间**中。我们需要先进入这个名为```std```的命名空间(也就是加```std::```的前缀),然后才能使用其中的```cout```与```endl```。 62 | 63 | > 在后续的学习中,我们将会遇到更多来自标准库的内容。使用由标准库提供的工具时,要记得在其前加上```std::```。为了保证文章的代码部分篇幅始终,后续正文的代码中将不再为标准库对象前加```std::```。章节提供的随例源代码文件则不受影响。 64 | 65 | ```return```在主函数中用于直接**结束程序的运行**,其后所跟的 0 则是程序结束时反馈给操作系统的**状态码**。在大多数操作系统中(如 Win NT 系列与 *nix 系列),0 意味着程序是顺利执行完成并正常结束运行的。 66 | 67 | ------ 68 | 69 | 如果看不懂,没关系,尚未有过基础时入门一个新的学科并非易事。当遇到都不懂的地方时,最好的办法是再多读几遍,并尝试结合上下文阅读。书读百遍,其意自现。 70 | 71 | 在下一节中,我们将完整地配置一回手头的计算机,执行刚刚的第一道程序。 -------------------------------------------------------------------------------- /1/1-4.md: -------------------------------------------------------------------------------- 1 | # 篮子与瓶子 · 初识类型 2 | 3 | 我们在日常生活中,经常会遇到为物品归类的问题。 4 | 5 | 物品归类的方法有很多,最常用的方法之一是以容器为依据分类。例如,我们可以用篮子装各种的固体,或是用瓶子装各种的液体。 6 | 7 | 在 C++ 中,我们同样有不同的容器可以使用,也同样有不同形态的数据提供给容器承载。 8 | 9 | 下面,我们就来认识一些最基本的数据形态——**字面量**。 10 | 11 | ------ 12 | 13 | > 0 14 | 15 | 这是什么?对,是数字 0! 16 | 17 | 零是一切的开始,不仅仅是数字,世间万物何尝不是从零开始? 18 | 19 | 让我们试一试将它在屏幕上呈现出来——沿用之前那些尚不明确合成分具体用途的程序,仅仅修改我们已经了解的用于输出信息的 cout 一行: 20 | 21 | ```cpp 22 | #include 23 | int main() 24 | { 25 | std::cout << 0 << std::endl; 26 | return 0; 27 | } 28 | ``` 29 | 30 | 很好,我们做到让屏幕输出数字了! 31 | 32 | ------ 33 | 34 | 在 C++ 中,我们不仅能表达十进制的数字,也能表达八进制与十六进制的数字,他们经常在实战中被使用。 35 | 36 | 要想表达一个八进制数,仅需让数字前多一个零即可。例如: 37 | 38 | ``` 39 | 0777 40 | 0167730 41 | ``` 42 | 43 | 要想表达一个十六进制数,需要在数字前加 0x 或 0X 的前缀,且数字中的字母部分不区分大小写。例如: 44 | 45 | ``` 46 | 0x12CD 47 | 0xeef09ac3 48 | ``` 49 | 50 | ------ 51 | 52 | 我们接下来了解一下浮点数。从数学角度来看,其实它就是小数: 53 | 54 | ``` 55 | 3.14159 56 | 0.618 57 | ``` 58 | 59 | 而在计算机科学中,小数在计算机内部可以被表达为浮点数与定点数。它们都可用于表达小数,但其具体实现方式有点区别:定点数的小数点位置是固定的,无论是整数部分还是小数部分,它们都只能表达固定的范围;浮点数的小数点则可以任意“浮动”,在小数点所能及的范围下,你可以用浮点数的模式表达很大与很小的数字。 60 | 61 | 要想了解浮点数的工作原理,我们需要先复习数学上的科学记数法。 62 | 63 | > 2.1936 x 10^4 = 21936 64 | > 1.7203 x 10^-6 = 0.0000017203 65 | > 3.8500 x 10^10 = 38500000000 66 | 67 | 科学记数法给予了我们一种手段,将一个很大或很小的数字变为一组相对较短的乘法算式,不仅少写了大量的“0”,而且也便于我们一眼就能看出这个数字的规模。 68 | 69 | 浮点数其实就是这么做的——它将原本的小数拆为三部分:符号位、底数与指数。符号位用于表达这个小数是正数还是负数;底数相当于上面例子中左侧的数字,表达范围必须在 1 到 10 之间(包含 1);指数则相当于上面例子中 10 的次幂。 70 | 71 | 由此,C++ 还提供了另一些表达方式——以科学记数法表达小数,也可以是省略了整数或小数部分的小数: 72 | 73 | ``` 74 | 3.14159e0 // 3.14159 75 | 3E1 // 30 76 | 618E-3 // 0.618 77 | 123. // 123 78 | .001 // 0.001 79 | ``` 80 | 81 | 其中,E 与 e 是用于表达科学记数法的标识符,其左侧是底数,右侧是以底数所用进制为底的指数(例如,左侧为 10 十进制,右侧就以 10 为底)。 82 | 83 | ------ 84 | 85 | 刚刚的浮点数中,出现了符号位的身影,并且我们知道它用于表达这个数是正数还是负数。 86 | 是的,不论是整数还是浮点数,它们都可以表达负数——只要在数字的开头加一个减号就可以了: 87 | 88 | ``` 89 | -233 90 | -3.1415926 91 | ``` 92 | 93 | 看起来与数学中的相差无几,不过还是有所差别——在数学中,减号(负号)与数字一整体直接被看作负数;而在 C++ 语言中,这却相当于先声明一个正整数,然后对其取负。 94 | 95 | ------ 96 | 97 | 如果你尝试直接写一个很大很大的整数,编译器会报错: 98 | 99 | ```cpp 100 | cout << 12345678987654321 << endl; 101 | ``` 102 | 103 | 怎么办呢?很简单,在数字末尾加个```L```(或```l```)即可: 104 | 105 | ```cpp 106 | cout << 12345678987654321L << endl; 107 | ``` 108 | 109 | 很显然,C++ 的整数也是有表达范围的。而数字末尾加的```L```,则意味着我们扩大了数字的表达范围。 110 | 111 | 我们将在后续的章节中,介绍由具体的不同类型引申而来的不同大小限定的数字。 112 | 113 | ------ 114 | 115 | 还记得在我们刚刚开始学习时所写的第一道程序么? 116 | 117 | ```cpp 118 | cout << "Hi, C++ World!" << endl; 119 | ``` 120 | 121 | 其中,```"Hi, C++ World!"```中被双引号括起来的内容是可以任意变动的。例如: 122 | 123 | ```cpp 124 | "Hello, Steve! Where's Chino?" 125 | "You have said JoJo, right?" 126 | ``` 127 | 128 | 像这样以双引号包裹的内容称作**字符串**,字符串由一系列字符组成。 129 | 130 | 当然,字符串的内容也可以含有其它自然语言的文字,不一定非得是英语: 131 | 132 | ```cpp 133 | "衬衫的价格是 9 镑 15 便士,所以你选择——C。" 134 | ``` 135 | 136 | 编译后,可能会由于编译时与运行时的字符集不匹配,输出含有除数字与字母等在 ASCII 字符集之外的文字时很可能会出现乱码。这并没有什么很好的解决方案,因为不同用户所使用的设备可用的字符集很可能是不同的,这也就造成了输出文字的不通用。 137 | 138 | 不过,我们仍然可以尽可能规避这些问题。 139 | 140 | 要想在你的机器上正确显示中文,一种妥当的办法是使用**宽字符**字符串——在字符串开头加一个```L```即可: 141 | 142 | ```cpp 143 | L"衬衫的价格是 9 镑 15 便士,所以你选择——C。" 144 | ``` 145 | 146 | 如果希望字符串的内容也可被其它机器使用,另一种可选的办法是让它以 Unicode 字符集保存——开头加```u8```(8 位)、```u```(16 位)或```U```(32 位)均可: 147 | 148 | ```cpp 149 | u"衬衫的价格是 9 镑 15 便士,所以你选择——C。" 150 | ``` 151 | 152 | Unicode 又称作万国码,它涵盖了全球绝大部分民族的文字,并且也包含了大量的特殊符号。以它来作为字符串的字符集,可以使所有支持 Unicode 的机器可以做到正确显示此类文字。放心,现代的操作系统没有不支持这个字符集的。 153 | 154 | ------ 155 | 156 | 前面有提到过,字符串实际上就是由一批字符连起来的整体。 157 | 158 | 那么,字符又是怎么表示的呢? 159 | 160 | 很简单,以单引号包裹一个字符即可。注意只能写数字与英文字母等在 ASCII 字符集内的字符: 161 | 162 | ```cpp 163 | 'a' 164 | '1' 165 | ``` 166 | 167 | 同样地,它可以像字符串那样;使用```L```、```u```、```U```前缀改变使用的字符集,不过一般情况下我们用不到。 168 | 169 | 并且字符也可以直接交给```cout```输出: 170 | 171 | ```cpp 172 | cout << 'N' << endl; 173 | ``` 174 | 175 | ------ 176 | 177 | ### 习题 178 | 179 | - 尝试自己写点什么要输出的内容,以```cout```将内容写在屏幕上。 180 | - 尝试以```cout```在屏幕上输出一个浮点数,并调整数字的大小,找一找有什么规律。 -------------------------------------------------------------------------------- /1/1-5.md: -------------------------------------------------------------------------------- 1 | # 思维的体操 · 初识控制流 2 | 3 | 我们身处一个充满理性与逻辑的世界。即便是市井小民,也难免要以这样的思维做事情: 4 | 5 | - 早上起床后,先寻找自己的衣服穿上: 6 | - 穿内衣了吗?嗯,昨晚睡觉时本来就没脱,不用再穿了。 7 | - 穿上衣了吗?没有,那么就先穿上衣。 8 | - 穿裤子了吗?也没有,赶紧穿上,记得系好裤腰带。 9 | - 穿鞋子了吗?只穿了个拖鞋。但在穿拖鞋之前,好歹得穿个袜子吧?再检查一下: 10 | - 穿袜子了吗?没有,先套上袜子。 11 | - 然后再穿上鞋子。 12 | - 戴手表了吗?等等,你没有买过表(假如)!那还是别戴了,总不能拿记号笔再手腕上画一个吧?哈哈~ 13 | 14 | 其实,这就是程序,一个简单的早起穿衣程序,执行者也就是我们自己。 15 | 16 | C++ 语言的用途,便是位我们手头的设备制定像这样的程序——能作出判断,并在不同的情况下做不同的事情。 17 | 18 | 接下来,我们便开始了解 C++ 是如何指挥计算机的。 19 | 20 | ------ 21 | 22 | > 0 23 | 24 | 这是什么?对,老相识了,还是数字零! 25 | 26 | 零是一个分界线,实数范围内,它既比任何正数小,又比任何负数大: 27 | 28 | > 0 < 15 29 | > 30 | > -3 < 0 31 | 32 | 计算机能判断这种事情吗?能!就像这样: 33 | 34 | ```cpp 35 | if (0 < 15) 36 | { 37 | cout << "0 小于 15 是正确的" << endl; 38 | } 39 | if (-3 < 0) 40 | { 41 | cout << "-3 小于 0 是正确的" << endl; 42 | } 43 | ``` 44 | 45 | **if** 是 C++ 的一个**关键字**,含义是用于判断其后所跟小括号包裹的内容是否成立。如果成立,小括号后跟的一对花括号的内容会被执行,反之则不会被执行。 46 | 47 | C++ 力求整洁,所以对于花括号内只有“一句话”的 if 语句,花括号是可以省略的: 48 | 49 | ```cpp 50 | if (0 < 15) cout << "0 小于 15 是正确的" << endl; 51 | if(-3 < 0) cout << "-3 小于 0 是正确的" << endl; 52 | ``` 53 | 54 | 别忘了末尾的分号! 55 | 56 | ------ 57 | 58 | if 语句中,由小括号包裹的内容称作**条件**;而如果小括号后紧跟的是一对花括号,我们称这一对花括号及其包裹的内容整体叫作**语句块**。 59 | 60 | 也许你会想,小括号里写的是条件,那么是不是什么条件都可以望着里头写呢?就像这样: 61 | 62 | ```cpp 63 | if (今天下雨) cout << "记得带伞!" << endl; 64 | ``` 65 | 66 | 很遗憾,行不通。事实上,如果 C++ 智能得可以看得懂我们用自然语言写的句子,我们哪还要写什么程序呢? 67 | 68 | 计算机,正如其名,它是用来计算的机器,所以我们应当让它做的是计算!这种计算可以是数学层面的,例如四则运算或比较数字大小之类的: 69 | 70 | ```cpp 71 | if (3 + 2 == 5) cout << "3 加 2 为 5" << endl; 72 | if (3 + 2 != 6) cout << "3 加 2 不为 6" << endl; 73 | if (5 - 6 <= 12) cout << "5 减 6 的差不大于 12" << endl; 74 | if (233 > 0) cout << "233 是一个正数" << endl; 75 | ``` 76 | 77 | 也许你会对其中的```==```、```!=```与```<=```感到奇怪。其实它们分别相当于数学中的等于(=)、不等于(≠)与小于等于(≤)。我们将在后续章节中深入学习像这样的**运算符**。 78 | 79 | ------ 80 | 81 | 有时候,我们不仅需要处理条件成立时的情况,也需要处理条件**不**成立的情况: 82 | 83 | ```cpp 84 | if (12 > 0) 85 | { 86 | cout << "12 是正数" << endl; 87 | } 88 | else 89 | { 90 | cout << "12 不是正数" << endl; 91 | } 92 | ``` 93 | 94 | **else** 是另一个 C++ 中的关键字,如果与它相匹配的 if 中的条件**不成立**,else 后跟的语句块会被执行。它与 if 正好相反,如果条件成立,语句块中的内容是不会被执行的。 95 | 96 | 当然了,它也能像 if 那样。如果语句块内同样只有“一句话”,else 后亦可省去花括号: 97 | 98 | ```cpp 99 | if (12 > 0) cout << "12 是正数" << endl; 100 | else cout << "12 不是正数" << endl; 101 | ``` 102 | 103 | else 后还可紧跟一个新的 if,以便于进行连续的判断: 104 | 105 | ```cpp 106 | if (12 > 0) cout << "12 是正数" << endl; 107 | else if (12 == 0) cout << "12 是 0" << endl; 108 | else cout << "12 是负数" << endl; 109 | ``` 110 | 111 | ------ 112 | 113 | if 和 else 究竟是凭什么判断条件是非的呢? 114 | 115 | 我们在 if 后那对小括号所写的各种条件,不论怎样,它们最终都会被转换为 **true** (真) 或 **false** (假) 这两种**值**。if 与 else 正是依靠于此实现判断的。 116 | 117 | true 与 false 是什么呢?它们在 C++ 中被称作**布尔值**(bool),专用于表达逻辑上的**真**与**假**。它们不仅是像 if 与 else 那样的**关键字**,并且也和数字、字符串一样是**值**。 118 | 119 | 它们可以直接成为 if 的条件: 120 | 121 | ```cpp 122 | if (true) cout << "true" << endl; 123 | if (false) cout << "false, 这句话永远也不会被执行……" << endl; 124 | ``` 125 | 126 | 它们也同样是比较数字后的结果: 127 | 128 | ```cpp 129 | 15 > 0; // true 130 | 15 < 0; // false 131 | ``` 132 | 133 | 并且,它们还具有数值的性质,能直接参与比较运算中的相等与不相等运算: 134 | 135 | ```cpp 136 | 12 > 11 == true; // true 137 | 12 > 25 == false; // true 138 | 12 > 25 != true; // true 139 | 12 > 25 == true; // false 140 | ``` 141 | 142 | 布尔值是很有用的类型!我们将在后续的学习中经常用到它们。 143 | 144 | ### 习题 145 | 146 | - 构思一份生活中或游戏中做某一件具有流程的事情的全过程,要求**至少**有一处需要**条件**才能做(或不做)的步骤。 -------------------------------------------------------------------------------- /1/2-1.md: -------------------------------------------------------------------------------- 1 | # 常态 · 基本内置类型与变量 2 | 3 | 还记得之前的篮子与瓶子吗?它们作为不同类型物质的容器,被针对性地以不同的材料与形状设计出来,以适应人们的日常需求。 4 | 5 | 想一想,瓶子可以用塑料、陶瓷或是蜡纸这类隔水的材料制作,而篮子得用麻绳、布料或是橡胶这类易于变形的材料制作。它们都是装载物质的容器,但它们所承载的物质类型不同,也提醒了人们如何选择正确的容器。 6 | 7 | 在 C++ 中亦是如此——我们所持有的数据决定了承载该数据的容器类型;同样地,容器的类型亦反过来决定我们应向其装载的数据类型。 8 | 9 | 下面,我们就来探寻 C++ 中最基本的一批容器吧。 10 | 11 | ------ 12 | 13 | > 0 14 | 15 | 这是什么?对,还是它,数字零! 16 | 17 | 零是什么?首先它肯定是个数。再细分以下,它还是个整数,是一个既不可算作正数、亦不可算作负数的整数。 18 | 19 | 我们可以使用类型为```int```的容器存储数字零,```int```是英语单词 integer (整数) 的缩写。在这里,我们先随便取个容器名```a```: 20 | 21 | ```cpp 22 | int a = 0; 23 | ``` 24 | 25 | 类型必须写在开头,其后是容器的名字,而等于号之后便是我们想要装载的内容物。 26 | 27 | 在 C++ 中,我们称这样的容器叫作**变量**。变量本质上就是**具名的数据**,它有具体的可承载数据的类型,并且也有一片用于放置此类型数据的空间。就拿刚刚的例子来讲,```int```类型的每个变量都最少占据 4 字节的空间。 28 | 29 | 如同刚刚的程序所作的操作,是**声明**一个变量。在声明的同时,我们还可以通过在气候紧跟一个等于号与欲赋予的值,使得这个变量被**初始化**。在这个例子中,我们便将数字```0```作为变量```a```的初始值。在变量受到初始化的同时,它也就相当于被**定义**为了某一个**值**。 30 | 31 | ------ 32 | 33 | 难道 C++ 只提供了一个```int```类型么?当然不是。光是表达整数这个事情,C++ 就给了我们一大批的整数类型供使用,不过就是其所能表达的数字范围有所区别罢了: 34 | 35 | | 类型 | 最小表达范围 | 通常表达范围 | 36 | | -------------------------------------- | ------------------- | ------------------- | 37 | | ```char``` | $[2^{-7}, 2^7)$ | $[2^{-7}, 2^7)$ | 38 | | ```short``` 或 ```short int``` | $[2^{-15}, 2^{15})$ | $[2^{-15}, 2^{15})$ | 39 | | ```int``` | $[2^{-15}, 2^{15})$ | $[2^{-31}, 2^{31})$ | 40 | | ```long``` 或 ```long int``` | $[2^{-31}, 2^{31})$ | $[2^{-31}, 2^{31})$ | 41 | | ```long long``` 或 ```long long int``` | $[2^{-31}, 2^{31})$ | $[2^{-63}, 2^{63})$ | 42 | 43 | 一般来讲,我们使用```int```或```long```类型便足以满足日常需求了。更广的数字表达范围也许能方便我们表达更大的数字,但由此也将会带来更大的空间占用;同样的,更窄的数字表达范围虽然能够很好地减少空间占用,但它也仅适用于对空间占用要求严格、需表达的整数又较小的场合。 44 | 45 | ------ 46 | 47 | 回忆先前有关二进制数在计算机中如何表达的知识,我们知道整数的最开头以为用于表达数字的正负,气候才是数字本身的内容。 48 | 49 | 有没有办法让最开头那一位也作为数字呢?有!在类型前加```unsinged```即可: 50 | 51 | ```cpp 52 | unsigned int a = 0; 53 | ``` 54 | 55 | 如果我们为某个整数类型加上```unsigned```前缀,它将**不能表达负数**,并且**正数的表达范围也会变为原来的 2 倍**。它通常适用于保证用不到负数并且也不使用负数的场合,例如计算物体间的距离——再怎么调整,我们都找不到距离为负数的两个物体。 56 | 57 | 这样不带正负号的类型称作**无符号类型**,由其承载的正数也可称作**无符号数**。无符号是整数类型的特权,除此之外的其它任何类型都不能也无法添加```unsigned```前缀。与之相反,带符号的类型被称作**有符号类型**,可通过在类型前加``signed``来声明。 58 | 59 | 默认情况下,除了```char```类型以外的整数类型均为有符号类型,无需额外在类型前加```signed```前缀。```char```类型默认情况下是否有符号,是根据具体所在的运行环境而定的。 60 | 61 | 无符号类型的数字表达范围其实与其对应的有符号类型的一样宽,形象地讲其实也就是把表示负数的那部分范围“挪”到了右边: 62 | 63 | | 类型 | 最小表达范围 | 通常表达范围 | 64 | | -------------------------------------------------------- | ------------- | ------------- | 65 | | ```unsigned char``` | $[0, 2^8)$ | $[0, 2^8)$ | 66 | | ```unsigned short``` 或 ```unsigned short int``` | $[0, 2^{16})$ | $[0, 2^{16})$​ | 67 | | ```unsigned int``` | $[0, 2^{16})$ | $[0, 2^{32})$ | 68 | | ```unsigned long``` 或 ```unsigned long int``` | $[0, 2^{32})$ | $[0, 2^{32})$ | 69 | | ```unsigned long long``` 或 ```unsigned long long int``` | $[0, 2^{32})$ | $[0, 2^{64})$ | 70 | 71 | ------ 72 | 73 | 除了整数之外,我们有时也需要表达更为复杂的小数,而这种数字无法完整储存在整数类型的变量中。 74 | 75 | C++ 提供了另一批类型,即**浮点数类型**,可使我们得以储存一定**精度**的小数: 76 | 77 | | 类型 | 表达范围 | 78 | | ------------ | ------------------------------------------ | 79 | | ```float``` | 最低 6 位有效数字,小数位数 $[-38, 38]$ | 80 | | ```double``` | 最低 15 位有效数字,小数位数 $[-308. 308]$ | 81 | 82 | 我们一般会选择```double```作为首选的浮点数类型,不仅仅是因为其足够高的精度,也因为现代大多数的设备都对```double```类型有硬件上的效率优化——甚至要比较小```float```类型还快。 83 | 84 | **精度**一词,实际上指的是该类型可表达的最大有效位数,而非数字小到多少位,或是数字大到多少位。例如,以下数字虽然有着悬殊的大小差距,但其精度其实是相等的: 85 | 86 | > $1.23168809 × 10^{91}$ 87 | > 88 | > $3.14159263 × 10^0$ 89 | > 90 | > $2.19857822 × 10^{-82}$ 91 | 92 | ------ 93 | 94 | 还记得最初我们向屏幕打印的话么? 95 | 96 | ```cpp 97 | "Hi, C++ World!" 98 | ``` 99 | 100 | 这看起来更像是一系列字符,而不是什么数字。 101 | 102 | 那么,字符使用什么类型的变量储存呢?答案是```char```类型: 103 | 104 | ```cpp 105 | char a = 'a'; 106 | char b = 'b'; 107 | char abc[] = "abc"; 108 | ``` 109 | 110 | 变量```a```与```b```分别仅存储了一个字符,而变量```abc```却因为后面跟了个```[]```,结果它却可以接受以双引号包裹的 **C 风格字符串**。 111 | 112 | 不必惊讶,我们将在后续的章节学习有关**数组**的内容,而刚刚的变量```abc```便是将要介绍的数组的一种特殊形式——**字符数组**,也就是刚刚所提到的 C 风格字符串。 113 | 114 | 除了```char```类型,C++ 也提供了一批其它的字符类型,便于我们使用其它的字符集表达文字: 115 | 116 | | 类型 | 占用空间大小 | 说明 | 117 | | ---------------------- | ------------ | ------------------------- | 118 | | ```char``` | 1 字节 | 普通字符类型 | 119 | | ```wchar_t``` | 2 字节 | 宽字符类型 | 120 | | ```char8_t```(C++20) | 1 字节 | Unicode 字符类型,8 位版 | 121 | | ```char16_t``` | 2 字节 | Unicode 字符类型,16 位版 | 122 | | ```char32_t``` | 4 字节 | Unicode 字符类型,32 位版 | 123 | 124 | 这些类型与先前介绍的一批字符字面量是相互匹配的,亦因此这些字符字面量便也是有对应类型的变量容纳的。 125 | 126 | ------ 127 | 128 | 总结以下,我们认识了以下的变量类型: 129 | 130 | - 用于表达整数的```char```、```short```、```int```、```long```与```long long``` 131 | - 用于修饰整数类型、使之成为无符号数的```unsigned``` 132 | - 用于表达浮点数的```float```、```double``` 133 | - 用于表达字符的```char```、```wchar_t```、```char8_t```、```char16_t```与```char32_t``` 134 | 135 | 类型是 C++ 的重要组成部分,它构成了 C++ 的大部分内容。接下来,我们将学习更深层次的类型系统。 136 | 137 | ------ 138 | 139 | ### 习题 140 | 141 | - 录入下面的程序,尝试修改程序中的变量名与初始值并允许,体验一下定义变量的感觉。 142 | 143 | ```cpp 144 | #include 145 | int main() 146 | { 147 | int apple = 1; 148 | int pen = 1; 149 | char pineapple = 'a'; 150 | std::cout << "I have " << apple << " apple" << std::endl 151 | << "I have " << pen << " pen" << std::endl 152 | << "Uh! Apple-pen!" << std::endl; 153 | << " I have " << pen << " pen" << std::endl 154 | << " I have " << pineapple << " pineapple" << std::endl 155 | << "Uh! Pineapple-pen!" << std::endl; 156 | return 0; 157 | } 158 | ``` 159 | 160 | -------------------------------------------------------------------------------- /1/2-2.md: -------------------------------------------------------------------------------- 1 | # 融合态 · 复合类型 2 | 3 | 以瓶子与篮子作为物品分类后使用的容器,是很好的想法。然而,实际情况并非如此轻松: 4 | 5 | - 买了一堆蔬果,短时间内放在篮子中还不成问题,但时间长久便会腐烂。这时候,也许我们该考虑把它们放在冰箱里。 6 | - 氢氟酸能够与玻璃反应、腐蚀玻璃容器。液体理应放在瓶子中,不过如果碰上这样厉害的液体,也许我们得额外补充一下使用什么样的瓶子——例如陶瓷瓶或钢瓶。 7 | - 液氮很容易升华,瞬间产生的高压气体很容易把任何日常使用的瓶子炸的粉碎。看来普通的瓶子是不能用了,也许我们得使用带内胆的专用储气钢瓶才能容得下它。 8 | 9 | 我们时常会遇到各种复杂的需求,而 C++ 自身提供的那点点类型当然是不够用的。这就需要我们视情况构造符合需求的类型。构造新的类型,形象地讲也就是将类型与类型、类型的一些特性拼凑起来,成为更为复杂的类型。 10 | 11 | 接下来,我们将首先了解最基本的复合类型——指针、引用与数组。 12 | 13 | ------ 14 | 15 | 我们知道,计算机最本质的任务是处理数据,而处理数据的最基本的场所便是内存。 16 | 17 | 我们在程序中声明的变量都将储存在内存中,而每个变量都会在内存中有一条属于自己的**地址**: 18 | 19 | > 0 x 80002017 1600 (```int```)```a``` 20 | > 21 | > 0 x 80002021 2100000009 (```int```)```b``` 22 | > 23 | > 0 x 80002025 6.18e-1 (```double```)```c``` 24 | > 25 | > 0 x 80002029 (与上一单元连用,```double```类型一次占用 8 字节空间) 26 | > 27 | > 0 x 80002033 0 (```int```)```d``` 28 | > 29 | > ...... 30 | 31 | 形象地讲,每个变量都在内存中“租”了点空间供容纳数据,而这些空间的“门牌号”便是变量所在的地址。也许有些变量的类型很“肥”,“租”下来的空间比别人的要大一些(如```double```、```long long```类型);也许有些变量的类型又很“瘦”,“租”下来的空间又比别人的小一些(如```char```、```short```类型)。不管怎么个“租”法,没有哪个变量是有权利“占据”其它尚还“健在”变量的空间的,只得一个挨一个地找闲余的空间使用。 32 | 33 | 为什么要提到变量在内存中的“住宿情况”呢?因为我们接下来要介绍的类型,便是用来帮助我们顺着相当于“门牌号”地内存地址**访问**对应变量所占据的空间的: 34 | 35 | ```cpp 36 | int a = 3; 37 | int *p = &a; // 声明一个指针 p,指向变量 a 38 | std::cout << p << std::endl // 第一行输出 p 实际的值,即 a 的地址 39 | << *p << std::endl; // 第二行输出访问 p 所存地址对应的变量 a 的值 40 | ``` 41 | 42 | 仔细观察一下,新的变量```p```有何特殊之处? 43 | 44 | 是的,一是```p```的前头多了个星号,二是右侧用于初始化变量```p```的变量```a```前头多了个与(and)的缩写(即```&```)。 45 | 46 | 我们把像变量```p```这样前头有个星号的变量称作**指针**。这个名字取得其实很形象,指针者,便是指向某个位置的针。它便是我们用于顺着“门牌号”访问变量的工具。 47 | 48 | 指针自身其实也是变量。再说得直白点,它就是稍微长得特殊点的变量,同样也需要和其它普通变量那样在内存中“租”点空间使用。这“租”下来的空间不干别的,只用于放和它所指示同类型变量的地址。 49 | 50 | 光有指针、没有地址怎么能行?所以,刚刚的程序便以变量```a```为例子,将变量```a```的地址存进了变量```p```中。在变量```a```前加一个与缩写(```&```),其实表达的意思就是“取变量```a```的地址”。与缩写(```&```)是一个**运算符**,它与加减乘除这些符号其实算是一回事`(仅仅是计算机方面的,数学中可没有它!)`,用于对其附近的对象做些什么运算。就拿刚刚的“```&```”来讲,它的用途便是提取它右侧对象的地址。所以,```&a```整体的涵义就是“变量```a```的地址”。 51 | 52 | 在将变量```p```安排妥当后,我们便要看一看变量```p```所能发挥的能力了。执行刚刚的程序段落,我们可以看到类似以下的输出: 53 | 54 | > 1920389632 55 | > 56 | > 3 57 | 58 | 第二行的输出是固定的 3 —— 其实就是变量```a```的值。而第一行的输出就飘忽不定了,不仅是在不同的机器上,甚至重新运行几遍,得到的数字(很可能)都不一样。 59 | 60 | 这个看似不断变化的数字到底是怎么来的呢?我们知道,这个数字其实是变量```a```在内存中的地址,这一点也可以在代码行中的注释看到。而在现代操作系统中,每次程序运行时被分配的内存片段都是不一样的,亦因此每次程序运行时变量```a```所占用的空间也在不断变动着。 61 | 62 | > 说点题外话,每次分配给正在执行的程序的内存片段都不一样,是出于安全上的考虑——在计算机技术的早期发展阶段,操作系统为程序分配内存的方式仅仅是简单地按顺序分配空闲的内存,而这一漏洞是黑客们得以实现缓冲区溢出攻击的重要前提条件。感兴趣的读者可自行查阅相关资料。 63 | 64 | 于是第一行输出的奇怪数字便解释得通了,它不过是每次运行时变量```a```所存放的内存空间位置不断在变化罢了。 65 | 66 | 虽然第二行输出的是变量```a```的值,但```cout```接收到的内容并不是变量```a```本身,而是带了个星号的变量```p```,即```*p```。当```*```出现在某个变量或表达式(现在可暂且理解为算式的一个别称)之前时,它也就相当于与刚刚的```&```一个类型的符号,用于对其右侧对象进行**解引用**。更直白地讲,它用于读取其右侧对象所存储的“门牌号”地址,再根据这个地址找到对应的内存空间“查水表”——将该部分空间的数据**以指针声明时所用的类型**提取出来。它此时便代表了那个被引用的变量本身,我们可以对其进行读写。就刚刚的例子来看,```*p```其实就是先顺着变量```p```给的地址找到变量```a```所占据的内存空间,然后以变量```p```声明时所用的```int```类型将这部分空间的数据提取出来,最后就成了我们再屏幕上看到的数字 3 —— 即变量```a```所储存的值。 67 | 68 | ------ 69 | 70 | 指针的类型是可以跟随你想指向的变量类型的。不过,很多时候我们其实无从得知将需要指向什么类型的变量。 71 | 72 | 既然是指针,让其指向的变量类型也便是由我们自由定义的。我们能否做出这样一种指针,这种指针可以随心所欲地指向任意类型地变量呢? 73 | 74 | 答案是肯定的,确实有这样的通用类型指针,允许我们使用它指向任意类型的变量: 75 | 76 | ```cpp 77 | int a = 1; 78 | double b = 2; 79 | void *pa = &a, *pb = &b; // void * 类型指针可接受任意类型的指针 80 | ``` 81 | 82 | 而要想将这种指针变回具体的数据却是有些复杂的操作。我们将在后续学习类型转换时具体了解有关于此的数据类型转换方法。 83 | 84 | ------ 85 | 86 | 指针可以指向任意类型的变量,其中当然也包括指针类型: 87 | 88 | ```cpp 89 | int a = 1; 90 | int *p = &a; // 第一层指向 a 91 | int **pp = &p; // 第二层指向 p;注意标识符 pp 前连续使用了两个星号 92 | ``` 93 | 94 | 是的,它们形成了一个“链条”,即```pp```指向```p```,```p```又指向```a```。 95 | 96 | 而要想通过第二层的指针```pp```访问到变量```a```的值,我们需要连续使用两次解引用: 97 | 98 | ```cpp 99 | std::cout << **pp << std::endl // 输出变量 a 的值,即 1 100 | << *p << std::endl // 输出指针 p 的值,即变量 a 的地址 101 | << p << std::endl; // 输出指针 pp 的值,即指针 p 的地址 102 | ``` 103 | 104 | 要正确理解```**pp```的工作机制,我们需要将这个式子从右往左读。从右侧开始,```*pp```算作一个整体,得到的是指针```p```;紧接着,第二个```*```再对刚刚计算得到的```p```(即原来的```*pp```)再作一次解引用运算,相当于执行的是```*p```,得到的便是变量```a```的值了。 105 | 106 | 像这样的链式调用并不是全部。在实际场景种,你也许还能见到如同网状或树状的指针引用关系,不过这已经是后话了。 107 | 108 | ------ 109 | 110 | 有时候,我们虽然定义了指针,但一时半会还找不到需要它需要指向的变量。或者,当我们我们原本让指针指向的变量不再需要被我们继续使用,亦一时半会儿找不到顶替的新变量给指针指向。 111 | 112 | 此时,我们可以将其设置为一个空值: 113 | 114 | ```cpp 115 | int a = 1; 116 | int *pa = &a; // 先将指针 pa 指向 a 117 | pa = nullptr; // 再利用赋值将 pa 设置为一个空值,使其不指向任何对象 118 | int *pn = nullptr; // 指针 pn 刚定义时便被设置为空值 119 | ``` 120 | 121 | 其中的```nullptr```便是所谓的空值。它是 C++ 的关键字之一,实质上就是数字 0 而已。它是专为指针定制的空值,相比于 0 来讲,它能够更直观地传达给程序员这样一层意思——我们即将对一个指针作出操作。 122 | 123 | 另外,我们在实际编程时,一般会初始化尚还不确定指向谁的指针,而恰当的做法便是赋予这样的指针的值为 ```nullptr```。 124 | 125 | ------ 126 | 127 | 总结一下,我们学习了这样一些内容: 128 | 129 | - 指针(pointer)是储存了具体某个内存地址的一类特殊变量,它可帮助开发者间接地访问内存中的数据,进而实现一些特别的用途。要定义指针类型,仅需在普通类型后加```*```即可。 130 | - 取指针运算符(```&```)用于获取某个变量所在的内存地址。使用时需要将其写在被取指针的变量之前。 131 | - 解指针运算符(```*```)用于将某个指针转换回其所指向的数据,开发者可以像使用普通变量一样使用被解的指针。转换回的数据类型将与原本指针所指向的数据类型一致。 132 | 133 | 指针是自 C 语言以来就有的语言特性。直接使用指针很容易写出有内存溢出风险的程序,即使是有丰富经验的老手也不能保证万无一失,故而请仅在必要时使用指针!在未来的学习中,我们还将深入探索指针,并探寻规避指针缺陷的途径。 -------------------------------------------------------------------------------- /1/2-3.md: -------------------------------------------------------------------------------- 1 | # 固化态 - 常量限定符 2 | 3 | 世间有许多亘古不变的真理,人类不得撼动亦无法改变它的存在。不断地认识与利用真理,恰是人类文明不断进步的表现。 4 | 5 | 而在 C++ 中,同样也存在许多不得变化、不可变化的事物。我们要想使它们变得真正不可变,就需要我们主动对其作出限制。不可变是一层很好的"保护膜",可以帮助我们预防修改了不当修改的数据,也可以提醒我们数据的不可变性质。 6 | 7 | 接下来,我们便了解一下变量的不可变形态--常量。 8 | 9 | ------ 10 | 11 | 常量相当于不可更改其中内容的变量,我们只得使用其中的数据,却不得修改其中的数据: 12 | 13 | ```cpp 14 | const double pi = 3.141592653; 15 | ``` 16 | 17 | 这是最经典的应用了--定义一系列数学上的常量,这样我们进行数学计算时便方便多了: 18 | 19 | ```cpp 20 | double r = 3.5; 21 | const double pi = 3.141592653; 22 | std::cout < "S = " << pi * r * r << std:;endl; 23 | ``` 24 | 25 | 上面的程序演示的是圆的面积计算,其中变量```r```是半径,常量```pi```则是圆周率。 26 | 27 | 而要想定义一个常量,很简单,在类型名前加```const```即可。请注意,```const```总是应当在所有具体的类型前出现。例如: 28 | 29 | ```cpp 30 | const long long MAX_SIZE = 99999999999999999999L; 31 | ``` 32 | 33 | ------ 34 | 35 | ```const``` 不仅可用于普通变量,也可用于指针与引用。这里既可以是指针或引用所指向的类型拥有 ```const``` 属性,也可以是指针或引用本身带有 ```const``` 属性。 36 | 37 | 我们先来看一下指针,```const``` 在指针声明的不同位置有不同的用途: 38 | 39 | ```cpp 40 | int i = 1; // 一个变量 41 | const int ci = 2; // 一个常量 42 | const int *p1 = &ci; // 一个指向常量的指针 43 | int *const p2 = &i; // 一个指向变量的常量指针 44 | ``` 45 | 46 | 看起来是否很迷惑?不用慌,记住一句话,就能轻松区分它们: 47 | 48 | > 星号(\*)前的全是指向的对象的类型,星号(\*)后的全是自己的类型。 49 | 50 | 什么意思呢?先看前一句话,星号前的类型是我们要指向对象的类型,它只与指向的对象有关系,不影响指针本身: 51 | 52 | ```cpp 53 | const int a = 3; 54 | const int *p1 = &a; // 星号前为 const int,意即指向 const int 55 | const char b = 's'; 56 | char const *p2 = &b; // 星号前为 char const,意即指向 const char 57 | ``` 58 | 59 | 后一句话则是针对指针本身而言了,在星号后面写一个 ```const```,意味着指针本身变得不可修改: 60 | 61 | ```cpp 62 | int a = 3; 63 | int *const p = &a; // 星号后为 const,这个指针就不可被更改指向谁了 64 | ``` 65 | 66 | 当然了,也可以一起使用: 67 | 68 | ```cpp 69 | const int ca = 6; 70 | const int *const cp = &ca; // 不仅指向的对象不可更改,自己也不可更改 71 | ``` 72 | 73 | ------ 74 | 75 | 引用相比较于指针要简单一些,因为引用实质上就是别名,为别名设置 ```const``` 与为引用到的对象设置 ```const``` 没有什么区别: 76 | 77 | ```cpp 78 | const int a = 3; 79 | const int &ref = a; // 引用的对象即为常量 80 | ``` 81 | 82 | 不过,非 ```const``` 的普通变量同样可被声明带有 ```const``` 的引用所指向: 83 | 84 | ```cpp 85 | int a = 3; // 这里改为一个普通变量 86 | const int &ref = a; // 同样可行 87 | ``` 88 | 89 | 令人疑惑的是,站在变量 ```a``` 自身的角度来看,它是可以更改的;但要是站在常量引用 ```ref``` 的角度来看,它却又是不可更改的。 90 | 91 | 实际上,变量 ```a``` 仍旧可以更改,不过是 ```ref``` 在“故弄玄虚”罢了。如果硬要以 ```ref``` 修改变量 ```a```,也是有办法的: 92 | 93 | ```cpp 94 | const_cast(ref) = 6; // “强行”修改 ref 引用的变量 a 储存的值为 6 95 | ``` 96 | 97 | 在刚刚的例子中,```const_cast``` 用于消除所给对象的不可变属性。比如 ```const int &``` 类型的对象,经过 ```const_cast``` 处理后会变为 ```int &```,也就是消除了 ```const``` 属性。```const_cast``` 是 C++ 关键字之一,可以在程序的任意位置使用。 98 | 99 | 它的使用格式很简单: 100 | 101 | ```cpp 102 | const_cast(<欲消除不可变属性的对象>) 103 | ``` 104 | 105 | 像先前提到的“故弄玄虚”地将可变对象约束为不可变对象的常量引用称作**顶层 const**;而从一开始就附带不可变属性的对象,包括指针与引用类型,称作**底层 const**。```const_cast``` 只能正常处理顶层 ```const```,而处理底层 ```const``` 的结果会是未定义的。 106 | 107 | ------ 108 | 109 | C++ 还提供了一个与底层 ```const``` 性质相似的另一类 ```const```,它的关键字是```constexpr```。与```const```不同的是,```constexpr```的值是在程序尚且还在编译时就定下来的,在运行时不可能被改变。由于它是编译时就确定的,因此它甚至不需要占用额外的内存来创建空间、存储自身,直接写死在编译后的可执行程序中。不过,它也仅适用于定义在编译时便能确定下来的值,而在程序运行时才能确定的值是不能以```constexpr```定义的: 110 | 111 | ```cpp 112 | constexpr double pi = 3.1415926535; //很显然,constexpr 比 const 更适合表达数学常量 113 | constexpr double pi_square = pi * pi; // 正确,参与初始化计算的对象均为 constexpr 114 | constexpr double pi_half = pi / 2; // 正确,字面量(数字)同样也相当于 constexpr 115 | ``` 116 | 117 | 本质上,先前所学的诸如数字、字符串这类字面量就带有```constexpr```属性,而且理所应当--字面量本来就是在程序尚未编译时就确定下来的值。 118 | 119 | 由于带```constexpr```属性的常量与字面量均可在编译时就确定其值,因此我们还可以用这些值做一些符合运算,定义新的```constexpr```常量,就像刚刚的示例一样。 120 | 121 | 据此,我们也可以看到一个有趣的现象--C++ 编译器会在编译时完成尽可能多的常量计算,以此提升程序的运行性能,减少运行时不必要的计算: 122 | 123 | ```cpp 124 | std::cout << "4π = " << 4 * pi << std::endl 125 | << " π * π * π = " << pi * pi * pi << std::endl; 126 | ``` 127 | 128 | 在上面的例子中,编译器很可能会在编译时就将```4 * pi```与```pi * pi * pi```的结果计算完成,然后直接"替换"掉原来的式子。这样一来,程序运行时就无需再大费周章地重算一遍,直接使用编译器提前算好的计算结果即可。 129 | 130 | ------ 131 | 132 | 从字面量到变量,再从变量回到常量,无不是返璞归真的过程。善加利用常量,可以大幅提升程序的性能。 133 | 134 | ------ 135 | 136 | ### 习题 137 | 138 | - 尝试口述下列程序的运行结果 139 | - ```std::cout << 1 + 2 + 3 + 4 << std::endl``` 140 | - ```constexpr int c = 3;``` 141 | - ```constexpr int cc = c * c + c;``` 142 | - ```std::cout << cc * c + cc << std::endl``` 143 | 144 | -------------------------------------------------------------------------------- /1/2-4.md: -------------------------------------------------------------------------------- 1 | # 拟态 · 自动推断类型 2 | 3 | 有时候,我们在日常与他人交流时,所说的话会省略一部分内容,而对方仍然能理解意思: 4 | 5 | - 你看着办(如果这事情能行,你就尝试去做做看)。 6 | - 不行就停下来(如果你做不下去,就先停下来吧)。 7 | - 太贵了(这一套设备太贵了,买不起)。 8 | 9 | 我们可以发现,这些省略句都需要处于一定的语境下。没有上下文的帮助,对方很难理解句子的意思,或错误地以为是其它的含义。 10 | 11 | 在 C++ 中,我们同样可以如此——通过上下文中的一些信息,可以让编译器自动推断出某个变量应当是什么类型。下面,我们就来具体了解下它的原理。 12 | 13 | ------ 14 | 15 | 有时,我们会写一些挺复杂的类型,就像这样: 16 | 17 | ```cpp 18 | const char[20] name = "langyo"; 19 | constexpr long long size = 10001L; 20 | const double pi = 3.14159265; 21 | ``` 22 | 23 | 很显然,写这么长的类型真的很费事。 24 | 25 | 怎样偷懒呢?使用 ```auto``` 就可以了: 26 | 27 | ```cpp 28 | auto name = "langyo"; 29 | constexpr auto size = 10001L; 30 | const auto pi = 3.14159265; 31 | ``` 32 | 33 | ```auto``` 是一种特殊的数据类型,它可以根据定义变量时所给的初始化值,自动推断出此变量的类型。就刚刚的例子而言,```"langyo"``` 推断得到的是```const char *```,```10001L``` 推断得到的是 ```long long```,```3.14159265``` 推断得到的是 ```double```。 34 | 35 | ```auto``` 之上也可以额外添加类型标识,如 ```const```、```constexpr``` 、```* ```与 ```&```。在刚刚的例子中,```constexpr auto``` 与 ```const auto``` 便分别被推断为 ```constexpr long long``` 与 ```const double```。 36 | 37 | 就基本数据类型而言,```auto``` 在推断时遵循一些简单的规则: 38 | 39 | - 字符串字面量转换为 ```const char[]```(也可以理解为 ```const char*```)。 40 | - 整型数字字面量默认转换为 ```int```。 41 | - 浮点型数字字面量默认转换为 ```double```。 42 | - 字符字面量默认转换为 ```char```。 43 | 44 | 当然,```auto``` 也可以直接凭一个现有的变量推断类型: 45 | 46 | ```cpp 47 | int a = 10; 48 | auto b = a; // 推断类型为 int 49 | auto str1 = "langyo" // 推断类型为 const char* 50 | auto str2 = str1; // 同样也是 const char* 51 | ``` 52 | 53 | ------ 54 | 55 | 有时候,我们并不方便直接使用 ```auto``` 声明新变量。也许是因为我们不能使用 ```auto``` 默认的推断效果,也许我们只需要某个变量的类型: 56 | 57 | ```cpp 58 | constexpr double t = 0.14159265; 59 | auto pi = 3 + t; // 很遗憾,推断类型为 int,因此小数部分会被抛弃 60 | 61 | float p = 6.66; 62 | auto m = p; // p 的类型能容纳浮点数,正好要用到 63 | m = 2.33; // 我们虽然需要 p 的类型,但却不需要 p 的值 64 | ``` 65 | 66 | 此时,```auto``` 便显得有些有心无力了。我们需要一个能具体指定为某变量类型的类型。 67 | 68 | 怎么办呢?我们可以使用 ```decltype```: 69 | 70 | ```cpp 71 | double t = 0.14159265; 72 | decltype(t) n = 3 + t; // 此时该变量的类型与 t 变量一致,即 double,而不是原来默认的 int 73 | 74 | float p = 6.66; 75 | decltype(p) m = 2.33; // 此时该变量的类型与 p 变量一致,并且我们也能立即给出自己的初始值 76 | ``` 77 | 78 | ```decltype``` 可以接受字面量、变量、常量等对象,其格式如下: 79 | 80 | ```cpp 81 | decltype(<欲被提取类型的对象>) 82 | ``` 83 | 84 | ```decltype``` 与 ```auto``` 有些类似,都是自动推断类型,并且其附近同样可以添加像 ```const``` 之类的类型限定。不同之处在于,```auto``` 直接通过检查初始化对象推断类型,而 ```decltype``` 则是检查由开发者手动指定的对象来推断类型。```decltype``` 不对所提供的对象作出额外限制,但提取得到的类型可能会丢失一些被提取源头类型的一部分属性,具体机制会在以后的章节讨论。 85 | 86 | 另外,```decltype``` 还有一种特殊格式: 87 | 88 | ```cpp 89 | decltype(auto) 90 | ``` 91 | 92 | 如果仅仅用作字面量初始化,也就是像数字、字符串之类的,它与 ```auto``` 的表现其实没有多大区别。不过,当它用在函数的返回值或以函数返回值初始化的变量时,它的工作机制会有所变化。我们将在未来学习函数相关的内容时具体深入探究。 93 | 94 | ------ 95 | 96 | ```auto``` 与 ```decltype``` 两种自动推断类型在现代 C++ 程序中经常出现,它们是节约思考事件、提高编程效率的利器。有了它们,我们就无需为了类型一事大量翻阅文档与他人的源代码,并且也减小了类型书写错误的概率。 -------------------------------------------------------------------------------- /1/2-5.md: -------------------------------------------------------------------------------- 1 | # 叠加态 · 自定义数据结构 2 | 3 | 我们所熟知的一切事物都或多或少的由一些特征,这其中有不少特征是可以量化的: 4 | 5 | - 随便找些方的东西,我们可以测量出它的长、宽、高 6 | - 我们在地球上所处的位置,可以用经纬度与海拔表示 7 | - 商店所出售的一切物品,都至少有一个进口价(成本价)与售价。 8 | 9 | 这样的例子实在是太多了。 10 | 11 | 由此,C++ 拥有一系列的特性,使得我们可以将松散的数据组合起来,成为新的个体,进而便于我们使用。 12 | 13 | 下面,我们就来探究有关组合自定义数据类型的方式与思想。 14 | 15 | ------ 16 | 17 | 最简单的自定义数据类型的方式,莫过于给我们已经掌握的类型取个额外的名字: 18 | 19 | ```cpp 20 | typedef long number; // 给 long 类型取个别名,叫 number 21 | ``` 22 | 23 | 在这里,我们为 ```long``` 类型取了一个别名叫 ```number```。现在,我们便可以将 ```number``` 作为一个类型使用了,本质上它仍然是 ```long``` 类型: 24 | 25 | ```cpp 26 | number num1 = 1; // 定义一个类型为 number 的变量 27 | long num2 = 2; // long 类型仍然可以正常使用 28 | number num3 = num2; // 即使名字不同,但它们本质上类型是一致的 29 | ``` 30 | 31 | ```typedef``` 的使用格式如下: 32 | 33 | ```cpp 34 | typedef <原类型> <新类型名称>; 35 | ``` 36 | 37 | 原来的类型可以带有类型限定,例如 ```unsigned```: 38 | 39 | ```cpp 40 | typedef unsigned long long big_num; 41 | ``` 42 | 43 | 而且原类型也可以不是 C++ 自带的类型: 44 | 45 | ```cpp 46 | typedef big_num big_number; 47 | ``` 48 | 49 | 如果要额外带上 ```const```、```*``` 这类标识,```typedef``` 是不能应付的。这时我们可以使用 ```using```: 50 | 51 | ```cpp 52 | using number_ptr = number *; 53 | using const_number = const number; 54 | using const_long = const long; 55 | ``` 56 | 57 | ```using``` 的声明顺序与 ```typedef``` 相反,且中间有等于号隔开: 58 | 59 | ```cpp 60 | using <新类型名称> = [类型标识] <原类型>; 61 | ``` 62 | 63 | ------ 64 | 65 | 另一种自定义数据类型的方式是组合。通过组合,我们可以将多个零碎的类型整合为一个新的类型: 66 | 67 | ```cpp 68 | struct block 69 | { 70 | int x; 71 | int y; 72 | int z; 73 | }; 74 | ``` 75 | 76 | 我们在这里定义了一个新的名为 ```block``` 的类型,可以存储一个三维坐标,其中分别定义了类型为 ```int``` 的**成员变量** ```x```、```y```、```z```,以表达长、宽、高。 77 | 78 | ```struct``` 用于定义一个**类**,它可以容纳一系列变量作为其成员。其格式如下: 79 | 80 | ```cpp 81 | struct <类名> 82 | { 83 | ... 84 | } [以该类作为类型建立的变量列表]; 85 | ``` 86 | 87 | 由 ```struct``` 定义的类型称作**类类型**,或也可以简称为**类**。由这个类类型定义的变量,也可以被称作是这个类的一个**实例**。在花括号内,我们可以如同刚刚的例子那样,声明一系列的成员,例如声明新的成员变量。在花括号之后,我们可以就以这个类型声明一些变量,也可以不声明。但不论怎样,**末尾必须以一个分号结束**。 88 | 89 | 如果需要在建立类的同时就准备一些这个类的实例,可以在花括号与分号之间写一系列变量名,用逗号隔开: 90 | 91 | ```cpp 92 | struct block 93 | { 94 | int x; 95 | int y; 96 | int z; 97 | } b1, b2, b3; 98 | ``` 99 | 100 | 而且我们同样可以对这些新变量进行初始化: 101 | 102 | ```cpp 103 | struct block 104 | { 105 | int x; 106 | int y; 107 | int z; 108 | } b1 = { 1, 1, 1 }, b2 = { 2, 3, 4 }, b3 = { -1, 3, 2 }; 109 | ``` 110 | 111 | 这种初始化方式称作**列表初始化**,它可以对形同列表的对象赋予一系列值,其花括号中的每个值按顺序与列表中的每个位置一一对应。在后续有关数组与标准库容器的学习中,我们将更深入地了解它地工作原理。 112 | 113 | ------ 114 | 115 | 我们不一定非得在定义类的地方声明这个类类型的变量。事实上,只要**声明**过这个类,我们就可以在声明的位置之后任意使用这个类创建新变量: 116 | 117 | ```cpp 118 | struct block {...}; 119 | int main() 120 | { 121 | block b1; // 先前已有 block 的定义,可放心使用 122 | block b2 = { 6, 6, 6 }; // 而且也可以正常使用列表初始化 123 | ... 124 | } 125 | ``` 126 | 127 | 如果将 ```block``` 那部分移动到 ```main``` 函数的后面,编译器会因为找不到类类型的声明而报错——编译器很笨,它在前半部分解析完之前,不会去撇一眼的后半部分: 128 | 129 | ```cpp 130 | int main() 131 | { 132 | block b1; // 错误,此时 block 类尚未有过声明 133 | ... 134 | } 135 | struct block {...}; // block 类的定义被移动到后面了 136 | ``` 137 | 138 | 如果硬要写在后头怎么办?很简单,在用的地方之前**声明**一次这个类即可: 139 | 140 | ```cpp 141 | struct block; // 只是声明这个类的存在;因为只是声明,所以不需要花括号及其内容 142 | 143 | int main() {...} // 此时 main 函数内部就可以正常使用 block 类了 144 | 145 | struct block // 此时才正式定义这个类 146 | { 147 | int x, y, z; 148 | }; 149 | ``` 150 | 151 | 顺便一提,像前一个将 ```struct``` 写在开头的例子那样,即刚出现就对其进行定义的行为,相当于在**声明**的同时立即**定义**这个类。 152 | 153 | ------ 154 | 155 | 前期工作已经做好,接下来我们就来尝试使用它们: 156 | 157 | ```cpp 158 | struct block { 159 | int x, y, z; 160 | }; 161 | 162 | int main() 163 | { 164 | block b1 = { 1, 2, 3 }; 165 | cout << "b1 的位置是" << b1.x << ", " << b1.y << ", " << b1.z << endl; 166 | block b2 = { 1, 2, 3 }; 167 | cout << "b2 的位置是" << b2.x << ", " << b2.y << ", " << b2.z << endl; 168 | return 0; 169 | } 170 | ``` 171 | 172 | 要访问类的实例中的内容,需要在该变量后紧跟一个句点(```.```),再在其后紧跟要访问的**成员名**。目前来讲,我们所定义的类中暂时只有成员变量 ```x```、```y```、```z```,它们本质上与普通变量没有多大差别。 173 | 174 | ------ 175 | 176 | 类型别名与自定义类是可以混用的。举例来讲,我们可以定义两个类 ```position``` 与 ```size```,分别标识位置与大小: 177 | 178 | ```cpp 179 | struct position 180 | { 181 | int x, y, z; 182 | }; 183 | struct size 184 | { 185 | int x, y, z; 186 | }; 187 | ``` 188 | 189 | 很显然,它们都有相同的三个成员变量 ```x```、```y```、```z```。既然这样,我们可以试着定义一个通用的含 ```x```、```y```、```z``` 的类,然后给它取两个别名,达到同样的效果: 190 | 191 | ```cpp 192 | struct 3DVector 193 | { 194 | int x, y, z; 195 | }; 196 | typedef 3DVector position; 197 | typedef 3DVector size; 198 | ``` 199 | 200 | 自定义类不仅能取别名,也能嵌套。例如,我们可以将刚刚的两个类 ```position``` 与 ```size``` 再组合,建立一个新类 ```entity```——它是一个具有大小和位置信息的物体类: 201 | 202 | ```cpp 203 | struct entity 204 | { 205 | char[] name; 206 | position pos; 207 | size s; 208 | }; 209 | ``` 210 | 211 | 对于这种嵌套了别的类的类,使用列表初始化时数据也会是嵌套的,且顺序要保证按照定义类时使用的顺序: 212 | 213 | ```cpp 214 | entity e = { "ball", { 1, 2, 3 }, { 2, 2, 2} }; 215 | cout << "Name:" << e.name << endl 216 | << "Position:" << e.pos.x << ", " << e.pos.y << ", " << e.pos.z << endl 217 | << "Size:" << e.s.x << ", " << e.s.y << ", " << e.s.z << endl; 218 | ``` 219 | 220 | 由此可见,类中的类也可以被访问,方式同样是句点加成员名。不论嵌套多深的类,都可以间接地以这种方式访问。 221 | 222 | ------ 223 | 224 | 类型别名与自定义类是很有效的数据组织手段,而本节仅仅起到敲门砖的作用。随着学习的深入,我们将在未来的实践中更加深入地熟悉与掌握它们。 -------------------------------------------------------------------------------- /1/3-1.md: -------------------------------------------------------------------------------- 1 | # 简单的数学课 · 初识表达式 2 | 3 | 首先我们来看一道极为简单的计算题: 4 | 5 | > 1 + 1 = ? 6 | 7 | 我相信你读完这行算式的瞬间,就知道它等于 2 了。 8 | 9 | 那么,计算机是否也能做到瞬间算出它的结果呢? 10 | 11 | 当然能了!我们来试试用 C++ 让计算机算一算: 12 | 13 | ```c++ 14 | #include 15 | using namespace std; 16 | int main() 17 | { 18 | cout << 1 + 1 << endl; 19 | return 0; 20 | } 21 | ``` 22 | 23 | 我们在之前学到过,```cout```用于向控制台窗口写上你想写的内容,```endl```用于换行与刷新内容。如果你成功地执行了这道程序,可以看到控制台窗口中写了个 2。 24 | 25 | 是不是觉得太简单了?那就稍微复杂点吧: 26 | 27 | > (147 + 258) × 369 - (550 + 233) ÷ 29 = ? 28 | 29 | 这种人类算起来颇为费事的式子,交给计算机仍然是轻而易举: 30 | 31 | ```c++ 32 | #include 33 | using namespace std; 34 | int main() 35 | { 36 | cout << (147 + 258) * 369 - (550 + 233) / 29 << endl; 37 | return 0; 38 | } 39 | ``` 40 | 41 | 当你将这行程序敲进计算机时,也许会发现乘号和除号有那么点不同——乘号用星号(*)表达,除号则用斜杠(/)表达。 42 | 43 | 为什么会这样呢?其实是因为你的键盘上没有所谓的乘号与除号,哈哈~几十年前设计键盘的工程师们脑洞大开,将星号作为乘号使用。而使用斜杠作为除号的原因还有一个:在数学中,除法还可以用分数的形式表示,所以这个斜杠其实就是横着写的分数线——左侧的被除数相当于分子,右侧的除数也就相当于分母。 44 | 45 | 好了,如果你成功地运行了这段代码,可以看到结果是 149437。 46 | 47 | 我们再深入些,尝试让计算机先接收我们输入的值,再根据我们提供的公式算出结果。 48 | 49 | 举个例子,要计算一个立方体的体积,我们可以用公式: 50 | 51 | > 长 × 宽 × 高 52 | 53 | 设长为```a```,宽为```b```,高为```c```。我们可以写这样一道程序解决问题: 54 | 55 | ```c++ 56 | #include 57 | using namespace std; 58 | int main() 59 | { 60 | int a, b, c; // 定义表达长宽高的三个整数类型的变量 61 | cin >> a >> b >> c; // 输入三个变量的值 62 | cout << a * b * c << endl; // 输出三个变量的乘积 63 | return 0; 64 | } 65 | ``` 66 | 67 | 这一次我们可以自己定义想使用的数字了。随便输入两组数字试试: 68 | 69 | > \> 1 2 3 70 | > 71 | > < 6 72 | > 73 | > \> 23 3 3 74 | > 75 | > < 207 76 | 77 | 我们可以看到,计算机忠实地按照给定的数字与公式,算出了我们想要的结果。 78 | 79 | 80 | 81 | 像 ```1 + 1```、```a + b + c```、```2 * (a * b + b * c + a * c)```(立方体表面积公式)这样的式子,我们可以统一称呼它们为表达式。表达式可以看作一个计算单元,包含若干个参与运算的对象(如数字、变量)和若干个运算符(如加号、乘号、小括号),对它求职会产生一个结果(如数字)。 82 | 83 | 表达式其实就是我们所熟知的算式、方程等再计算机学中的专用名称。实际上,就连```cout << ... << endl```这样的内容页同样是表达式,```cout```、```endl```同样是对象,```<<```也同样是运算符,我们将在后续的章节中深入学习。 84 | 85 | 86 | 87 | ### 习题 88 | 89 | - 写一份用于求立方体表面积的程序,要求接收的三个数字分别存储在变量```a```、```b```、```c```中。 -------------------------------------------------------------------------------- /1/3-2.md: -------------------------------------------------------------------------------- 1 | # 数字间的反应 · 初识运算符 2 | 3 | 在开始之前,我们先来回忆一下,在数学中,我们有哪些数学符号可用? 4 | 5 | 四则运算有加减乘除,比较数字则有大于、小于、大于等于和小于等于。有时,我们还会用到不等于、等于和小括号等符号。 6 | 7 | 这些符号组成了数学中的算式。同样的,C++ 也有与之功能一致的符号可以用在表达式中,我们称这些符号叫运算符。 8 | 9 | ------ 10 | 11 | 首先介绍一下用于算术运算的运算符。不仅有加减乘除(+、-、*、/),C++ 还额外给我们提供了求余运算符。 12 | 13 | 求余是什么?通常情况下,我们可以将它看成是除法中的求余数。举个例子: 14 | 15 | > 7 ÷ 2 = 3 ······ 1 16 | 17 | ```7 % 2```的结果是```1```,也就是上述式子中的余数。 18 | 19 | 需要注意的是,求余运算两边的数字必须是整数。 20 | 21 | 不仅求余运算符有特殊之处,就连除法运算符也颇具特殊之处——两个整数相除,得到的是一个整数,小数部分会被舍弃: 22 | 23 | > 7 / 2 = 3 (不是 3.5) 24 | 25 | ------ 26 | 27 | C++ 不仅能表达正数和 0,也可以表达负数。负数的写法与数学上的写法类似,在数字前加一个减号(相当于负号)即可: 28 | 29 | > -233 30 | 31 | 如果要使用负数作四则运算,一定要记得在负数两边加一对小括号包裹起来,以和减号区分开来: 32 | 33 | ```cpp 34 | (-233)*(-666)+(-999)/(-333)-(-123) 35 | ``` 36 | 37 | 另外,有关减法与负号之间的关系,其实以下两种写法产生的效果是一样的: 38 | 39 | > 233 + (-66) = 233 - 66 40 | 41 | 这不仅在 C++ 中是一致的,在数学上也同样成立。 42 | 43 | ------ 44 | 45 | 我们在数学上经常会使用括号改变算式的求值顺序: 46 | 47 | > {[3 * (1 + 2) + 6] * (9 - 6)} * [12 * ( 3 + 4)] 48 | 49 | 在数学中,我们必须按照运算顺序分别使用小括号、中括号和大括号,但在 C++ 中却不需如此麻烦。我们只需要使用小括号就够了: 50 | 51 | ```cpp 52 | ((3 * (1 + 2) + 6) * (9 - 6)) * (12 * (3 + 4)) 53 | ``` 54 | 55 | 在表达式中,小括号最有用的用途之一,是强行改变原有的运算顺序。当然,小括号不仅仅能做到这些,它的其他用户我们以后再作讨论。 56 | 57 | ------ 58 | 59 | C++ 不仅可以表达算术运算,也可以表达逻辑运算。大于号、小于号和等于号我们是有办法直接输入的,但小于等于、大于等于和不等于又怎么输入呢? 60 | 61 | 很简单,把大于号、小于号或感叹号与等于号拼接起来即可: 62 | 63 | ```cpp 64 | 3 >= x // 3 ≥ x 65 | 4 <= y // 4 ≤ y 66 | 1 != z // 1 ≠ z 67 | ``` 68 | 69 | 另外,在 C++ 中,等于并不是以一个等于号表达的,而是需要连续写两个: 70 | 71 | ```cpp 72 | 22 == 10 + 12 // 相当于数学中的 22 = 10 + 12,返回的值为 true 73 | ``` 74 | 75 | 为什么要这么麻烦?因为 C++ 中还有一种操作叫做赋值,它是用单一一个等于号表达的。如果不能明确区分它们的含义,等于号将会带有歧义性。 76 | 77 | ------ 78 | 79 | 接下来我们再来认识一些专用于逻辑运算的运算符。它们并不能直接对诸如数字、字符乃至更复杂的类型作运算,而只能对布尔值作运算。逻辑运算符有非(not,符号为“!”)、或(or,符号为“||”)和与(and,符号为“&&”)。 80 | 81 | 它们到底能干什么用呢?举个例子: 82 | 83 | ```cpp 84 | 18 <= x && x <= 45 85 | ``` 86 | 87 | 这一句的意思是,如果```x```不小于 18,**并且**也不大于 45,那么得到的值为真,除此之外的一切情况得到的值均为假。 88 | 89 | ```cpp 90 | x <= 18 || x >= 65 91 | ``` 92 | 93 | 这一句的意思是,**只要**```x```小于 18 **或**```x```大于 65,得到的值就为真。如果```x```既不小于 18 也不大于 65,那就只能为假了。 94 | 95 | 我们兴许还可以组合一下,表达得稍微复杂点: 96 | 97 | ```cpp 98 | 3 < x && x < 12 || 18 < x && x < 21 || x == 25 99 | ``` 100 | 101 | 注意,与(&&)的优先级要比或(||)高,所以在这个表达式中,最先计算的是```3 < x && x < 12```、```18 < x && x < 21```和```x == 25```。这一段的意思便是,如果```x```在 3 与 12 之间,或是在 18 与 21 之间,或者等于 25,都能使得这个表达式的求值结果为真。 102 | 103 | 还有一个名为非的符号,它的用途是反转一个布尔值,也就是真变假、假变真: 104 | 105 | ```cpp 106 | !(3 < x && x < 12) 107 | 3 >= x || x >= 12 108 | ``` 109 | 110 | 以上两个式子的求值结果是一样的——将判断“x 是否在 3 到 12 之间”的结果反转一下,就成了“x 是否不在 3 到 12 之间”的结果,或也可以看成是“x 是否小于等于3,或大于等于 12”的结果。 111 | 112 | 注意,非(!)符号比与(&&)符号和或(||)符号的优先级都要高,所以诸如以下这样的写法并不是等价的: 113 | 114 | ```cpp 115 | !3 < x && x < 12 // 相当于 3 >= x && x < 12 116 | !(3 < x && x < 12) // 相当于 3 >= x || x >= 12 117 | ``` 118 | 119 | ------ 120 | 121 | 逻辑运算是相当强大的计算手段,但不能将得到的布尔值利用起来,逻辑运算便毫无意义。其中一种利用布尔值的手段,便是使用三元运算符。 122 | 123 | 三元运算符有点特殊,因为它是由两个相互独立的运算符组成的整体: 124 | 125 | > <条件> ? <条件为真时的值> : <条件为假时的值> 126 | 127 | 有点奇怪,对吧?举个实际的例子感受一下,计算出租车行驶```x```km 路要收取的车费: 128 | 129 | ```cpp 130 | x <= 3 ? 8 : 8 + 1.5 * (x - 3) 131 | ``` 132 | 133 | 当```x```小于等于 3 km 时,收取的费用便是固定的起步价 8 元;当```x```大于 3 km 时,收取的费用是以起步价为基础,每多 1 km 额外收取 1.5 元。 134 | 135 | 这个表达式也同样地表达了刚刚的逻辑:当```x```小于等于 3 时,这个表达式的值是 *8*,否则为*8 + 1.5 × (x - 3)*。 136 | 137 | 另外,三元运算符也是可以嵌套的,用于表达更复杂的计算。随便举个例子,感受一下: 138 | 139 | ```cpp 140 | money < 10000 ? 141 | (likeChocolate ? "Buy chocolate" : "Buy normal sweet") 142 | : 143 | (needHouse ? "Buy a house" : "Buy a car") 144 | ``` 145 | 146 | 三元运算符也可以叫成三目运算符。另外,三元运算符默认是从右往左计算的,也就是所谓的右结合。 147 | 148 | ------ 149 | 150 | 有时候,我们不仅需要用到四则运算作算术操作,也有可能会用到位运算。位运算,实际上是指操作数字的每一个二进制位。位运算有六种:与(&)、或(|)、异或(^)、非(~)、左移(<<)与右移(>>)。 151 | 152 | 我们先来回顾一下有关二进制位的知识。二进制如同十进制一样,可以进行四则运算,区别仅仅在于它只有 0 和1 两个数字: 153 | 154 | >1 + 1 = 10 155 | > 156 | >110 - 11 = 11 157 | > 158 | >11 * 101 = 1111 159 | > 160 | >1100 / 110 = 10 161 | 162 | 由于二进制数字只有 0 和 1,所以我们不仅能进行算术运算,还可以将两个二进制数的每一个数字逐个比对,作与逻辑运算相似的运算。 163 | 164 | 我们随便取两个长度一致的二进制数试一试: 165 | 166 | ``` 167 | a = 10001001 168 | b = 01101100 169 | a & b = 00001000 // 与运算,只有第 5 位数两边都是 1 170 | a | b = 11101101 // 或运算,只有第 4、7 位数两边都不是 1 171 | ~a = 01110110 // 非运算,a 的每一位 0 变 1,1 变 0 172 | a ^ b = 11100101 // 异或运算,只有第 4、5、7 位数两边数字一样 173 | // (有关异或的概念,请阅读 1.1.3 章) 174 | ``` 175 | 176 | 可以看到,位运算实际上就是对每一个二进制位作逻辑运算。 177 | 178 | 左移(<<)与右移(>>)进行的并不是逻辑运算,而是将原有的数字以二进制的形式向左或向右移动若干位。举个例子,我们为一个类型为 unsigned short 的数字移位,它的长度为 16 个二进制位: 179 | 180 | ``` 181 | 00000000 00101111 << 6 => 00001011 11000000 182 | ^ ^ ^ ^ 183 | 47 << 6 == 3008 (右侧填充 6 个 0) 184 | 00000000 00101111 << 14 => 11110000 00000000 185 | ^ ^ ^ 186 | 47 << 14 == 61440 (右侧填充 14 个 0,左侧溢出 2 位) 187 | 00000000 00101111 >> 4 => 00000000 00000010 188 | ^ ^ ^ 189 | 47 >> 4 == 2 (左侧填充 4 个 0,右侧溢出 4 位) 190 | ``` 191 | 192 | 需要注意的是,移位有可能会造成溢出,也就是将一部分原有的数字“挤”除了所在容器所能存储的最大范围。编译器没有办法也不会去检查你所要移动的位数是否会导致溢出,因此,在涉及移位运算时,一定要考虑清楚有关溢出的问题。 193 | 194 | > **小技巧** 195 | > 196 | > 由于二进制的一些特殊性质,我们每将数字左移一位,便相当于对数字乘以一次 2;反过来,每将数字右移一位,便相当于给数字整除以一次 2: 197 | > 198 | > ​ 13 << 1 == 13 * 2 199 | > 200 | > ​ 66 >> 2 == 66 / 4 201 | > 202 | > 求除以 2 的次幂的余数也因此可以这么写: 203 | > 204 | > ​ 137 % 8 == 137 - 137 >> 3 << 3 205 | 206 | ------ 207 | 208 | 看到这里,你也许会对之前所学的```cin```、```cout```抱有疑问,为什么对这两个对象进行“左移”、“右移”,实现的效果却不是刚刚所介绍的数字位移?这些符号或许还能有别的含义? 209 | 210 | 使得,在这里我想稍稍剧透一下,运算符的行为时可以被我们自定义的,在后续的章节中,我们会就针对此深入学习。对于```cin```、```cout```来讲,他们的“左移”、"右移"被标准库自定义了,变成了“输出”与“输入”。 211 | 212 | ------ 213 | 214 | 总结一下,我们认识了以下的运算符: 215 | 216 | - 用于四则运算的 ```+ - * /```以及求余用的```%```。 217 | - 改变运算顺序用的小括号```()```。 218 | - 用于比较大小的```> < >= <= == !=```。 219 | - 用于根据逻辑运算结果选择表达式用的三元运算符```...?...:...```. 220 | - 用于综合逻辑运算结果的```&& || !```。 221 | - 用于进行位运算的```& | ^ ~ << >>```。 222 | 223 | ### 习题 224 | 225 | - 在你的身边寻找一些同时有数值与逻辑关系的事物,尝试用表达式写出来。 -------------------------------------------------------------------------------- /1/3-3.md: -------------------------------------------------------------------------------- 1 | # 传送带 · 赋值操作 2 | 3 | 我们时常让计算机作出各种各样的计算,但如果不能将计算结果截留下来,先前的劳动便成了昙花一现,变得没有了意义。 4 | 5 | 也许我们一时还可以用```cout```,将算得的结果立即变为屏幕上的黑底白字: 6 | 7 | ```cpp 8 | cout << 1 + 1; 9 | ``` 10 | 11 | 也许我们还可以用```cin```和变量。变量的存在给予了我们截留运算结果的可能,但目前为止,我们还仅仅在让它承载从键盘获得的值: 12 | 13 | ```cpp 14 | int x, y, z; 15 | cin >> x >> y >> z; 16 | cout << x * y * z; 17 | ``` 18 | 19 | 接下来,我们便学习如何为变量赋予真正接纳数据的能力。 20 | 21 | ------ 22 | 23 | 变量可以被赋予一个值,这种操作便叫作赋值: 24 | 25 | ```cpp 26 | int x, y, z; 27 | x = 1; 28 | y = 2 + 3; 29 | z = x + 2 * y; 30 | ``` 31 | 32 | 赋值时使用单一的一个等于号,左侧为被赋值的对象,右侧为用于赋值的值。左侧的可持久存储的对象所含的值称为左值,右侧的赋值之后结果就会被丢弃的值称为右值。 33 | 34 | 分左、右值的目的何在?其实这是想为程序员传达这样一种讯息:左值(例如变量、类的成员)不会凭空小时,它就在那里,你总有机会将其值取走;右值(例如一段表达式)就真的是一闪即逝,不论你是否将这个值存进某个左值所在的容器,或是将其作为某一段在它之上的表达式的一部分继续计算,求完这个值,它便会消失。赋值的目的,也本就是将如过客般的右值,变为原本左值所在容器的新主人。 35 | 36 | 看起来好复杂的样子。 37 | 38 | 不必忧虑,我们仍然可以继续接下来的学习,因为左、右值的性质直到靠后的章节才会用到。目前为止,我们仅需了解过左、右值,并且能做到使用一个单一的等于号赋值,就足够了。 39 | 40 | ------ 41 | 42 | 赋值可以有什么新花样么? 43 | 44 | 答案是,有!下面来看一看混杂了各种运算符的奇怪赋值方法: 45 | 46 | ```cpp 47 | a += 1; 48 | b -= 5; 49 | c *= a + b + c; 50 | ``` 51 | 52 | 这些是什么玩意?为什么等于号和普通符号粘在一起了? 53 | 54 | 其实,它们与以下表达式是等价的: 55 | 56 | ```cpp 57 | a = a + 1; 58 | b = b - 5; 59 | c = c * (a + b + c); 60 | ``` 61 | 62 | 如果你是第一次看到这种表达式,不由得会觉得不合情理。毕竟,像```a = a + 1```这种表达式在数学上根本不可能成立! 63 | 64 | 等一等,我们之前有了解过,两个等号才是比较相等,而一个等号的含义是赋值。 65 | 66 | 也就是说,这个表达式其实并没有什么毛病,只不过是有些违背常规思维罢了。 67 | 68 | 为了理解这个式子到底是怎么计算的,我们需要站在计算机的角度,具体地看每个步骤: 69 | 70 | 1. 取出```a```,我们先假设```a```原本等于 2 吧。 71 | 2. 计算```a + 1```,也就是 2 + 1 = 3。 72 | 3. 将算得的 3 存进```a```,此时```a```便从原本的 2 变成了 3。 73 | 74 | 噢!这不就是让```a```自己加一嘛! 75 | 76 | 照此推下去,我们也可以得出,```b = b - 5```是指让```b```自己减去 5,```c = c * (a + b + c)```是让```c```自己乘上```a + b + c```的和。 77 | 78 | 刚开头的像```a += 1```这样的式子,便是像```a = a + 1```这样的式子的简写形式。如同```+=```这种将其它二元运算符与赋值用的等于号拼起来是想传达一种理念:它左侧的变量不仅是此次运算的参与者,并且同时也是运算对象的承载者。这样的操作称为**复合赋值**。 79 | 80 | ------ 81 | 82 | 我们经常会遇到要让变量自增或自减 1 的情形。 83 | 84 | 例如,游戏到了新的一回合,那就: 85 | 86 | ```cpp 87 | round += 1; 88 | ``` 89 | 90 | 打怪升了一级,那就: 91 | 92 | ```cpp 93 | level += 1; 94 | ``` 95 | 96 | 游戏角色阵亡,扣除一次复活机会,那就: 97 | 98 | ```cpp 99 | chances -= 1; 100 | ``` 101 | 102 | 也许你会觉得,这样写也差不多够用了诶,起码比```round = round + 1```这种的好多了。 103 | 104 | 不过,还有更简便的写法,它们被称作**递增**与**递减**: 105 | 106 | ```cpp 107 | round++; // 或 ++round 108 | level++; // 或 ++level 109 | chances--; // 或 --chances 110 | ``` 111 | 112 | 得,这下连等于号都不用写了。 113 | 114 | 看起来是挺简单的——把```+=```或```-=```改为```++```与```--```就好了。不过,与复合赋值不同的是,这个```++```与```--```是既可写在变量之前,也可写在变量之后的。 115 | 116 | 这么做有区别么?有!简而言之,区别在于是先取值后赋值,还是先赋值后取值。 117 | 118 | 我们写个程序试一试: 119 | 120 | ```cpp 121 | #include 122 | using namespace std; 123 | int main() 124 | { 125 | int n = 233, m = 233; 126 | cout << ++n << m++ << endl; 127 | cout << n << m << endl; 128 | return 0; 129 | } 130 | ``` 131 | 132 | 运行时的输出是这样的: 133 | 134 | >234 233 135 | > 136 | >234 234 137 | 138 | 前一个```n```似乎合情合理,输出的是加过一后的值,但后一个```m```却还没加就把数字打出来了。不过,从第二行```cout```的输出结果可以看出,它们都自己加了一,工作的挺正常。 139 | 140 | 我们可以从中看出,如果递增(减)运算符是写在某变量之前的(称作**前置运算符**或**前缀运算符**),那么递增(减)将立即执行,你所收集到的值也会是加(减)过后的样子;如果写在某变量之后(称作**后置运算符**或**后缀运算符**),这个变量的递增(减)操作虽然也会立即执行,但执行过后你取得的并不是加(减)过后的值,而是该变量原本的值复制得到的一份副本。当我们将递增(减)用在了复合表达式时,便需要额外注意这些问题了。除非必要,我们一般使用前置版本的运算符,以避免不必要的复制造成的性能损失。 141 | 142 | ------ 143 | 144 | 也许有聪明的读者,在思考能不能把值赋给一个右值,比如说数字之类的。 145 | 146 | 很遗憾,这种操作是不合法的——右值并没有储存其值的空间,哪怕你真的把值给了它,它也没地方存,只能是丢弃: 147 | 148 | ```cpp 149 | 233 = 666; // 错误,233 是个字面量,属于右值 150 | 1.5 = 3.4; // 错误,1.5 是个字面量,也属于右值 151 | ``` 152 | 153 | 另外,你也无法为一个常量赋值——常量是不可变的量,怎么能容许被赋值改变呢? 154 | 155 | ```cpp 156 | constexpr auto a = 233; 157 | const auto b = a * 3; 158 | a = 666; // 错误,constexpr 下的是常量中的常量,编译时就定型了,根本改不了 159 | b = 666; // 错误,带有 const 意味着你不得改变它的值 160 | ``` 161 | 162 | ------ 163 | 164 | 现在我们回顾一下: 165 | 166 | - 赋值,用于改变一个变量的值,如```a = 1 + b```。 167 | - 复合赋值,可以用在自增或自减的情况,如```a += 3```,它与```a = a + 3```是等价的。 168 | - 递增与递减,是复合赋值的特殊情况,为一个变量自行加一或减一,如```a++```与```++a```均和```a += 1```等价。它既可写在变量前,也可写在变量后,分别称作**前置运算符**(前缀运算符)与**后置运算符**(后缀运算符),这将会决定它赋值与取值的先后顺序。 169 | - 列表初始化等高级些的初始化方法,也适用于赋值。 170 | - 不能为一个右值或常量赋值。 171 | 172 | ### 习题 173 | 174 | - 不以计算机运行,直接口述下述程序的运行结果,然后再以计算机验证你的答案: 175 | 176 | ```cpp 177 | int n = 3; 178 | cout << n++ << endl; 179 | cout << ++n << endl; 180 | ``` 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /1/3-4.md: -------------------------------------------------------------------------------- 1 | # 转换状态 · 类型转换 2 | 3 | 篮子与瓶子最大的区别在于,一个用于装固体,一个用于装液体。 4 | 5 | 不过,篮子里其实也可以装液体——只不过最终液体会漏光;瓶子里也可以装固体——也许这固体得切成丁或是压扁成条才可能放得进去,也许根本就没办法放进去。 6 | 7 | 再看 C++ 中的数据类型,它们也各自有着各自的烦恼——整数虽然能变为浮点数,但浮点数变整数后会丢失小数部分;整数能转换为字符,但浮点数要转换为字符实在是不太方便,等等。 8 | 9 | 现在,我们就来看看它们各自的烦恼吧。 10 | 11 | ------ 12 | 13 | 最常见的转换操作莫过于算术上的类型转换了。我们时常会碰到各种不同数字间的类型转换,例如: 14 | 15 | - 某些情况下,我们需要抛弃浮点数的小数部分,例如 3.14 变为整数 3。 16 | - 为了截取低位的整数,原本占了 8 个字节的 long long 类型的数字需要压成仅占 1 字节的 char 类型。 17 | - 在整数与浮点数进行计算时,如果不先把整数转换为浮点数,计算后的结果便会与你所想象的有很大出入。 18 | - 当我们发觉原本的浮点数类型精度不够用时,需要将其转换为精度更高的浮点数类型。 19 | 20 | 为了能让接下来对类型转换的研究顺利进行,我们需要先回顾一下之前所学的一些基本数据类型: 21 | 22 | - 整数类型,依据占用的空间从小到大排序,分别有```bool```、```char```、```short```、```int```、```long```与```long long```。其中```bool```同样也专用于表达布尔值,```char```同样也是字符类型。 23 | - 浮点数类型,也依据占用的空间从小到大排序,分别由```float```、```double```与```long double```。 24 | - 给类型前加```unsigned```前缀,意味着该类型将不能表达负数,并且正数的表达范围会比原本类型大一倍。 25 | - 布尔值、浮点数不能使用```unsigned```前缀,只能由整数使用。 26 | 27 | ------ 28 | 29 | C++ 可以在暗中帮助我们完成大部分的转换——将在运算符右侧的类型转换为与左侧相同的类型,这种转换叫作**隐式转换**。 30 | 31 | 隐式转换中算术类型的转换遵循这样一些规则,很好理解: 32 | 33 | - 如果两侧均为整数类型,且左侧的类型比右侧的大,那么右侧的类型会被**提升**为左侧的类型,其中原本的数字不受影响。例如: 34 | 35 | ```cpp 36 | unsigned int un_i = 3; 37 | int i = 2; 38 | un_i + i == 5; // 右侧的 int 类型被提升为 unsigned int 类型 39 | long l = 5; 40 | l + un_i == 8; // 右侧的 unsigned int 类型被提升为 long 类型 41 | ``` 42 | 43 | - 如果两侧均为整数,且均比```int```小,则会先全部转换为```int```,再作具体的计算。这个规则不干扰上一条规则——如果欲进行的操作是赋值,那么计算完成后得到的```int```类型结果会被裁剪为与左侧相同的类型。例如: 44 | 45 | ```cpp 46 | unsigned short a = 1; 47 | unsigned char b = 2; 48 | auto c = a + b; // 两个均比 int 小的整数类型的运算结果类型是 int 类型,故 auto 推断为 int 类型 49 | char d = a + b; // 虽然运算结果是 int 类型,但在计算完成后就根据左侧类型自动裁剪为 char 类型了 50 | ``` 51 | 52 | - 如果左侧为浮点数类型,右侧的类型会自动转换为与左侧相同的浮点数类型。例如: 53 | 54 | ```cpp 55 | 1.5 + 2; // 右侧的 int 类型自动转换为 double 类型 56 | 1.5L + 2; // 右侧的 int 类型自动转换为 long double 类型 57 | ``` 58 | 59 | - 如果左侧为浮点数类型,且右侧为一个比```int```小的整数类型,右侧也将先提升为```int```类型再进行转换。例如: 60 | 61 | ```cpp 62 | short s = 1; 63 | 1.5 + s; // 先将 s 提升为 int 类型,再转换为 float 类型 64 | ``` 65 | 66 | - 如果左侧为布尔值类型,右侧为整数,则视右侧的值决定——右侧值为 0 时转换为 false,右侧值不为 0 时转换为 true。如果右侧为浮点数,则会先转换为```int```类型再进行判断。例如: 67 | 68 | ```cpp 69 | bool a = 1; // true 70 | bool b = 2; // false 71 | bool c = 23333; // true 72 | ``` 73 | 74 | ------ 75 | 76 | 能做到隐式转换的不仅是数字、字符,还有数组、指针: 77 | 78 | - 将数组转换为指针后,该指针指向的是数组的第一个元素。这种隐式转换本质上是数组到指针的**退化**(decay)。例如: 79 | 80 | ```cpp 81 | int arr[10]; 82 | int *p = arr; // p 指向 arr[0] 83 | ``` 84 | 85 | - 空指针(```nullptr```)能转换为任何一种指针类型。这种特性可用于初始化一个指针变量,也可用于标记某指针所指向的内容已经失效。例如: 86 | 87 | ```cpp 88 | int *i = nullptr; // 将 i 初始化为一个空指针 89 | int *j = &i; // 将 j 初始化为指向 i 的指针 90 | j = nullptr; // 再清空 j,使其成为空指针 91 | ``` 92 | 93 | - 任意非常量指针均能转换为```void *```指针,任意常量指针均能转换为```const void *```指针。例如: 94 | 95 | ```cpp 96 | int n = 3; 97 | int *i = &n; // 将 i 初始化为一个指向 n 的指针 98 | void *j = i; // i 可以无阻直接转换为 void * 指针 99 | ``` 100 | 101 | - 指向同一类型非常量的指针可转换为常量指针。例如: 102 | 103 | ```cpp 104 | int n = 3; 105 | int *i = &n; // i 指向 n,且如果通过 i 访问 n,n 可更改 106 | const int *j = i; // j 指向 n,且如果通过 j 访问 n,n 不可更改 107 | ``` 108 | 109 | 我们将在后续章节中,继续学习有关类类型的转换——为我们自己创造的类型定制转换规则。 110 | 111 | ------ 112 | 113 | 有些时候,我们不得不以某种方式强行改变对象的类型——当隐式转换不够用时,**显式转换**便派上用场了。 114 | 115 | 在正式介绍专用于显式转换类型的运算符之前,有必要严肃强调,强行转换对象很可能会导致不可预料的后果,在这么做之前一定要想清楚你正在做什么! 116 | 117 | - static_cast 118 | 119 | 这应该是最常见也最常用的转换运算符了。它可以接受除了常量向非常量转换以外的任意类型转换。 120 | 121 | 它的格式是这样的: 122 | 123 | > static_cast< *类型* >( *对象* ) 124 | 125 | 尖括号内填写想要转换到的类型,小括号内则是被转换的对象。另外,你可以填写一个引用类型,转换后的结果将会是一个左值。 126 | 127 | 使用它转换类型时,如果是将一个占用空间较大的整数向占用空间较小的整数类型转换(例如```int```转```short```),或是将精度较高的浮点数向精度较低的浮点数类型转换,会造成一定的数据丢失。 128 | 129 | 通常来讲,机器会丢弃高位部分的数据,例如: 130 | 131 | ```cpp 132 | int i = 65793; 133 | auto u_s = static_cast(i); // i 被裁剪为 257 134 | auto u_c = static_cast(i); // i 被裁剪为 1 135 | ``` 136 | 137 | 它同样可用于```void *```类型——我们使用```static_cast```指示其要特化的指针类型: 138 | 139 | ```cpp 140 | int a = 1; 141 | void *p = &a; // 此时指针 p 中虽然存储了变量 a 的地址,但我们无法直接将其转换回 int * 类型 142 | int b = *(static_cast(p)); // 将指针 p 转换为了具体的指针类型,使其可以被正确解引用 143 | ``` 144 | 145 | 146 | 147 | - const_cast 148 | 149 | 先前我们了解过,```static_cast```不能接受从常量到非常量的转换。要想突破这一层限制,我们可以先使用```const_cast```对常量解除其常量属性。请注意,这种转换操作**只对顶层```const```有效**;如果对底层```const```这么做,并尝试修改转换后得到的对象,其行为是未定义的,将产生不可预测的后果。 150 | 151 | 它的格式与```static_cast```类似,但所填的类型必须与被转换对象的非常量形式一致: 152 | 153 | > const_cast< *对应的类型* >( *对象* ) 154 | 155 | 例如: 156 | 157 | ```cpp 158 | int i = 1; 159 | const int &ref_i = i; // 这是一个顶层 const,表面上不可更改,实际指向的 i 可以更改 160 | const int c_i = 2; // 这是一个底层 const,它本身属性即为不可更改 161 | const_cast(ref_i) = 3; // 正确,可以消除顶层 const 162 | const_cast(c_i) = 3; // 错误,不可以消除底层 const 163 | ``` 164 | 165 | - reinterpret_cast 166 | 167 | 这种转换运算符用于从低层次的字节级别对某对象作出强制转换。它相较于```static_cast```更加“危险”,因为这种转换操作将无视所有的转换规则,C++ 将毫无保留地将浮点数的每一字节写入以目标类型创建的返回值对象。 168 | 169 | 如果用得正确,它可以帮助你转换一些不易转化为二进制位的类型到二进制位;如果胡乱使用,所造成的后果也将会是十分严重的。 170 | 171 | 它的格式也与其它转换运算符相仿: 172 | 173 | > reinterpret_cast< *类型* >( *对象* ) 174 | 175 | - 旧式转换 176 | 177 | 这种转换方式相当于上文提及的```static_cast```与```const_cast```的中和产物,现行标准下已不再使用,本手册不多作介绍。除非有特殊需求(如中国的 NOI 竞赛与 CSP 认证考级仍在使用 C++98 旧标准),我们都**不应当**使用这种转换方式。 178 | 179 | - dynamic_cast 将在后文介绍,它是 C++ 的面向对象中的重要的类型转换运算符。 180 | 181 | ### 习题 182 | 183 | - 尝试写一个程序,输入一个小数部分大于 5 位的浮点数,将其以类型转换的方式将其转换为仅 1 位小数的浮点数并输出。 -------------------------------------------------------------------------------- /1/4-1.md: -------------------------------------------------------------------------------- 1 | # 包裹中的包裹 · 语句与作用域 2 | 3 | 语言,本质上就是字、词、句的集合。在自然语言中,我们所说的话语都是在特定环境下才能正确理解的。特定的环境可以是发言者与倾听者的身份、在何时、在何地、发生过什么事情等等。这些内容可以被统一称作**上下文**(context)。在 C++ 中同样有上下文的概念,接下来我们就一探究竟。 4 | 5 | ------ 6 | 7 | 经过先前的学习,我们对声明变量的操作可以说是轻车熟路了: 8 | 9 | ```cpp 10 | int main() 11 | { 12 | int a = 1; 13 | int b = 2; 14 | return 0; 15 | } 16 | ``` 17 | 18 | 此时,我们在```main```函数的范围内可以直接使用```a```与```b```两个变量,也就是可以在花括号范围内使用它们。 19 | 20 | 花括号的用途不仅仅是```main```函数的边界,它还可以划定一片区域。在区域外的代码不能使用区域内的内容,但区域内的代码可以访问区域外的内容。举个例子,我们可以在```main```函数的花括号内再使用一对花括号: 21 | 22 | ```cpp 23 | int main() 24 | { 25 | int a = 1; 26 | { 27 | int b = 2; 28 | cout << a << b << endl; // 在这里可以访问变量 a 与 b 29 | } 30 | cout << a << endl; // 在这里只能访问变量 a,无法访问变量 b 31 | return 0; 32 | } 33 | ``` 34 | 35 | 很显然,```main```函数内的新的花括号将其内的代码与外界隔开,这个"外界"包括```main```函数的其余部分与```main```函数之外的全部。花括号内部可以访问外界的一切,但外界不得访问花括号内的内容。 36 | 37 | 要想将花括号内的私有内容公开出去,最简单的办法就是将私有变量的值交给外界储存: 38 | 39 | ```cpp 40 | int s; // 在外界准备一个空变量 41 | { 42 | int b = 2; // 在内部准备一个有值的私有变量 43 | s = b; // 将私有变量的值拷贝给外界的空变量 44 | } 45 | ``` 46 | 47 | 这种由花括号划定的区域就是**作用域**。作用域可以嵌套,也可以连续写多个。子作用域内还可以声明与外界重名的变量,声明后,这个名字指代的将会是刚刚定义的内部变量。而不是外界的变量: 48 | 49 | ```cpp 50 | int main() 51 | { 52 | int a = 1; // 可访问变量 a 53 | { 54 | int b = 2; // 可访问变量 a 与 b 55 | { 56 | int a = 3; // 可访问变量 a 与 b,这里的 a 指代的是这一行所定义的 a,而不是外界的 a 57 | } 58 | } 59 | { 60 | int d = 4; // 可访问变量 a 与 d 61 | } 62 | return 0; 63 | } 64 | ``` 65 | 66 | 变量既可在函数内部声明,也可在函数外部声明。在函数内声明的一切变量均可归类为局部变量。相对的,在没有其它限制的情况下,不在函数内声明的变量可归类为全局变量: 67 | 68 | ```cpp 69 | int globalVar = 1; // 声明一个全局变量 70 | int main() 71 | { 72 | int localVar = 2; // 声明一个局部变量 73 | { 74 | int localVar2 = 3; // 仍然是声明了一个局部变量 75 | } 76 | return 0; 77 | } 78 | ``` 79 | 80 | 全局变量在程序的任何位置都可被访问到。由于它方便快捷,因此常被用于快速编写算法。不过,它也有着命名污染的缺陷。在实际工程项目中,不应当直接使用全局变量。否则很容易因为变量命名问题而陡增项目维护难度。命名空间(namespace)是可靠的解决办法,我们将在后续章节中具体学习。 81 | 82 | ------ 83 | 84 | 作用域是实现各种编程范式的基础,它能将应当分清公私的内容隔开,从而不混淆不同部分逻辑所声明的数据。由此看来的是全局变量于局部变量之分,究其本质,是函数作用域与全局作用域的区分。 -------------------------------------------------------------------------------- /1/4-2.md: -------------------------------------------------------------------------------- 1 | # 对数据提问 · 条件语句 2 | 3 | 在生活中,我们经常需要对周边的事物作出一些判断: 4 | 5 | - 如果我抢到了限时出售的傻 Fu Fu 的 Miku,我会在到货时拍照发到自己的圈子里炫耀一番。 6 | - 如果明天不下雨且《姜子牙》已经上映,我会去电影院看这个电影,否则我还是继续在家里刷知乎吧。 7 | - 如果星巴克哪天能免费送咖啡,我会抽空去星巴克的门店坐一坐的。 8 | 9 | 不论以上的如果有多奇怪,它们都是对未来可能发生的某事作出了预判,并预先制定了行动方案。 10 | 11 | 计算机也可以这样。相比较于计算器,计算机多出了简单的判断能力,使其实际可做的事情要比计算器多得多。即使是二进制数字间的碰撞,我们也可以凭此做许多事了。 12 | 13 | 下面,我们将以 C++ 的条件语句,发挥计算机在逻辑判断方面的巨大潜力。 14 | 15 | ------ 16 | 17 | 回忆一下,我们之前学习了一些专用于判断的运算符,如大于、小于、或、与。现在,我们尝试使用它们决定某些代码是否执行: 18 | 19 | ```cpp 20 | int n; 21 | cin >> n; 22 | if (n < 10) cout << "n 小于 10!" << endl; 23 | ``` 24 | 25 | 我们先从键盘获取一个整数 ```n```,然后判断```n```是否小于 10.```if```语句可用于根据一段表达式的执行解雇哦,判断是否要执行其所包裹的语句。它的格式是这样的: 26 | 27 | ```cppp 28 | if (<条件>) <满足条件时执行的语句>; 29 | ``` 30 | 31 | 如果想执行多个语句,也可以用一对花括号跟在小括号后面,就像这样: 32 | 33 | ```cpp 34 | if (<条件>) 35 | { 36 | <语句1>; 37 | <语句2>; 38 | ... 39 | } 40 | ``` 41 | 42 | 小括号的条件其实就是诸如 ```k * x + b < y```、```a == 12 * 15``` 这类表达式,唯一的限制是表达式执行完后得是一个布尔值,或可以转换为布尔值。例如,整数 0 会被隐式转换为 ```true```,其它整数会被转换为 ```false```。 43 | 44 | 上述两种形式均可使用。前一种单语句的形式必须在末尾加分号,后一种用花括号包裹的形式则不需要这么做。 45 | 46 | ------ 47 | 48 | “如果”这个词也许够用了,但它仅仅是正面的假设。很多时候,我们不光要“如果”,还需要“否则”,以表达在某种假设下的相反形式的处理措施。 49 | 50 | 这时候,我们可以在 ```if``` 段落后使用 ```else```: 51 | 52 | ```cpp 53 | int n; 54 | cin >> n; 55 | if (n < 10) cout << "n 小于 10!" << endl; 56 | else cout << "n 不小于 10!" << endl; 57 | ``` 58 | 59 | 我们在先前的例子中加入了对 ```else``` 的使用,使程序能同时处理条件成立与不成立的两种情况。```else``` 的语法与```if```类似,不过不需要条件: 60 | 61 | ```cpp 62 | if (<条件>) ... 63 | else <不满足条件时执行的语句>; 64 | ``` 65 | 66 | 它同样也支持以花括号包裹的语句块,以执行多个语句: 67 | 68 | ```cpp 69 | if (<条件>) ... 70 | else 71 | { 72 | <语句1>; 73 | <语句2>; 74 | ... 75 | } 76 | ``` 77 | 78 | ```else``` 不能单独使用,它必须紧跟一处```if```语句,否则将因没有能依附的条件而报错。如果只想处理某个条件不成立的情况,我们可以变通地为```if```地条件取反来解决问题: 79 | 80 | ```cpp 81 | if (!(<条件>))... 82 | ``` 83 | 84 | ------ 85 | 86 | 很多时候,我们的问题并不能仅靠一个如果解决。要么是同一个条件可能有多种情况,要么是判断某个条件之前需要另一个条件作为前提。 87 | 88 | 我们先来看一个条件有多种情况的例子: 89 | 90 | ```cpp 91 | int score; 92 | cin >> score; 93 | cout << "评分等级: "; 94 | if (score >= 85) cout << "A 优秀" << endl; 95 | else if (score >= 70) cout << "B 良好" << endl; 96 | else if (score >= 60) cout << "C 合格" << endl; 97 | else cout << "D 不合格" << endl; 98 | ``` 99 | 100 | 这是一个简单的分数评价示例,它将 ```else``` 与 ```if``` 写在一起使用,以连续地判断数字的范围。 101 | 102 | ```else``` 后紧跟 ```if``` 的写法很简单: 103 | 104 | ```cpp 105 | else if (<另一个条件>) ... 106 | ``` 107 | 108 | 它既像 ```else``` 一样,其前必须有一个紧邻的```if```;也像```if```一样,拥有一个新的判断条件。并且,由于它可以连续书写,所以我们可以以此方便地表达对各种特定情况的处理逻辑。 109 | 110 | ------ 111 | 112 | 另一种特别的形式是嵌套的```if```: 113 | 114 | ```cpp 115 | if (n >= 85) 116 | { 117 | if (n == 100) cout << "S 满分" << endl; 118 | else if (n >= 95) cout << "A+ 非常优秀" << endl; 119 | else cout << "A 优秀" << endl; 120 | } 121 | else if ... 122 | ``` 123 | 124 | 考 85 分与考 100 分差别还是蛮大的,为了照顾考满分的人才的感受,我们可以对 85 分及以上的分数再作细分。在刚才补充的程序中,外层的 ```if``` 内部还包含了一批 ```if```。这相当于先判断分数是否不小于 85 分,如果能满足这个条件,再具体执行下一层的 ```if``` 判断。 125 | 126 | ```if``` 可以嵌套任意层数的 ```if```,只要能拿出具体的条件就能写出来: 127 | 128 | ```cpp 129 | if (age >= 18) 130 | { 131 | if (money >= 2000) 132 | { 133 | if (level >= 20) 134 | { 135 | cout << "满足入会条件" << endl; 136 | } 137 | } 138 | } 139 | ``` 140 | 141 | 不过写太多层 ```if``` 会让程序难以阅读。这时,我们还可以通过使用与运算符(```&&```)写出同样的逻辑: 142 | 143 | ```cpp 144 | if (age >= 18 && money >= 2000 && level >= 20) 145 | cout << "满足入会条件" << endl; 146 | ``` 147 | 148 | 另外,值得注意的是,要小心嵌套的 ```if``` 中 ```else``` 语句对 ```if ```语句的选取。```else``` 会优先选取最近的一个```if```,而如果所在层找不到能用的 ```if```,编译器将到上一层语句块寻找先前的 ```if```,直到找到为止。另外,花括号可以改变 ```else``` 语句的匹配行为。举个例子: 149 | 150 | ```cpp 151 | // 此段代码的 else 自动匹配到了内层的 if 152 | if (age >= 18) 153 | if (money >= 2000) 154 | cout << "满足入会条件" << endl; 155 | else cout << "不满足入会条件" << endl; 156 | 157 | // 此段代码的 else 自动匹配到了外层的 if,这需要花括号将此 else 与内部的 if 隔开 158 | if (age >= 18) 159 | { 160 | if (money >= 2000) cout << "满足入会条件" << endl; 161 | } 162 | else cout << "不满足入会条件" << endl; 163 | ``` 164 | 165 | 就程序的可读性而言,如果遇到了多路 ```if``` 嵌套的情况,请尽量使用花括号明确各个 ```if``` 执行哪些代码,以避免 ```else``` 语句跟错了 ```if```。 166 | 167 | ------ 168 | 169 | ```if``` 与 ```else``` 支撑着 C++ 程序的基本逻辑运转,是 C++ 程序中最重要、最不可或缺的一部分。在未来的学习与实践中,我们将经常使用它们,帮助我们书写富有逻辑性的程序。 -------------------------------------------------------------------------------- /1/4-3.md: -------------------------------------------------------------------------------- 1 | # 对重复操作的化简 · 迭代语句 2 | 3 | 计算机本质上是个高级机械,所以它很擅长做不断重复的工作。而将枯燥乏味的各种计算工作交给计算机来办,是再合适不过的事了。 4 | 5 | 我们接下来将继续深入,探索 C++ 是如何做到循环操作的。 6 | 7 | ------ 8 | 9 | 先来尝试一道简单的数学题: 10 | 11 | > 求 1 + 2 + 3 + ... + n 的结果,其中 n 是一个正整数。 12 | 13 | 如果知道 ```n``` 是几,我们也许可以这么写,简单粗暴: 14 | 15 | ```cpp 16 | // 设 n 为 10 17 | cout << 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 << endl; 18 | ``` 19 | 20 | 但如果我们事先不知道 ```n``` 的值呢? 21 | 22 | 此时,就该 ```for``` 上场了: 23 | 24 | ```cpp 25 | int n, sum = 0; // n 为输入的数字,sum 为要输出的累加数值 26 | cin >> n; 27 | for (int i = 1; i <= n; ++i) 28 | { 29 | // 这对花括号内的内容将执行 n 遍 30 | sum = sum + i; // 在第 i 遍时,将 i 累加到 sum 上 31 | } 32 | cout << sum << endl; 33 | ``` 34 | 35 | ```for``` 与先前的 ```if``` 有些相似,其后也跟着一对小括号,且小括号后也可跟单独一条语句或一对花括号组成的语句块。 36 | 37 | 它的格式如下: 38 | 39 | ```cpp 40 | // 第一种 41 | for (<初始化语句>; <循环条件>; <迭代操作>) <满足条件时执行的语句>; 42 | 43 | // 第二种 44 | for (<初始化语句>; <循环条件>; <迭代操作>) 45 | { 46 | <语句1> 47 | <语句2> 48 | ... 49 | } 50 | ``` 51 | 52 | 小括号内的东西有点多,我们逐个分析。 53 | 54 | 首先是初始化语句。你可以在这里定义一个或多个**同类型**的变量。例如,定义新变量```int i = 0``` 与赋值 ```ch1 = 'a', ch2 = 'b'``` 均是可以的。 55 | 56 | 再之后是循环条件。这里写的内容与 ```if``` 后跟的条件是一样的,是一个最终运算结果为布尔值的表达式。这个条件的运算结果将决定循环是否(再次)执行——为 ```true``` 时将执行一遍循环体,并进入下一轮判断;为 ```false``` 时则结束循环,执行循环体之后的内容。 57 | 58 | 最后的迭代操作可以是任何表达式,它在每一轮循环的结尾都会执行一次。很多时候,我们都会在此写诸如 ```i++``` 或 ```j -= 2``` 这类自增或自减的表达式。 59 | 60 | 我们再拿刚刚的例子具体剖析: 61 | 62 | ```cpp 63 | for (int i = 1; i <= n; ++i) 64 | ``` 65 | 66 | 第一部分很好理解,声明了个变量 ```i``` 并初始化为 ```1```。值得注意的是,在 ```for``` 语句的小括号内创建的变量仅在 ```for``` 语句的循环体内有效,出了这个范围便无法使用这类变量: 67 | 68 | ```cpp 69 | for (int i = 1; i <= n; ++i) 70 | { 71 | cout << i << endl; // 正确,可以使用 72 | } 73 | cout << i << endl; // 错误,无法使用由 for 声明的变量 i 74 | ``` 75 | 76 | 第二部分与第三部分一起看,可以发现 ```i``` 由此可以从 ```1``` 数到 ```n```。 77 | 78 | 稍微变一下刚刚的例子: 79 | 80 | ```cpp 81 | for (int i = 1; i < n; ++i) 82 | ``` 83 | 84 | 注意第二部分的小于等于改成了小于。这么做会导致其改为从 ```1``` 数到 ```n - 1```,而不是数到 ```n```。 85 | 86 | 为什么呢?我们不妨设想一下第 ```n - 1``` 次循环执行结束时的场景。在循环的末尾,先执行了 ```++i```,使 ```i``` 从原本的 ```n - 1``` 变为 ```n```,然后进入下一轮循环前的判断。由于 ```i``` 现在的值是 ```n```,已不满足 ```i < n``` 这个条件,便不再执行第 ```n``` 次循环,而继续执行循环体外的部分。而修改前的条件 ```i <= n``` 可以接受 ```i``` 等于 ```n``` 时的情况,所以可以执行第 ```n``` 次循环。 87 | 88 | 另外,```for``` 的小括号中三个部分均可省略,例如: 89 | 90 | ```cpp 91 | for (; tag == 's';) ...; // 只有条件 92 | for (;; count += 3) ...; // 只有迭代动作,无限循环 93 | for (;;) ...; // 什么都不写,无限循环 94 | ``` 95 | 96 | 哪怕三个部分一个都不写,也**必须**写两个分号占位。 97 | 98 | 有关无限循环的内容,我们会在后文的跳转语句部分具体了解。 99 | 100 | ------ 101 | 102 | 除了 ```for``` 以外,我们还可以使用 ```while``` 与 ```do...while``` 做到循环。相比较于 ```for``` 来讲,这两者更纯粹、更简单,因为它们只需要提供判断条件即可,不需要初始化与迭代操作。 103 | 104 | 我们先来看一下 ```while```: 105 | 106 | ```cpp 107 | // 第一种 108 | while (<条件>) <满足条件时执行的语句>; 109 | 110 | // 第二种 111 | while (<条件>) 112 | { 113 | <语句1> 114 | <语句2> 115 | ... 116 | } 117 | ``` 118 | 119 | 当程序执行到此处时,会先检查条件。条件满足时,才会进行第一次循环,否则会连第一次循环都不执行,转而执行循环体外的下一条语句。之后便是检查条件与执行循环体的交替过程,条件失效时便不再执行循环体。 120 | 121 | 它其实也可以看作是 ```for``` 的基础版。如果我们将 ```for``` 改为以 ```while``` 实现,将会是这样的: 122 | 123 | ```cpp 124 | // for 形式 125 | for (int i = 0; i < n; ++i) cout << i << endl; 126 | // while 形式 127 | int i = 0; 128 | while (i < n) 129 | { 130 | cout << i << endl; 131 | ++i; 132 | } 133 | ``` 134 | 135 | 很明显,用 ```while``` 实现与 ```for``` 类似的功能更是费劲,而且也将原本仅在循环体内有效的变量 ```i``` 拿了出来,导致循环体外也能访问到变量 ```i``` 了。是否需要这么做,还需要看具体需求。 136 | 137 | ------ 138 | 139 | 我们再来看一下 ```do...while```: 140 | 141 | ```cpp 142 | // 第一种 143 | do <满足条件时执行的语句> while (<条件>); 144 | 145 | // 第二种 146 | do 147 | { 148 | <语句1> 149 | <语句2> 150 | ... 151 | } while (<条件>); 152 | ``` 153 | 154 | 与 ```while``` 不同的是,不论条件怎样,```do...while``` 总是会先执行一次循环体,然后再作判断。当条件不满足时仍会结束循环。 155 | 156 | 如果不好理解,你可以想象它是先无条件执行了一遍循环体,然后再执行同样条件的 ```while``` 循环: 157 | 158 | ```cpp 159 | <循环体...> 160 | while (<条件>) <循环体...>; 161 | ``` 162 | 163 | 尤其需要注意的是,```do...while``` 的末尾**一定要加分号**,切记! 164 | 165 | ------ 166 | 167 | 循环与什么最搭配呢?数组这类东西当然是最适合的了! 168 | 169 | 数组的下标是从 0 开始数起的一系列自然数,而 ```for``` 很是擅长数下标: 170 | 171 | ```cpp 172 | int a[5] = { 1, 2, 3, 4, 5 }; 173 | for (int i = 0; i < 5; ++i) cout << a[i] << endl; 174 | ``` 175 | 176 | 我们可以看到,这段程序将 ```i``` 从 0 数到 4,正好用在了选取数组 ```a``` 的共 5 个元素上。 177 | 178 | 是否有更简单的办法呢?有,方法是使用**范围 for**: 179 | 180 | ```cpp 181 | for (auto n : a) cout << n << endl; 182 | ``` 183 | 184 | 看起来是否特别简洁? 185 | 186 | 这便是范围 for,它的格式如下: 187 | 188 | ```cpp 189 | for (<变量类型> <变量名> : <可被迭代的对象>) ... 190 | ``` 191 | 192 | 变量名可以写成值传递的形式,也可以写成引用传递的形式。我们来看两个例子: 193 | 194 | ```cpp 195 | // 值传递 196 | for (auto n : a) n *= 2; 197 | // 执行结束后,a = { 1, 2, 3, 4, 5 },没有任何变化 198 | 199 | // 引用传递 200 | for (auto &n : a) n *= 2; 201 | // 执行结束后,a = { 2, 4, 6, 8, 10 },每一个元素均被修改 202 | ``` 203 | 204 | 回忆一下先前对引用的知识,引用本质上就是对象的别名。如此一来,引用传递的意思,其实就相当于将所取得的那个元素本身传递过来,于是对此变量的各种修改也同样作用在了对应的各个元素。 205 | 206 | 值传递很好理解。在值传递模式下,每个元素都会被拷贝一次,我们对此变量的各种修改仅仅是相当于修改了对应元素的副本。因此,我们不论作出如何的修改,都不会影响到对应的元素本身。 207 | 208 | 最后再来看一下可被迭代的对象。除了数组之外,它还支持对列表初始化得到的常量列表进行遍历: 209 | 210 | ```cpp 211 | for (auto ch: { 'm', 'i', 'k', 'u' }) cout << ch; 212 | cout << endl; 213 | ``` 214 | 215 | 在后续章节中,我们还将了解由标准库提供的许多容器,它们也可以用在范围 ```for``` 上。 216 | 217 | ------ 218 | 219 | 循环同样是 C++ 程序中不可或缺的一部分。```for```、```while``` 与 ```do...while``` 三者为我们提供了条件,使得程序进行大规模运算成为了可能。借助于现代计算机强劲的性能,我们将能由此摆脱各种繁冗纷杂的计算。 -------------------------------------------------------------------------------- /1/4-4.md: -------------------------------------------------------------------------------- 1 | # 跃迁至圈外 · 跳转语句 2 | 3 | 经过先前对条件判断与条件循环的学习,我们已经可以据此构造出基本的逻辑流程了。不过,仅凭这么点工具似乎仍然不够用。 4 | 5 | 接下来,我们还将再度深入,了解更进一步的流程控制方法,让整个程序的流程变得完全可控。 6 | 7 | ------ 8 | 9 | 先来看个简单的问题: 10 | 11 | > 如何做到输入 n 个数字,再求出它们的和? 12 | 13 | 这应该不难,我们先知道```n```是多少,再连续接收```n```次数字,接收一次就累加一次: 14 | 15 | ```cpp 16 | int n; 17 | cin >> n; // 先获取 n 18 | int sum = 0; // 初始化一个存贮累加结果的变量 19 | for (int i = 0; i < n; ++i) // 循环 n 次 20 | { 21 | int a; // 每轮循环都新声明一个变量 22 | cin >> a; // 每轮循环都接收一次数字 23 | sum += a; // 将接收到的数字累加进 sum 24 | } 25 | cout << sum << endl; // 输出结果 26 | ``` 27 | 28 | 不过,如果不知道```n```这个确切的循环次数,这种程序就不能解决问题了。 29 | 30 | 再来看一下修改后的问题: 31 | 32 | > 如何做到输入若干个数字,当某次输入的数字为 0 时就结束输入,再求出它们的和? 33 | 34 | 由于输入数字的数量是未知的,我们需要不断地获取输入,这就需要无限循环地执行获取输入与累加的过程。但同时,我们又需要再输入的数字为 0 时结束循环。 35 | 36 | 仅凭单纯的循环与判断实现这样的逻辑很是麻烦。现在,我们尝试一下更简便的做法: 37 | 38 | ```cpp 39 | int sum = 0; // 初始化一个存贮累加结果的变量 40 | for (;;) // 无限循环 41 | { 42 | int a; // 每次循环都新声明一个变量 43 | cin >> a; // 每次循环都接受一次数字 44 | if (a == 0) break; // 如果 a 为 0,就跳出循环 45 | else sum += a; // 如果 a 不为 0,继续累加;这里的 else 其实也是可以不写的 46 | } 47 | cout << sum << endl; // 输出结果 48 | ``` 49 | 50 | ```break``` 用于跳出当前所在的循环体,可以在 ```for```、```while```与```do...while``` 中使用。在这个例子中,我们在一句```if```内使用了 ```break```,使得循环得以在特定条件下主动跳出。 51 | 52 | 要使用```break```,只需要保证它是在某个可被跳出的语句块内,没有其它限制。下面的例子展示了其跳出循环体的能力: 53 | 54 | ```cpp 55 | while (10 > 6) break; // 执行了一次判断,进入循环后立即跳出 56 | do break while (15 < 30); // 还没来得及执行判断,循环就被跳出 57 | for (;;) break; // 刚进入无限循环就被跳出 58 | ``` 59 | 60 | ------ 61 | 62 | 很多时候,```break``` 被用于跳出**死循环**,或也可以称为**无限循环**。顾名思义,这种循环是无法自发停止的。 63 | 64 | 要写出一个死循环,只需要保证循环条件始终为真(```true```)就可以了: 65 | 66 | ```cpp 67 | while (true) ...; // 一个最经典的 while 死循环 68 | while (2 * 3 == 6) ...; // 2 * 3 == 6 的结果始终为 true,形成死循环 69 | while (a - 1 < a) ...; // a - 1 < a 恒成立,结果始终为 true,形成死循环 70 | do ... while (true); // 一个最经典的 do ... while 死循环 71 | for (;;) ...; // 一个最经典的 for 死循环,其中的条件可被省略,默认始终为 true 72 | for (;true;) ...; // 将隐含的条件始终为 true 明确写出来的样子 73 | ``` 74 | 75 | 死循环可用于不定量数据的输入、输出与不间断的数据监视、数据处理等。很明显,这类死循环在外界条件发生变化时,便需要使用 ```break``` 结束循环,例如数据全部读写完成、用户发起了停止的指令等。 76 | 77 | ------ 78 | 79 | 有时候,我们需要在循环不结束的情况下,只结束这一轮的循环,这类需求并不少见。我们来看个具体的问题: 80 | 81 | > 请列举 200 以内的素数 82 | 83 | 素数是什么?只能被这个数本身和 1 整除的大于 1 的自然数就是素数。像 2、3、5、7、23、51 这类就是素数,而像 4、6、10、30 这类很明显可以被其它数字整除的数字就不是素数。 84 | 85 | 根据以上的概念,我们可以确定基本思路,是将每个数字都拿来试除一遍。只要出现一次余数为 0 的情况,就可以断定它不是素数。 86 | 87 | 具体的实现如下: 88 | 89 | ```cpp 90 | bool flags[200]; // 用于存储 200 以内的各数是否为素数的标识 91 | for (int i = 2; i < 200; ++i) // 从 2 数到 200,逐个检查 92 | { 93 | flags[i] = true; // 先假设第 i 个数是素数的标识 94 | for (int j = 2; j < i; ++j) // 从 2 数到 i - 1,逐个与 i 试除 95 | { 96 | if (i % j == 0) // 当余数为 0 时,相当于被整除 97 | { 98 | flags[i] = false; // 设置第 i 个数不是素数的标识 99 | break; // 结束这一层循环,继续对下一个数的检查 100 | } 101 | } 102 | } 103 | 104 | for (int i = 2; i < 200; ++i) // 逐个输出判断结果 105 | if (flags[i]) cout << "第" << i << "个数是素数" << endl; 106 | ``` 107 | 108 | 这个程序先定义了一个很大的数组,用于存储这 200 为数字是否为素数的标识。外围的循环从 2 数到 199,列举每个数字;内围的循环则是从 2 数到这个数字的前一个数字,分别将它们作为除数与这个数字相除。当列举的所有数字部都无法整除它时,就可以断定它是一个素数了。 109 | 110 | 不过,这个程序还有可以改进的空间。在刚刚的程序中,我们将从 2 到 i - 1 的每个数字都用了一遍,但实际上我们只需要拿素数作除法就可以了: 111 | 112 | ```cpp 113 | bool flags[200]; 114 | for (int i = 2; i < 200; ++i) 115 | { 116 | flags[i] = true; 117 | for (int j = 2; j < i; ++j) 118 | { 119 | if (!flags[i]) continue; // 如果不是素数,停止本轮循环,直接进入下一轮循环 120 | if (i % j == 0) 121 | { 122 | flags[i] = false; 123 | break; 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | ```continue``` 用于结束其所在循环体的本轮循环。它与 ```break``` 的区别在于,```break``` 会直接结束整个循环,而 ```continue``` 则不会结束整个循环,仅仅是结束这一轮循环而已,下一轮循环会立即开始。 130 | 131 | 它的使用条件与 ```break``` 也没有太大的出入: 132 | 133 | ```cpp 134 | while (true) continue; // 无限循环,并且永远不能被跳出 135 | do continue while (true); // 上一个无限循环的另一种形式 136 | for (int i = 0; i < 100; ++i) // 一个只输出奇数的例子 137 | { 138 | if (i % 2 == 0) continue; // 如果为偶数,结束本轮循环,下面的语句也就不会被执行 139 | cout << i << endl; // 打印当前列举到的数字 140 | } 141 | ``` 142 | 143 | 回到刚刚过滤素数的例子。从数学角度来看,要知晓一个数字是否为素数,只需要将其与比此数字小的所有数字相除一遍就可以了,无需连同非素数也来一遍——重复的非素数与其之前的部分素数有公因数,因此只除以一遍这个素数便足矣。 144 | 145 | 所以,```continue``` 在修改后程序中的作用就很明显了,其实就是判断不为素数为真的情况下,结束此轮循环,避免无用的运算。在执行下一轮循环前,```for``` 语句中的 ```++j``` 会照常执行。 146 | 147 | ------ 148 | 149 | 也许,```if``` 和 ```else``` 够我们拿来用作判断了,但碰到大批量判断某个变量的各种值时,一个一个地写 ```if``` 和 ```else``` 实在是很费事: 150 | 151 | ```cpp 152 | if (n == 1) target = "原木"; 153 | else if (n == 2) target = "木板"; 154 | else if (n == 3) target = "圆石"; 155 | else if (n == 4) target = "沙子"; 156 | ... 157 | ``` 158 | 159 | 幸好,C++ 提供了 ```switch```,它可以帮助我们轻松地书写大规模对同一个变量的判断逻辑: 160 | 161 | ```cpp 162 | switch(n) 163 | { 164 | case 1: 165 | target = "原木"; 166 | break; 167 | case 2: 168 | target = "木板": 169 | break; 170 | case 3: 171 | target = "圆石"; 172 | break; 173 | case 4: 174 | target = "沙子": 175 | break; 176 | ... 177 | } 178 | ``` 179 | 180 | ```switch``` 后跟一对小括号,其中是欲判断的变量。在这之后的一对花括号内有若干个以 ```case``` 为开头、冒号为末尾的标记,其中 ```case``` 后以空格隔开一个**常量**。```switch``` 语句会执行其中一个 ```case``` 标记后的语句,执行哪一个取决于被判断的变量与其中哪个标识对应的常量相等。从寻到的标记开始,除非遇到 ```break``` 语句,否则将会一直执行到 ```switch``` 语句块的末尾。 181 | 182 | 它的格式是这样的: 183 | 184 | ```cpp 185 | switch (<被判断的值>) 186 | { 187 | case <常量1>: 188 | <值等于常量1时执行的一系列语句> 189 | case <常量2>: 190 | <值等于常量2时执行的一系列语句> 191 | ... 192 | default: 193 | <没有任何常量与给定值相等时执行的逻辑> 194 | } 195 | ``` 196 | 197 | 198 | 199 | ```default``` 用于处理被判断的值不等于 ```switch``` 中任意一个 ```case``` 后跟的变量的情况。一般来讲,我们都应当在 ```switch``` 中写一个 ```default``` 标识,哪怕真的没啥事可做: 200 | 201 | ```cpp 202 | switch (...) 203 | { 204 | default: 205 | // 如果无事可做,最好也留个 default 206 | } 207 | ``` 208 | 209 | 为什么非得留个 ```default``` 呢?这算是一种良好的习惯,它可以帮助后来阅读与修改这个程序的人(包括你自己)完整理解这段选择逻辑的行为,不至于出现错误的改正。用大白话讲,如果没加 ```default ``` 又被后来者看到了,他很可能会补上一个 ```default```,并添加一些违背原作者意愿的逻辑,得不偿失。 210 | 211 | ```break``` 不光可用于跳出循环体,也可以用于跳出 ```switch``` 语句块。有些人会想当然地以为,从一个 ```case``` 开始,执行到下一个 ```case``` 时,会自动跳出 ```switch```,实际上却不是这样: 212 | 213 | ```cpp 214 | switch(3) 215 | { 216 | default: // 从这里开始还算正常 217 | cout << "default" << endl; 218 | case 1: // 到这里它是不会停的! 219 | cout << 1 << endl; 220 | case 2: // 到这里仍然不会停! 221 | cout << 2 << endl; 222 | } 223 | ``` 224 | 225 | 这段程序执行后,三行 ```cout``` 都会被执行!所以,要想各个 ```case``` 互不干扰,一定要记得在各段 ```case``` 的末尾加 ```break```。 226 | 227 | 这种特性并非一无是处。如果我们需要整合一批 ```case``` 执行同一批逻辑,使用刚刚所谓不停执行的特性会方便许多: 228 | 229 | ```cpp 230 | switch(n) 231 | { 232 | case 1: 233 | case 3: 234 | case 5: 235 | cout << "n 为 1, 3 或 5" << endl; 236 | break; // 别忘了! 237 | case 2: 238 | case 4: 239 | default: 240 | cout << "n 为 2, 4 或其它值" << endl; 241 | // 如果这段 case 在 switch 语句块的末尾,break 写不写都一样 242 | break; 243 | } 244 | ``` 245 | 246 | ------ 247 | 248 | 至此,我们便掌握了一切在 C++ 中实现结构化程序逻辑的基本工具。它们是程序中最基本的组成部分,熟练使用它们,可以帮助你构筑无限的可能! 249 | 250 | -------------------------------------------------------------------------------- /1/5-1.md: -------------------------------------------------------------------------------- 1 | # 化繁为简 · 函数声明与使用 2 | 3 | 先来看个简单的问题: 4 | 5 | > Steve 新建造了一个海底农场,专用于大规模地种植海带,用以生产绿色、快捷的干海带块燃料。每个干海带块都需要 9 片海带进行生产。请问,已知他此次收获的海带数量,他最多可以加工出多少个干海带块? 6 | 7 | 不难看出,这其实也就是要我们作一次除法题,直接给出总数除以 9 后去掉小数部分的结果就好了。 8 | 9 | ```cpp 10 | // ... 11 | int main() 12 | { 13 | int kelps; // 储存海带数量的变量 14 | cin >> kelps; // 接受海带数量 15 | int dry_kelp_blocks = kelps / 9; // 计算干海带块的数量并存储 16 | cout << dry_kelp_blocks << endl; // 输出干海带块的数量 17 | return 0; 18 | } 19 | ``` 20 | 21 | 不过,事情并没有就此结束: 22 | 23 | > Steve 卖干海带块赚了一大把绿宝石,而现在他想把绿宝石压制成块储存起来。9 个干海带块换得一块绿宝石,且 9 块绿宝石可以压制为一个绿宝石块。他想知道,已知有多少个干海带块,求他最终能获得多少个绿宝石块? 24 | 25 | > Steve 用大把的绿宝石块买了一个铁傀儡刷怪塔,现在他又有了大把大把的铁锭,他想将铁锭也压制成块进行存储。9 个铁锭可以合成一个铁块。已知铁锭的数量,求能支撑多少个铁块? 26 | 27 | 也许您会想到再写一些除法算式来解决问题,不过,重复写同样的语句是很烦人的事。有没有更简洁的办法呢? 28 | 29 | 答案是肯定的,解决方案便是将算式写成一个函数: 30 | 31 | ```cpp 32 | int make(int n) 33 | { 34 | return n / 9; 35 | } 36 | ``` 37 | 38 | 这是什么东西?看着有点像先前的```main()```啊? 39 | 40 | 我们先别急着理解它,而是看看开头那段程序是怎么用函数“改进”的: 41 | 42 | ```cpp 43 | int main() 44 | { 45 | int kelps; 46 | cin >> kelps; 47 | int dry_kelp_blocks = make(kelps); // 注意这里,kelps / 9 被换掉了 48 | cout << dry_kelp_blocks << endl; 49 | return 0; 50 | } 51 | ``` 52 | 53 | 原本的```kelps / 9```被替换成了```make(kelps)```后,程序仍然能像之前那样输出正确的结果,而之前称之为函数的莫名其妙的一段程序也有```make(int n)```和```n / 9```的字样。者之间死于有种关联,使得编译器把```make(kelps)```当成了```kelps / 9```来解释。 54 | 55 | 实际上,刚刚的```make(int n)```就是一个函数。函数(function)本质上就是一段被命名了的代码块,可以被重复执行,且无需重复去写一样的代码块。它可以接受 0 个或多个参数,并且它可以选择返回一个结果数值。 56 | 57 | 听起来有些云里雾里,不太容易理解。我们可以用这张图来描述它到底是干什么的: 58 | 59 | > 图 60 | 61 | 当使用```make(kelps)```时,```kelps```储存的值(假设为```233```)被传进了名为```make```的函数中,而```make```将传进来的这个值赋予了新的名字```n```。此时在函数内部便多了个局部变量```n```,其储存的值正是刚刚传过来的```233```。这个函数会对局部变量```n```与数字 9 相除,得到的结果便代表着```make(kelps)```的值。```kelps```在这里被称作实参,是实际参数的简称;而在这个函数开头定义的```n```在这里被称作形参,是形式参数的坚称。这个函数对局部函数```n```做出计算后,将结果值以```return```语句返回给了使用```make(kelps)```位置的表达式,这个结果值也被称作返回值。 62 | 63 | 我们无需立即就去理解透彻实参、形参和返回值的概念,现在首要的任务是先搞懂函数究竟是什么,以及为什么我们要使用它、它能为我们带来什么。 64 | 65 | 前面我们有了解到,函数其实就是一段有名字的程序,我们可以在其它函数(包括```main```,它本质上其实也就是一个函数)内部使用它们,而且可以重复地使用。既然可以重复地使用,我们便可以将需要执行多次地程序段写成一个函数,这样便不需要费事地重复写一样的程序了。 66 | 67 | 我们会在未来学习更多用于减少代码量的“偷懒”方法,而要理解更多的“偷懒”方法,必须先理解函数。更具体的剖析,将在下一章详细讲解。 68 | 69 | 在本章的最后,我们将 Steve 的额外问题解决一下吧。下面是计算绿宝石块数量的程序: 70 | 71 | ```cpp 72 | #include 73 | using namespace std; 74 | 75 | int make(int n) // 接受一个参数 n,返回一个类型为 int 的值 76 | { 77 | return n / 9; // 返回 n 除以 9 取整的结果 78 | } 79 | 80 | int main() // 整个程序的入口 81 | { 82 | cout << "请输入干海带块的数量: "; 83 | int n; 84 | cin >> n; 85 | 86 | // 思考一下,make(make(n)) 是什么意思? 87 | cout << endl << "绿宝石块的数量为 " << make(make(n)) << endl; 88 | 89 | return 0; 90 | } 91 | ``` 92 | 93 | 这是我们第一次完整地写一次带函数地程序。值得注意地是,函数```make```是写在函数```main```之前的,这里就需要提到初学者很容易栽的坑之一了————函数在使用前必须声明。 94 | 95 | 什么意思呢?我们先试试把```make```拿到·```main```后面试试: 96 | 97 | ```cpp 98 | // ... 99 | int main() 100 | { 101 | // ... 102 | } 103 | 104 | int make(int n) 105 | { 106 | // ... 107 | } 108 | ``` 109 | 110 | 将修改后的程序交给编译器,它会告诉你找不到哪里有什么```make```函数。 111 | 112 | 很奇怪吧?因为编译器很“笨”,它不会先把整个程序通读一遍,而是老老实实一个字一个字地读。换句话说,如果把```make```函数写在```main```函数后头,编译器在解决掉```main```函数之前,是根本不会“感受”到```main```函数存在的。 113 | 114 | 那怎么才能在将```make```函数放在```main```函数后时,能让编译器正确识别呢?答案很简单,在```main```函数之前和编译器先打声招呼就好了: 115 | 116 | ```cpp 117 | // ... 118 | int make(int); // 在使用前声明一个尚未具体定义的函数 119 | 120 | int main() 121 | { 122 | // ... 123 | } 124 | 125 | int make(int n) 126 | { 127 | // ... 128 | } 129 | ``` 130 | 131 | 这个声明基本上与定义函数的格式一致,不过后面以花括号括起的代码段被替换为了一个分号,以表示这仅仅是事先声明有这个函数而已,定义的事情“以后再说”。 132 | 133 | 下面两种声明语法是等价的————在函数声明中,您可以不为参数命名: 134 | 135 | ```cpp 136 | int make(int); 137 | int make(int n); 138 | ``` 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 这是 C++ 的世界! 2 | 3 | > 最近该电子书将准备更新 4 | 5 | 6 | 7 | 知识共享许可协议本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。 8 | 9 | 10 | 11 | * [序](first.md) 12 | 13 | ### 第一弹 · 新的征程 14 | 15 | **第一章 · 初来乍到** 16 | 17 | * [第一节 · 刷新认知 · 编写第一道程序](1/1-1.md) 18 | * [第二节 · 欲工必先求利器 · 编程环境配置](1/1-2.md) 19 | * [第三节 · 本源之论 · 基础计算机知识扫盲](1/1-3.md) 20 | * [第四节 · 篮子与瓶子 · 初识类型](1/1-4.md) 21 | * [第五节 · 思维的体操 · 初识控制流](1/1-5.md) 22 | 23 | **第一弹 · 第二章 · 数据鉴解** 24 | 25 | * [第一节 · 常态 · 基本内置类型与变量](1/2-1.md) 26 | * [第二节 · 融合态 · 复合类型](1/2-2.md) 27 | * [第三节 · 固化态 · 常量限定符](1/2-3.md) 28 | * [第四节 · 拟态 · 自动推断类型](1/2-4.md) 29 | * [第五节 · 叠加态 · 自定义数据结构](1/2-5.md) 30 | 31 | **第一弹 · 第三章 · 计算术** 32 | 33 | * [第一节 · 简单的数学课 · 初识表达式](1/3-1.md) 34 | * [第二节 · 数字间的反应 · 初识运算符](1/3-2.md) 35 | * [第三节 · 传送带 · 赋值操作](1/3-3.md) 36 | * [第四节 · 转换状态 · 类型转换](1/3-4.md) 37 | * [第五节 · 剥皮的技巧 · 数组与成员访问](1/3-5.md) 38 | 39 | **第四章 · 逻辑风暴** 40 | 41 | * [第一节 · 包裹中的包裹 · 语句与作用域](1/4-1.md) 42 | * [第二节 · 对数据提问 · 条件语句](1/4-2.md) 43 | * [第三节 · 对重复操作的化简 · 迭代语句](1/4-3.md) 44 | * [第四节 · 跃迁到圈外 · 跳转语句](1/4-4.md) 45 | * [第五节 · 缓兵之计 · 异常处理语句](1/4-5.md) 46 | 47 | **第五章 · 函数探幽** 48 | 49 | * [第一节 · 化繁为简 · 函数声明与使用](1/5-1.md) 50 | * [第二节 · 以不变应万变 · 深入参数与返回值](1/5-2.md) 51 | * [第三节 · 总指挥中心 · 主函数](1/5-3.md) 52 | * [第四节 · 分身术 · 函数重载](1/5-4.md) 53 | * [第五节 · 自助服务 · 默认实参与函数指针](1/5-5.md) 54 | 55 | **第六章 · 标准库** 56 | 57 | * [第一节 · 军械库 · 初识标准库](1/6.md) 58 | * [第二节 · 可供理解的数据 · 字符串库](1/6-2.md) 59 | * [第三节 · 无限车厢 · 向量库](1/6-3.md) 60 | * [第四节 · 游走于每一处 · 迭代器](1/6-4.md) 61 | * [第五节 · 瓶子与漏水防护 · 数组库与再探原生数组](1/6-5.md) 62 | 63 | **第七章 · 类之传奇** 64 | 65 | * [第一节 · 系统的构造 · 类的定义与封装](1/7-1.md) 66 | * [第二节 · 高级容器 · 类类型](1/7-2.md) 67 | * [第三节 · 询问自己 · this指针](1/7-3.md) 68 | * [第四节 · 公共场所 · 类作用域与静态成员](1/7-4.md) 69 | * [第五节 · 亮相前的准备 · 类的构造函数](1/7-5.md) 70 | 71 | ### 第二弹 · 技术革命 72 | 73 | **第八章 · 常规武器** 74 | 75 | * [第一节 · 取之不竭 · 顺序容器](2/8-1.md) 76 | * [第二节 · 一一对应 · 关联容器](2/8-2.md) 77 | * [第三节 · 瑞士军刀 · 泛型算法](2/8-3.md) 78 | * [第四节 · 摄取与排放数据 · 文件输入输出](2/8-4.md) 79 | * [第五节 · 电报机 · 字符串流](2/8-5.md) 80 | 81 | **第九章 · 掌控之力** 82 | 83 | * [第一节 · 无中生有 · 初识动态内存](2/9-1.md) 84 | * [第二节 · 封印符文 · 智能指针](2/9-2.md) 85 | * [第三节 · 瓶子工厂 · 动态数组](2/9-3.md) 86 | * [第四节 · 意念造山河 · lambda表达式](2/9-4.md) 87 | * [第五节 · 瞬移术 · 右值引用与对象移动](2/9-5.md) 88 | 89 | **第十章 · 运算工具** 90 | 91 | * [第一节 · 机械臂的自我修养 · 初识重载运算符](2/10-1.md) 92 | * [第二节 · 换一种走路的方式 · 递增递减运算符定义](2/10-2.md) 93 | * [第三节 · 换一种交流的方式 · 成员访问运算符定义](2/10-3.md) 94 | * [第四节 · 换一种干活的方式 · 函数调用运算符定义](2/10-4.md) 95 | * [第五节 · 换一种变脸的方式 · 类型转换运算符定义](2/10-5.md) 96 | 97 | **第十一章 · 构造世界** 98 | 99 | * [第一节 · 从原子到宇宙 · 面向对象概述](2/11-1.md) 100 | * [第二节 · 宗族的继承 · 基类与派生类](2/11-2.md) 101 | * [第三节 · 进化论 · 虚函数与抽象基类](2/11-3.md) 102 | * [第四节 · 遗产分配 · 访问控制与派生类作用域](2/11-4.md) 103 | * [第五节 · 偷懒的诀窍 · 合成函数与虚析构](2/11-5.md) 104 | 105 | 106 | **第十二章 · 模板蓝图** 107 | 108 | * [第一节 · 绘制蓝图 · 初试定义模板](2/12-1.md) 109 | * [第二节 · 容器类型的传递 · 转发模板](2/12-2.md) 110 | * [第三节 · 原地不动 · 深入理解移动操作](2/12-3.md) 111 | * [第四节 · 无限剑制 · 可变参数模板](2/12-4.md) 112 | * [第五节 · 特殊人格 · 模板特例化](2/12-5.md) 113 | 114 | **第十三章 · 特种部队** 115 | 116 | * [第一节 · 匹配想法 · 正则表达式](2/13-1.md) 117 | * [第二节 · 铁索连环 · 变长元组tuple](2/13-2.md) 118 | * [第三节 · 逻辑的空间 · 定长状态表bitset](2/13-3.md) 119 | * [第四节 · 不可知的未来 · 随机数](2/13-4.md) 120 | * [第五节 · 有条不紊 · 格式化输入输出](2/13-5.md) 121 | 122 | **第十四章 · 统筹总领** 123 | 124 | * [第一节 · 划分领地 · 命名空间](2/14-1.md) 125 | * [第二节 · 安全部门 · 异常处理库](2/14-2.md) 126 | * [第三节 · 摆脱单亲状态 · 多重继承与虚继承](2/14-3.md) 127 | * [第四节 · 小别致 · 枚举与联合类](2/14-4.md) 128 | * [第五节 · 俄罗斯套娃 · 局部类与嵌套类](2/14-5.md) 129 | 130 | ### 番外篇 · 语林外史 131 | 132 | * [这片土地的旧时光 · 详解 C++98 与新版 C++ 的不同](ex/1.md) 133 | * [与硬件背靠背 · C++、C语言与汇编的协同](ex/2.md) 134 | * [同时进行的任务 · 并行与并发库](ex/3.md) 135 | * [启动《Minecraft》 · 系统调用库](ex/4.md) 136 | * [地心探索 · 内存分配控制](ex/5.md) 137 | * [类型身份证 · RTTI](ex/6.md) 138 | * [牛奶还是奶牛 · 位域与字节序](ex/7.md) 139 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [索引](README.md) 4 | * [序](first.md) 5 | * [关于作者](about.md) 6 | 7 | ## 第一弹 · 第一章 · 初来乍到 8 | 9 | * [第一节 · 刷新认知 · 编写第一道程序](1/1-1.md) 10 | * [第二节 · 欲工必先求利器 · 编程环境配置](1/1-2.md) 11 | * [第三节 · 本源之论 · 基础计算机知识扫盲](1/1-3.md) 12 | * [第四节 · 篮子与瓶子 · 初识类型](1/1-4.md) 13 | * [第五节 · 思维的体操 · 初识控制流](1/1-5.md) 14 | 15 | ## 第一弹 · 第二章 · 数据鉴解 16 | 17 | * [第一节 · 常态 · 基本内置类型与变量](1/2-1.md) 18 | * [第二节 · 融合态 · 复合类型](1/2-2.md) 19 | * [第三节 · 固化态 · 常量限定符](1/2-3.md) 20 | * [第四节 · 拟态 · 自动推断类型](1/2-4.md) 21 | * [第五节 · 叠加态 · 自定义数据结构](1/2-5.md) 22 | 23 | ## 第一弹 · 第三章 · 计算术 24 | 25 | * [第一节 · 简单的数学课 · 初识表达式](1/3-1.md) 26 | * [第二节 · 数字间的反应 · 初识运算符](1/3-2.md) 27 | * [第三节 · 传送带 · 赋值操作](1/3-3.md) 28 | * [第四节 · 转换状态 · 类型转换](1/3-4.md) 29 | * [第五节 · 剥皮的技巧 · 数组与成员访问](1/3-5.md) 30 | 31 | ## 第一弹 · 第四章 · 逻辑风暴 32 | 33 | * [第一节 · 包裹中的包裹 · 语句与作用域](1/4-1.md) 34 | * [第二节 · 对数据提问 · 条件语句](1/4-2.md) 35 | * [第三节 · 对重复操作的化简 · 迭代语句](1/4-3.md) 36 | * [第四节 · 跃迁到圈外 · 跳转语句](1/4-4.md) 37 | * [第五节 · 缓兵之计 · 异常处理语句](1/4-5.md) 38 | 39 | ## 第一弹 · 第五章 · 函数探幽 40 | 41 | * [第一节 · 化繁为简 · 函数声明与使用](1/5-1.md) 42 | * [第二节 · 以不变应万变 · 深入参数与返回值](1/5-2.md) 43 | * [第三节 · 总指挥中心 · 主函数](1/5-3.md) 44 | * [第四节 · 分身术 · 函数重载](1/5-4.md) 45 | * [第五节 · 自助服务 · 默认实参与函数指针](1/5-5.md) 46 | 47 | ## 第一弹 · 第六章 · 标准库 48 | 49 | * [第一节 · 军械库 · 初识标准库](1/6-1.md) 50 | * [第二节 · 可供理解的数据 · 字符串库](1/6-2.md) 51 | * [第三节 · 无限车厢 · 向量库](1/6-3.md) 52 | * [第四节 · 游走于每一处 · 迭代器](1/6-4.md) 53 | * [第五节 · 瓶子与漏水防护 · 数组库与再探原生数组](1/6-5.md) 54 | 55 | ## 第一弹 · 第七章 · 类之传奇 56 | 57 | * [第一节 · 系统的构造 · 类的定义与封装](1/7-1.md) 58 | * [第二节 · 高级容器 · 类类型](1/7-2.md) 59 | * [第三节 · 询问自己 · this指针](1/7-3.md) 60 | * [第四节 · 公共场所 · 类作用域与静态成员](1/7-4.md) 61 | * [第五节 · 亮相前的准备 · 类的构造函数](1/7-5.md) 62 | 63 | ## 第二弹 · 第八章 · 常规武器 64 | 65 | * [第一节 · 取之不竭 · 顺序容器](2/8-1.md) 66 | * [第二节 · 一一对应 · 关联容器](2/8-2.md) 67 | * [第三节 · 瑞士军刀 · 泛型算法](2/8-3.md) 68 | * [第四节 · 摄取与排放数据 · 文件输入输出](2/8-4.md) 69 | * [第五节 · 电报机 · 字符串流](2/8-5.md) 70 | 71 | ## 第二弹 · 第九章 · 掌控之力 72 | 73 | * [第一节 · 无中生有 · 初识动态内存](2/9-1.md) 74 | * [第二节 · 封印符文 · 智能指针](2/9-2.md) 75 | * [第三节 · 瓶子工厂 · 动态数组](2/9-3.md) 76 | * [第四节 · 意念造山河 · lambda表达式](2/9-4.md) 77 | * [第五节 · 瞬移术 · 右值引用与对象移动](2/9-5.md) 78 | 79 | ## 第二弹 · 第十章 · 运算工具 80 | 81 | * [第一节 · 机械臂的自我修养 · 初识重载运算符](2/10-1.md) 82 | * [第二节 · 换一种走路的方式 · 递增递减运算符定义](2/10-2.md) 83 | * [第三节 · 换一种交流的方式 · 成员访问运算符定义](2/10-3.md) 84 | * [第四节 · 换一种干活的方式 · 函数调用运算符定义](2/10-4.md) 85 | * [第五节 · 换一种变脸的方式 · 类型转换运算符定义](2/10-5.md) 86 | 87 | ## 第二弹 · 第十一章 · 构造世界 88 | 89 | * [第一节 · 从原子到宇宙 · 面向对象概述](2/11-1.md) 90 | * [第二节 · 宗族的继承 · 基类与派生类](2/11-2.md) 91 | * [第三节 · 进化论 · 虚函数与抽象基类](2/11-3.md) 92 | * [第四节 · 遗产分配 · 访问控制与派生类作用域](2/11-4.md) 93 | * [第五节 · 偷懒的诀窍 · 合成函数与虚析构](2/11-5.md) 94 | 95 | ## 第二弹 · 第十二章 · 模板蓝图 96 | 97 | * [第一节 · 绘制蓝图 · 初试定义模板](2/12-1.md) 98 | * [第二节 · 容器类型的传递 · 转发模板](2/12-2.md) 99 | * [第三节 · 原地不动 · 深入理解移动操作](2/12-3.md) 100 | * [第四节 · 无限剑制 · 可变参数模板](2/12-4.md) 101 | * [第五节 · 特殊人格 · 模板特例化](2/12-5.md) 102 | 103 | ## 第二弹 · 第十三章 · 特种部队 104 | 105 | * [第一节 · 匹配想法 · 正则表达式](2/13-1.md) 106 | * [第二节 · 铁索连环 · 变长元组tuple](2/13-2.md) 107 | * [第三节 · 逻辑的空间 · 定长状态表bitset](2/13-3.md) 108 | * [第四节 · 不可知的未来 · 随机数](2/13-4.md) 109 | * [第五节 · 有条不紊 · 格式化输入输出](2/13-5.md) 110 | 111 | ## 第二弹 · 第十四章 · 统筹总领 112 | 113 | * [第一节 · 划分领地 · 命名空间](2/14-1.md) 114 | * [第二节 · 安全部门 · 异常处理库](2/14-2.md) 115 | * [第三节 · 摆脱单亲状态 · 多重继承与虚继承](2/14-3.md) 116 | * [第四节 · 小别致 · 枚举与联合类](2/14-4.md) 117 | * [第五节 · 俄罗斯套娃 · 局部类与嵌套类](2/14-5.md) 118 | 119 | ## 番外篇 · 语林外史 120 | 121 | * [这片土地的旧时光 · 详解 C++98 与新版 C++ 的不同](ex/1.md) 122 | * [与硬件背靠背 · C++、C语言与汇编的协同](ex/2.md) 123 | * [同时进行的任务 · 并行与并发库](ex/3.md) 124 | * [启动《Minecraft》 · 系统调用库](ex/4.md) 125 | * [地心探索 · 内存分配控制](ex/5.md) 126 | * [类型身份证 · RTTI](ex/6.md) 127 | * [牛奶还是奶牛 · 位域与字节序](ex/7.md) 128 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | # 关于作者 2 | 3 | -------------------------------------------------------------------------------- /first.md: -------------------------------------------------------------------------------- 1 | # 序 2 | 3 | 欢迎来到 C++ 的世界! 4 | 5 | 这个序暂时先放在这里,待以后有时间编辑。 6 | 7 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | # 作者自己的摘录笔记 2 | 3 | 这是作者用于整理、归纳知识点的笔记。它虽然也能拿来看,但并不是本电子书的一部分。 4 | 5 | 笔记取自 C++ Primer 5th edition 中文版。 6 | 7 | ## 编写一个简单的 C++ 程序 8 | 9 | 每个 C++ 程序都包含一个或多个函数(function),其中一个必须命名为 **main**。操作系统通过调用 main 来运行 C++ 程序。 10 | 11 | 一个函数的定义包含四部分:*返回类型(return type)*、*函数名(function name)*、一个括号包围的*形参列表(parameter list,允许为空)*以及*函数体(function body)*。虽然 main 函数在某种程度上比较特殊,但其定义与其他函数是一样的。 12 | 13 | main 函数的返回类型必须为 int,即整数类型。int 类型是一种**内置类型(built-in type)**,即语言自身定义的类型。 14 | 15 | 函数定义的最后一部分是函数体,它是一个以左**花括号(curly brace)**开始,以右花括号结束的语句块(block of statements): 16 | 17 | ```cpp 18 | { 19 | return 0; 20 | } 21 | ``` 22 | 23 | 这个语句块中唯一的语句是 return,它结束函数的执行。如果需要,return 还会向调用者返回一个值。当 return 语句包括一个值时,此返回值的类型必须与函数的返回类型相容。 24 | 25 | 在大多数操作系统中,main 的返回值还被用来指示程序运行结束时的状态。返回值 0 表明程序运行成功,非 0 的返回值由使用的操作系统定义,通常用来指出错误类型。 26 | 27 | ## 编译、运行程序 28 | 29 | 编写好程序后,我们就需要编译它。如何编译程序依赖于你使用的操作系统和编译器。 30 | 31 | 很多 PC 机上的编译器都具备集成开发环境(Integrated Developed Environment, IDE),将编译器与其他程序的创建与分析工具封装在一起。在开发大型项目时,这类程序是非常有用的工具,但需要一些时间来学习如何高效地使用它们。 32 | 33 | 大部分编译器,包括集成 IDE 使用的编译器,都会提供一个命令行界面。 34 | 35 | 无论你使用命令行界面或 IDE,大多数编译器都要求程序源代码存储在一个或多个文件中。程序文件通常被称为*源文件(source file)*,其中写好的程序被称为*源代码(source code)*。在大多数操作系统中,源文件的名字都有特定的后缀,后缀是由一个句点紧接着一个或多个字符组成的。后缀可以告诉操作系统,这个文件存储的内容是 C++ 程序。不同编译器可能使用不同的后缀命名约定,最常见的包括 ```.cc```、```.cxx```、```.cpp```、```.cp```以及```.C```。 36 | 37 | ## 初识输入输出 38 | 39 | C ++ 语言并未定义任何专用的输入输出(IO)语句,取而代之的是包含了一个全面的**标准库(standard library)**来提供 IO 设施。 40 | 41 | **iostream** 库包含两个基础类型 **istream** 与 **ostream**,分别表示输入流与输出流。一个流就是一个字符序列,是从 IO 设备读出或写入 IO 设备的。术语“流”(stream)意图表达的是,随着时间的推移,字符是按照顺序生成或消耗的。 42 | 43 | 标准库顶一个 4 个 IO 对象。为了处理输入,我们使用一个名为 **cin** 的 istream 类型的对象。这个对象也被称为**标准输入(standard input)**。对于输出,我们使用一个名为 **cout** 的 ostream 类型的对象。此对象也被称为**标准输出(standard output)**。标准库还定义了其他两个 ostream 对象,分别名为 **cerr** 和 **clog**。我们通常用 cerr 来输出警告和错误信息,因此它也被称为**标准错误(standard error)**。而 clog 则是用来输出程序运行时的一般性日志信息用的。 44 | 45 | ```cpp 46 | #include 47 | int main() { 48 | std::cout << "请输入两个数字:" << std:endl; 49 | int x = 0, y = 0; 50 | std::cin >> x >> y; 51 | std::cout << "数字" << x 52 | << "与" << y 53 | << "的和为" << x + y 54 | << std::endl; 55 | return 0; 56 | } 57 | ``` 58 | 59 | 第一行告诉编译器我们想要使用 iostream 库。尖括号中的名字(在这里是指 ```iostream```)指出了一个**头文件(header)**。每个使用标准库设施的程序都必须包含相关的头文件。```#include```指令和头文件的名字必须写在同一行中,不能换行分开写,也不能粘在一起紧挨着写。通常,```#include```指令必须出现在所有具体程序定义的函数之外。我们一般将一个程序的所有```#include```指令都放在源文件的开头位置。 60 | 61 | main 的函数体中第一句执行了一个**表达式(expression)**。在 C++ 中,一个表达式可以产生一个计算结果,它由一个或多个运算对象和(通常是)一个运算符组成。这条语句中的表达式使用了**输出运算符(<<)**在标准输出上打印消息: 62 | 63 | ```cpp 64 | std::cout << "请输入两个数字:" << std:endl; 65 | ``` 66 | 67 | ```<<```运算符接受两个运算对象:左侧的运算对象必须是一个 ostream 对象,右侧的运算对象就是您想打印的值。此运算符将给定的值写到给定的 ostream 对象中。输出运算符的计算结果就是其左侧的运算对象本身,因此我们还可以对着这次运算的结果再一次(乃至连续多次)使用这个运算符。 68 | 69 | 70 | --------------------------------------------------------------------------------