指针
内存地址
要理解指针的含义和使用方法,首先要了解计算机的数据存储。计算机使用多个连续的二进制位来记录数据。学过数字逻辑的同学很容易理解,内存就是成组的寄存器按规律排列而成,可以简单的理解为一个表格,每个格子内保存一个二进制的0或1。按照一定的次序和组合读取这个表格,就可以由二进制数得到写入的信息。
为了准确的读取信息,我们需要对这些格子进行分组和编号,一般是相邻的八个格子为一组,也就是一个字节,然后对表格中的组编号,也就是按次序对字节编号。这个过程叫内存编址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qLfKG7Dc-1580870952656)(en-resource://database/435:1)]
C++的指针,就是专门用来存放内存地址的数据类型。对于初学者来说可能有些难以理解,但有过一定的编程经验的同学应该能够体会到使用指针必要性。
严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了避免混淆,我们约定:"指针"是指地址,是常量,"指针变量"是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。
从指针的意义上来说,指针实际上是指向一个“数据”,一个数据结构在内存中的存储都是连续的,而依据指针指向的“数据”的首地址,可以方便的读取整个“数据”。
指针的定义
在C++语言中,定义指针的方式如下:
<类型名>* <变量名>
//例如
int* pInt;
string* pString;
float* pFloat;
double* p1, p2;
也有这样的写法:
<类型名> *<变量名>
但不太推荐。相比于后者,前一种写法能够更直观的说明“变量名”是一个指针,当然,这只是笔者个人的习惯,不必拘泥于此。
识别指针
大部分时候可以使用sizeof运算符来获取变量的长度,再依据长度判断某个变量是指针还是其他数据类型。
指针的分类
一般按照指针指向的对象分类,如果指针指向的对象也为指针,可以按级划分,按照此原则,C++指针可以分为单级间指针和多级间指针。单级间指针直接指向对象,多级 间指针仍然指向指针。指针的级数由指针定义时的指针标识符表示,每出现一个*就增加一级。
指针初始化
指针被定义后,仅依据其指向类型分配了一个内存单元,而没有对指针进行初始化处理。如果没有初始化,那么指针的指向是随机的、未知的,即野指针。直接引用野指针可能回破坏程序的运行,甚至影响到操作系统的安全。使用指针前必须进行初始化。一般有两种方法。
- 给指针赋空值:赋予数值0或NULL。
- 内存地址:使指针指向某个特定的地址。具体如下:
- 可以使用一个已经被初始化的指针地址来初始化一个指针,之后两个指针将会指向相同的单元。如果两个指针的类型相同,可以直接赋值;否则应转换为与被初始化指针相同类型的指针。
- 可以使用已经定义的变量来初始化指针,此时需要用到取地址运算符&。
指针= & 变量;
同样需要指针和变量对应的类型相同。
3. new分配内存单元
指针 = new 类型名;
指针 = new 类型名[n];
其中,[n]表示需要n个"类型名"长度的存储单元;new返回新分配的内存单元地址。当申请完内存单元后,如果不再需要就要收回这个内存单元,此时需要delete运算符来完成。
delete 指针名;
delete []指针名;
- malloc函数:
由c中继承来的方法。使用malloc/free对来分配和释放动态内存,
extern void* malloc(unsigned int num_bytes);
此函数在头文件malloc.h中,其功能是申请nums_bytes字节的连续内存块。如果申请成功则返回该块的首地址;否则返回空指针NULL。
type* p;
p = (type*)malloc(sizeof(type)* n);
p是type型指针,sizeof(type)是计算一个type型数据需要的字节数,n表示需要存储n个type型数据。(type*)是对malloc的返回值进行强制转换。该式的含义是申请可以存储n个type型数据的内存块,并且将块的首地址转换为type型赋给p。
当用malloc分配的内存不再使用时,应使用free()函数将内存块释放。
free p;
同delete一样,free也没有破坏指针p的内容,只是告诉系统收回这片内存单元,可以重新利用。所以free后,最好将p显式置空指针。
指针运算
指针作为变量,可以进行运算处理,但c++指针的运算种类很有限,且变化规律受其所指向的类型的制约。
算术运算
指针只能进行加减两种算术运算。指针的加减变化规律要受所指向的类型约束。它只能与以整型作为基类型的数据 类型进行运算,或者在指针变量之间。指针的加减运算不是单纯的在原地址基础上加减1,而是加减一个数据类型的长度。所以,指针运算中"1"的意义随数据类型的不同而不同。
尤其要注意的是指针的运算要限定在事先申请号的内存单元内,最近在做leetcode题目时常遇到越界错误,就是忽略了这一点。
两指针之间只能进行减运算。两个指针的减法表示计算它们之间的元素个数。如果差为负 数,表示地址高的指针需要后移几次才能到地址低的指针处。如果是正数,表示地址低的 指针需要移动几次才能前进到地址高的指针处。这个值实际是指针地址的算术差除以类型 宽度得到的。
关系运算
指针的关系运算是比较地址间的关系,这包括两方面:一方面是判断指针是否为空;另一 方面是比较指针的相对位置。进行关系运算的两个指针必须指向相同的数据类型
p1==p2:判断p1和p2是否指向同一个内存地址。
p1>p2:判断p1是否处于比p2高的高地址内存位置。
p1>=p2:判断p1是否处于不低于p2的内存位置。
p1<p2:判断p1是否处于比p2低的低地址内存位置。
p1<=p2:判断p1是否处于不高于p2的内存位置。
p1==0/p1==NULL:判断p1是否是空指针,即什么都不指。
p1!=0/p1!=NULL:判断p1是否 不是空指针,即指向某个特定地址。
指针的指针
即指向指针的指针,又称二级间指:
type** p;
按这样的方式可以定义n级间指,只需在类型名后加上对应数量的‘*\’号即可。
指针赋值
很少直接把地址值赋给指针,一般都是利用已定义的变量完成
p1 = &var; //把var的地址赋给p1
p1 = p2; //把p2的值赋给p1
*p1 = var; //把var放进p1指向的地址
‘*’和‘&’
*操作符也叫间接访问运算符,用来表示指针所指的变量,结合性为从右到左,属于单目运算。*运算符后跟的必须是指针变量。如果作为左值,则是向指针所指单元中写入数据。如果作为右值,则是从指针所指单元中读数据。
&操作符是取地址运算符,表示变量的地址值。可以用于一般变量也可用于指针变量。
指针和引用
引用就是别名或同义词,它是同一块内存单元的不同名称。常用于替代传值方式,传递参 数和返回值。具有指针的特点,可节省内存复制带来的开销。
type &ref=var;
type是类型名称,&在此不是求地址运算,而是起标识作用,ref是引用的名称,var是与引用同类型的变量名称。容易发现,对ref和var使用&运算,得到的地址值是一样的,即两个变量使用的是同一块内存空间。
关于引用的详细内容可以参考引用的用法
特殊指针
void型指针
无类型指针,没有类型,只是指向一块申请号的内存单元
void* p;
定义了一个指针p,但未规定应该按何种格式来解释其指向的内存单元的内容。p本身的存储空间是定义时就申请好的,但p指向的空间可以到需要时再申请。
如下语句
void x; //不被允许
void只是说明被定义变量的类型,而不涉及内存的分配。
在malloc()函数的声明格式中,返回值就为void型。这表明malloc只是按照要求的大小分配了内存单元,不负责解释这些内存单元的格式。因此,在使用malloc时,一定要用强制类 型转换,转换为需要的类型。
空指针
空指针就是什么都不指的指针,一般赋0值或NULL值。常用于指针的初始化。需要注意的是,空指针不能进行取值(*)操作。
- void型指针是无类型指针,它只是说明还没有对被指向的内存单元进行格式化解释。
- 野指针表示指针声明后没有初始化,没有指向特定的内存单元。
- 空悬指针表示指针指向的内存单元被释放了,该指针可能指向任何地方,也可能还指向原单元。
- 空指针则是指指针什么都不指。