一、 一级指针

什么是一级指针

所有普通变量的地址都是一级指针,存放“一级指针”的变量就是一级指针变量

什么是普通变量?

凡是定义时,类型中没有*的就是普通变量,char aint b,类型中没有*,都是普通变量


二、 一级指针类型

1. 类型结构

普通变量的类型 + *

char a = 'a';
char *p = &a;  //char * = char + *

&a的类型为char *,放&a的变量p自然也是char *
=两边天然的一致,不一致的话就需要强制转换

2. 如何理解 ( char * )

  • char:指针指向空间的解释方式
  • *:它是一个指针

3. a / &a / p / *p / &p各自的含义

char a = 'a';
char *p = &a;
  • achar 普通变量
  • &aa的指针,类型为char *
  • pchar *指针变量
  • *pp中指针所指向的空间
  • pchar **p解引用时抵消一个*char就是p指向空间的解释方式

&p:变量p的指针,类型为char **char * + *)


二、多级指针

什么是多级指针?

指针变量的指针就是多级指针
指针变量也是变量,所以指针变量的每个字节也是有地址的,那么“指针变量”第一个字节的地址就是多级指针

在多级指针的类型中,*的个数代表了指针的级数,n*就是n级。

多级指针 之 二级指针

一级指针变量的指针就是二级指针,存放二级指针的变量就是二级指针变量

1. 类型结构

二级指针的类型结构为,一级指针类型 + *,比如:

int a = 100;
int *p1 = &a;
int **p2 = &p1; //int ** = int * + *

&p1的类型为int **,存放&p1的变量p2自然也是int **

2. 如何理解( int ** )

int ** 解读:

  • int * 指针指向空间的解释方式
  • * 最后一个星表示它是指针

3. 各自的含义

a / p1 / *p1 / &p1 / p2 / *p2 / **p2 / &p2

int a = 100;
int *p1 = &a;
int **p2 = &p1;
  • aint变量
  • p1int *指针变量,用于存放int *的指针
  • *p1p1中指针所指向的空间

指向空间的解释方式?

  • p1int **p1解引用时,抵消一个*,解释方式为int
  • &p1:一级指针变量p1的指针,为int **的二级指针
  • p2int **指针变量,用于存放int **的二级指针
  • *p2:一级解引用,代表的是p2中指针所指向的空间

指向空间的解释方式?

  • p2int **,解引用时,抵消一个*,解释方式为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. 不同之处

不同之处在于解引用的深度。

一级指针:解引用深度为1级
二级指针:解引用深度为2级
三级指针:解引用深度为3级
….

3. 不同级别之间强制转换

不同级别之间的强制转换,改变的是解引用的深度。

1)例子1

int a = 10;
int *p = &a;        //&a为int *

解引用深度:*p为一级,抵消一个*后,按照int去解释所指向的a空间

int a = 10;
int **p = (int **)&a;    // &a为int *

解引用深度变为了2级:
*p:抵消一个*,按照int *去解释所指向的a空间

**p:抵消两个**,按照int去解释a10所指向的空间

**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)这不是你应该访问的空间
  • 2)人家只允许读,你偏要写
    像这种情况,编译也没问题,但是运行会有指针错误。

非法指针举例
(1)一级指针

int *p1;

int fun(void)
{
    int *p2;

    *p1 = 100;
    *p2 = 200;
}

*p1 = 100*p2 = 200都存在问题:

  • p1:为全局变量,p1会被自动初始化为0,也就是说P1中的地址默认为0,但是0地址是没有对应实际存储空间的
  • p2fun函数的自动局部变量,自动局部变量站在未初始化时为随机值,这个随机值会带来很大的问题
  • 问题1:
    如果这个随机值没有对应任何空间的话,*p解引用访问时的会导致指针错误,然后程序会被终止

  • 问题2:
    如果恰好有对应某个空间,如果这个空间不允许写访问的话还好,
    因为这会直接导致指针错误,程序被终止,程序员就回去排查错误

允许写的话更糟糕,因为这个空间很可能是其它变量的空间,这会导致数据的篡改

所以对指针进行解引用时,指针必须是合法的,只有这样才能访问正确的空间

改进以上代码,比如:

int a = 0;
int *p1 = &a;

int *p2;
p2 = malloc(4);

(2)多级指针
1)例子1

int *p1;
int **p2 = &p1;

int fun(void)
{
    **p2 = 100;
}

*p2 ———————> p1 一级解引用是正确的

**p2 ——————> *(*p2) ————————> *p1 ——————> *((int *)0) ————> X

二级解引用是指针错误

改进:

int a = 0;
int *p1 = &a;
int **p2 = &p2;