指针~第一幕【用牛角包和盘子来解释指针】

1 篇文章 0 订阅

     指针,听起来很高大上吧,毫不夸张的说在我最初见到指针这一词时,我本以为就是学习操作屏幕上的鼠标指针……经过初阶的学习后对当时的天真感到很十分尴尬。指针这东西确实麻烦但不难理解,接下来我用盘子、牛角包、和手的关系来解释指针和指针应该怎么用。


一、地址是什么

        首先我们先来了解地址。我们在写程序时,写入的任何一个数值,任何一个变量,任何一个自定义函数都要在内存开辟一块儿空间,为了更好更便捷的访问内存空间,我们把内存中一个字节大小的空间称为内存的最小单元,每一个单元都有一个属于他的编号,这个编号就是这个内存单元的地址。

        若编译器选择32位虚拟地址空间(vs2019为例,选择X86的环境),CPU就会为内存单元开辟32bite位的地址序列,如下图是变量a的地址

                  

而这串十六进制数字用二进制表示为:        

        若编译器选择64位虚拟地址空间,CPU就会为内存单元开辟64bite位的地址序列(这个我不举例了)所以我们发现地址的实质是一串二进制数,那么如何通过这一串二进制数找到相应的内存空间,这就是我接下来要讲的指针的作用。

二、指针是什么

        简单地说,指针就是存放地址的变量,通过取地址符&将变量的地址取出,然后将地址赋给指针变量,通过解引用的方式就能找到a变量中所存放的值。我用一个例子示范

#include<stdio.h>
int main()
{
    int a = 10;//在内存中开辟四个字节空间大小,用来存放int型的变量a
    int *p = &a;//用取地址符&取出变量a的地址放入指针变量*p中
                //此时注意,int所占四个字节空间,指针指向的地址是第一个字节的地址
    int b = *p;//用解引用符将p指针指向的变量的内容取出,放入变量b中

    return 0;
}

三、用牛角包和盘子解释指针

        接下来,我分别写出两个功能函数来实现"交换两个变量的数值"这一程序

#include<stdio.h>
void func1(int p1, int p2)
{
	int temp = p1;
	p2 = p1;
	p1 = temp;
}
void func2(int* p1, int* p2)//通过指针来接受传入的两个地址
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
int main()
{
	int a = 2, b = 4;
	func1(a, b);
	printf("a的值为:%d b的值为:%d\n", a, b);//将a,b的值传入func1函数中
	func2(&a, &b);
	printf("a的值为:%d b的值为:%d\n", a, b);//将a,b的地址传入func2函数中
	return 0;
}

 

        运行程序我们发现,将两个数的数值传入,通过函数func1运算后a、b的数值并没有发生调换,而把a,b的地址传入func2中通过指针解引用的方式来交换数值却成功的发生了调换,这是为什么呢?是因为魔法吗?不不,是因为指针,接下来我通过类比的方式帮助大家理解。

        我们把程序比作一场晚宴,把变量a、b比作供食客享用的牛角包,每一个牛角包必须存放在盘子里,盘子就相当于内存地址,这时指针就相当于服务生的手,我的手托起装着牛角包a的盘子,就相当于指针真指向了变量a的地址,我的手上就存放着这个盘子,也就相当于指针变量存放了变量a的地址;如果我想得到a、b的值,就要对指针解引用,相当于我要把盘子抽取掉,手上剩下的就是牛角包这个道理。

        回到上述的程序,我将变量a、b的值传入func1函数中,通过临时创建的形参p1,p2接收,就好比我模仿着牛角包a、b的形状又做出两个一模一样的牛角包p1、p2,然后将他们存放在新的盘子里,此时服务生func1调换左右手盘子上面包的位置只是交换了牛角包p1和牛角包p2的位置,对牛角包a、b没有影响,所以说值传递根本不能来改变a、b变量本身。

       而对于地址传递来说,就相当于我把装有牛角包a、b的盘子交给了服务生func2,他调换了左右手盘子上面包的位置,此时a盘装着牛角包b,b盘装着牛角包a,这就从本质上改变了a、b的值。对于func1函数我们还要认识到,函数形参只是临时变量,当函数运行完后形参自动销毁,甚至这个函数开辟的内存空间也会释放;相当于牛角包p1、p2被嘴馋的服务员func1吃完了,吃完后服务员func1也被辞退了(卖的还没吃得多)。

