Skip to content

Latest commit

 

History

History
1172 lines (592 loc) · 29.8 KB

File metadata and controls

1172 lines (592 loc) · 29.8 KB

泛讲篇

初识C++

在本章我想要以一个简单的例子来引入C++的使用,并给读者一个初印象。

我会用尽可能简单的代码来展示C++的基本功能,比如输出字符,然后让它做一些简单的计算器可以做的事情。

我还要给读者介绍一些必知必会的基本概念,本章可以算作是泛讲篇的泛讲。

开始一个C++程序

这里介绍编译器的选择和语言标准的问题(本书默认使用C++17标准)。

然后带领读者编译并运行出一个最基本的C++程序(Hello World)。

这本书不负责教读者如何配置编译器。如果实在配不出来,建议读者用Coliru或者Wandbox等在线编译,效果差不多。

数据与信息

这里我想给读者介绍什么是数据,数据如何体现信息。

然后简介一下常用的数据类型,我在这里将其粗糙地分为整数、浮点数、字符数三类。(指针什么的,本章不讲)。

要提及,字符正是用数据来体现信息的一种形式(ASCII)。

数据的定义和使用

在这里我会介绍数据如何定义(限于前面只介绍了三种类型,我在这里只用int, doublechar)。

然后通过修改cout输出的内容,或者是用算术运算符来拼接它们,让它们做像计算器一样的事情。

这里我只对运算符做简单概括,不会搞得太复杂。

运算符与类型

在这里我必须让读者建立起对类型的敏感性。

8/3这样的经典例子来说明类型不同会让结果有很大差异。

也通过%运算符和()的使用,让读者认识到C++(乃至其它语言)对运算符的含义有各自的规定。

sizeof与内存空间

通过sizeof运算,让读者对“内存空间”有初步的印象,知道不同类型的内存空间不一样。

另外我还要简单提一句,sizeof和之前接触到的运算不同,它是在编译时计算出来的。

类与对象

介绍下类与对象的基本概念,因为之后会频繁用到,不可不提。但是仅限于概念。

数据的基本操作

常量与变量

介绍如何定义和使用const常量和enum常量(enum只是开个头,以后还会用它的)。

介绍下#define(编译时操作)和它的缺陷。

(注意:常量不是一个“类型”,而是类型限定符)

基本数据类型

整型

int, long long, unsigned之类。介绍signedunsigned类型的数据范围区别。

通过sizeof运算求它们的内存占用。同时指出这样的结果因系统而异,让读者对系统差异有初步印象。

还有不同进制下的数据,也可以稍微谈谈。

浮点型

float, double, long double之类。介绍它们的数据范围。

介绍一下fl后缀,并比较整型和浮点型的后缀使用情况。

字符型

char, char8_t, char16_t和Unicode的简单介绍。

夹带一点字符串,但不多讲。

布尔型

bool型。特别之处在于和bool紧密相关的两个关键字turefalse。我会阐述它们和10的关系。

运算符

运算符的操作数、返回值

通过函数的视角来看待运算符,即每个运算符都有参数返回值

运算符的优先级

这里以算术运算符和赋值运算符和cout使用的左移位运算符为例,讲解其优先级的问题。

都讲到cout<<了,顺便提一句重载吧。

运算符的结合性

这里以连续赋值a=b=c和连续除法a/b/c为例,介绍运算符的结合性。

以我所知,很多人初学的时候是并没有搞懂结合性为何物的。

运算的语义

a<b<ca==b==c为例,谈谈运算符及其可能带来的语义困惑,帮读者规避这个易踩的雷。

顺便提一句语法和语义的区别。

数据类型转换

类型间的差异

主要介绍整型与浮点型的差异,包括数据范围和精度。

还要介绍整型与整型、浮点型与浮点型的差异。

这样一来我就说清了“为什么要做类型转换”。

隐式类型转换

先介绍一下运算符对数据类型的隐式转换,比如char+int或者是int*double

1.0*a/b为例,介绍我如何使用隐式转换来避免计算整数除法时的精度损失。

还要运用结合性的知识,解释1.0*a/ba/b*1.0的区别。

显式类型转换

介绍C++的显示类型转换方法,包括C风格、函数风格和static_cast风格的。

我还会阐述static_cast的优点,并建议使用此方法。

