指针——从一个苹果开始

本文详细介绍了指针在计算机内存中的作用,包括内存地址、指针的概念、取址和解引用运算符的应用,以及指针与数组、空指针和野指针的区别。通过实例展示了指针在函数参数传递中的重要性,强调了正确使用和边界检查的必要性。
摘要由CSDN通过智能技术生成

指针的概念和基本原理

        内存地址和指针

        在计算机内存中,每个变量都被存储在一个特定的内存地址中。指针就是存储这些地址的变量,它指向内存中的某个位置,允许我们直接访问这个位置的数据。

        有一种老生常谈的说法:

就假设有⼀栋宿舍楼,把你放在楼⾥,楼上有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果想找到你,就得挨个房⼦去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号,而有了房间号,如果你的朋友得到房间号,就可以快速的找房间,找到你。

        其核心思想也无非是,你在哪,我怎么才能快速的找到你,然后根据指示办事。就假设你是函数,老师就是main函数,老师找你办事,就比如去某某课室削苹果(有些违和的比喻),显然你没有从老师手中拿到苹果(参数),所以你不能在老师的办公室完成这项任务,所以你到了那,拿起了那个苹果削了起来。

        很好,现在这个苹果已经从没有削过皮,变成了削过皮的苹果,也就是说说你在没有接受参数,仅仅获得了一个地址的前提下,就完成了对变量的修改。

        取址运算符(&)

        取址运算符(&)用于获取变量的地址,但很显然,在计算机里&pingguo,并不会真的给你一个课室的地址,而是会返回四个十六进制组成的地址。

0x006FFD73 
0x006FFD72 
0x006FFD71 
0x006FFD70

        如图所示,分别出现了四段不同的十六进制数,我们就假设其中一段就是&pingguo的地址,很显然他们是连续的,而且并非从零开始,也并非到零就结束,同样也不是从上到下,依次递减,但这里蕴含着大端小端的知识点,我也就不过多赘述。(我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可⾏的。)

#include <stdio.h> 
int main(){ int a = 10; 
printf("%p\n", &a); 
return 0; }

        指针的声明和初始化

        指针类型千千万,但常用的也无非是int类形指针、char类型指针,在C语言中,指针的声明使用星号(xxx*)来表示,例如int *ptr,表示ptr是一个指向int类型数据的指针。char *ptr,表示ptr是一个指向char类型的指针。指针的初始化可以通过给它赋值一个地址来实现,例如int *ptr = &pingguo;将指针ptr初始化为变量pingguo的地址。

#include <stdio.h> 
int main(){ 
int a = 10; 
int* pa = &a;//取出a的地址并存储到指针变量pa中 
return 0;}//如果该类指针不用,请将他至为空指针(NULL),至于为什么我后面在细讲

        解引用运算符(*)

        解引用运算符(*)用于访问指针所指向的变量。很显然,我们已经将苹果的地址拿到了,而拿到了地址使用要怎么使用呢,我们不可能告诉电脑,叫他去削苹果,因为他分不清什么是课室,什么是苹果,很有可能电脑在拿到苹果的地址的时候,把课室削了一层墙皮下来。那现在我们应该这么办,没错,就是告诉电脑,你要走进课室,找到那个苹果,换而言之就叫解引⽤操作符(*)。

#include <stdio.h> 
int main(){ 
int a = 100; 
int* pa = &a; 
*pa = 0; 
return 0;}

        小实验:通过 printf("%zd\n", sizeof(xxx *)),可以看到指针变量的大小,请自行观察总结规律。(x86和x64环境下的大小不一样哦:)

        指针-int与char

        观看下面的代码,观察运行结果中的规律。

#include <stdio.h> 
int main(){ 
int n = 10; 
char* pc = (char*)&n; 
int* pr = &n; 
printf("n = %p\t", &n); 
printf("n = %p\n", &n);
printf("pc = %p\t", pc);
printf("pr = %p\n", pr); 
printf("pc = %p\t", pc + 1); 
printf("pr = %p\n", pr + 1); 
return 0; }

x64环境下

x86环境下

        你有看出什么端倪吗,对,char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。请记住这个规律,以后我们常会用到。

        注:还有种指针叫void*,这种指针也很常用,但在下过于懒,就不想过多赘述了,反正就是用于接受的指针,但是没有特别指明,既你给什么指针,他就是什么指针。(在函数参数的部分,⽤来接收不同类型数据的地址。)

  • 指针的基本应用和技巧

        指针与数组

        指针和数组在C语言中密切相关。数组名本身就是一个指向数组首元素的指针,可以通过指针和解引用来遍历数组,这在处理数组和多维数组时非常有用。

#include <stdio.h> 
int main(){ 
int i = 0; 
int n[10] = { 1,2,3,4,5,6,7,8,9,10 }; 
for (i = 0; i < 10; i++) 
{ printf("n[%d] = %p\n", i, n[i]); } 
return 0; }

