[C++]C++Primer Chapter 2
变量和基本类型
2.1 基本内置类型
C++定义了一套包括算术类型(arithmetic type)和空类型(void)在内的基本数据类型。其中算术类型包含了字符、整型数、布尔值和浮点数。空类型不对应具体的值,仅用于一些特殊的场合。
2.1.1 算术类型
包括整型(integral type,包括字符和布尔类型在内)和浮点型。
2.1.3 字面值常量
一个形如42的值被称作字面值常量(literal),这样的值一望而知。每个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型。
1.整型和浮点型字面值
- 整型字面值具体的数据类型由它的值和符号决定。
- 默认的,浮点型字面值是一个double。
2.字符和字符串字面值
- 由单引号括起来的一个字符称为char型字面值,双引号括起来的零个或多个字符则构成字符串型字面值。
- 字符串字面值的类型实际上是由常量字符构成的数组(array)
- 编译器在每个字符串的结尾处添加一个空字符(′\0′),因此,字符串字面值的实际长度要比它的内容多1。
3.布尔字面值和指针字面值
- true和false是布尔类型的字面值
- nullptr是指针字面值
2.2 变量
变量提供一个具名的、可供程序操作的存储空间。
2.2.1 变量定义
1.列表初始化
- 当用于内置类型的变量时,这种初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错
2.默认初始化
- 默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。
- 如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。
- 定义于任何函数体之外的变量被初始化为0。
- 定义在函数体内部的内置类型变量将不被初始化(uninitialized)。
2.2.2 变量声明和定义的关系
声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)负责创建与名字关联的实体。
- 如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量
- 如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。
静态类型
C++是一种静态类型(statically typed)语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查(type checking)。在C++语言中,编译器负责检查数据类型是否支持要执行的运算,如果试图执行类型不支持的运算,编译器将报错并且不会生成可执行文件。程序越复杂,静态类型检查越有助于发现问题。
2.2.3 标识符
2.2.4 名字的作用域
2.3 复合类型
复合类型(compound type)是指基于其他类型定义的类型。
2.3.1 引用
引用即别名
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。
2.3.2 指针
指针引用对比
- 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
- 指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
void*指针
- void*是一种特殊的指针类型,可用于存放任意对象的地址。
- 利用void*指针能做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个void*指针。不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
1 |
|
从右向左阅读r的定义。离变量名最近的符号(此例中是&r的符号&)对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int指针。
2.4 const限定符
因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化。
默认状态下,const对象仅在文件内有效
1 |
|
编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到bufSize的地方,然后用512替换。
为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义(参见2.2.2节,第41页)。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。
某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其他(非常量)对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了
1 |
|
如上述程序所示,file_1.cc定义并初始化了bufSize。因为这条语句包含了初始值,所以它(显然)是一次定义。然而,因为bufSize是一个常量,必须用extern加以限定使其被其他文件使用。file_1.h头文件中的声明也由extern做了限定,其作用是指明bufSize并非本文件所独有,它的定义将在别处出现。
2.4.1 const的引用
对常量的引用,简称为常量引用,对常量的引用必须添加const限定符。
初始化和对const的引用
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成(参见2.1.2节,第32页)引用的类型即可。尤其是,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式
2.4.2 指针和const
1 |
|
2.4.3 顶层const
顶层const(top-level const)表示指针本身是个常量
底层const(low-level const)表示指针所指的对象是一个常量。
2.4.4 constexpr和常量表达式
常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。
显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
constexpr变量
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化
字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型”(literal type)。
算术类型、引用和指针都属于字面值类型。自定义类Sales_item、IO库、string类型则不属于字面值类型,也就不能被定义成constexpr。
尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。
指针和constexpr
必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关
1 |
|
p是底层const,q是顶层const
2.5 处理类型
2.5.1 类型别名
typedef和using
不要把原始类型替换进去,这种理解是错误的。
2.5.2 auto类型说明符
1.使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型
2.auto一般会忽略掉顶层const,同时底层const则会保留下来,比如当初始值是一个指向常量的指针时。如果希望推导出顶层const,需要显示声明。
2.5.3 decltype类型指示符
它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
decltype处理顶层const和引用的方式与auto有些许不同。如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。
decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用。