至于const_cast, dynamic_castreinterpret_cast,我会留到精讲篇来讲。

程序的流程控制

本章主要讲解C++的流程控制语句if, for, while, switch这些。

简介:结构、流程与顺序

编译器如何处理代码?

简单介绍一下什么是编译时行为(与语法挂钩),什么是运行时行为(与语义挂钩)。

比如说sizeof#define就是编译时行为;而类型转换和变量的计算就是运行时行为。

或许可以带上constexpr

终端如何处理输入/输出?

可以谈谈终端如何处理输入和输出,我们键盘上的输入是如何被程序获取,内容又是如何呈现到显示屏上的。简单讲讲就行。

程序的结构

介绍程序的三种基本结构:顺序、选择、循环

语句的运算次序

&&||++--运算符为例,介绍运算次序。

要简单提一句值计算副作用的关系。

选择结构

if-else结构

让读者从计算机的视角来理解选择结构的流程。

if-else块;单独使用if;以及else-if连用(注意,没有else if这个关键字,它只是elseif的嵌套使用而已)。

switch-case结构

switch-case-break的使用。特别要让读者注意的是break的作用,我把它解释为:switch块当中的内容依然是顺序运行的,break起到退出的作用。

合并case也要讲一下。

循环结构

for循环

这里只讲基本for循环,范围for留到讲数组。

重点在于解释for的三段语句各自的含义。

while循环

这里讲while循环。不得不提的是while(cin>>a)这样的用法。cin>>a有一个返回值,这个返回值可以隐式类型转换为bool,于是可以被while接收。

do-while循环

一笔带过一下do-while循环。这个东西不很常用,但是作为很基本的语法,应该知道。

continuebreak语句

介绍continuebreak语句在循环体内的使用。

作用域初步

通过几个实验,让读者意识到有些数据是有作用域的。

更进一步,要让读者明白数据是有生存期的。

关于为什么要安排一个先讲函数然后把复合类型和函数穿插在一起讲的顺序

其实我觉得如果先讲复合数据类型再讲函数的话,数组还好说,指针真的很难讲,因为你用一些输出地址值的示例代码一点也不直观,看了半天读者可能还是不知道自己到底为什么要用指针,所以我觉得把指针放在函数参数里讲会更容易接受。

但是另一个问题是,如果我先讲函数的话,很多参数类型我没讲,所以我好像讲不了太多,单开一章显得很没必要。

我一度想要把函数、指针和数组放在同一章讲,但觉得还是不合适,因为它们虽然有交集,但还是相对有独立性的知识,揉在一起讲显得太臃肿。

所以我安排了这么一个先讲函数初步,再讲复合类型(和函数参数揉在一起)、再讲自定义类型(和返回类型揉在一起),最后回来讲函数进阶的顺序,有点无奈之举。

函数初步

函数的概念

黑盒

我们不需要知道它的构造原理,只需要知道它的用途就可以使用它。

封装性

函数是相对孤立的,只有接收信息(参数)和返回信息(返回值)的接口。

代码复用

可以不必为每个功能重复写多次代码。

低耦合度

对函数内部作微调,一般不需要对外部进行修改。

函数的定义和使用

必需结构

返回类型和参数类型、函数命名和函数体。函数体中返回的值必须能转换成规定的返回类型,否则就是未定义行为。

顺便给读者普及下什么是未定义行为。

实参和形参

介绍下什么是实参、什么是形参,它们的区别和联系是什么。

函数的使用

max为例,测试下函数能否使用。

再用多个函数组成一个例子,看一下多个函数嵌套调用的效果。

函数的声明

在使用函数之前就要声明,但可以把定义放在后面。

声明可以多次,定义必须唯一。

“值计算”与“副作用”

谈谈函数的副作用,以及我们如何利用副作用。

有些时候调用函数不是为了使用它的返回值(我们可能将返回类型设置为void,或者干脆用弃值表达式),恰恰是为了使用它的副作用。

函数重载

介绍这种方法的同时,还要介绍下在与函数重载同时存在时,可能引发的二义性问题。

函数参数默认值

(简单水水)

函数递归

以求阶乘为例,讲讲函数递归是怎么一回事。

函数模板简介

谈谈为什么要用函数模板。不往深里讲。

用函数模板写一个max函数,看看效果。

复合类型及其使用

指针(pointer)

内存与地址

