注:参考 C++ Primer(第 5版)书籍,本文是精简后的知识点
目录
复合类型
本章介绍复合类型的其中几种:引用和指针。
注: 一个声明语句由一个基本数据类型(base type) 和紧随其后的一个声明符(declarator)列表组成。声明符其实就是变量名,此时变量的类型也就是声明的基本数据类型。
1. 引用
C++11增加了一种引用:所谓的“右值引用(rvalue conference)”,这种引用主要用于内置型,后续文章再详细说明。严格来说,当我们使用术语“引用(reference)”时,指的其实是“左值引用(lvalue conference)”,本文使用的是左值引用。
引用即别名,引用类型 引用(refers to) 另外一种类型。通过将声明符写成 &d 的形式来定义引用类型,d 是声明的变量名。
int i = 1024;
int &d = i; // d 指向ival(是ival的另一个名字)
int &ivalue; // 报错:引用必须初始化
引用并非对象,相反的,它只是为一个已经存在的对象起另外的名字。
d = 2; // 把 2 赋值给d 指向的对象,即赋给 i
int ii = d; // 与 ii = i 结果一样
引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
int &reValue1 = 10; // 错误: 引用类型的初始值必须是一个对象
double dValue = 10;
int &reValue2 = dValue; // 错误:此处引用类型的初始值必须是 int 型对象
2. 指针
2.1. 指针是什么?
指针(pointer) 是 “指向(point to)” 另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。
2.2. 指针与引用的区别
- 其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且指针的生命周期内它可以先后指向几个不同的对象。
- 其二,指针无须在定义时赋初值,而引用必须初始化,一旦初始化,就和它的初始对象一直绑定在一起。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化 (不推荐,因为容易导致野指针的问题,当指针不在使用,最好将其置为 nullptr 或者 NULL(旧版c++)),也将拥有一个不确定的值。
- 其三,引用只能为一级,指针可以为多级:
int **p1; // 正确:指向指针的指针
int *&r1; // 正确:指向指针的引用
int &*p2; // 错误:指向引用的指针是非法的
int &&r2; // 错误:指向引用的引用是非法的
要理解 r1 的类型到底是什么,最简单的办法是从右向左阅读的定义。离变量名最近的符号 (此例中是&r的符号&) 对变量的类型有最直接的影响,因此r1是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明引用的是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int 指针。
2.3. 指针的定义和获取对象的地址
定义指针类型的方法将声明符写成 d 的形式,其中 d 是变量名。指针存放了某个对象的地址,想要获取该对象的地址,就得使用 取地址符(操作符 &):
//指针的定义
int *p1,*p2; // p1 和 p2 都是指向 int 型对象的指针
double dp,*dp1; // dp 是double型对象,dp1 是double型指针
//获取对象的地址
int value = 42;
int *p = &value; // p 存放变量 value 的地址,或者说 p 是指向变量value的指针
注意:因为引用不是对象,没有实际内存地址,所以不能定义指向引用的指针。
int value = 10;
int &*p = value; // 错误:不允许使用指向引用的指针
2.4. 指针值
指针的值(即地址)应属下列4 种状态之一:
- 指向一个对象。
- 指向紧邻对象所占空间的下一个位置。
- 空指针,意味着指针没有指向任何对象。
- 无效指针,也就是上述情况之外的其他值。
2.5. 利用指针访问对象
如果指针指向了一个对象,则允许使用 解引用符(操作符 * ) 来访问该对象:
int val = 12;
int *p = &val; // p 存放了变量val的地址
cout<<*p<<endl; // 由符号 * 得到指针 p 所指的对象 ,叫做解引用操作
注意:解引用操作仅适用于那些明确指向了某个对象的有效指针
要想搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指对象的值不太容易,最好的办法就是记住赋值永远改变的是等号左侧的对象。
- 当写出如下语句时:
int *pi = 0;
int ival = 10;
pi= &ival ; // pi的值被改变,现在 pi指向了ival
意思是为 pi 赋一个新的值,也就是改变了那个存放在 pi 内的地址值。
- 相反的,如果写出如下语句:
*pi = 0; // ival 的值被改变,指针 pi 并没有改变
则 *pi (也就是指针 pi 指向的那个对象) 发生改变 。
2.6. 其他指针操作
任何非 0 的指针对应的条件值都是为true
if(p)表示判断指针变量 p 是否为空指针。如果 p 不为 nullptr 或者不是一个无效的地址,条件成立,否则条件不成立。
if(*p) 表示判断指针 p 所指向的地址上的值是否为真。这通常用于判断指针所指向的对象是否存在或是否具有某种特定的状态。如果 *p 的值为非零(或者是布尔值 true),条件成立,否则条件不成立。
2.7. void * 指针
void* 是一种特殊的指针类型,可用于存放任意对象的地址。一个 void* 指针存放着一个地址,这一点和其他指针类似。不同的是,我们对该地址中到底是个什么类型的对象并不了解:
double obj=3.14,*pd =&obj;// 正确: void* 能存放任意类型对象的地址
void*pv=&obj; // obj可以是任意类型的对象
pv=pd; // pv可以存放任意类型的指针
利用 void* 指针能做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个 void* 指针。不能直接操作 void* 指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
概括说来:以void* 的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。
要访问void*指向的内存对象,您通常需要将 void * 转换为特定的类型指针。void * 主要用于在不知道指向的数据类型的情况下传递和存储指针。在访问指针指向的具体类型之前,通常需要进行明确的类型转换。
int x = 42;
void *p = &x;
int *intPtr = static_cast<int*>(p);
int value = *intPtr; // 这里的 value 就是 x 的值,即 42
3. 理解复合类型的声明
3.1. 涉及指针和引用的声明
建议把修饰符和变量标识符写在一起: int *p; (而不是 int* p; 这样容易产生误导,但也不是错误的,只是风格上的选择。)
3.2. 指向指针的指针
通过 * 的个数可以区分指针的级别。也就是说,** 表示指向指针的指针,*** 表示指向指针的指针的指针,以此类推:
int ival = 1024;
int *pi = &ival; //pi指向一个int型的数
int **ppi = π //ppi指向一个int型的指针
3.3. 指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:
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
总结:
以上就是复合类型(指针和引用)的相关内容,希望能够让你更加理解指针和引用的本质和区别,并在使用上不会产生混淆和错误理解,以及规范自己的编程思维。
希望大家多多支持,码字不易,若有错误请指出,共同学习!
欢迎关注🔎点赞👍收藏⭐️留言📝