天天看点

2021.1.31学习日志2.3 复合类型2.4 const限定符

2.3 复合类型

​ 复合类型就是只基于其它类型定义的类型,本次将介绍两种引用和指针

2.3.1 引用

​ 在c++11中用两种引用,有右值引用和左值引用,右值引用我们将在13.6.1节中继续学习,而我们常说的引用为左值引用

​ 引用换句话来说,就是为对象取了另一个名字,自生并非对象,通过将声明符写成“&”+“变量名”来定义引用类型,因为是为一个对象取地别名,所以其必须有明确的对象,即必须被初始化

​ 引用的定义就表明着与对象的绑定,对其的操作都是在与之绑定的对象上完成的

​ 引用自身并不是对象所以不能定义引用的引用,并且引用的类型要与与之绑定的类型要匹配

2.3.2 指针

​ 指针(pointer) 是“指向(point to)”另外种类型的复合类型。与引用类似,指针也实现了对其他对象的问接访问。然而指针与引用相比又有很多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其他内置类型样, 在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

​ 指针可以指向一个对象,指针内储存对象的地址,可以通过访问地址间接访问那个对象

​ 定义指针类型的方法将声明符写成d的形式,其中d是变量名。如果在一条语 句中定义了几个指针变量,每个变量前面都必须有符号:

int *ip1, *ip2; // ip1 和ip2都是指向int型对象的指针

double dp, *dp2; // dp2 是指向double型对象的指针,dp是double型对象获取对象的地址
           

​ 指针存放某个对象的地址,要想获取该地址,需要使用取地址符(操作符&);

int ival = 42

int *P = &ival; // p存放变量ival的地址,或者说p是指向变量ival的

           

​ 指针第二条语句把p定义为一个指向int的指针,随后初始化P令其指向名为ival的in对象。因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。

​ 除了2.4.2节(第56页)和15.2.3 节(第534页)将要介绍的两种例外情况,其他有指针的类型都要和它所指向的对象严格匹配:

double dval;

double *pd = &dval; // 正确:初始值是double型对象的地址

double *pd2 = pd

/正确:初始值是指向double对象的指针

int *pi = pd;

/错误:指针pi的类型和pd的类型不匹配

)i = &dval;

/错误:试图把double型对象的地址赋给int型指针
           

​ 因为在声明语句中指针的类型实际上被用于指定它所指向对象的类型,所以二者必须呢。如果指针指向了一个其他类型的对象, 对该对象的操作将发生错误。

指针值

​ 指针的值(即地址)应属下列4种状态之一+

​ 1.指向一个对象。

​ 2.指向紧邻对象所占空间的下一个位置。

​ 3.空指针,意味着指针没有指向任何对象。

​ 4.无效指针,也就是上述情况之外的其他值。

​ 就算是第二,第三种可能是有效的指针但如果没有指向任何对象,访问此类的指针是不被允许的,且很容易出错

利用指针访问对象

​ 如何运用指针访问指向的对象,就要运用到解引用符“*”,对指针的解引用会得出所指的对象,因此如果给解引用的结果赋值,实际上也是给指针所指的对象赋值

空指针

​ 因为没有指向的指针是很危险的,所以我们经常要将定义好的指针再赋给它(使其指向)一处人畜无害的空区

int *p1 = nullptr;

//等价于int *pl= 0;

int*p2 = 0;

//直接将p2初始化为字面常量0

//需要首先#include cstdlib

int *p3 = NULL;

//等价于int*p3=0;

           

​ 过去的程序中常用到名为NULL的预处理变量,来给指针赋值,这个变量在头文件cstdlib中定义,其值为0.预处理器是指运行编译过程之前就的一段程序,预处理不属于命名空间std。

​ 在新的标准之下,现在的c++程序尽量使用nullptr,避免使用NULL

​ 建议初始化所有的指针,并且在可能的情况下,尽量等定义了对象之后再定义指向它的指针。

赋值和指针

​ 指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的点就是引用本身并非一个对象。一旦定义了引用, 就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。

​ 指针和它存放的地址之间就没有这种限制了。和其他任何变量(只要不是引用)一样,给指针赋值就是令它存放一个新的地址,从而指向一个新的对象:

​ 有时候要想搞清楚- 条赋值语句到底是改变了指针的值还是改变了指针所指对象的值不太容易,最好的办法就是记住赋值永远改变的是等号左侧的对象。

其他指针操作

​ 无论是作为条件出现还是参与比较运算,都必须使用合法指针,更多介绍在3.5.3节

void*指针

​ void*指针是一种特殊的指针,能存放任意类型的对象,但相对于普通指针,也仅仅能存放地址,无法直接操作所指向的对象,因为对象类型不知道

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

​ 概括说来,以void的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象,关于这点将在19.1.1 节有更详细的介绍,4.11.3节将讲述获取void指针所存地址的方法。

2.3.3 理解复合类型的声明

​ 一条语句中,虽然基本数据类型是相同的,但是声明符的形式可以不同

​ 声明符包括了类型修饰符(*和&)

定义多个变量

​ 涉及指针或引用的声明,一般有两种写法

​ 1.将修饰符和变量标识符写在一起。这种形式强调变量具有的复合类型(常用)

int *p1,*p2;
           

​ 2.将修饰符和类型名写在一起,并且每条语句只定义一个变量(避免以为相关类型修饰符作用于全局)(少用)

​ 提示:选择两种方法的关键在于,选择并坚持一种写法,不要总是变来变去

指向指针的指针

​ 一般来说,声明符中的修饰符的个数并没有限制。指针像其他对象一样也有自己的地址,因此允许将指针的地址再存放到另一个指针中

​ 通过’*‘的个数可以区别指针的级别。也就是说,表示’**'指向指针的指针

int ival = 1024;
int *pi = &ival; // pi指向一个int型的数
int **ppi = π // ppi指向一个int型的指針

           

​ 解引用int型指针会得到一个int型的数,同样,解引用指向指针的指针会得到一-个指针。此时为了访问最原始的那个对象,需要对指针的指针做两次解引用:

cout << "The value of ival \n"

<< "direct value:”<<ival<<"\n"

<< "indirect value: ”<< *pi < < "\n"

<< "doubly indirect value:" << **ppi

<< endl ;
           

​ 该程序使用三种不同的方式输出了变量ival的值:第种直接输出: 第二种通过 int型指针pi输出;第三种两次解引用ppi,取得ival的值。

指向指针的引用

​ 引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:

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


           

​ 要理解r的类型到底是什么,最简单的办法是从右向左阅读r的定义。离变量名最近的符号(此例中是&r的符号&)对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int指针

​ 面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义

2.4 const限定符

继续阅读