#include <stdio.h> 
int main(){ 
int i = 0; int n[10] = { 1,2,3,4,5,6,7,8,9,10 };
 for (i = 0; i < 10; i++) { printf("n[%d] = %p\n", i,*(n+i)); } 
return 0; }

        由此我们也不难看出,其实数组也是一种指针,其关键就在于首元素,其核心便是数组名。

        空指针和野指针

        必须小心处理空指针和野指针。空指针是指不指向任何有效地址的指针,而野指针是指指向未知或无效地址的指针。对空指针和野指针进行解引用操作会导致程序崩溃或者出现未定义的行为。而在我曾经看到的中曾这么描述他:

        当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。

        因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。

        我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来,就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓前来,就是把野指针暂时管理起来。

        不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去使⽤。

        简单来说,空指针啥也不是,就是一个指向空气的东东,而野指针只有天知道他指向何处,我们还拿老师叫你削苹果为列,假设老师指向天空,你知道那是哪里吗,显然你是懵逼的。那假设老师随机指向了一个教室,你走进去发现老师所说的“苹果”,其实是校长,你说这时候还该不该削。不削会怎么样我不知道,但我知道你要是削了,你的学生生涯怕是怕是就此结束了。(也就是程序出问题,问题大抵有三种,你们可以去查查看)

        所以当你创建一个指针时,即便不用也请将他赋值为NULL,也就是将他变为空指针,毕竟就算削不了苹果,削空气,也比削“校长”好。(当然读写NULL也会报错就是了,只不过这么做了,后面只需要判别他是不是空就成了)

        指针算术的边界检查

        在使用指针进行算术运算时,必须小心边界检查,以避免指针越界访问导致的错误和安全问题。这么说恐怕就有点抽象,打个比喻把,就像是玩游戏贪吃蛇,这个贪吃蛇他有点特殊,撞到了墙,碰到了自己,并不会使游戏结束,他仍会硬着头皮走下去。

        然后你就在游戏玩啊玩啊,一开始你并没有发现什么端倪,一切都是那么的稀疏平常,那么的平平无奇,但就有那么一下,你一个失误把贪吃蛇完成了衔尾蛇,你却猛然的发现,游戏不仅没有结束,那条蛇也不受你的控制了,他不断的走啊走啊,每吃掉后面一点,前面就长一点,你会发现这条蛇,永远也停不下来了。(这个不停的代码,忘记是啥了,是一个只能在x86环境下才能复现的代码,有没有好兄弟告诉一声……)

        你觉得这是你打开的方式不对,所以你决定重新再开一局,结果心不在焉的你,一不小就撞上了墙,好消息,这次它停了下来。坏消息,游戏直接崩了,冥冥之中你有股不详的预感,你觉得远不于此,似乎这个游戏变成了一个潘多拉魔盒,不可预测了起来。

        你打开百度一查,这问题没有年代,歪歪斜斜的每页写着“指针越界”几个字。你横竖睡不着,仔细看了半夜,才从字缝里看出字来,满满的都写着三个字,“野指针”。(建议自己调查一下)

  • 指针的重要性和优势-函数参数传递

        学了这么多(大雾),我们任然不清楚指针有什么用,从开始我说削苹果开始,指针视乎只是获得一个地址,让函数接收到一个地址后,按照原先所设定的程序运行,在此期间视乎普通的传参就能解决,就像是老师把苹果给你,你削了就OK了,何必要专门跑到一个课室在削呢,这不多此一举吗。学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?

        例如:写⼀个函数,交换两个整型变量的值,⼀番思考后,我们可能写出这样的代码:

#include <stdio.h>
void Swap1(int x, int y)
{
 int tmp = x;
 x = y;
 y = tmp;
}
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 printf("交换前:a=%d b=%d\n", a, b);
 Swap1(a, b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}

        如图所示,在程序运行之后,程序视乎并没有如常所愿的交换,他没发挥作用?不,这个想法你立马就否决掉了,因为诚实的程序,只会一五一十的完成了他的命令,但那是什么问题呢,是返回值?不,返回只能返回一个数,没法赋给两值。思索片刻,你立马就相出了答案,对,参数,函数所接受的参数,无非是从主函数那,复制来的数,是行参,并非原本真正的数,就像你帮别人交作业,你只是抄了一份,然后把抄来的交了上去,别人让你交的作业,还在原处,根本没动。

        这时候你想到了指针,找到这两个数的地址,也就是找到这两个数,真正在哪?而不是复制过来的,抄来的。于是,你重新写了一份:

void Swap2(int*px, int*py)
{
 int tmp = 0;
 tmp = *px;
 *px = *py;
 *py = tmp;
}
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 printf("交换前:a=%d b=%d\n", a, b);
 Swap1(&a, &b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}

        我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传 递给了函数,这种函数调⽤⽅式叫:传址调⽤。

 结尾

        我知道兄弟们的流量很珍贵,我也知道我的文章不过依托答辩,所以我就不浪费兄弟萌的流量啊,真的不是我懒啊,真的,相信我辛某人啊,简单的指针运用就写到这里啊,再见兄弟们。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值