C++ Primer 学习 -- Day 1

1、Hello,World!

1.1、输入输出

#include <iostream>     //引入库
using namespace std;    //使用名字为 std 的命名空间
int main(){
    int x, y;
    std::cin >> x >> y;
    std::cout << x << std::endl;    //是字母l不是数字1哦
}

① 前缀 std:: 指出名字 cin 和 cout 是定义在名为 std 的命名空间(namespace)中的。命名空间可以帮助我们避免不经意的名字定义冲突,以及使用库中相同名字导致的冲突。标准库定义的所有名字都在命名空间 std 中。

② 输出语句中的 endl 的效果为:结束当前行,并将与设备关联的缓冲区(buffer)中的内容刷到设备中。此缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。所有在调试中使用cout 语句测试时 + “<< std::endl” 不容易出错

所得:规范书写很重要,不要觉得麻烦,省略了好像也没事就不写

1.2、while(std::cin >> value)

​ 当我们使用一个 istream 对象作为条件的时候,其效果是检测流的状态。如果流是有效的,即流未遇到错误,那么检测成功。当遇到文件结束符(end-of-file),或遇到一个无效输入时(如定义value为 int 类型,cin输入的值不是整数,istream对象的状态就会变为无效。处于无效状态的 istream 对象会使条件变为假。

2、变量和基本类型

2.1.1、unsigned

​ 一般int、short、long 和long long 都是带符号的,通过在这些类型名前添加unsigned 就可以得到无符号类型(不能取负值,但是能表示的正数值域翻倍)

建议

① 当明确知晓数值不可能为负时,选用无符号类型。

② 在算术表达式中不要使用char 或 bool ,只有存放字符或布尔值时才使用它们。因为类型char 在一些机器上有符号的,在另一些机器上是无符号的,所以使用char 进行运算特别容易出问题。

③ 执行浮点数运算选用 double,精度更高,运算更快。

2.1.2 类型转换

① 把一个非布尔型的算术值赋给布尔类型时,初始值为0 则结果为 false,否则结果为 true。

② 当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。

③ 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能工作、可能崩溃、可能生成垃圾数据。

bool b = 42;                         // b 为 true;
unsigned char c = -1;                // 假设char 占8 比特,c 的值为 255
signed char c2 = 256;                // 假设char 占8 比特,c 的值是未定义的

建议

避免无法预知和依赖于实现环境的行为

① 无法预知的行为源于编译器无须(有时是不能)检测的错误。即使代码编译通过了,如果程序执行了一条未定义的表达式,仍可能产生错误。

② 程序也应该尽量避免依赖于实现环境的行为。如果我们把 int 的尺寸看成是一个确定不变的已知值,那么这样的程序就称作不可移植的(nonportable)。当程序移植到别的机器上后,依赖于实现环境的程序可能就会发生错误。

建议

切勿混用带符号类型和无符号类型

​ 如果表达式里既带有符号类型又有无符号类型,当带符号类型取值为负时会出现异常结果,这是因为带符号数会自动转换成无符号数。

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl;    // 输出 -84
std::cout << u + i << std::endl;    // 如果 int 占 32 位,输出 4294967264

// 注意,无符号的 for 循环问题
// 错误:变量 u 永远也不会小于 0 ,循环条件一直成立
for (unsigned u = 10; u >= 0; --u) 
    std::cout << u << std::endl;     //当 u == 0 时,执行 --u 会成正数

// 解决方法其一
unsigned u = 10;
while (u > 0) {    
    --u;                    //先减 1 ,这样最后一次迭代时就会输出 0     
    std::cout << u << std::endl;
}

2.2.1 变量定义

对象:一块能存储数据并具有某种类型的内存空间

提醒

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。

列表初始化

// 以下 4 条语句都可以 定义一个名为 units_sold 的 int 变量并初始化为 0
int units_sold = 0;
int units_sold = {0}; 
int units_sold{0};
int units_sold(0);

// 注意:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错:
long double ld = 3.1415925536;
int a{ld}, b = {ld};      // 错误:转换未执行,因为存在丢失信息的风险
int c(ld), d = ld;        // 正确:转换执行,且确实丢失了部分值

提醒

建议初始化每个内置类型的变量。虽然并非必须这么做,但如果我们不能确保初始化后程序安全,那么这么做不失为一种简单可靠的方法

2.2.2 变量声明和定义

​ C++ 语言支持**分离式编译(separate compilation)**机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。

​ 为了支持分离式编译,C++ 语音将声明和定义区分开来。**声明(declaration)使得名字为程序所知,而定义(definition)**负责创建与名字关联的实体。

// 如果想声明一个变量而非定义它,就在变量名前添加关键字 extern,而且不要显式地初始化变量:
extern int i;    // 声明 i 而非定义 i
int j;           // 声明并定义 j

// 注意:如果我们由 extern 关键字标记的变量赋一个初始值,就抵消了 extern 的作用,变成了定义
extern double pi = 3.1416;   // 定义

变量能且只能被定义一次,但是可以被多次声明。

提醒

当你第一次使用变量时再定义它

​ 一般来说,在对象第一次被使用的地方附近定义它是一种好的选择,因为这样做有助于更容易地找到变量的定义。更重要的是,当变量的定义与它第一次被使用的地方很近时,我们也会赋给它一个比较合理的初始值。

2.3.1 引用

**引用(reference)**为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明符写成 &d 的形式来定义引用类型,其中 d 是声明的变量名。

int ival = 1024;
int &refVal = ival;                  // refVal 指向 ival (是 ival 的另一个名字)
int &refVal2;                        // 报错:引用必须被初始化

引用特点

① 引用必须初始化。一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值**绑定(bind)**在一起,而不是将初始值拷贝给引用。一旦绑定在一起后就无法再绑定其他对象了。

② 引用并非对象,而是别名,不能定义引用的引用。定义一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的。

refVal = 2;                          // 把 2 赋给 refVal 指向的对象,此处即是赋给了 ival
int ii = refVal;                     // 与 ii = ival 执行结果一样

③ 除两种特例外,引用的类型要和与之绑定的对象严格匹配。而且,引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

int &refVal = 10;                    // 错误:引用类型的初始值必须是一个对象
double dval = 3.14;                  
int &refVal5 = dval;                 // 错误:此处引用类型的初始值必须是 int 型对象

2.3.2 指针

**指针(pointer)**是 “ 指向(point to)” 另外一种类型的复合类型。通过将声明符写成 *d 的形式来定义指针类型,其中 d 是变量名。

double dval;
double *pd = &dval;                  // 正确:初始值是 double 型对象的地址,此 * 是声明符
double *pd2 = pd;                    // 正确:初始值是指向 double 对象的指针

int zero = 0;
int *pi = zero;                      // 错误:nt 变量不能直接赋值给指针
int *pi = &zero;                     // 定义指针pi,指向 zero 的地址
*pi = i;                             // 给 pi 指向的地址赋值

指针特点

① 指针存放某个对象的地址,要想获取该地址,需要使用取地址符(操作符&)。注:因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。但是存在对指针的引用

② 除两种特例外,指针的类型要和所指向的对象严格匹配。可指向字面值或某个表达式的计算结果。

③ 访问的指针必须有效。

④ 如果指针指向了一个对象,则允许使用解引用符(操作符 * )来访问该对象。对指针解引用会得到所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值。

⑤ int 变量不能直接赋值给指针。

关键概念

某些符合有多重含义,想 & 和 * 这样的符合,即能用作表达式里的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义。

int i = 42;
int &r = i;                    // & 紧随类型名出现,因此是声明的一部分,r 是一个引用
int *p;                        // * 紧随类型名出现,因此是声明的一部分,r 是一个指针
p = &i;                        // & 出现在表达式中,是一个去地址符
*p = i;                        // * 出现在表达式中,是一个解引用符
int &r2 = *p;                  // & 是声明的一部分,* 是一个解引用符

建议

在新标准下,在 C++ 程序初始化空指针最好使用 nullptr ,同时尽量避免使用NULL。

① nullptr 是一种特殊类型的字面值,它可以被转化成任意其他的指针类型。

② NULL 是预处理变量(preprocessor variable),它的值就是 0 ,当用到一个预处理变量时,预处理器会自动的将它替换为实际值。

建议

初始化所有指针,如果实在不清楚指针应该指向何处,就把它初始化为nullptr 或者 0 。

​ 在大多数编译器环境下,如果使用了未经初始化的指针,则该指针所占内存空间的当前内容将被看作一个地址值。访问该指针,相当于去访问一个本不存在的位置上的本不存在的对象。糟糕的是,如果指针所占内存空间恰好有内容,而这些内容又被当作了某个地址,我们就很难分清它到底是合法的还是非法的。

void * 指针

​ void * 是一种特殊的指针类型,可用于存放任意对象的地址。

​ 利用void * 能做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另一个 void * 指针。不能直接操作 void * 所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定再这个对象上做哪些操作

2.3.3 理解复合类型的声明

变量的定义包括一个基本数据类型(base type)和一组声明符。在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。也就是说,一条定义语句可能定义出不同的数据类型的变量

// i 是一个 int 型的数,*p 是一个 int 型指针,r 是一个 int 型引用
int i = 1024, *p = &i, &r = i;

提醒

很多程序员容易迷惑于基本数据类型和类型修饰符的关系,其实后者不过是声明符的一部分罢了。

​ 如 int* p ,int 为基本数据类型(而非int*), * 为类型修饰符,仅仅修饰 p 而已。

指向指针的引用

​ 要理解 r 的类型到底是什么,最简单的办法就是从右往左阅读 r 的定义。离变量名最近的符合对变量具有最直接的影响:

int i = 42;
int *p;                             // p 是一个 int 型指针
int *&r = p;                        // r 是对指针 p 的引用

r = &i;                             // r 引用了一个指针,因此给 r 赋值 &i 就是令 p 指向 i
*r = 0;                             // 解引用 r 得到 i, 也就是 p 指向的对象,将 i 的值改为 0

2.4.0 const 限定符

特点:

① 一旦定义,其值不能改变,改变则程序报错。

② const 对象必须初始化。

const int i = get_size();             // 正确:运行时初始化
const int j = 42// 正确:编译时初始化
const int k;                          // 错误:未初始化
j = 10;                               // 错误:试图向 const 对象写值

提醒

默认状态下,const 对象仅在文件内有效。当多个文件中出现了同名的 const 变量时,其实等同于在不同文件中分别定义了独立的变量。

笔记

如果想在多个文件之间共享 const 对象,即只在一个文件中定义 const,而在其他多个文件中声明并使用它。则必须在变量的定义之前添加 extern 关键字。

 // file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h 头文件
extern const int bufSize;     // 与 file_1.cc 中定义的 bufSize 是同一个

// 第 2 行的 extern 是限定 bufSize 能被其他文件使用。
// 第 4 行的 extern 是指明 bufSize 并非本文件独有,它的定义在别处出现。因此没有初始化

2.4.1 const 引用

​ 可以把引用绑定到 const 对象上,我们称之为对常量的引用(reference to const),简称 常量引用。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。

const int ci = 1024;
const int &r1 = ci;                // 正确:引用及其对应的对象都是常量
const int &r2 = 42;                // 正确
r1 = 42;                           // 错误:r1 是对常量的引用

特点

① 常量引用可以绑定非常量对象,非常量引用不能指向一个常量对象(有const 限定说明值不能变了,所以可以取变量的一个值进行引用,不能把一个不能改变的值引用给变量)。

const int ci = 1024;
int &r1 = ci;                          // 错误:试图让一个非常量引用指向一个常量对象

int i = 42;
const int &r2 = i;                     // 正确:常量引用可以绑定非常量对象

// 注意 r2 绑定 i 是合法的,但是不能通过 r2 来修改 i 的值。i 的值可以通过其实途径修改
cout<< r2;                             // 输出 42
r2 = 40;                               // 错误,不可该
i = 40;                          
cout<< r2;                             // 输出 40

② 初始化常量引用时允许任意表达式作为初始值,只有该表代表式的结果能转化成引用的类型即可。不像普通引用一样,要求引用的类型要和与之绑定的对象严格匹配。

double dval = 3.14;
const int &ri = dval;                   // 正确,ri 的值为 3

// 上述代码在计算机实际操作的过程为:
const int temp = dval;                  // 由双精度浮点数生成一个临时的整型变量 
const int &ri = temp;                   // 让 ri 绑定这个临时变量

​ 对普通的引用,即 ri 不是常量,那就允许对 ri 赋值,这样就会改变 ri 所指引对象的值。也就是说取别名,所以就要求引用的类型要和与之绑定的对象严格匹配,不然在计算机实际操作中,由于类型不同,ri 绑定是 temp 一个临时的量,而不是 dval,这就与本意不同了,给 ri 赋值只会改变 temp 的值并不会 dval 的值,这就不叫别名了。所以C++认为这种行为非法。

​ 而对常量的引用,并不能对 ri 进行赋值,虽然它绑定是是临时变量,但那是用户的选择,他确实是想要一个 int 型的值,给这个值取别名,符合用户的本意,后续也不会有通过 ri 改变 dval 的想法。所以允许这种操作。

2.4.2 指针和const

特点:

① 类似于常量引用,指向**常量的指针(pointer to const)**不能用于改变其所指对象的值。一个指向常量的指针可以指向一个非常量对象(类型不一样哦),但是指向非常量的指针不能指向一个常量对象。

② 常量指针必须初始化,一定初始化就不能再改变了。指向常量的指针可以不初始化。常量指针是指指针是一个常量,这并不意味着不能通过修改指针来改变其所指对象的值。与指向常量的指针是两个概念,这个不可以修改所指对象的值。

const int p = 3;                          // 错误
int *pi = &p;                             

int q = 4const int *q = &q;                        // 指向常量的指针,const int 规定基本数据类型

int errNumb = 0;
int *const curErr = &errNumb;             
// curErr 常量指针,将一直指向 errNumb,不能改变,*const 说明指针是一个常量,即不变的是指针本身的值而非指向的那个值

const double pi = 3.14159;
const double *const pip = &pi;

// 离 pip 最近的符号为 const , 说明 pip 本身为一个常量,对象的类型由声明符剩余部分决定。声明符下一个字符是 *,意思是 pip 是一个常量指针。最后,该声明语句的基本数据类型部分 const double 确定了常量指针指向的是一个 双精度浮点型的常量。

*pip = 2.72;                               // 错误:不可改
*curErr = 1;                               // 正确:把curErr所指的对象置为 1 

题目

int &r = 0;                                 // 错误:变量的别名为常量,no no no
int i = 42;
const int &r2 = i;                          // 正确:常量的别名为变量,只是常量的一厢情愿罢了,抓住了一刻以为抓住了永远,但是允许幻想

int *const p2 = &i2;                        // 如果i2是const int,这是不对的;
const int &const r2;                        // 没有初始化
int i, *const cp;                           // 错误:常量指针未初始化;
const int *p;                               // 正确:指向常量的指针可以不初始化。

const int ic, &r = ic;                      // 错误:常量ic未初始化;

2.4.3 顶层 const

顶层 const(top-level const) 表示指针本身是一个常量;

底层 const(low-level const) 表示指针所指的对象是一个常量;

​ 顶层 const 可以表示任意的对象是常量。底层 const 则与指针和引用等复合类型的基本类型部分有关。对底层 cosnt 对象执行拷贝操作时,拷入和烤出对象必须具有相同的底层 const 资格,或者两个对象的数据类型必须能够转化。一般来说,非常量可以转化为常量,反之则不行。

int i = 0;
int *const p1 = &i;                           // 顶层 const:不能改变 pl 的值
const int ci = 42;                            // 顶层 const:不能改变 ci 的值
const int *p2 = &ci;                          // 底层 const:可以改变 p2 的值(类型得相同)

const int *const p3 = p2;                     // 靠右的 const 是顶层 const,靠左的是底层 const
const int &r = ci;                            // 用于声明引用的 const 都是 底层const

i = ci;                                       // 正确
p2 = p3;                                      // 正确:p2 和 p3 都是底层 const
p2 = &i;                                      // 正确:int* 能转化成 const int*
int *p = p3;                                  // 错误:p3 为底层,p 并非
int &r = ci;                                  // 错误:普通的 int& 不能绑定到 int 变量上
const int &r2 = i;                            // 正确:const int& 可以绑定到一个普通 int s

2.4.4 constexpr 和常量表达式

常量表达式

常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。一个对象(或者表达式)是不是常量表达式由它的数据类型和初始值共同决定。

const int max_files = 20;                       // max_files 是常量表达式
int staff = 27;                                 // 不是
const int sz = get_size();                      // 不是

constexpr 变量

C++ 11 新标准规定,声明为 constexpr 的变量一定是一个常量,而且必须用常量表达式初始化。一般来说,如果你认定变量是一个常量表达式,那就把它声明成 constexpr 类型。

常量表达式的值需要在编译时就得到计算,因此声明 constexpr 时用到的类型必须有所限制。

​ 函数体内定义的变量一般来说并非存放在固定地址中,因此 constexpr 指针不能指向这样的变量。相反的,函数体外的对象其地址不变,可以用来初始化 constexpr 指针。

​ 函数可以定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。因此也能初始化 constexpr 指针

constexpr int mf = 20;                          // 20 是常量表达式
constexpr int sz = size();                      // 只有当 size() 是一个 constexpr 函数时
                                                // 才是一条正确的声明语句

​ 普通函数不能作为 constexpr 变量的初始值,但是可以用 constexpr 函数去初始化。

字面值类型

​ 算术类型、引用和指针都属于字面值类型。自定义类、IO 库、string 类型则不容易字面值类型,也就不能定义成 constexpr 。

​ 尽管指针和引用都能定义成 constexpr ,但它们的初始值却受到严格的限制。一个 constexpr 指针的初始值必须是 nullptr 或者 0,或者是存储于某个固定地址中的对象。

指针和 constexpr

在 constexpr 声明中如果定义了一个指针,限定符 constexpr 仅对指针有效,与指针所指的对象无关

const int *p = nullptr;                         // p 是一个指向整型常量的指针
constexpr int *q = nullptr;                     // q 是一个指向整数的常量指针
                                                // (constexpr把它定义成了顶层 const)

​ 与其他常量指针类似,constexpr 指针即可以指向常量也可以指向一个非常量:

constexpr int i = 42;
int j = 0;
// i 和 j 必须定义在函数体之外
constexpr const int *p = &i;                    // p 是常量指针,指向整型常量 i
constexpr int *pi = &j;                         // pi 是常量指针,指向整数 j

2.5.1 类型别名

让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用类型的真实目的

// 法一
typedef double wages;                           // wage 是 double 的同义词
typedef wages base, *p;                         // base 是 wage 的同义词,p 是 double* 的同义词

// 法二
using SI = Sales_item;                          // SI 是 Sales_item的同义词

// 使用
wages hourl;                                    // 等价于 double hourl
SI item;

// 复合类型别名
typedef char *pstring;                           // 含 * 表明当用到 pstring 时,其基本数据类型为指针
char c = 'a';
const pstring cstr = &c;                         // cstr 是常量指针,基本数据类型为 char
cout<< *cstr;                                    // 输出: a

// 注意对 line 15 的理解,并非以下语句:
const char *cstr = &c;                           // 这样 cstr 是指向常量的指针,基本数据类型为 const char
                                                 // 不能简单替换,得看 pstring 的数据类型

const pstring *ps;                               // ps 是一个指针,它的对象是指向char的常量指针

2.5.2 auto 类型说明符

C++ 11 新标准引入了 **auto **类型说明符,用它就能让编译器替我们去分析表达式所属的类型。显然 auto 定义的变量必须有初始值。

auto item = val1 + val2;                           // 由 val1 和 val2 相加的结果推断出 item的类型

// 用 auto 一条语句定义多个变量时,所有变量的初始基本数据类型必须一样
auto i = 0, *p = &i;                               // 正确:i 是整数,p 是整型指针
auto sz = 0, pi = 3.14                             // 错误:sz 和 pi 的类型不一致

// 注意:符号& 和 * 只从属于某个声明符,而非基本数据类型的一部分
int j = 0;
const int cj = 0auto &m = cj, *p = &cj;                            // 正确: m 是对整型常量的引用, p是指向整型常量的指针
auto &n = j, *p2 = &cj;                            // 错误:i 的类型是 int,而 &ci 的类型是 const int 

编译器推断出来的 auto 类型有时候和初始值的类型不完全一样,编译器会适当地改变结果类型使其更符合初始化规则

// 情况一
int i = 0, &r = i;
auto a = r;                                         // a 是一个整数

// 情况二:auto 一般会忽略顶层 const,同时底层 const 会被保存下来
const int ci = i, &cr = ci;
auto b = ci;                                        // b 是一个整数
auto c = cr;                                        // c 是一个整数
auto d = &i;                                        // d 是一个整数指针
auto e = &ci;                                       // e 是一个指向整数常量的指针

// 如果希望推断出的 auto 类型是一个顶层 const,需要明确指出:
const auto f = ci;                                  // ci 的推演类型为 int,f 是 const int 类型

// 还可以将引用类型设为 auto,此时原来的初始化规则仍然适用:
auto &g = ci;                                       // g 是一个整型常量的引用,绑定到 ci
auto &h = 42;                                       // 错误:不能为非常量引用绑定字面值
const auto &j = 42;                                 // 正确:可以为常量引用绑定字面值

2.5.3 decltype 类型指示符

当我们希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。可以用decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不会实际计算表达式的值,

decltype(f()) sum = x;                               // sum的类型就是函数f的返回类型

//注:编译器并不会实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型。换句话说,编译器为sum指定的类型是什么呢?就是假如f被调用的话将会返回的那个类型。

注意

decltype 处理顶层 const 和引用的发生与 auto 有些许不同。如果 decltype 使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内);

