本文内容遵循 Effective C++ 所提及的代码规范,供(高中)社团课备课所用,禁止转载或摘编。
由于各种原因,我最终还是决定以后的社团课主要以 C++ 语言的学习为主,而且大部分时间都会由我来讲课而不是像之前一样放相关教学视频。现在学校电脑上还没有装环境,今天我准备是单纯以我讲为主。
嗯我们来看一下屏幕。
#include <bits/stdc++.h> /* 引用"万能"头文件(C++20 新增 modules 尚未成熟,暂不推荐使用) */ using namespace std; /* using声明,当找不到时,尝试从命名空间std::中获取它 */ int main () { /* 主函数,这是程序开始的地方 */ cout << "Hello, World!" << endl; /* 输出流向控制台输出 “你好世界” */ return 0; /* 主函数结束,并向调用进程该主函数的进程返回 0 */ }
屏幕上这个是 C++ 语言代码的基本框架。我们现在还不必详细一一探究每一行分别是什么意思,只需要把它当作是一个模板,每次写代码时复制上去即可。我们现在来运行一下代码。
可以看到,程序向控制台输出了 Hello, World!
这一行字符串,说明我们的代码运行成功了。在这个社团课中,我所讲的都是在 C++14 标准下的代码。C++ 是一门中级语言,她综合了高级语言和低级语言的许多特点,但她不是像 python 那样的解释型语言,而是一门编译式的语言,所以它从你编写的代码到可以直接运行的可执行文件往往需要进行四个步骤:
- 第一步,预处理。预处理负责处理头文件和注释,她本质上是一些指令,用以指示编译器在实际编译之前所需完成的预处理内容。预处理语句不属于C++语句,不同编译器的预处理指令规则可能有所不同。
- 第二步,编译。编译器将生成汇编代码文件。因为她是使用静态类型的编程语言,所以类型检查就是在这里进行。
- 第三步,汇编。对汇编代码文件进行汇编,以转成机器指令,从而生成目标文件。
- 第四步,链接。一些动态链接库在这里合并到你的文件中。
这样,你的代码就成功变为了可执行文件。
学习编程是一回事,学习如何以某种语言设计并实现你所预期的程序则是另一回事。这种说法对 C++ 尤其适用。C++ 是在完全保留 C语言 语法之上对 C语言 许多不足之处进行扩充的一门编程语言,多种编程风格都被 C++ 所支持,巨大而变化多端的设计可以被直接表现出来,并且被有效实现出来。
一组明智选择并被精心设计的 classes
,functions
和 templates
可使程序编写容易、直观、高效并且远离错误。我们来看一下这两段代码。(以下代码仅作演示,不必记忆或理解。)
第一段代码是用 C语言 编写的。
#include <stdio.h> /* 引用C语言标准输入输出头文件(万能头只在 C++ 有) */ int add (int a, int b) { /* 定义函数 add int(int, int)*/ return a + b; /* 函数返回 a + b 计算结果 */ } int main() { /* 定义主函数,这是C语言程序开始的地方 */ printf ( "1 + 1 = %d\n", add (1, 1) ); /* 调用格式化输出函数 printf 向控制台输出 函数 add (1, 1) 的返回值。 */ return 0; /* 主函数回调正常返回 0 */ }
这段代码暂且先不管每句语句是什么意思。我希望让程序计算 1 + 1,程序输出了 2,说明确实能计算 1 + 1 = 2 的功能。现在我们将代码中的 1 改为小数,来计算 0.3 + 1.6,看看会发生什么。
1 -------------------------------- Process exited after 0.01506 seconds with return value 0
可以看到,原本应该返回 1.9 的代码返回了 1,在背后偷偷发生了隐式类型转换,这不是我们所想要的。换一个数据类型就要重写函数,十分不方便。而在 C++ 中,我们可以将这些 int
改为 auto
,让编译器自动识别类型,就像这样。
#include <iostream> /* 引用 C++ 标准输入输出流库 */ template <typename _ta, typename _tb> /* 声明模板,在C++20之后可省略 */ auto /* 利用 auto 关键字和返回值后置语法,让编译器 */ add (_ta const& a, _tb const& b) /* 对函数返回值类型进行自动推导(若 */ -> decltype (a + b) { /* 返回值不进行隐式类型转换,可省略 decltype)*/ return a + b; /* 这样,对返回值类型的推导就可以通过直观清晰 */ } /* 的方式描述。 */ int main () { /* C++ 中静态构造函数优先于主函数,不再是程序开始的地方 */ std::cout << "0.3 + 1.6 = " << add (0.3, 1.6) << std::endl; /* 引用 std:: 标准输出函数向控制台输出 add() 函数返回值。 编译器通过模板对 rhs 的参数类型自动解析,其中,endl 表示换行并刷新缓冲区。可通过 cin.tie 等方式提高效率 */ return 0; }
程序成功输出了 1.9。当然,C++ 所提供的语法糖或强大功能还有很多,比如刚才这段程序,我们也可以用 Lambda 匿名函数,就像这样。
#include <iostream> using namespace std; auto add = [](auto a, auto b) { return a + b; }; int main () { cout << "0.3 + 1.6 = " << add (0.3, 1.6) << endl; return 0; }
有个广为流传的笑话,C++ 最后的程序会像这样:
auto auto; /* auto 自动推导 */ auto auto (auto) = { auto + auto; }; auto auto () = { auto (auto(0.3 + 1.6)); auto auto; }
这个社团课不是单纯告诉你编程算法,而是让你了解到 C++ 的许多语法特性。你可能曾经并不知道 C++ 是个编程语言或对它有某些使用经验,但我相信,在座的各位对数学上的公式等内容肯定都已经有了很高的掌握程度,于是我主要还是讲解这个语言本身,使你以后写出的软件易理解,易维护,可扩充,高效并且有着你所预期的行为。虽然你们下学期要学的是 python 而不是 C++,但是,可要知道,python 的第一个发行版本就是由 C 编写的,且里边的很多语法都与 C++ 有着很高的相似度。
之前提到,C++ 完全保留了 C语言 的语法。其实,最开始的 C++ 只是在 C 的基础上加上了最基本的面向对象概念中的 class 类,并取名为 “带类的C”,而后才慢慢发展成为一门强大的编程语言,一门多重范式的编程语言,一个同时支持过程形式、面向对象形式、元编程形式、泛型形式和函数形式的编程语言。它的名字 C++ 也是用的 C 中的自增运算符 ++ 命名的。其实,如果 C++ 抛弃 C 的语法,不需要支持例如 C语言 八种整数类型等,那么 C++ 将远比现在简单。我最开始也是学 C ,但当我开始自己写图形库之后,才意识到在 C 中很多想要的都很难去实现,便转战 C++。当然,C++ 的成功也有 C语言 不小的功劳,比如 C++ 适合写底层,是因为 C语言 从内存模型等方面对底层操作提供了很多支持。
不过,即使强大,但反而可能导致了不易入门。你需要学习掌握很多专业术语,这我也会在以后的社团课上逐一说明。比如在《Effective C++》书中提到的一些问题:你该选择 inheritance
继承还是 templates
模板,该选择 public
继承还是 private
继承还是 composition
复合?该选择 pass_by_value
以值传递,还是 pass_by_reference
以指针传递?等等。即使在面试中,有人可能会说熟练使用 C++,但除了标准制定者之外,只有极少聪明人敢说精通。即使你完全知道该做什么,完全进入正轨,还是可能有些棘手。什么是 assignment
操作符的适当返回型?什么是 bind
绑定器?何时该令析构函数为 virtual
虚拟?当 operator new
无法找到足够内存空间时该如何行事?等等。
我之前也说过,我所讲的内容都是基于 C++1y(C++14)也就是14年指定的 C++ 标准。这是一个比较新的标准,可能有些编译器比如 VC6.0 等不能很好的支持。我这里主要用 Dev-C++,一个极为简陋但对初学者十分友好的编译器。我这里有这个编译器的安装包,如果想要可以下课后直接上来拷贝。我也会把这个编译器的安装包放群里,大家回去记得加一下群。
现在我先来简单说一下输出吧。而对输出语句中符号各自含义什么的我会放到后面去讲,详细的输出语法我也会放在第二节课讲。在 C++ 中,在控制台输入的语句格式是:
cout << 要输出的内容;
每个输出的内容之间用两个小于号分隔开。比如要输出数字 256 ,就写成这样:
cout << 256;
换行最好用 std::endl
。当然也可以输出文字。输出常量字符串时要在输出的内容两边加上两个双引号。注意一定要用英文的双引号,不然电脑可不能识别。而且,只要是在 C 或 C++ 中,每句语句末尾都需要加上一个分号 ‘ ; ‘,这代表语句的结束。
嗯,输出暂且先说这么多,接下来我们来简单讲讲变量。这里的内容可能有点难,不过请不要担心,在第二课,对于这一部分,我会从最基础的和你讲起。
在 C++ 中,一个变量就好比一个容器,你可以在这个容器里存放一些东西。你首先需要给她命名,这样你在需要她的时候就可以直接引用这个名字去访问容器。你只能给她取英文名,下划线或是数字,而且要保证不能有重名。( ‘$’ 往往也被允许作为变量的名字,但我并不推荐你这么做,因为这可能并不具备可移植性 )否则,计算机就不知道你是要叫谁。命名空间,便是为了解决这一问题而生。
不过,这个容器是有一定限制的。变量,其实就是在内存中申请分配的一段连续的内存空间的名称,这个空间不可能做到无限大,所以你必须要指定她所占用的空间大小,或应该存放什么样的数据。当然,C++ 提供了 new
关键字或可变长数组等,可以动态分配内存,在这里就暂且不提。( 注意区别 Python 这样的动态类型语言 )
抛开 C语言 本身的语法,C++ 就和 Python 很像了。在她的世界里,几乎一切皆对象。其中,你指定变量的语句叫声明式。你通过声明式告诉编译器某个变量的名称和类型,但略去细节。注意对于 int 这样的内置类型,除非是 extern
,否则都不是声明式。声明式的语法是:
(模板 限制符)类型 变量名 (: 继承类) ;
就像这样:
extern int x; /* 对象声明式 */ void func (int a); /* 函数声明式 */ class point3d; /* 类声明式 */ template <typename T> /* 模板声明式 */ class canvas;
学过一点 C++ 的可能会问,int
整型不是一个内置类型吗?为什么是一个对象?可能有些人会把“对象”一词保留给用户自定义类型,但并不如此。当然,现在还不必深究。
而定义式的任务是提供给编译器一些声明式所遗漏的细节。对对象而言,定义式是编译器为对象分配内存的起点。定义式的语法是:
(模板) (限制符)类型 变量名 (: 继承类) (函数或结构体)
比如:
int num;
特别地,类型的定义式语法是:
using 类型别名 = 原类型; /* C++ 新增类型别名定义式语法 */ typedef 原类型 类型别名; /* C/C++ 旧式类型别名定义式 */
但是,你光是让编译器给你分配内存,你什么也不干,显然没有意义。你往往需要在里面装东西,才能体现出他们的价值。给予对象初值,也就是第一次往里存放数据的过程叫初始化。如果是在说 C语言,我可能会和你讲,初始化的方法是在定义式后写上赋值语句,但 C++ 并不完全是这么实现的。在 C++ 中,初始化由构造函数执行。每个类型编译器已经为你提供了对应的 default 构造函数( [注] 所谓 default 构造函数是一个可被调用而不带任何实参的构造函数,这样的构造函数要不是没有参数,要不是就是每个参数就有缺省值)。姑且先不管函数是什么,就当她是很多语句的组合,及构造函数就当她是赋值语句的组合。我们先来看看代码长什么样。
fruit apple{6.5};
在这句语句中,我将 “apple” 作为了变量的名字,并告诉编译器其类型是 “fruit”。然后我在名字后边写了一对大括号,里面写了一个数字 6.5,也就是我要将 6.5 作为 apple 的初值。这个花括号的专业术语是初始化列表。当然,你也可以用小括号,直接表面要调用构造函数,或是直接用 C语言 的语法,即赋值,就像这样:
int a(5); /* C++语法,调用 int 的构造函数 */ double a = 3.14; /* C 语法,对 a 进行赋值。*/ /* 在 C++ 中,对于类类型为调用拷贝构造函数 */
其中,int 表示整型,即可以存储从 -2^31 到 2^31-1 的整数。double 表示双精度浮点型,即可以存储小数(double 在计算机内部是以科学计数法的形式存储的)。还需要记住两个类型,就是 char
和 string
。char 表示字符,表示是一个符号(见 ASCII 编码系统)。string 表示字符串,表示是一段文字。直接写 char
的单个字符时,两边要用英文单引号将其包含在内;直接写 string
的一个字符串时,两边要用双引号包含。注意如果要换多行写同一个字符串,每行的开头和结尾都需要写双引号,就像这样:
string str = "Welcome " /* 多个常量字符串编译器 */ "to Our Programming" /* 会将其自动合并为一个 */ " Club!";
C++ 也提供了不必每行都写双引号的多行字符串,不是很常用,就像这样:
string str = R"(Welcome to Our Programming Club!)"; /* 多行字符串(其实是 raw 原始字符串) */
我们可以试试输出刚才定义好的变量:
#include <bits/stdc++.h> using namespace std; /* 将命名空间 std:: 全局放开 */ int main () { /* 主函数 */ int i { 2021 }; /* 定义整型变量 i */ char c ('!'); /* 定义字符型变量 c */ string str = "Welcome to Our" " Programming Club "; /* 定义字符串 str */ cout << str << i << c << endl; return 0; /* 主函数返回 */ }
程序成功输出了以下结果:
Welcome to Our Programming Club 2021! -------------------------------- Process exited after 0.01415 seconds with return value 0
今天的社团课我就先上到这里,因为没有环境,暂时就不布置作业了,接下来还有一点时间就留给大家写作业。我上面会放一个社团的宣传视频,你们可以看看。
*参考文献:[ 1 ] Effective C++: 55 Specific Ways to Improve Your Programs and Designs,3 / (美) 梅耶 ( Meyers, S. ) 著