变量提供一个具名的、可供程序操作的储存空间。
变量中的数据类型决定着变量所占内存空间的大小和布局方式,该空间能存储的值的范围,以及变量能参与的运算。
“变量”和“对象”一般可以互换使用。
对象:是指一块能储存数据并具有某种类型的内存空间。
不同的人使用对象的场景不同:
变量定义的基本形式是:首先是类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。
int sum = 0 , value; //sum、value和units_sold都是int
列表中的每个变量名的类型都由类型说明符指定。
int x; double y; //其中的int和double都是类型说明符
定义时还可以为一个或多个变量赋初值。
int sum = 0 , value = 0;
其中string是一种库类型,表示一个可变长的字符序列。
std::string book("0-201-78345-X"); //book通过一个string字面值初始化
book的定义用到了库类型std::string,string是一种表示可变长字符序列的数据类型,上面使用的是一种初始化string的方法,直接一种是把字面值拷贝给string对象。
当对象在创建时获得了一个特定的值,我们说这个对象被初始化(initialized)。
初始化时的值可以是任意复杂的表达式。
当一次定义了两个或多个变量时,对象的名字随着定义也就马上可以使用了。
因此在同一条定义语句中,可以用先定义的变量值去初始化后定义的其他变量;
double price = 109.99 , discount = price * 0.16; //正确:price先被定义并被赋值,随后被用于初始化discount。 double salePrice = applyDiscount(price , discount); //正确:调用函数applyDiscount , 然后用函数返回值初始化salePrice。
在C++语言中,初始化和赋值是两个完全不同的操作。
初始化:创建变量时赋予其一个初始值。
赋值:把对象的当前值擦除,而以一个新值来替代。
在C++中有几种不同初始化的方式,以下列出四种初始化的方式:
int units_sold = 0; int units_sold = {0}; int units_sold{0}; int units_sold(0);
⚠️第三种的初始化的方式,作为C++11新标准的一部分,用来花括号来初始化变量得到了全面应用。
用花括号的初始化的形式被称为列表初始化(list initializaton)。现在,无论是初始化对象还是某些时候为对象新赋值,都可以使用这样一组由花括号括起来的初始值了。
当用于内置类型的变量时,这样的初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错。
long double id = 3.141592; int a(id) , b = {id}; //错误:转换未执行,因为存在丢失信息的危险。 int c(id) , d = id; //正确:转换执行,且确实丢失了部分值
使用long double 的值初始化int变量时可能丢失数据,所以编译器拒绝了a和b的初始化请求。其中,至少id的小数部分会丢失掉,而且int也可能存不下id的整数部分。(这里指的是普遍现象,并不是指上述例子)。
平常我们可能不会故意用long double转换int , 但是我们经常在不经意之间做出这种初始化。
如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了“默认值”。
默认值由什么决定:
- 变量类型
- 定义变量的位置
如果是内置类型的变量未被显示初始化,其值由定义的位置决定。
定义于任何函数体之外的变量被初始化为0.
一种例外情况:定义在函数体内部的内置类型变量将不被初始化(uninitialized),一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类值将引发错误。
每个类各自决定其初始化对象的方式。而且,是否允许不经初始化就定义对象也由类来决定。
绝大多数类都支持无须显示初始化而定义对象的方式,这样的类提供了一个合格的默认值。
string类规定如果没有指定初值则生成一个空串:
std::string empty; //empty非显式地初始化为一个空串 Sales_item item; //被默认初始化的Sales_item对象
一些类要求每个对象都显式初始化,此时如果创建了一个该类的对象而未被对其做明确的初始化操作,将引发错误。
为了允许把程序拆分成多个逻辑部分来编写,C++语言支持分离式编译(separate compilation)机制,该机制允许将程序分割成若干个文件,每个文件可被独立编译。
为了支持分离式编译,C++语言将声明和定义区分开。
声明(declaration) :使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。
定义(definition): 负责创建与名字关联的实体。
变量声明规定了变量的类型和名字,这一点上与定义是相同的。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。
如果想声明一个变量而非定义它,就在变量名前添加 关键字extern,而且不要显式地初始化变量:
extern int i; //声明I而非定义i int j; //声明并定义j
任何包含了显式初始化的声明即成为定义。我们能给由extern关键字标记的变量赋一个初始值,但是这么做也就抵消了extern的作用。
extern语句如果包含初始值就不再是声明,而变成了定义:
extern double pi = 3.1415; //定义
在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。
变量能且只能被定义一次,但是可以被多次声明
变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。
tip:建议初始化每一个内置类型的变量。虽然并非必须这么做,但如果我们不能确保初始化后程序安全,那么这么做不失为一种简单可靠的方法。
C++是一种静态类型(statically typed)语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查(type checkig)
程序越复杂,静态类型检查越有助于发现问题。然而,前提是编译器必须知道每一个实体对象的类型,这就要求我们在使用某个变量之前必须声明其类型
C++的标识符(identifier)由字母、数字和下画线组成,其中必须以字符或下画线开头。标识符的长度没有限制,但是对大小字母敏感:
//定义4个不同的int变量 int somename,someName,SomeName , SOMENAME;
用户自定义标识符要求:
变量命名规范:
- 标识符要能体现实际含义
- 变量名一般用小写字母,如:index,不要使用Index或INDEX
- 用户自定义的类名一般以大写字母开头,如Sales_item
- 如果标识符由多个单词组成,则单词间应有明显区分,如:student_loan 或 studentLoan,不要使用studentloan
不论是在程序的什么位置,使用到的每个名字都会指向一个特定的实体:变量、函数、类型等。然而,用一个名字如果出现在程序的不同位置,也可能指向的是不同实体。
作用域(scope)是程序的一部分,在其中名字有其特定的含义。C++语言中大多数作用域都以花括号分隔。
名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
名字main定义于所有花括号之外,它和其他大多数定义在函数体之外的名字一样拥有全局作用域(global scope)。
而定义于main内的变量仅作用于main的开头花括号至末端花括号内,出了main函数所在的块就无法访问了,因此说此变量拥有块作用域(block scope)。
作用域能彼此包含,被包含(或者说被嵌套)的作用域称为内层作用域(inner scope),包含着别的作用域的作用域被称为外层作用域(outer)
作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问这个名字。同时,允许在内层作用域中重新定义外层作用域已有的名字:
#include <iostream> //该程序仅用于说明:函数内部不宜定义与全局变量同名的新变量 int main(){ int reused = 42; //reused拥有全局作用域 //输出#1:使用全局变量reused;输出42 0 std::cout << reused << "" << unique << std::endl; int reused = 0; //新建局部变量reused,覆盖了全局变量reused //输出#2:使用局部变量reused;输出0 0 std::cout << resued << "" << unique << std::endl; //输出#3:显式得访问全局变量reused;输出 42 0 std::cout << ::reused << "" << unique << std::endl; return 0; }
此处需要注意的是:输出#3使用作用域操作符来覆盖默认的作用域规则,因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。结果是,第三条输出语句使用全局变量reused,输出42 0。
⚠️:如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。