四、指针的正确用法

      了解完了牛角包...哦不是,了解完指针后我们来掌握如何正确使用指针

    1.指针的正确定义

      在定义指针时需要三个部分,指针名、指针标识符*、指针类型

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

      如上图所示,指针名为p,' * ' 说明p为指针变量,int说明指针类型是整型的

      有些同学说,说是*p才是指针,单独的p什么也不是。在这里我可以负责任的说' * '只有在定义指针时用来表明他是个指针变量,在定义完后任意情况里,*p都代表对指针解引用,也就是取得指针所指向地址的值。

    2.指针的正确使用

      1.指针在使用时一定要初始化,或者指向NULL,否则会指向内存中的随机值

        

        指针的未初始化可能会造成内存的非法访问

       2.指针在指向数组时,要注意指针的越界访问

#include<stdio.h>
int main()
{
	int arr[4] = { 1,2,3,4 };
	int* p = arr;
	for (int i = 0; i < 6; i++)
    //这里在arr[3]后,指针向后访问非数组成员,在打印时可能打印出随机值
	{
		printf("%d", arr[i]);
	}
	return 0;
}

 

        3.在返回值为指针变量的函数中,在主函数中的指针不能接受返回值中的临时变量的地址

        因为函数在调用完后,函数体,形参,返回值所占的内存空间都会被释放,当返回值为地址被释放时,刚才调用的函数在返回的这个地址上所赋的值已经被释放,所以这个地址所赋的值现在为随机值。

五.指针类型

        很多同学都有这种感觉,就是指针变量和被指向的元素必须是同一类型,类型不同指针不能访问变量,这里我先把结论放出来

1.指针类型决定了,指针在解引用时访问变量内容字节数的权限。
2.在数组中或者其他地方指针做+-整数运算时(其实就是地址指向的移动)时指针类型决定了,指针向前或者向后移动了多少字节数。

具体是什么意思我举例帮助大家理解

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	char* p = (char*)arr;
	for (int i = 0; i < 40; i++)
	{
		*p++ = 0x11;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%#x\n", arr[i]);
	}
	return 0;
}

        如上图代码,我们知道int类型的数据占4个字节的内存空间,所以数组arr[10]所占空间大小为40个字节,将arr数组的类型强转为char*,让一char类型的指针p指向arr数组,char类型的指针每次只能访问一个字节的数据,所以指针p的移动的距离为一字节,p解引用赋值将arr数组每一字节的数值改为0x11

上述代码的输出结果为:

  

 再来看下面一串代码

#include<stdio.h>
int main()
{
    char arr1[40] = { 0 };
	int* p1 = (int*)arr1;
	for (int i = 0; i < 10; i++)
	{
		*p1++ = 0x11;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%#x\n", arr1[i]);
	}
    return 0;
}

        char类型的变量占1字节的内存空间,arr1数组总共占40个字节的内存空间,用int类型的指针p1指向arr1首元素的地址,p1++就向后访问4个字节空间的内存地址,然后将指向的第一个字节空间的值改变为0x11

运行结果为:

六.指针运算

        指针在使用的过程中也存在着加减运算,在这里我介绍两种指针的运算
1.指针+ - 整数,就是指针在指向地址时的移动(移动的字节数取决于指针变量的类型) 
2.指针-指针运算((地址-地址)/(类型所占字节数))得到的是指针与指针之间的元素个数,前提是两个指针必须指向同一空间,如下图

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p1 = &arr[0];
	int* p2 = &arr[9];
	printf("p2与p1之间的元素个数为:%d", p2 - p1);
	return 0;
}

运行结果为:

七.拜拜辣,先介绍这么多


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值