C++ 代码中常见的符号

首先来看一段 C++ 程序的源代码:

// hello-world.cpp

#include <iostream>

int main() {
    std::cout << "Hello!";
}

下面的内容会介绍 C++ 代码中的符号和基本语法,希望藉此向读者介绍 C++ 代码的语义。

注意:下面的这些符号都是 ASCII 码表中对应的符号,也是我们常说的“英文符号”或“半角符号”。

# 井号

通常,我们会在代码中看到# 符号起始的行

#include <algorithm>
#include "MyClass.h"

也有这样的:

#ifdef WIN32
    /* some code here */
#endif

之前我们说到,C/C++ 是一个需要编译的语言,在编译前,需要对代码做一些预处理(preprocess)。而跟在 # 符号后面的,叫做预处理指令,用来告诉预处理器对代码执行哪些预先的处理。

预处理过程发生在编译之前。如 include 指令,就是将其后所指明的文件复制到对应的位置。比如 #include <algorithm> 就是将 algorithm 文件中的内容复制到对应的地方;同理,#include "MyClass.h" 就是将 MyClass.h 文件的内容复制进来。

这些复制进来的文件也叫做头文件(headers),我们会在后续“多文件编程”的章节展开介绍。至于 include 指令中 <>"" 的区别,以及其他指令的含义,我们会在 C/C++ 预处理命令的章节具体介绍。

变量

在 C/C++ 中,变量(variables)对应着内存中一块可以修改的数据。用户可以通过一个名称来访问这块数据,这个名称就是变量名。

赋值

使用赋值运算符 = 可以为变量赋值。比如 a = 5 就是将 5 这一个值赋给了变量 a,之后,变量 a 的值就是 5 了。

函数

不同于数学上“函数”的概念,C/C++ 中的函数(function)一般是若干语句的集合。我们也可以将其称作“子过程(subroutine)”。

函数可以接收若干值,这叫做函数的参数。函数中一般会使用这些参数,根据参数的不同,函数的执行也可能会有不同的结果。函数也可以返回某个值,即函数的返回值。

通过使用函数,我们可以将具体的实现过程封装起来,也可以将问题拆分成若干部分,方便于代码的编写、修正与测试;还能将代码中重复的过程提取出来,形成一个函数,减少重复代码。

; 分号

英文分号通常用作语句(statement)之间的分割。一个语句通常完成会赋值声明调用等操作,我们将在下面讲解这些操作。

下面的代码就是三条语句。我们可以将语句分在几行写,但由于有分号作为语句之间的间隔,将其写在一行也是没有问题的。

a = 1; b = 2; c = 3;

在 C/C++ 中,语句之间可以有任意多的分号。一条语句也可以在中间的地方断开,分在多行完成。

a = 1; b = 2; c = 3;
d = 
    a + b + c;

{} 花括号

一对花括号(braces)中可以包含若干语句,我们习惯将花括号及其包裹的若干语句称为一个代码块(code block)。

{
    a = 1;
    b = 2.34;
}

花括号也可以在变量初始化时使用,其中包括的是一个或者一些值。

int a[] = {1,2,3};

"" 引号

在引号中的文本叫做“字符串(string)”,也就是一连串字符。

我们也将这些写在代码中的这些字符串的文本形式称为“字符串字面量(string literal)”。在字符串字面量中要表示控制字符,则需要使用转义字符,如 \b\n 等。

<> 尖括号

尖括号通常和 C++ 中的模板相关。模板是“泛型(generic)编程”中的一个概念。所谓“泛型”,即“通用类型”。有些程序的逻辑是和具体数据类型无关的,我们便可以使用泛型思想编写程序,这样的程序代码就叫做模板。在使用时,针对不同的数据类型,我们向模板中填入具体的参数,由此实例化出对应的代码。这提高了代码复用性,使得程序员可以编写更少的代码。

尖括号中通常填写数据类型的名称。比如,some_function 是一个函数模板。当我们书写 some_function<int> 时,编译器则会为我们生成针对 int 类型的代码,用英文描述就是“of int type”。

关于什么是“函数”,我们会在下面进行介绍。

注释

有时候,我们可能会希望能够在代码中记录一些说明语句作用的文本。这些文本叫做注释(comment)。注释会被编译器忽略掉,对于编译器来说,它们相当于不存在。

也有时候,我们在调试代码的过程中,会希望一些语句不被执行,也可以将代码“注释掉(comment out)”。

注释分为单行注释多行注释。在每一行中,//(连续的两个“斜线”)之后的文本,直到这一行结束,皆为注释(字符串中出现的“//”除外),这称为单行注释。多行注释以 /*(斜线和一个星号)开始,以 */(星号和一个斜线)为结束,并且不支持嵌套。也就是说,/* 会和最近的 */ 配对,将其中的内容形成注释,这就会导致嵌套的注释出现错误。

// This is a single-line comment.

// int a = 1; // commented out; won't be executed

/* This is a 
   multi-line
   comment.  */

续行符

单独出现在行末的反斜线(back slash)“\”会将下一行的内容接续到这一行来,也就是两行并作一行。也就是说,如果在一个单行注释行末添加续行符,则该单行注释的下一行也会变为注释。

// this is a single-line comment\
    but acctuall takes two lines

:: 作用域范围解析符

