第2章 变量和基本类型
更新记录:
(1)2023/2/14
待更新:
(1)[p30]扩展字符集
(2)[p31]内置类型的机器实现
(3)[p34]含有无符号类型的表达式
(4)[p37]指定字面值的类型(细节)
(5)[p55]深入理解第一种例外的原理
(6)[p58]执行对象拷贝操作时顶层const与底层const的区别
(7)[p59]constexpr变量(补充)
(8)[p59]字面值类型(补充)
(9)[p60]指针和constexpr(细节)
(10)[p61]指针、常量和类型别名
(11)[p61]复合类型、常量和auto
(12)[p63]decltype和引用
(13)[p64]2.6自定义数据结构
2.1 基本内置类型
2.1.1 算术类型[p30]
算术类型的分类
算术类型分为整型和浮点型,其中整型包括布尔型、字符型、扩展的字符型(用于扩展字符集)、带符号的(signed)、不带符号的(unsigned)。
算术类型注意事项
1、基本数据类型分为整型和空类型。
2、算术类型的尺寸在不同机器上有所差别,C++标准规定了这些尺寸的最小值。
3、类型unsigned int可以缩写为unsigned。
算术类型的选择
1、当明确知晓数值不可能为负时,选用无符号类型。
2、使用int执行整数运算,如果超过int范围,则使用long long。
3、在算术表达式中不使用char或bool。
4、执行浮点数运算一般用double而不用float。
2.1.2 类型转换[p32]
类型转换规则
类型所能表示的值的范围决定了转换的过程。
1、将一个非布尔值赋值给布尔类型:初始值为0则结果为false,否则结果为true。
2、将一个布尔值赋给非布尔类型:初始值为false则结果为0,初始值为true则结果为1。
3、将一个浮点数赋给整数类型:结果仅保留浮点数中小数点之前的部分。
4、将一个整数值赋给浮点类型:小数部分记为0;若该整数所占空间超过浮点类型的容量,精度可能有缺失。
5、赋给无符号类型一个超出它表示范围的值:结果是初始值对无符号类型表示数值总数取模后的余数。
6、赋给带符号类型一个超出它表示范围的值:结果是未定义的。
含有无符号类型的表达式
切勿混用带符号类型和无符号类型:如果表达式里既有带符号类型又有无符号类型,当带符号类型为负时会出现异常结果,因为带符号数会自动转换成无符号数。
2.1.3 字面值常量[p35]
一个形如42的值被称作字面值常量,字面值常量的形式和值决定了它的数据类型。
整型和浮点型字面值
1、整型字面值可以写作十进制数、八进制数或十六进制数形式:0开头为八进制,0x或0X开头为十六进制。
2、整型字面值具体的数据类型由它的值和符号决定:
(1)十进制字面值是带符号数(不能unsigned),八进制和十六进制字面值可能是带符号的也可能是无符号的(可以unsigned)。
(2)十进制字面值类型是能容纳下当前值的int、long、long long中的最小尺寸。
(3)八进制和十六进制字面值类型是能容纳下当前值的int、unsigned int、long、unsigned long、long long和unsigned long long。
(4)类型short没有对应的字面值。
3、十进制字面值不可能是负数:-42是对一个十进制字面值取负值。
4、浮点型字面值:表现为一个小数或以科学计数法表示的指数,指数部分用E或e标识,例如:
3.14159 3.14159E0 0. 0e0 .001
默认情况下,浮点型字面值是一个double。
字符和字符串字面值
1、字符:单引号括起来的字符,如'a'
。
2、字符串字面值:常量字符构成的数组,如"Hello World!"
。
如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分割,则它们为一个整体,可分行书写:
cout << "a really, really long string literal "
"that spans two lines" << endl;
3、字符和字符串字面值的区别:
(1)'A’就是单独的字符A;
(2)"A"是个数组,包含两个字符:A和空字符(‘\0’)。
转义序列
1、普通转义序列:\t
、\n
、\'
等
2、泛化的转义序列
(1)\x后面紧跟1个或多个十六进制数字
(2)\后面紧跟1个、2个或3个八进制数字
举例:\7(响铃)、\12(换行符)、\40(空格)、\0(空字符)、\115(字符M)、\x4d(字符M)
指定字面值的类型
如3.14159L、42ULL等。
布尔字面值与指针字面值
1、布尔字面值:true, false
2、指针字面值:nullptr
2.2 变量
2.2.1 变量定义[p38]
重点概念
1、对象:一块能存储数据并具有某种类型的内存空间。
2、初始化:创建变量时赋予其一个初始值。
3、赋值:将对象的当前值擦除,而以一个新值来替代。
初始化与赋值是两种完全不同的操作。
初始化的方式
1、从左到右初始化
double price = 109.99, discount = price * 0.16; // 右边的式子使用左边刚初始化的变量。
2、四种等价初始化方式
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);
列表初始化(C++11)
列表初始化指用花括号来初始化变量。
列表初始化用于内置类型变量时,若存在丢失信息风险,则报错。
long double ld = 3.1415926536;
int a{ld}, b = {ld}; // 错误:转换未执行,因为存在丢失信息的危险
int c(ld), d = ld; // 正确:转换执行,且确实丢失了部分值
3、默认初始化:定义变量时没有指定初值
内置类型变量未被显式初始化时,若定义于任何函数体之外,则被初始化为0;若定义在函数体内部,则不被初始化,此时处于未定义状态。
使用未初始化的变量将带来无法预计的后果,建议初始化每一个内置类型的变量,如
int i = 0;
4、类对默认初始化的规定:绝大多数类都支持无需显式初始化而定义对象,这样的类提供了合适的默认值,例如string类规定如果没有指定初值则生成一个空串。
2.2.2 变量的声明与定义的关系[p41]
变量的声明与定义
1、变量的声明
(1)概念:使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。
(2)特点:规定了变量的类型与名字(与定义相同的一个特点)。
2、变量的定义
(1)概念:负责创建与名字关联的实体。
(2)特点:规定了变量的类型与名字、申请了存储空间、可能为变量赋一个初始值。
注解与举例
1、任何包含显式初始化的声明即成为定义。
2、变量能且只能被定义一次,但是可以被多次声明。
3、多文件(分离式编译):
(1)多个文件中使用同一个变量,就必须将声明与定义分离。
(2)该变量的定义必须出现在且只能出现在一个文件中。
(3)其他用到该变量的文件必须对该变量进行声明,但不能重复定义。
4、C++是静态类型语言:指在编译阶段检查类型。
extern int i; // 仅声明i
int j; // 声明并定义j
extern double pi = 3.1416; // 仅定义pi
在函数体内部初始化一个由extern标记的变量,将引发错误。
2.2.3 标识符[p42]
变量命名规范与C++关键字。
2.2.4 名字的作用域[p43]
大部分作用域以花括号分隔。
建议:第一次使用变量时再定义它。
1、全局作用域:整个程序范围内都可使用。
2、块作用域:离开某个花括号分隔的块就无法使用了。
3、嵌套的作用域:允许在内层作用域中重新定义外层作用域已有的名字,从而覆盖它,通过作用域操作符::
访问外层作用域变量。
#include <iostream>
using namespace std;
int a = 42; // a拥有全局作用域
int main()
{
int b = 0; // b拥有块作用域
cout << "a = " << a << " b = " << b << endl; // a = 42 b = 0
int a = 0; // 新建局部变量a,覆盖了全局变量a
cout << "a = " << a << " b = " << b << endl; // a = 0 b = 0
// 使用"::"显式访问全局变量a
cout << "a = " << ::a << " b = " << b << endl; // a = 42 b = 0
}
2.3 复合类型
复合类型:基于其他类型定义的类型。
2.3.1 引用(左值引用)[p45]
引用的定义
定义引用时,将引用与其初始值对象那个绑定,不是将初始值拷贝给引用。引用与其初始值对象同时变化。
int ival = 1024;
int &r = ival;
除两种例外,其他所有引用的类型都要与其绑定的对象严格匹配。
引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
引用的特点
1、引用本身不是一个对象
(1)引用即别名;(2)不能定义引用的引用;(3)不能定义指向引用的指针。
2、不允许对引用赋值和拷贝
3、引用必须初始化
4、无法令引用重新绑定到另一个对象上
int &r1 = 10; // 错误:引用类型的初始值必须是一个对象
double num = 3.14;
int &r2 = num; // 错误:此处引用类型的初始值必须是int型的对象
2.3.2 指针[p47]
指针与引用的区别
1、指针本身是一个对象;2、允许对指针的赋值和拷贝;3、无需在定义时赋初值;
4、可以先后指向不同的对象;5、可以定义指针的指针。
指针的定义与使用
1、指针的定义
int ival = 42;
int *p = &ival;
除两种例外,其他所有指针的类型都必须与它所指向的对象严格匹配。
double dval;
double *pd = &dval; // 正确:初始值是double型对象的地址
double *pd2 = pd; // 正确:初始值是指向double对象的指针
int *pi = pd; // 错误:指针pi的类型和pd的类型不匹配
pi = &dval; // 错误:试图把double型对象的地址赋给int型指针
2、指针值(即地址)可能的四种状态
(1)指向一个对象;
(2)指向紧邻对象所占空间的下一个位置:没有指向具体对象,不能访问;
(3)空指针:意味着指针没有指向任何对象,不能访问;
(4)无效指针:上述情况之外的其他值,试图拷贝或以其他方式访问无效指针的值都将引发错误。
3、利用指针访问对象
(1)指针必须指向一个具体对象(必须是有效指针);
(2)使用解引用操作符*。
4、&与*
(1)&可用于声明复合类型:声明引用(随类型名);也可以用作运算符:取地址(单独)。
(2)*可用于声明复合类型:声明指针(随类型名);也可用作运算符:解引用(单独)。
空指针
1、定义空指针的三种方法:
int *p1 = nullptr; // C++11
int *p2 = 0;
int *p3 = NULL;
2、不能将“int类型的变量”赋值给指针(哪怕值为0)。
int zero = 0;
pi = zero; // (设pi为int型指针)错误:不能将int变量直接赋给指针
3、建议初始化所有指针:不清楚指针指向,就初始化为nullptr或者0。 等定义了对象之后再定义指向它的指针。
int *pi = 0; // pi被初始化,但没有指向任何对象
赋值和指针
1、*p = 0
:修改指针所指对象的值,不修改指针所指对象(不修改指针的值)。
2、p = 0
(假设p是指针):修改指针的值,使其成为空指针,不指向任何对象。
其他指针操作
1、将指针p用于条件表达式中(指针必须拥有合法值,空指针合法)
if (p)
:若p为空指针,结果为false;若p指向一个具体对象,结果为true。
2、指针的比较
对于两个类型相同的合法指针,可以使用==
, !=
,根据指针存放的地址值是否相同决定运算结果(布尔值)。
指针存放的地址值相同的三种情况:都为空、都指向同一对象、都指向同一对象的下一地址。
void*指针
void*
指针可用于存放任意对象的地址,但不知道所指对象的类型,因此不能操作所指对象。以void*
指针的视角来看内存空间仅仅是内存空间,无法访问内存空间所存的对象。
void*指针的用途:(1)与别的指针比较;(2)作为函数的输入或输出;(3)赋给另一个void*指针。
double obj = 3.14, *pd = &obj;
void *pv = &obj; // 正确:obj可以是任意类型的对象
pv = pd; // 正确:void*能存放任意类型对象的地址
2.3.3 理解复合类型的声明[p51]
1、一条定义语句可以定义出不同类型的变量,例如:
int i = 1024, *p = &i, &r = i;
2、定义多个复合类型的误解
int* p1, p2; // 正确的理解为:p1是指向int的指针,p2是int型变量
int *p1, *p2; // 正确的理解为:p1, p2都是指向int的指针
为了避免误解,对于涉及指针或引用的声明,应坚持使用以下两种正确写法中的一种:
(1)将类型修饰符(*或&)与变量标识符(p1等)写在一起:
int *p1, *p2; // p1、p2都是指向int的指针
(2)将类型修饰符和类型名写在一起,且每一句只定义一个变量:
int* p1; // p1是指向int的指针
int* p2; // p2是指向int的指针
指向指针的指针
声明符中修饰符的个数没有限制。允许把指针的地址存放到另一个指针当中,例如:**
表示指向指针的指针,***
表示指向指针的指针的指针,以此类推。
int ival = 1024;
int *pi = &ival;
int *ppi = π // ppi为指向int型指针的指针
cout << **ppi << endl; // 需要两次解引用得到最原始对象的值
指向指针的引用
引用本身不是对象,不能定义指向引用的指针。但是可以定义指向指针的引用,其指的是对指针的引用,例如:
int i = 42;
int *p; // p是一个int型指针
int *&r = p; // r是一个对指针p的引用
r = &i; // r是指针p的引用,因此给r赋值&i就是令p指向i,等效于p = &i;
*r = 0; // 等效于*p = 0;
/*
从右向左阅读法:距离变量名最近的符号决定变量的类型
*&r是一个引用,其余部分决定r引用的类型
int *&r = p; 中r引用的是一个int指针
*/
2.4 const限定符
2.4.0 初始化和const[p53]
const对象的初始化
1、const对象一旦创建后其值就不能改变,因此const对象必须初始化。 有以下三种初始化方式:
const int i = get_size(); // 正确:运行时初始化
const int j = 42; // 正确:编译时初始化
const int k; // 错误:k是一个未经初始化的变量
2、只能在const类型的对象上执行不改变其内容的操作。
其中有一种操作就是初始化:如果用一个对象去初始化另一个对象,则它们是不是const都无关紧要,例如:
int i = 42;
const int ci = i; // 可以用int型给const int型赋值
int j = ci; // 可以用const int型给int型赋值
默认状态下,const对象只在文件内有效
1、该规则产生的背景:
以编译时初始化方式定义一个对象,则编译器在编译过程中将用到该变量的地方都替换成对应的值,例如:
const int bufSize = 512; // 编译器会找到代码中所有用到bufSize的地方将其替换为512
要执行以上替换,编译器必须能找到变量bufSize的初始值512。如果程序包含多个文件,则每个使用bufSize的文件都必须能访问到初始值512,因此每个文件都必须有对bufSize的定义,与同一变量不能重复定义相矛盾。
2、该规则的具体解释:
为了避免重复定义,const对象被设定为只在文件内有效:当多个文件都出现const对象bufSize时,每个文件的bufSize都是不同的bufSize,即在不同文件中分别定义了独立的变量。
3、该规则带来的问题的解决方案:**如果想在多个文件之间共享const对象,为了只在一个文件中定义const,而在多个文件中声明并使用它,对于const变量不管声明还是定义都添加extern。**例如:
// test.h
#pragma once
extern const int bufSize = 512; // bufSize的定义
// test.cpp
#include "test.h"
#include <iostream>
using namespace std;
int main()
{
extern const int bufSize; // bufSize的声明,与test.h中定义的bufSize是同一个
cout << bufSize << endl; // 输出结果为512
}
2.4.1 const的引用(常量引用)[p54]
常量引用概述
对常量的引用带有const,对常量的引用不能用于修改它绑定的对象的值。“常量引用”即“对常量的引用”或“对const的引用”。
const int ci = 1024;
const int &r1 = ci; // 正确:r1是对常量的引用,r1与其引用的对象ci都是常量
// r1不能用来修改ci的值,无论ci是否带有const
r1 = 42; // 错误:无法令引用重新绑定到另一个对象上
int &r2 = ci; // 错误:试图用一个非常量引用指向一个常量对象
初始化和对const的引用
常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。
1、最常用规则:
(1)如果a是常量(有const修饰),则只能用const修饰的r引用a,r不能用于修改a的值;
(2)如果a是变量(不带const),也可以用const修饰的r引用a,r仍不能用于修改a的值,但是可以通过其他途径修改a的值。 即允许将const int&绑定到一个普通int上。
2、[2.3.1提到的两种例外中的第一种]
初始化常量引用时允许用任意表达式作为初始值,只要该表达式结果能转换成引用类型即可:允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。 例如:
int i = 42;
const int &r1 = i; // 正确:允许为一个常量引用绑定一个非常量的对象
const int &r2 = 42; // 正确:允许为一个常量引用绑定一个字面值
const int &r3 = r1 * 2; // 正确:允许为一个常量引用绑定一个一般表达式
int &r4 = r1 * 2; // 错误:r4为非常量引用,不符合第一种例外情况
3、深入理解第一种例外的原理
2.4.2 指针和const[p56]
指向常量的指针
指向常量的指针的定义:
int num = 42;
const int *ptr = # // ptr是指向常量的指针
和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
[2.3.2]中提到的两种例外的第一种:允许令一个指向常量的指针指向一个非常量对象。
指向常量的指针不能用于改变其所指对象的值,但可以修改指针指向的对象(即指针的值可变,即存放在指针的地址可变)。
最重要的两条规则:
(1)如果a是常量(有const修饰),则只能用const修饰的指针p指向a,不能通过p修改a的值;
(2)如果a是变量(没有const修饰),也可以用const修饰的指针p指向a,不能通过p修改a的值,但是可以通过其他途径修改a的值。
const double pi = 3.14;
double *ptr = π // 错误:pi是常量,必须用指向常量的指针指向它
const double *cptr = π // 正确:pi是常量,必须用指向常量的指针指向它
*cptr = 42; // 错误:指向常量的指针不能用于改变其所指对象的值
double dval = 3.14;
cptr = &dval; // 正确:允许令一个指向常量的指针指向一个非常量对象;但是不能通过cptr改变dval的值
常量指针
常量指针的定义:
int num = 42;
int *const ptr = # // ptr是常量指针
常量指针指的是指针自己是常量(因此必须初始化),其值(存放在指针中的地址)不可修改,但有些情况(如下所述)可以用该指针修改其指向对象的值。如果常量指针指向一个变量(该变量不被const修饰),则可以用该常量指针修改其所指向对象的值;如果是一个指向常量的常量指针,则不能用该指针修改其所指向对象的值。
int num = 0;
int *const ptr1 = # // ptr1将一直指向num,可以用ptr1修改num的值
const double pi = 3.14159;
const double *const ptr2 = π // ptr2是一个指向常量的常量指针
*ptr1 = 2; // 正确:ptr1可用于修改num的值
*ptr2 = 2.72; // 错误:ptr2不能用于修改pi的值
从右向左阅读法理解常量指针的定义:
int *const ptr1 = #
// 离ptr1最近的符号是const,则ptr1本身是常量,剩余部分int *决定其类型为指向int类型的常量指针。
int val = 42;
const int *ptr3 = &val;
// ptr3左侧说明ptr3是指向int类型常量的指针,相当于*ptr3被const修饰,不能改变*ptr3(其所指向对象)的值。
2.4.3 顶层const与底层const[p57]
1、顶层const指针、底层const指针与同时具有顶层底层const的指针的比较:
表现 | 本质 | 特点 | |
---|---|---|---|
底层const指针 | const int *ptr; | 指向常量的指针 | 可以指向常量或变量;不能通过指针修改其指向对象的值;指针指向的对象/指针的值/存放在指针的地址可变 |
顶层const指针 | int *const ptr; | 常量指针 | 可以指向常量或变量;可以用来修改其指向对象的值;指针指向的对象/指针的值/存放在指针的地址不可变 |
顶层底层const指针 | const int *const ptr; | 指向常量的常量指针 | 可以指向常量或变量;不能通过指针修改其指向对象的值;指针指向的对象/指针的值/存放在指针的地址不可变 |
只有指针类型能同时具有底层和顶层const。
2、顶层与底层const的扩展
除了表示常量指针,顶层const还可以表示任意对象是常量,对任意数据类型都适用。
底层const与指针和引用等复合类型的基本类型部分有关。
3、理解顶层const与底层const的口诀:
(1)前不改值后地址不变;(const在前的指针不能修改其所指对象的值,const在后的指针所指向的地址不能改变)
(2)顶层比底层简单,底层涉及地址;
(3)指针的顶层const在后,普通数据类型的顶层const在前。
(4)用于声明引用的const都是底层const。
int i = 0;
int *const p1 = &i; // p1是常量指针,不能改变p1的值,顶层const
const int ci = 42; // 不能改变ci的值,顶层const
const int *p2 = &ci; // p2是指向常量的指针,允许改变p2的值,底层const
const int *const p3 = p2; // 右侧是顶层const,左侧是底层const
const int &r = ci; // 用于声明引用的const都是底层const
4、执行对象拷贝操作时顶层const与底层const的区别
(1)将带有顶层const的变量赋值给不带顶层const的变量可行
#include <iostream>
using namespace std;
int main()
{
const int ci = 42;
int i = ci; //将ci的值拷贝给i时,忽略了ci的顶层const,也就是说i不被顶层const修饰,可以修改i的值。
i = 2;
cout << "ci = " << ci << endl;
cout << "i = " << i << endl;
return 0;
}
(2)都具有底层const时,顶层const不影响赋值
int i = 0, j = 0;
const int *p1 = &i; //p1指向i,p1被底层const修饰,可以修改指向,但不能修改i的值
const int *const p2 = j; //p2指向j,p2被顶层和底层const修饰,既不能改变指向,又不能修改j的值
p1 = p2; //p1, p2都被底层const修饰,而顶层const不影响赋值,所以本句成立
(3)一般来说,非常量可以转化为常量,反之则不行。
2.4.4 constexpr和常量表达式[p58]
常量表达式
1、常量表达式的概念:
常量表达式指值不会改变,且在编译过程中就能得到计算结果的表达式。
2、常量表达式概念的理解:
(1)不用运行就能得到结果。
(2)如果要求用户输入才能有确定值,那么编译过程中无法得到结果,就不是常量表达式。
(3)字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
3、决定是否常量表达式的因素:数据类型、初始值。
const int a = 20; // a是常量表达式:数据类型(const int)、初始值都满足
const int b = a + 1; // b是常量表达式:数据类型(const int)、初始值都满足
int c = 27; // c是变量,不是常量表达式:数据类型(int)不满足、初始值满足
const int size = get_size(); // sz不是常量表达式,因为get_size()具体值要在运行后才能得到:数据类型满足、初始值不满足
constexpr变量(C++11)
1、用途:将变量定义为constexpr类型以便由编译器验证变量的值是否为常量表达式。如果不是常量表达式,则在编译时报错。
constexpr int a = 20; // 正确:20是常量表达式
constexpr int b = a + 1; // 正确:a + 1是常量表达式
constexpr int sz = size(); // 只有当size是一个constexpr函数时才是一条正确的声明语句
2、建议:如果认定一个变量是一个常量表达式,可以将它声明为constexpr类型,让编译器判定到底是否为常量表达式。
字面值类型
1、字面值类型的特点:比较简单,值也显而易见、容易得到。
2、字面值类型举例:算术类型、引用、指针。
指针和constexpr
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关。
const int *p = nullptr; // p是一个指向整型常量的指针,底层const
constexpr int *q = nullptr; // q是一个指向整数的常量指针,顶层const
与其他常量指针类似,constexpr指针既可以指向常量也可以指向非常量。
2.5 处理类型
2.5.1 类型别名[p60]
传统方法typedef
1、非指针形式
#include <iostream>
using namespace std;
int main()
{
typedef double wages; //现在用wages声明变量就相当于声明double类型的变量
wages a = 6.1; //a是一个double类型的变量,本句等价于double a = 6.1;
cout << a << endl;
return 0;
}
2、指针形式
#include <iostream>
using namespace std;
int main()
{
typedef int *pointer; //现在用pointer声明变量就相当于声明int*类型的变量
int a = 0;
pointer p = &a; //p是一个指向int类型的指针,p指向a,本句等价于int *p = &a;
cout << p << endl; //输出结果为a的地址
return 0;
}
using别名声明(C++11)
#include <iostream>
using namespace std;
int main()
{
using wages = double; //用的是自己创造的类型名,所以后面要用谁,谁就写在等号左边
wages a = 6.1; // a是一个double类型的变量,本句等价于double a = 6.1;
cout << a << endl;
return 0;
}
2.5.2 auto类型说明符(C++11)[p61]
auto的基本语法
1、auto的作用:让编译器根据所给初始值分析表达式所属的类型。
2、auto的使用
(1)能在一条语句中声明多个变量。
auto i = 0, *p = &i; // auto识别为int:i是整数、p是整型指针
(2)所有变量的初始基本类型都必须一样。 要么都是int,要么都是double,不能int和double混搭。
auto a = 0, b = 3.14; // a, b的类型不一样,会报错
复合类型、常量和auto
1、auto与引用
int i = 0, &r = i;
auto a = r; //此时auto将a识别为int类型,而非引用类型
2、auto与const
auto一般会忽略掉顶层const,同时底层const则会保留下来。
2.5.3 decltype类型指示符(C++11)[p62]
decltype的一般用法
decltype的行为:编译器分析表达式并得到它的类型,不实际计算表达式的值,然后返回其类型。
decltype(f()) sum = x; // sum的类型就是函数f的返回类型
// 编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型
const int a = 0, &r = b;
decltype(a) x = 0; //x的类型是const int
decltype(r) y = x; //y的类型是const int&,y绑定到变量x
decltype(r) z; //错误:z是一个引用,必须初始化