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::cin
、std::cout
等等。
.
、->
成员关系运算符
成员关系运算符,一般用来访问一个“结构”的成员或者属性。如 a.b
可以表示“a”的“b”,apple.color
就表示苹果的颜色。成员关系运算符“.
(单独的一个句点)”左边应该是一个具体的结构的名称,而相对的,“->
(一个横线和一个右尖括号)”的左边则是对一个结构的引用,比如结构所在的“地址”“位置”。成员关系运算符的右侧则是成员的名称。
[]
方括号
一般情况下,方括号内部填入数据,用来索引元素。对于一个数组,我们可以用如 a[5]
的方式,访问数组“a”的第 6 个元素;对于关联容器,我们可以在方括号中填入键,以访问其对应的值,如 color["apple"]
。
cin
、cout
、<<
、>>
C++ 为程序的标准输入(stdin)和标准输出(stdout)等封装为了流对象,即 std::cin
、std::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。
自减运算符类似,即 --x
和 x--
。
比较运算符
比较运算符有左右两个操作数,其表达式的值只能为 true
(真)或 false
(假)。
这些运算符包括 >
、<
、>=
、<=
、==
、!=
,他们的语义和数学上类似,也是用来比较左右两边元素的值的大小。
比如 5 < 6
的值为 true
,6 == 7
或 5 != 5
的值为 false
。
布尔逻辑运算
上述的 true
和 false
为布尔值(真值)。我们可以对布尔值进行若干运算:
- 逻辑与(
&&
运算符):运算符左右两操作数全为真,表达式值才为真,否则为假; - 逻辑或(
||
运算符):运算符左右两操作数全为假,表达式值才为假,否则为真(只要有一个操作数为真,表达式就为真); - 逻辑非(
!
元素符):对布尔值(真值)取反。
C/C++ 中,0
总是表示 false
,其余值向布尔值转换时都为“真”。此外,当一个布尔逻辑运算式的值已经可以确定时,其余部分的表达式不再进行求值操作,也称“短路求值(short-circuit evaluation)”。
字面量
直接出现在代码中的数字、字符串称为字面量。
表达式
操作符和操作数连接起来的即形成了表达式。操作数也可以是其他的表达式。
一般情况下,代码中出现的表达式即会被求值(evaluation),即表达式的值。