什么是一级指针:所有普通变量的地址都是一级指针,存放一级指针的变量就是一级指针变量。
什么是普通变量:在定义过程中,类型没有*号的变量就是普通变量。
普通变量的类型+*
char a = 'a';
char *p = &a; //char * = char + *
&a的类型为char *,放&a的变量p自然也是char *。=两边是天然一致的,不一致就需要强制类型转换。
- char:指针指向空间的解释方式。
- *:表示它是一个指针。
char a = 'a';
char *p = &a;
- a:char的普通变量
- &a:a的指针,类型为char*
- p:char*指针变量
- *p:p中指针所指向的空间
- p为char*,*p解引用的时候,抵消了一个*,char就是p指向空间的解释方式。
- &p:变量p的指针。类型为char **
什么是多级指针?
:指针变量的指针就是多级指针,指针变量也是变量,所以指针变量的每个字节也是有地址的,那么“指针变量”第一个字节的地址就是多级指针。
在多级指针的类型中,*的个数代表了指针的级数,n个*就是n级。
例如:一级指针变量的指针就是二级指针,存放二级指针的变量就是二级指针变量。
二级指针的类型结构为,一级指针类型 + *,比如:
int a = 100;
int *p1 = &a;
int **p2 = &p1; //int ** = int * + *
&p1的类型是int**,存放&p1的变量p2自然也是int**。
int **解读:
- int*:指针指向空间的解释方式。
· *:最后一个*代表它是指针。
a / p1 / *p1 / &p1 / p2 / *p2 / **p2 / &p2
int a = 100;
int *p1 = &a;
int **p2 = &p1;
a
:int
变量p1
:int *
指针变量,用于存放int *
的指针*p1
:p1
中指针所指向的空间p1
为int *
,*p1
解引用时,抵消一个*
,解释方式为int
&p1
:一级指针变量p1
的指针,为int **
的二级指针p2
:int **
指针变量,用于存放int **
的二级指针*p2
:一级解引用,代表的是p2
中指针所指向的空间,p2
为int **
,解引用时,抵消一个*
,解释方式为int *
**p2
:二级解引用,代表p2
所指向的p1
所指向的空间,这里就是a
的空间如果需要一个等价代换的话:
**p2——————>*(*p2)——————>*p1 ————————>a
&p2
:二级指针变量p2
的指针,类型为int ***
再例如下面这个情况:
int a = 100;
int *p1 = &a;
float **p2 = (float **)&p1;
*p2:按照float*解释p1
*p2 ——————> (float *)p1
**p2:按照float解释a
**p2 ————> *(*p2) -----> *((float *)p1) ——————> (float)a
不管是几级指针,都是一个地址,所有地址宽度都是一样的。
既然指针的宽度都是一样的,那么放指针的“指针变量”的宽度的也全都是一样的,不管它是多少级的指针变量。
不同之处在于解引用的深度。
一级指针:解引用深度为1级
二级指针:解引用深度为2级
三级指针:解引用深度为3级
….
不同级别之间的强制转换,改变的是解引用的深度。
1)例子1
int a = 10;
int *p = &a; //&a为int *
解引用深度:*p为1级,抵消了一个*,按照int去解释所指向的a空间。
int a = 10;
int **p = (int **)&a; // &a为int *
解引用深度变为了2
级:*p
:抵消一个*
,按照int *
去解释所指向的a
空间
**p
:抵消两个**
,按照int
去解释a
的10
所指向的空间
**p --> *(*p) ---> *(a) ———> *10 // 非法操作
事实上10
根本不是一个有效的地址,10
并没有对应有效存储空间,就算有对应空间,
也是一个不明情况的非法空间,所以不能以指针的方式去解引用10
,强行解引用的话,就会导致指针错误。
2)例子2
int a = 10;
int *p = &a;
int *p1 = (int *)&p; //&p为int **
解引用深度变为1级
*p1:抵消了一个*之后,以int
方式解引用p1
所指向p
空间,将p
中的指针&a
强行解释为一个整形数。
*p1 ——————>(int)p ————————>(int)&a
**p1
:无法编译通过
**p1 ---> *(*p1) ——————>*((int)p) ————————> *((int)&a) ——————> *(整形)
从以上等价后的结果可以看出,*(整形)
在尝试对一个整形数进行解引用,这是无法编译通过的。
我们前面说过,同一个数但是类型不同,会有很大区别,当编译器检测到你对一个整形数进行解引用时,会直接报类型错误,提示你,你在尝试解引用一个整形数
对于强类型语言来说,不同类型的数据有自己的使用规则,不能乱用,整形的数据就不能当做站指针来用,
如果强行使用,编译器就会报错
如果你非要当做整形的数来用,必须做强制转换。
**((int **)p1) ----> *(&a) ——————*(int *指针)
不同级别之间的强制转换,会改变解引用的深度,因此可能会导致某些解引用为非法操作,
所以对不同级别指针进行强制转换时,一定要慎重,只有当确实有强制转换的需求时,我们才会进行不同级别的强制转换
为什么某些解引用为非法操作呢?
因为不同级别之间的强制转换,会导某些指针为非法指针,非法的意思就是:
10
,将10
当做指针使用是不行的,因为不指向任何有效空间对指针进行解引用时,一定要确保指针为合法指针,不合法情况有如下几种:
(1)类型不正确,解引用的根本就不是指针:*(整数)
像这种情况,直接会导致编译不通过
(2)指针所指向的空间压根就没有:指针的类型对的,但是指针没有对应任何空间。所以对这种指针进行解引用时,会导致指针错误,压根找不到空间,像这种情况,编译没问题,但是运行时会有指针错误
3)指针类型没问题,也有对应实际的存储空间,但是没有访问权限:
1)一级指针
int *p1;
int fun(void)
{
int *p2;
*p1 = 100;
*p2 = 200;
}
*p1=100和*p2=100都有问题。
首先p1是全局变量,p1会被自动初始化为0,也就是说p1中的地址默认为0,但是0地址是没有对应实际内存空间的。
p2是fun函数内部的自动局部变量,自动局部变量栈在未初始化的时候为随机值,这个随机值会有问题:
1)问题1:如果这个随机值没有对应任何空间的话,*p
解引用访问时的会导致指针错误,然后程序会被终止
2)问题2:如果恰好有对应某个空间,如果这个空间不允许写访问的话还好,因为这会直接导致指针错误,程序被终止,程序员就回去排查错误。
允许写的话更糟糕,因为这个空间很可能是其它变量的空间,这会导致数据的篡改,所以对指针进行解引用时,指针必须是合法的,只有这样才能访问正确的空间。
下面是代码的改进:
int a = 0;
int *p1 = &a;
int *p2;
p2 = malloc(4);
2)多级指针
int *p1;
int **p2 = &p1;
int fun(void)
{
**p2 = 100;
}
*p2 —> p1
一级解引用是正确的,二级解引用是指针错误。
改进:
int a = 0;
int *p1 = &a;
int **p2 = &p1;