迟来的更新,一年前催促我更新的同学们还会看到这一篇文章吗?往事如风,就让我在2023年最后的时刻弥补一下各位的遗憾,今天抽出我一部分时间为你们讲解指针篇的中级部分吧
首先在入门篇的基础上,相信大家已经对指针有了初步的了解和使用能力,那么接下来我将讲解二级指针(多级指针)的理解和应用场景,首先我们来认识一下二级指针,其实很形象,一级指针既然是一个*号,那么二级指针不就是两个*号了吗?没错,二级指针的定义就是两个解引符号加上一个变量名称,至于数据类型不能说和一级指针一模一样,只能说完全相同,我们来看定义二级指针的代码:
int main()
{
int **p_2 = NULL; //定义一个名叫p_2的二级空指针
int *p_1 = NULL; //定义一个名叫p_1的一级空指针
return 0;
}
为了防止大家没在我的初级篇理解透彻,这里给大家说明一下空指针的意思,空指针顾名思义就是没用保存任何地址的指针,不存放任何地址(NULL),和野指针有着一定的区别,野指针存放随机的地址相当危险,作为专业的开发人员初始化指针时一定要置空
二级指针作用其实概括起来并不难,一句话就是用来储存一级指针的地址,解引方式和一级指针相同,但是需要注意的是一层解引是得到一级指针储存的内存,二级解引才是访问一级指针所指向内存的数据,有点绕对吗?看下方代码
int main()
{
int *p_1 = NULL;
int data = 1;
p_1 = &data; //一级指针储存data的地址
int **p_2 = NULL;
p_2 = &p_1; //二级指针储存p_1的地址
std::cout<<*p_1<<std::endl; //访问data数据
std::cout<<**p_2<<std::endl; //访问data数据
std::cout<<p_1<<std::endl; //访问data地址
std::cout<<*p_2<<std::endl; //访问data地址
std::cout<<&p_1<<endl; //访问p_1的地址
std::cout<<p_2<<std::endl; //访问p_1的地址
//所以p_2 = &p_1就是保存了p_1的地址,懂否?
return 0;
}
那么储存一级指针的地址有什么用呢?我们上次说明了一级指针可以解决形参和实参不一致的问题,那么二级指针是不是同样可以这么使用呢?当然可以了,我们举个例子,这个例子使用了堆空间(这个东西放到高级篇讲解)
void initBlock(int **p)
{
*p = new int;
}
int main()
{
int *p=NULL;
initBlock(&p);
*p = 0;
cout<<*p<<endl;
return 0;
}
好了,相信有的同学看到这里看到有点疑惑,如果有以下疑惑:为什么形参采用二级指针?
那么你们可以动手把我的形参改成一级的,然后传入参数去掉&符号,运行一下就知道结果了。
其实以上方式是我们自定义数据结构初始化的时候经常使用的操作,比如链式结构等等....
二级指针的第二种用法,将函数内部的变量带出来,看过我博客的都知道栈帧这个东西,函数结束调用时会自动释放掉栈,那么变量自然也就不存在了,但是我们可以使用二级指针保存内存条上面的地址进行操作(不建议),栈上的空间被释放内存会变得十分不稳定。所以我们使用static变量,函数结束调用时内存依然存在,不会消失,那么怎么玩呢?看以下代码:
void test(int **p_2)
{
static int k = 1;
*p_2 = &k;
}
int main()
{
int **p_2 = NULL;
int *p_1 = NULL;
p_2 = &p_1;
test(p_2);
cout<<**p_2<<endl;
return 0;
}
怎么样?神奇吗?其实这个方式几乎用不到,但是我想告诉你们的是,任何方式只要懂得了他的原理,那么就不会因为问题改变而手足无措,接下来我们看看原理图:
最终结果如上图所示。
其实我们只要知道一个道理即可,任何指针,哪怕十级指针也是变量,那么变量就要遵守变量的规则,堆,栈等等,学习从来不是散乱无章的,而是系统的,希望大家可以有个系统的学习。
最后,请大家思考,既然二级指针保存的是一级指针的地址,那么一级指针又是变量,那么他的地址也是整数,所以二级指针和一级指针所保存的类型相同?
答案是对的,在C语言语法中指针可以直接被赋值为整形,但是C++去除了这个操作,为了更加安全所以做了保护措施,大家可以把.cpp文件改成.c然后去尝试这个操作,相信会有更多的理解
最后,马上元旦了,祝大家元旦节愉快