C++学习笔记 复合类型

复合类型是指基于其他类型定义的类型

一条声明语句由一个基本数据类型(base type)和紧随其后的一个声明符(declarator)列表组成,每个声明符命名了一个变量并指定该变量为于基本数据类型有关的某种类型

 

引用

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

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

定义引用时,程序将引用和它的初始值绑定在一起,而不是将初始值拷贝给引用,一旦初始化完成,引用将和它的初始值对象一直绑定在一起

无法令引用重新绑定到另外一个对象,因此引用必须初始化

定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的:为引用赋值,实际上把值赋给了与引用绑定的对象;获取引用的值,实际上是获取了与引用绑定的对象的值

引用本身并不是一个对象,因此不能定义引用的引用

引用的定义:

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号 & 开头

1 int i = 1024, i2 = 2048;
2 int &r1 = i, r2 = i2;
3 int &r3 = i, &r4 = r2;

除了一些例外情况,其他所有引用的类型都要和与之绑定的对象严格匹配,而且,引用只能绑定在对象上,而不能于字面值或者某个表达式的计算结果绑定在一起

 

指针

指针也实现了对其他对象的间接访问,与引用不同的是:

a)指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的声明周期内它可以先后指向几个不同的对象

b)指针无需再定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值

通过将声明符写成 *d 可以定义指针类型,其中 d 是变量名,如果在一条语句中定义了几个指针变量,每个变量前必须有符号 * 

1 int *ip1, *ip2;        // ip1和ip2都是指向int类型对象的指针
2 double dp, *dp2;      // dp2是指向double型对象的指针,dp是double型对象

获取对象的地址

指针存放某个对象的地址,要想获取该地址,需要使用取地址符 & 

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

这里第二条语句把p定义为一个指向int的指针,随后初始化p令其指向名为ival的int对象

引用不是对象,没有实际地址,不能定义指向引用的指针

除了一些例外情况,其他所有指针的类型都必须和它所指向的对象严格匹配,因为在声明语句中,指针的类型实际上被用于指定它所指向的对象的类型

如果指针指向了一个其他类型的对象,对该对象的操作将发生错误

指针值

指针的值(地址)应属于下列4种状态之一:

a)指向一个对象

b)指向紧邻对象所占空间的下一个位置

c)空指针,即没有指向任何对象

d)无效指针,即上述情况之外的其他值

试图访问无效指针的值将引发错误

同时尽管第2种和第3种形式的指针是有效的,但它们没有指向任何具体对象,所以试图访问此类指针对象的行为也不被允许

利用指针访问对象

如果指针指向了一个对象,则允许使用解引用符 * 来访问该对象

1 int ival = 42;
2 int *p = &ival;
3 cout << *p;

对指针解引用会得出所指的对象,如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值

解引用操作仅适用于那些确实指向了某个对象的有效指针

空指针

空指针不指向任何对象,在试图使用一个指针之前,代码可以首先检查它是否为空

得到空指针最直接的办法就是使用字面值 nullptr 来初始化指针,nullptr 是一种特殊类型的字面值,它可以被转换成任意其他的指针类型

另一种办法,可以通过将指针初始化为字面值0来生成空指针

此外,还可以使用预处理变量 NULL 来给指针赋值,它在头文件cstdlib中定义,值为0

新标准下,最好使用 nullptr 并尽量避免使用 NULL

把int变量直接赋给指针式错误的,既是int变量的值恰好等于0也不行

使用未经初始化的指针容易引发运行时错误,因此,应尽量初始化所有指针,并且尽量在定义了对象之后再定义指向它的指针

如果实在不清楚指针应该指向何处,就将其初始化为nullptr或0

赋值和指针

给指针赋值就是令它存放一个新的地址,从而指向一个新的对象

赋值永远改变的是等号左侧的对象

1 pi = &ival;    // pi的值被改变,现在pi指向了ival
2 *pi = 0;       // ival的值被改变,指针pi并没有被改变

其他指针操作

只要指针拥有一个合法值,就能将其用在条件表达式内:如果指针的值是0,条件取false;任何非0指针对应的条件值都是true

对于两个类型相同的合法指针,可以用相等操作符(==)或不相等操作符(!=)比较它们,结果是布尔类型:

如果两个指针存放的地址值相同,即:

a)它们都为空  b)它们都指向同一个对象  c)或者都指向同一个对象的下一个地址

则它们相等;

反之则不相等,

 

需要注意:一个指针指向某对象,同时另一个指针指向另外对象的下一地址,这是也可能出现这两个指针相等的情形

不论是作为条件出现还是参与比较运算,都必须使用合法指针,否则会引发不可预计的后果

void* 指针

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

double obj = 3.14, *pd = &obj;
void *pv = &obj;
pv = pd;

不能直接操作 void* 指针所指的对象,因为不清楚这个对象是什么类型,也无法确定能在这个对象上做哪些操作

 

复合类型的声明

一条定义语句中可能定义出不同类型的变量

定义多个变量

在定义语句中,类型修饰符(*或&)并非作用于本次定义的全部变量

1 int* p1, p2;    // p1是指向int的指针,p2是int

这里,基本数据类型是int而非int*:* 仅仅修饰了p1,而对该声明语句中的其他变量,它并不产生任何作用

设计指针或引用的声明,一般有两种写法:

a)把修饰符和变量标识写在一起,这种形式着重强调变量具有的复合类型

1 int *p1, *p2;

b)把修饰符和类型名写在一起,且每条语句只定义一个变量,这种形式着重强调哦本次声明定义了一种复合类型

1 int* p1;
2 int* p2;

下面将坚持使用第一种写法

指向指针的指针

一般来说,声明符中修饰符的个数并没有限制,当有多个修饰符连写一起时,按照其逻辑关系详加解释即可

例如:指针是内存中的对象,向其他对象一样拥有自己的地址,因此允许把指针的地址再存放到另一个指针中:

通过 * 的个数可以区分指针的级别:** 表示指向指针的指针,*** 表示指向指针的指针的指针

1 int ival = 1024;
2 int *pi = &ival;    // pi指向一个int型的数
3 int **ppi = &pi;    // pi指向一个int型的指针

解引用 int 型指针会得到一个 int 型的数,同样,解引用指向指针的指针会得到一个指针

指向指针的引用

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

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

要理解 r 的类型到底是什么,最简单的办法是从右向左阅读 r 的定义,离变量最近的符号对变量的类型有最直接的影响

posted @ 2020-02-15 12:20  缘木  阅读(149)  评论(0)    收藏  举报