const int ci = 0, &cj = ci;
decltype(ci) x = 0;                                   // x的类型是const int
decltype(cj) y = x;                                   // y的类型是const int&,y绑定到变量x
decltype(cj) z;                                       // 错误:z是一个引用,必须初始化

decltype 和 auto 的另一处重要区别是,decltype 的结果类型与表达式形式密切相关。有一种情况需要特别注意:如果 decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给该变量加上了一层或多层括号,编译器就会把它当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype 就会得到引用类型。

// decltype 的表达式如果加上了括号的变量,结果将是引用
decltype((i)) d;                                      // 错误:d 是int&,必须初始化
decltype(i) e;                                        // 正确:e 是一个(未初始化的)int

提醒

引用从来都作为其所指对象的同义词出现,只有用在 decltype 处是一个例外。(如果使用引用类型,auto会识别为其引用对象的类型;而decltype会识别为引用的类型;)

decltype 和引用

如果 decltype 使用的表达式不是一个变量,则 decltype 返回表达式结果对应的类型。有些表达式将向 decltype 返回一个引用类型,这意味着该表达式的结果对象能作为一条赋值语句的左值(一般引用只有定义的时候 int &a = b;在其他时候不能作为赋值语句的左值):

// decltype 的结果可以是引用类型
int i = 42*p = &i, &r = i;
decltype(r + 0) b;                                  // 正确:加法的结果是int,因此b是一个(未初始化的)int
decltype(*p) c;                                     // c 是int&,必须初始化

// 因为 r 是一个引用,所以 decltype(r) 的结果是引用类型。如果想让结果类型是 r 所指的类型,可以把 r 作为表达式的一部分,如 r + 0,显然这个表达式的结果将是一个具体值而非一个引用。

// 如果表达式的内容是解引用操作,则 decltype 将得到引用类型。正如我们所熟悉的那样,解引用指针可以得到指针所指的对象,而且还能对这个对象赋值。因此 decltype(*p)的结果类型就是 int&,必须初始化

题目

// 指出所有变量的类型和值
int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
++c;
++d;

// 答:a int 4;b int 4 ;c int 4;d int& 4 

2.6

预处理器是在编译之前执行一段程序,可以部分地改变我们所写的程序。当预处理器遇到 #include 标记时就会用指定的头文件的内容代替 #include

  • 39
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值