【C++ Primer(第5版)笔记】第2章:变量和基本类型

第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 = &pi; // 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 = &num; // 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; // 错误:pi是常量,必须用指向常量的指针指向它
const double *cptr = &pi; // 正确:pi是常量,必须用指向常量的指针指向它
*cptr = 42; // 错误:指向常量的指针不能用于改变其所指对象的值

double dval = 3.14;
cptr = &dval; // 正确:允许令一个指向常量的指针指向一个非常量对象;但是不能通过cptr改变dval的值
常量指针

常量指针的定义:

int num = 42;
int *const ptr = &num; // ptr是常量指针

常量指针指的是指针自己是常量(因此必须初始化),其值(存放在指针中的地址)不可修改,但有些情况(如下所述)可以用该指针修改其指向对象的值。如果常量指针指向一个变量(该变量不被const修饰),则可以用该常量指针修改其所指向对象的值;如果是一个指向常量的常量指针,则不能用该指针修改其所指向对象的值。

int num = 0;
int *const ptr1 = &num; // ptr1将一直指向num,可以用ptr1修改num的值
const double pi = 3.14159;
const double *const ptr2 = &pi; // ptr2是一个指向常量的常量指针

*ptr1 = 2; // 正确:ptr1可用于修改num的值
*ptr2 = 2.72; // 错误:ptr2不能用于修改pi的值

从右向左阅读法理解常量指针的定义:

int *const ptr1 = &num;
// 离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是一个引用,必须初始化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值