介绍下有关内存和地址的基本概念。

指针的定义和使用

如何定义指针、赋值和运算。内容访问应当用*实现。

指针参数传递

为什么传递一个变量不能满足我们的要求?为什么传递指针能实现我们的目的?当我传入指针时,我提供了什么信息?

野指针问题

指针的运算

指针在加法运算时的返回值就是指针类型,而减法得到的是std::ptrdiff_t类型。

考虑了一下决定还是把函数指针放精讲篇。

指针常量和常量指针

简单讲讲它们的定义语法就行。

不过要强调下,怎么理解它们的定义语法。这对后面指针和数组的关系来说很重要。

(左值)引用与引用参数传递

泛讲篇不讲右值引用。

什么是引用?

这里介绍下(左值)引用的相关知识。包括type&const type&

它们的地址相同,说明只是别名。但const type&不能修改变量的值。

引用作为参数

引用传参的优点是不用特意去取参数的地址,比如cout<<var可以直接将var传入。

另外,因为不需要为另建副本,也省了内存和时间。

一维数组(array)

定义和使用

一维数组的定义/初始化语法比较多,都介绍下。

使用要用到下标运算符。

参数传递

一维数组传参的形式。

范围for循环

在这里可以讲讲范围for循环了。

数组的类型

typeidis_same来看看数组的类型都是什么样的,它和指针又是什么关系。

字符串(string)

概念

字符串是一系列的char字符构成的一串字符,'\0是结束符。

我们可以用char数组来存储字符串,但是数组的大小和字符串的长度并不是同一个概念。

字符串的定义

方式同样很多。

字符串的处理

写两个简单的函数,一个是strlen,另一个是strcpy,来实现一些基本的函字符串操作功能。

字符串的输入

字符串的输入有很多方法,也有很多注意事项,这里简单介绍cin>>cin.get()cin.getline()即可。多余的留给精讲篇。

另外值得强调的是输入流的问题,键盘行为和输入的字符有什么关系,cin如何解析这些字符。

指针与数组的复合类型

二维数组

相当于批量定义数组。

如何理解语法?

指针数组

相当于批量定义指针。

如何理解语法(尤其是和数组指针共同出现时,极易混淆)?

指向数组的指针

指针可以指向普通数据,当然也能指向数组!

如何理解语法?

二阶指针

指针一经定义,就需要存储值,当然也就有了地址。

指针能指向数组,当然也能指向指针!

动态内存分配

我们可以把数组当做指针来用,当然也可以把指针当作数组来用!

动态内存分配的基本语法

对于一阶指针,如何使用动态内存分配来分配一段内存空间,并在用完时回收。

高维数组的分配

对于二阶指针,如何使用动态内存分配来分配一段内存空间,并通过稳妥的方法来回收,从而避免内存泄漏。

布置分配(placement new)放在精讲篇。

自定义类型及其使用

枚举常量(enum)

枚举常量的好处是,可以使用有意义的单词来表达确定的含义,避免用数值这种含混不清的东西。

同时,枚举常量对本类型的可能取值作出了规定,不在规定中的名字和来自其它类型的名字都是禁止的。

scoped enumeration 放在精讲篇。这里只讲unscoped enumeration。

结构体(struct)

定义、声明和初始化

使用

(简单水水)

结构体与函数

这是很有意义的,因为在此之前我们的函数只能返回单个值,现在我们可以用结构体将多个值封装起来一起返回了。

实操:链表

通过对象指针成员和动态内存分配,写一个简单的单向链表。

联合体(union)

定义、声明和初始化

联合体的使用

如何利用unionstruct/array的形式,让数组元素有一个有意义的名字。

类(class)初步

关于成员变量的部分,读者已有基础,只需讲一下成员访问权限的问题。

还要稍微讲点成员函数的知识。

实操:使用vector

使用vector的成员函数,完成一些简单的功能。

代码工程

跨文件编译

头文件与#include

介绍下标准库头文件与自定义头文件的问题。

实操:跨文件编译

尝试将类和函数声明在头文件,编译在definition.cpp文件,并用main.cpp调用函数和定义对象。

命名空间

如何定义命名空间,怎样使用命名空间。

using namespace std的优点与缺点。

作用域与变量生存期

作用域

解释作用域的概念。

名称查找

既然谈到了作用域,就不得不谈谈名称查找了。

变量的生存期