我们可能会在 C++ 代码中见到两个连在一起的冒号,它叫做作用域范围解析符。不过它的名字不重要,我们只需要理解它的含义表示符号的所属即可。比如 A::B表示 A 中的 B

我们可能经常见到如同 using namespace std; 这样的语句,这个顾名思义,表示“使用 std 这个命名空间”。std 是 C++ 标准库的命名空间。使用了 using 语句后,我们就可以直接使用命名空间中的内容了,否则,则需要指明使用的符号来自于何命名空间,如 std::cinstd::cout 等等。

.-> 成员关系运算符

成员关系运算符,一般用来访问一个“结构”的成员或者属性。如 a.b 可以表示“a”的“b”,apple.color 就表示苹果的颜色。成员关系运算符“.(单独的一个句点)”左边应该是一个具体的结构的名称,而相对的,“->(一个横线和一个右尖括号)”的左边则是对一个结构的引用,比如结构所在的“地址”“位置”。成员关系运算符的右侧则是成员的名称。

[] 方括号

一般情况下,方括号内部填入数据,用来索引元素。对于一个数组,我们可以用如 a[5] 的方式,访问数组“a”的第 6 个元素;对于关联容器,我们可以在方括号中填入键,以访问其对应的值,如 color["apple"]

cincout<<>>

C++ 为程序的标准输入(stdin)和标准输出(stdout)等封装为了流对象,即 std::cinstd::cout 等。

>> 为连续的两个向右的尖括号,在 C 语言中为“位移运算符”,但是在 C++ 中它被赋予了新的含义,叫做“流提取运算符”。顾名思义,其语义为“从某事物中读取”。我们也可以从其形状——指向右方的箭头——中观察出这层语义来。

我们可以从输入流中提取需要的内容。比如我们想在程序中从标准输入读入信息,存入“a”中,就可以使用如 cin >> a(注意箭头的方向)的语句。

同理,<< 运算符为连续的两个向左的尖括号,叫做“流插入运算符”,语义为“向某事物中写入”。在程序中,我们可以把内容插入到标准输出流中,如 cout << a(注意体会箭头的方向)。

如果读者不清楚标准输入输出,可以阅读“命令行入门”章节。简单地说,标准输入默认就是从终端读入,标准输出默认输出到终端。

用户从文字终端的输入并不会直接交予程序,而是暂存在缓冲区(buffer)中,程序在需要时才会从其中读取。

同理,输出到标准输出的字符一般不会立即显示,也会暂存在输出缓冲区中,待缓冲区满后,才会将其中字符输出到文字终端。

当用户输出的内容需要换行时,可以向输出流插入 \n 字符。如果在换行的同时,需要内容即时显示在终端上——即刷新(flush)缓冲区——则可以向 std::cout 中插入 std::endl

算术运算符

对于常用的运算,C/C++ 中提供有 +-*/ 符号,分别对应着“加”“减”“乘”“除”四种操作。这些操作可以用于 C/C++ 的各种内建类型(built-in types)上。

对于普通的数字类型的数据,这些符号对应的就是“加”“减”“乘”“除”操作。对于用户自定义的类型,用户可以重载这些运算符以使其适用于这些类型,或赋予其新的含义。比如,若“a”“b”表示两个集合,则 a - b 就可以表示两个集合的求差的结果。

C/C++ 也提供如 +=-= 这样的操作符。这些运算符表示将 +- 操作的结果,再赋于运算符的左操作数。比如,a = a + 5 可以写作 a += 5

C/C++ 还提供了自增和自减运算符。该运算符将对其操作数进行语义上的“加 1”或“减 1”操作。

自增运算符分为前自增和后自增运算符,其执行的效果是一样的,不过该变量及自增运算符所形成表达式的值不一样。前自增运算符表达式的值为自增后的新值,而后自增运算符表达式的值为自增前的旧值。

假设初始 a 的值为 5,则 a++ 得到的值为 5,++a 得到的值为 6。分别进行两种操作后,a 的值都变为了 6。

自减运算符类似,即 --xx--

比较运算符

比较运算符有左右两个操作数,其表达式的值只能为 true(真)或 false(假)。

这些运算符包括 ><>=<===!=,他们的语义和数学上类似,也是用来比较左右两边元素的值的大小。

比如 5 < 6 的值为 true6 == 75 != 5 的值为 false

布尔逻辑运算

上述的 truefalse 为布尔值(真值)。我们可以对布尔值进行若干运算:

  • 逻辑与(&& 运算符):运算符左右两操作数全为真,表达式值才为真,否则为假;
  • 逻辑或(|| 运算符):运算符左右两操作数全为假,表达式值才为假,否则为真(只要有一个操作数为真,表达式就为真);
  • 逻辑非(! 元素符):对布尔值(真值)取反。

C/C++ 中,0 总是表示 false,其余值向布尔值转换时都为“真”。此外,当一个布尔逻辑运算式的值已经可以确定时,其余部分的表达式不再进行求值操作,也称“短路求值(short-circuit evaluation)”。

字面量

直接出现在代码中的数字、字符串称为字面量。

表达式

操作符和操作数连接起来的即形成了表达式。操作数也可以是其他的表达式。

一般情况下,代码中出现的表达式即会被求值(evaluation),即表达式的值。