基本内置类型
类型转换
- 给一个无符号类型赋值一个超出表示范围值时,其结果是初始值对无符号类型表示最大值取模后的余数。
- 给一个有符号类型赋值一个超出表示范围值时,其结果是未定义的(undefined)。
切勿混用无符号和有符号类型
如果表达式里既包含有符号类型,也包含无符号类型,当有符号类型的取值为负时会出现异常结果,因为有符号类型会自动转换成无符号类型。
变量
变量定义
变量定义基本形式:类型说明符(type specifier) + 变量名
当对象在创建时获得一个特定的值,我们就说这个对象被初始化(initialized)
了。初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
在C++11中,用花括号初始化变量的形式成为列表初始化(list initialization)
。无论是初始化对象还是某些时候为对象赋新值,都可以使用花括号括起来的初始值。当应用于内置类型时,如果使用列表初始化而且初始值存在丢失信息的风险,则编译器报错。
如果变量定义时没有指定初值,则变量被默认初始化(default initialized)
。如果是内置类型,定义于任何函数外的变量初始化为0,定义在函数体内部的变量则不被初始化(unitialized)
。未初始化的内置类型的值是未定义的。类对象如果没有显式地初始化,则其值由类确定。所以,建议初始化每一个内置类型的变量。
变量定义和声明的关系
声明(declaration)
使得名字被程序知道,声明规定了变量的类型和名字,但不分配存储空间。定义(definition)
创建与名字关联的实体,定义申请存储空间,并且可能还会为变量赋一个初始值。
如果想声明一个变量而非定义,就在变量名前添加关键字extern,而且不要显式地初始化变量。任何显式初始化的声明即成为定义。
注意:在函数体内部,如果初始化一个由extern关键字标记的变量,将引发编译错误。因为函数体内变量的作用域仅限于函数体内。
变量只能被定义一次,但可以被声明多次。
1 | extern int i; //声明i而非定义i |
复合类型
复合类型(compound type)
是指基于其他类型定义的类型。一条声明语句由一个基本数据类型(base type)
和紧随的一个声明符(declarator)
列表组成。声明符的形式可以不同,因此在一条语句中可以定义出不同类型的变量。
切记:符号&和*只从属于某个声明符,而非基本数据类型的一部分。
引用
引用只能绑定在对象上,不能与字面值或者表达式的计算结果绑定
引用并非对象,只是一个已经存在对象的别名,对引用的操作都是在与之绑定的对象上进行
引用并非对象,因此不能定义引用的引用
引用不是对象,没有实际的地址,因此也不能定义指向引用的指针
引用必须初始化,并且和初始值对象一直绑定在一起,无法重新绑定到另外一个对象
指针
指针本身为对象,因此可以赋值和拷贝
指针可以在生命周期内指向不同的对象
指针无须在定义时初始化
指针类型要和它指向的对象类型严格匹配
空指针
空指针(null pointer)
不指向任何对象。C++11标准中使用字面值nullptr
将指针初始化为空指针。
建议初始化所有指针,并且在可能情况下,尽量等定义了对象后再定义指向它的指针。如果不清楚指针指向何处,就把它初始化为nullptr。
其他指针操作
只要指针拥有一个合法的值,就能将它用在条件表达式中。如果指针值为0,则条件为false;指针值非0,则条件为true。
对于两个相同类型的合法指针,可以使用==或者!=比较,比较结果是布尔类型。
void*指针
void是一种特殊的指针类型,可以存放任意对象的地址。不能直接操作void指针所指向的对象,因为并不知道对象的真实类型。
指向指针的引用
1 | int i = 42; |
从右向左阅读声明的含义,离变量名最近的符号对类型有最直接的影响,因此r是一个引用。声明符的其余部分用来确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后,声明的基本数据类型指出r引用的是一个int指针。
面对一条比较复杂的指针或者引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。
const限定符
const用来定义常量,一旦创建后其值不可以再修改,所以const对象必须初始化。常量特性仅在改变操作时才会发生作用。
默认状态下,const对象仅在文件内有效。所以当多个文件出现同名的const变量时,其实等同于在不同的文件中分别定义了独立的变量。如果想在多个文件之间共享const对象,必须在变量的定义和声明前添加extern关键字。
1 | // file_1.cc定义并初始化一个常量,该变量能被其他文件访问 |
constexpr和常量表达式
常量表达式(const expression)
指不会改变并且在编译过程就可以得到计算结果的表达式。一个对象是不是常量表达式由它的数据类型和初始值共同决定。
constexpr变量
C++11新标准规定,允许将变量声明为constexpr
类型以便有编译器来验证变量值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。C++11新标准允许定义一种特殊的constexpr函数,这种函数在编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量了。
一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指向的对象无关。
1 | const int *p = nullptr; //p是一个指向整型常量的指针,即指针指向的对象不可以通过这个指针修改,但指针可以重新指向不同的对象 |
处理类型
类型别名
使用类型别名让复杂的类型名字变得简单明了、易于理解和使用。有两种方法定义类型别名:
使用typedef关键字,typedef声明的是基本数据类型
使用别名声明(alias declaration)
using SI = Sales_item; //SI是Sales_item的同义词
auto类型说明符
C++新标准引入auto类型说明符,让编译器分析表达式的类型。auto让编译器通过初始值推算变量的类型,所以auto定义的变量必须有初始值。
decltype类型说明符
有时希望从表达会的类型推断出要定义变量的类型,但不想用该表达式的值初始化变量。C++11新标准引入decltype,它的作用是选择并返回操作数的数据类型,却不实际计算表达式的值。
1 | //decltype的结果可以是引用类型 |
因为r是一个引用,因此decltype(r)的结果是引用类型。如果想让结果是r所指向的类型,可以将r作为表达式的一部分,从而表达式的结果将是一个具体的值而非引用。另外,如果表达式的内容是解引用,则decltype将得到引用类型。因为解引用指针可以得到指针所指向的对象,而且能给对象赋值,所以decltype(*p)的结果类型是int&,而不是int。最后,如果给变量加上一层或者多层括号,编译器会把它当成一个表达式,所以decltype会得到引用类型。