声明语句
概述
声明语句(Declaration statement)是指执行声明操作的程序片段。我们目前遇到的大部分情况下,声明语句在执行声明操作的同时也会执行定义操作,使得声明引入的名字可以使用。
声明语句是语句(Statement)的一种。语句是函数(Function)的组成部分。
声明语句的写法实际上比较复杂。不过我们目前所需掌握的简单的声明语句可以比较容易地写出。简单的变量声明语句由以下几部分组成:
类型说明符 变量名 初始化器;
例如:
int a{42};
你可以和我们上一章学过的写法作比较。下面就 int a{42};
这个例子来详细解释简单的变量声明语句的组成。
类型说明符
一上来开头的 int
叫做类型说明符(Type specifier),它指示声明变量的类型。我们已经知道了 int
代表变量的类型是整数类型。除此之外还有一些类型和它们的类型说明符目前你稍微了解即可:
double
表示一个小数类型,可以存储一个小数如3.14
5.21
;char
表示一个字符类型(Character type),它可以存储一个字符。(字符就是一个英文字母之类的东西,比如'a'
'D'
'h'
等;也可以是一部分符号如'?'
'='
'&'
;甚至还可以是数字'0'
'1'
……'9'
。(数字不是数。))你会注意到表示字符的时候需要用单引号括起来。
其余的类型我们会在本章的后续内容中介绍,因此刚才所述的这两种类型只需简单了解就够了。所以说,当类型说明符为 double
时,就表示目前声明并定义的变量是一个小数类型。
“小数类型”一词是为了便于理解,它的真实名称叫“双精度浮点型”,也就是类型说明符中
double
的含义。
变量名
在 int a{42};
这个例子中,a
叫做变量名,我们已经很熟悉了。在这里我们将介绍变量名的命名规则。
大部分情况下,变量名是由英文字母 a...z,A...Z
、数字 0...9
和下划线 _
组成的文本序列;其中这个序列的第一个字符不能是数字,同时整个变量名不得与关键字相同。
变量名是标识符(Identifier)的一种表现。上文所述的规则实际上是标识符的规则。
那么类似 a
b
c
sum
average
total
等等,都是正确的(指可以使用的,有时也叫作合法的)变量名。包含数字或者下划线的如 x1
a123
zhang_san
zhang_3
也是没有问题的。但是 3zhang
是没法用的,因为数字不能出现在第一个字符上。除了数字、英文字母和下划线之外的字符一般不能用于变量名中。比如 test#1
不能用作变量名,因为 #
这个符号不是数字、字母或下划线。同理, $123
Mc.Dowell
set-minus
之类的也都包含那些不能用作变量名的字符。除此之外,我不推荐使用下划线开头的命名。
不推荐的原因是可能会与标准库具体实现中的命名冲突;这些命名均为双下划线开头或者下划线+大写字母的方式开头。
最后一句“不得与关键字相同”是什么意思呢?关键字指的是在 C++ 程序中有特殊意义的单词。比如我们目前遇到的关键字有:
int
,它的含义是声明并定义整数类型;类似的double
和char
也是关键字;if
,它的含义是引入分支;else
,它的含义是引入分支的“否则”部分;while
,它的含义是引入循环。
正因为这些词有着如上所述的特殊意义,所以不能把它们用作变量名。C++ 至今包括 97 个关键词。为了方便参考,我将它们罗列在下面:
alignas | constinit | long | struct |
alignof | const_cast | mutable | switch |
and | continue | namespace | synchronized |
and_eq | co_await | new | template |
asm | co_return | noexcept | this |
atomic_cancel | co_yield | not | thread_local |
atomic_commit | decltype | not_eq | throw |
atomic_noexcept | default | nullptr | true |
auto | delete | operator | try |
bitand | do | or | typedef |
bitor | double | or_eq | typeid |
bool | dynamic_cast | private | typename |
break | else | protected | union |
case | enum | public | unsigned |
catch | explicit | reflexpr | using |
char | export | register | virtual |
char8_t | extern | reinterpret_cast | void |
char16_t | false | requires | volatile |
char32_t | float | return | wchar_t |
class | for | short | while |
compl | friend | signed | xor |
concept | goto | sizeof | xor_eq |
const | if | static | |
consteval | inline | static_assert | |
constexpr | int | static_cast |
你不用特意地去记住它们。我们常用的只有其中的三十几个,将在本书中慢慢介绍。所有关键字的含义在附录中都会有简单的介绍和使用说明。我在附录介绍了关于变量命名的一些习惯用法。
除此之外还有 6 个叫做保留字的东西。保留字可以被用作变量名,但同时也具有特殊意义。我建议尽量避免变量名与保留字相同。以下是这六个保留字:
override
final
import
module
transaction_safe
transaction_safe_dynamic
初始化器
C++ 中初始化器(Initializer)非常复杂繁琐,但好在我们只需掌握一种(或者两种)就够了。这种初始化器叫做列表初始化器,长成这样:
{初始化值}
用一对大括号围起来,然后里面是初始化值。变量的初始化值的含义是,当这个变量拥有它的存储空间的那一刻,指明这个存储空间应该放什么数据。比如 int a{42};
中 {42}
是初始化器, 42
就是初始化值:意思是当 a
拥有了存储空间之后,就放上 42
这个数。
第二种初始化器——直接初始化器
(参数列表)
,需要等到面向对象的时候才会接触到。此外,你可能还经常见到int a = 42;
这种写法,也即形如= 初始化值
的初始化器。这种写法在目前阶段可以认为和大括号初始化效果一致。
初始化器是可有可无的(正式的说法叫可选的(Optional))。也就是说,可以不写初始化器,这个时候对于变量获得的存储空间则不进行任何操作——因此当没有初始化器的时候,无法知道这个变量的值是多少。比如:
因此只在确定这个变量稍后会被赋值的情况下才省去初始化器。稍后会被赋值也包含稍后会允许用户输入到这个变量的情形。
总结
作为例子,以下列举了一些声明语句,它们的含义也用注释写出:
int a{42}; // 声明并定义 a 为整数类型,它的初始化值为 42
double pi{3.14}; // 声明并定义 pi 为小数类型,它的初始化值为 3.14
double sqrt2{1.41421}; // 声明并定义 sqrt2 为小数类型,它的初始化值为 1.41421
char letter{'G'}; // 声明并定义 letter 为字符类型,它的初始化值为字符 'G'。注意单引号的使用
char symbol{'@'}; // 声明并定义 symbol 为字符类型,它的初始化值为字符 '@'
同时,还有一种简化写法:对于相同类型的变量,可以将变量名+初始化器写在同一行,用逗号 ,
分隔,共用一个类型说明符。比如刚才的声明语句可以简化为:
// 声明并定义 a 为整数类型,它的初始化值为 42;声明并定义 b 也为整数类型,它的初始化值为 56
int a{42}, b{56};
// 下面两个都是 double 类型
double pi{3.14}, sqrt2{1.41421};
// 下面两个都是 char 类型
char letter{'G'}, symbol{'@'};
以后我们会经常使用这种简化。
这种简化只能用于简单类型。对于复合类型(如指针、数组、函数、引用)来说,这种简化会造成麻烦。
练习
- 练习使用声明语句:尝试不同类型、变量名和初始化器。尝试使用同类型声明语句的简化。编译它们来检测是否正确。
- 判断以下变量名是否合法:
3D64
lotus_1_2_3
a>b
McDowell
get3DBuffer
@guyutongxue
Donald Trump
student_name
class
。
练习参考答案
第二题:否(数字开头),是,否(含符号 >
),是,是,否(含符号 @
),否(含空格),是,否(与关键字相同)。