简单介绍下数据的生存期:自动生存期、静态生存期、线程生存期及动态生存期。

变量的链接方式

分为外部链接、内部链接和无链接。

编码风格

介绍一些常见的编码风格,并提出一些编码风格上的注意事项。

到这一章结束,读者应当具备了搭建简单工程的知识量。接下来的章节就要上强度了,我会带着读者多写一些代码的。

类与函数进阶

运算符重载

实操:vector语法糖

重载一些语法糖,让vector的操作更简单。边写边讲。

  • 重载<<运算符用于push_back
  • 重载后缀--运算符用于pop_back,重载前缀--运算符用于pop_front
  • 重载单目*运算符用于返回size。略微涉及一点autodecltype的使用。
  • 重载<运算符,用于字典序比较。
  • 利用已经重载好的<运算符,重载>, ==, !=, <=, >=运算符。这也是代码复用的一种方式。

运算符重载的规则

(从cppreference抄吧,要不)

先讲运算符重载再讲友元也有我的考虑。这样能破除两种常见的偏见:一是“友元函数是成员函数”;二是“运算符必须定义为友元”。

成员函数与友元函数

成员函数的声明与定义

valarray类为例,定义一些函数和运算符。

成员形式的运算符重载

实操:自建简易valarray

基于普通数组实现,属于低配版。

重载如下运算符,边写边讲。

  • 重载=+=
  • 重载[],强调它不能是友元,必须是成员函数。
  • 重载+*,既有成员函数版本,又有非成员函数版本。
  • 重载<<(ostream&,const valarray&),它是valarray的友元,但不是ostream的友元。

构造与析构

构造函数

构造函数与拷贝构造函数(提一下深浅拷贝的问题)。

移动构造函数放精讲篇。

成员的初始化

怎么使用初值列(成员初始化列表),为什么初值列更高效。

怎么定义和使用成员默认值,

析构函数

为何使用析构函数,怎样用。

实操:改进简易valarray

不用数组而是用指针+动态内存分配的方式来写valarray

添加一些好用的构造函数和析构。

特别需要注意的是,=运算符也需要添加一个“自我赋值”的判断,否则将会出现问题。

成员的属性

静态成员

常量成员

包括常成员函数

mutable

类型转换函数

类型转换可以通过转换构造函数或自定义转换函数实现,看怎么方便怎么用。

explicit

隐式类型转换可能不尽如人意,所以有些时候我们需要用显式类型转换。

介绍下用explicit来强制显式类型转换,避免不合适的隐式类型转换带来的问题。

复合类型与对象

string对象数组

std::string为例,介绍下对象数组,强调不同的下标运算符的含义可能是不同的(strs[0][0]这样)。

多用typeid,很好用的。

valarray与动态内存分配

再尝试一下用valarray指针来分配一批对象,理顺valarray指针申请的动态内存空间和valarray构造函数申请的动态内存空间有什么区别。

实操:简单的string

功能简介

简单介绍并带着读者用一下std::string,起码知道我们需要实现哪些功能。

规划

写一写string类的定义部分,成员函数先声明出来。

实现

把成员函数的功能写出来。

测试

看一下我写出来的string类效果如何。

类的继承

概念介绍

重点在于梳理清楚三组概念,不讲具体的技术。

整体与部分(has-a relationship)

对应C++中的对象成员的关系,比如一个人心脏大脑

模板与实例(instance-of relationship)

对应C++中的对象的关系,比如人类张三李四

属与种(is-a relationship)

对应C++中的基类派生类(公有或受保护继承)的关系,比如食肉目猫科犬科熊科

公开继承与受保护成员

基本语法

讲一下公开继承(public)的语法。

访问权限

谈谈公开继承的对象能访问基类的哪些信息,又如何访问。

顺便讲一下protected成员。

构造与初始化

重点介绍派生类对象的基类成员是如何构造和初始化的。

私有继承

(前述的valarri::Arr正是一个绝佳的例子)

构造与初始化

实操:适配vector<int>stack

这里通过私有继承vector<int>的方式写一个简单的stack,实现一些最基本的功能。

私有继承,还是用作成员?

比较一下私有继承方式和用作成员变量各自的优劣。

受保护继承和一些细枝末节的东西都塞到精讲篇。

多级继承

用一些小例子,介绍下多级继承即可。

