HOME/CPP Primer/

2.2 变量

Article Outline
TOC
Collection Outline

2.2 变量

变量提供一个具名的、可供程序操作的存储空间。C++中的每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。对C++程序员来说,“变量”和“对象”一般可以互换使用。

2.2.1 变量定义

变量定义的基本形式是:首先是类型说明符,随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。列表中每个变量名的类型都由类型说明符指定,定义时还可以为一个或多个变量赋初值:

int sum = 0, value, // sum、value和unit_sold都是int
unit_sold = 0;    // sum和unit_sold初值为0
Sales_item item;    // item的类型是Sales_item
// string是一种库类型,表示一个可变长的字符序列
std::string book("0-201-78345-X"); // book通过一个string字面值初始化

初始值

当对象在创建时获得了一个特定的值,我们说这个对象被初始化了。用于初始化变量的值可以是任意复杂的表达式。当一次定义了两个或多个变量时,对象的名字随着定义也就马上可以使用了。因此在同一条定义语句中,可以用先定义的变量值去初始化后定义的其他变量。

在C++语言中,初始化和赋值是两个完全不同的操作。初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

列表初始化

int unit_sold = {0};
int unit_sold{0};

当用于内置类型的变量时,这种初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错:

long double ld = 3.1415926536;
int a{ld}, b = {ld}; // 错误:转换未执行,因为存在丢失信息的危险
int c(ld), d = ld; // 正确:转换执行,且确实丢失了部分值

默认初始化

如果定义变量时没有指定初值,则变量被默认初始化,此时变量 被赋予了“默认值”。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。

如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。定义在函数体内部的内置类型变量将不被初始化。一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类值将引发错误。

每个类各自决定其初始化对象的方式。而且,是否允许不经初始化就定义对象也由类自己决定。如果类允许这种行为,它将决定对象的初始值到底是什么。

2.2.2 变量声明和定义的关系

为了允许把程序拆分成多个逻辑部分来编写,C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。

为了支持分离式编译,C++语言将声明和定义区分开来。声明使得名字 为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建与名字关联的实体。

变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。

如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量:

extern int i; // 声明i而非定义i
int j; // 声明并定义j

任何包含了显式初始化的声明即成为定义。我们能给由extern关键字标记的变量赋个初始值,但是这么做也就抵消了extern的作用。 extern语句如果包含初始值就不再是声明,而变成定义了:

extern double pi = 3.1416; // 定义

在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。

声明和定义的区别看起来也许微不足道,但实际上却非常重要。如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

2.2.3 标识符

C++的标识符由字母、数字和下画线组成,其中必须以字母或下画线开头。标识符的长度没有限制,但是对大小写字母敏感:

// 定义4个不同的int变量
int somename, someName, SomeName, SOMENAME;

C++语言保留了一些名字供语言本身使用,这些名字不能被用作标识符。 同时,C++也为标准库保留了一些名字。用户自定义的标识符中不能连续出现两个下画线,也不能以下画线紧连大写字母开头。此外,定义在函数体外的识符不能以下画线开头。

C++关键字

liagnas continue friend register true
alignof decltype goto reinterpret_case try
asm default if return typedef
auto delete inline short typeid
bool do int signed typename
break double long sizeof union
case dynamic_cast mutable static unsigned
catch else namespace static_assert using
char enum new static_cast virtual
char16_t explicit noexcept struct void
char32_t export nullptr switch volatile
const false private this while
constexpt float protected thread_local
const_cast for public throw

C++操作符替代名

and bitand compl not_eq or_eq
and_eq bitor not or xor

变量命名规范

变量命名有许多约定俗成的规范,下面的这些规范能有效提高程序的可读性:

  • 标识符要能体现实际含义。
  • 变量名一般用小写字母。
  • 用户自定义的类名一般以大写字母开头。
  • 如果标识符由多个单词组成,则单词间应有明显区分。

2.2.4 名字的作用域

作用域是程序的一部分,在其中名字有其特定的含义。C++语言中大多数作用域都以花括号分隔。

同一个名字在不同的作用域中可能指向不同的实体。名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。

嵌套的作用域

作用域能彼此包含,被包含(或者说被嵌套)的作用域称为内层作用域,包含着别的作用域的作用域称为外层作用域。

作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问该名字。同时允许在内层作用域中重新定义外层作用域已有的名字:

#include <iostream>
int reused = 10;
int main()
{
  int reused = 20; // 局部变量覆盖全局变量
  std::cout << reused << std::endl; // 输出20
}