继承中的常见问题

继承中的类型转换

对象之间的类型转换

指针/引用之间的类型转换

向上类型转换和向下类型转换如何实现,为什么 static_cast 在向下类型转换时不好用了。

基类指针可以指向派生类对象;基类引用可以引向派生类对象。它们能操作哪些成员。

动态类型转换与多态

为何要向下类型转换?

只剩下一种可能性:为了使用同名而不同功能的成员函数。

动态类型转换

dynamic_cast 需要依赖多态的基类,我们要靠虚函数来实现。

实操:不同形状几何图形的面积

抽象基类与纯虚函数

实操:矩形与正方形

定义一个抽象基类,让矩形与正方形都继承它。

多重继承

何为多重继承

std::iostream就是多重继承的绝佳实例。拿它讲就好。

语法、构造与初始化

多重继承的问题

在使用棱形继承时,基类的成员重复。

虚继承

吐槽一下,用“虚基类”这个名字是很容易引起误解的,这个virtual不是基类自己拥有的属性,而是“继承关系”的属性。精讲篇会细究这个问题。

virtual如何用于虚继承

效果演示

模板(Template)与泛型编程基础

函数模板

什么是泛型?

我们可以忽略它的具体类型,用一套普适的方法来处理各种类型的数据。

如何使用函数模板

template允许接收的参数可以是类型信息或数据信息。

注意:模板参数必须都是在编译时确定的。

constexpr的使用

函数模板的实例化与特化

实例化

显式实例化和隐式实例化。

函数模板的重载

swap函数为例,我们可以用它来交换两个T[]数组的内容。这是一种重载。

特化

特化模板函数,用于特殊的类型。

实操:自制is_same函数模板

类模板

什么是类模板?

我们可以忽略它的部分成员的具体类型,为具有相同特征的成员搭建一个通用的类模板。

实操:array类模板

一个非常简化的array类模板,只能实现最基础、最简单的功能,但足够用来讲解了。

类模板的友元、实例化与特化

实例化

显式实例化和隐式实例化。

与函数模板相似,类模板也不是一个预先给定的类。编译器根据需要,会根据类模板生成若干个对应的类定义。

友元

类模板对函数的友元、对函数模板的友元、对类的友元、对类模板的友元。

完全特化

如何通过完全特化,对某些特殊的类进行不同的处理。

不完全特化

is_same 类模板等。

STL简介

容器

带读者试试map

迭代器

简单介绍下迭代器。

函数对象

算法

带读者试试copy, sort, unique三种算法。

更多STL知识及相关概念放精讲篇了,要不然泛讲篇就变成精讲篇了。

实操:智能指针

带读者写一个auto_ptr类模板。

虽然auto_ptr在C++17中已经被移除了,但是我们泛讲篇不需要考虑得太细,能写出来一个简单的auto_ptr对初学者来说就已经是不小的成就了。

异常处理简介

本章只做简单介绍,详细的放精讲篇。

基本try-catch-throw结构

写一个简单的代码来测试一下这个结构。

异常类exception

简单介绍下异常类及其派生类。

实操:at成员函数

array类模板设计一个成员函数at,带范围检测,继承自std::exception

noexcept限定符

简单介绍下noexcept限定符的使用。

实操:简单矩阵类的设计

输入、输出流简介

信息在流中的传递

讲一讲设备之间如何通过流进行交互。

标准输入输出iostream

格式控制

格式标志(format flags)和std::ios_base::hex之类的。

还有iomanip库。

状态函数

good, eof等。

字符串输入输出sstream

简单展示一些例子就行。

文件输入输出fstream

打开模式

只讲简单的in, out, truncapp,至于binary,放精讲篇。

精讲篇

数据

左值与右值

C++11以前

分为左值和右值。

C++11以后

右值中划分出临终值和纯右值,而临终值与左值共同构成广义左值。

数据类型

基本类型

整型、浮点型等。

它们的二进制表示,以及整数的位运算。

复合类型

指针、数组、引用、结构体等。

特殊复合类型:右值引用

讲讲右值引用,以便后续使用。

const常量

const常量不是任何类型,它可以用于组合各种类型,以及组合成员函数(相当于组合了*this)。

volatile不讲

数据的定义和初始化

定义的语法和初始化的语法。

各种常见类型的初始化方法,比如直接初始化、列表初始化、统一初始化等等。

Most vexing parse

谈谈直接初始化的问题,它可能被误认为是函数定义而非初始化。

然而统一初始化也有它的问题,半斤八两吧。

类型转换

不同类型的转换问题。

static_castconst_castdynamic_castreinterpret_cast

运算

语句结构

从对象到语句

对象通过运算符(或直接)构成表达式。表达式可以拼接。

分号意味着一个表达式语句的结束。

还有很多其它的语句类型,比如for循环这种。

值计算和副作用

一个表达式可以有值计算和副作用,或兼而有之。

弃值表达式只用其副作用。

给函数添加[[nodiscard]]说明符可以对弃值表达式给出warning警告。

运算顺序

C++11以前

顺序点规则(简介即可)。

C++11以后

“按顺序早于”规则。这里不需要都讲,提几个常见的要点即可。

未定义行为

有些运算是未定义行为,可能会诱发未知结果,应当注意。

运算符

鉴于泛讲篇已经讲了不少,我主要对泛讲篇中没讲的部分做一下查漏补缺。

一般单目运算符的重载

单目运算符的参数只有一个,作为成员函数时是*this本身,作为非成员函数时就需要指定。

一般双目运算符的重载

双目运算符的参数有两个,作为成员函数时要提供另一个参数,作为非成员函数时就需要提供两个参数。

[]()的重载

C++17不允许[]接收多个参数,所以想要访问高维数据(如矩阵)可以用()的重载来实现。

它们都必须是成员函数。

动态内存布置分配

Placement new。主要是介绍下布置分配的语法和注意事项(结束一个对象的生存期时,需要主动调用析构函数)。

重载new/delete

简要介绍下newdelete的重载。

函数与闭包

函数模板相关

实例化与参数推导

编译器将如何根据代码中给定实参的类型,推导出函数的类型。

函数模板的重载

如何重载模板参数,来实现我们的特定目的。

显式特化与直接重载

显式特化与直接重载都能实现我们的目的。

变长实参

基本语法

常见应用

形参包

替代语法:initializer_list

函数指针与lambda

函数指针

定义和使用方法。

闭包的概念

就像我们更倾向用局部变量,而不是全局变量,来实现临时功能。

我们也更倾向用局部函数,甚至是不具名函数,来实现临时功能。

lambda表达式

lambda的定义和使用方式。

捕获方式

介绍一点常用的捕获方式。

函数对象

概念

什么是函数对象,为什么提出这个概念?

定义

如何通过重载()的方式,定度函数对象?

使用

通过几个算法,尝试使用函数对象。

访问权限

成员访问权限

public, protected, private

class默认为private,而struct默认为public

继承方式

public, protected, private

列张表概括一下,不同继承方式和访问权限组合起来是什么样的。

友元

类中的友元函数和友元函数模板;

类中的友元类和友元类模板。

类模板的友元函数。

构造与使用

构造函数

对于struct来说,有一种直接通过统一初始化来赋值的操作;而一旦细分了访问权限,这个操作即被禁止。

构造函数、拷贝构造函数和移动构造函数。

还有深浅拷贝的问题。

类模板相关

实例化与参数推导

同上的部分同上。

类模板模板的重载

同上的部分同上。

显式全特化

同上的部分同上。

显式部分特化

在这里要着重讲一下显式部分特化,这是相比于函数模板不同的地方。

(待补充)

虚继承

通过一系列实验,观察虚继承的效果。

阐释,为什么虚继承是一个关系。

标准模板库

基本概念

容器 container

迭代器 iterator

算法 algorithm

常用容器及功能

vector

list

set

map

常用算法及功能

swap

for_each

copy

transform

count

常用迭代器及功能

back_insert_iterator

input_iterator

output_iterator

实操:简易的vector类模板设计

实操:简易的list类模板设计

实操:简易的算法设计

count_if

for_each

transform

merge

选择std算法,还是容器自带方法?

异常处理(待补充)

文件操作(待补充)

附录(待补充)

运算符基本属性表

在这里对C++17为止的运算符的优先级、结合性和重载要求作一个整理。

ASCII码表(0~127)

在这里列出0~127的ASCII码表。

相关数学知识

这里整理C++中可能用到的相关数学知识。我没必要在主体内容中集中讲它,所以放到这里来。

进制转换

布尔代数基础

位运算