C语言中关于指针的一点点理解

去年创业开始就没有深度的去思考问题了,所以编程这个爱好,也就一直放下了。最近因为疫情在家闲的无聊,脑子里都是一些乱七八糟的事情,白天在教孩子认钟表时间的时候忽然想到了之前学习C语言时指针那里特别懵,有点云里雾里。今晚闲着没事儿把心里的疑问仔细的琢磨了一下, 略有感悟,随笔记下。

1、指针是指针,指针变量是指针变量。指针我们可以说是一个值,他代表一个唯一的内存地址。这有点像函数,每一个指针都有且只有一个对应地址,而一个地址(大部分情况下)可以被任意指针指向或者指向任意指针。举个例子,就是说,我们家的地址只有一个,从我家出发我可以去我任意一个朋友的家,我任意一个朋友只要按照我家的地址就能找到我的家。而指针变量是指针的载体,指针变量有特定类型,就像int char 一样,内存管理的时候会为这种类型开辟专用空间,指针变量的长度一般是4或8字节,这取决于操作平台是32位还是64位。这里多啰嗦一下,一般来说,一个字节是8个二进制位,所以4字节也就是我们常说的32位,32位所能储存的数据是2^32值,正好是4G,所以之前32位系统最大支持的内存容量就是4G,

指针这个东西理解起来比较绕。当他是动词含义的时候他就是指针,当他是名词含义的时候他是指针变量。我之前理解这个定义的时候是把指针变量想象成手电筒,而指针是手电筒发出的光束。

就是指针其实他的作用就是指向内存地址就像光一样,它将前方的那个点照亮。而至于照哪里则是看手电筒的位置。手电筒的位置发生了改变光也就发生了改变。 指针变量如果没有指针,即使他储存了地址信息也还是不能找到相应的地址空间的,就像我们就像我们虽然把手电筒对准了要照亮的地方,如果没有光,那么我们还是无法实现。而指针就不一样了,指针既然是光束,那么除了手电筒发出的光束可以照亮目标之外, 目标也可以发射光束来让我们追朔到它。这就是说指针变量必须有指针动作才能找到相应内存空间, 而内存空间我们直接提取他的指针比如我们在做数组元素定位的时候经常用到的 *(a + 1)这其实就是一个指针。它可以不被变量储存而直接调用。

2、指针不是平面的而是立体的,指针可以指地址,也可以指向另一个指针。这个理解起来可能有一些困难,举个例子,这有点像侦探片里的情节,如果我是一个侦探,当我按照线索找到一个地方后,无法找到罪犯,而是找到了另一条线索指向了一个新的地方。这个地方有可能还是只能找到另一条线索去指向下一个地点,直到最后我找到了罪犯。这就是二级指针或者多级指针。

3、指针的跨步单位是由指针定义类型决定的,这里主要是涉及到指针运算,就是说在对指针进行运算的时候,指针的偏移单位根据指针定义类型的不同而不同。

#include<stdio.h>
int main(void)
{
    char* point_char = NULL; //字符类型指针
    char* point_str = NULL; //字符串类型指针
    int* point_int = NULL; //int类型指针
    int* point_list = NULL; //数组类型指针

    char exam_char = 'a';  //定义字符变量
    char exam_str[] = "Hello"; //定义字符串变量
    int exam_int = '100'; //定义int变量
    int exam_list[10] = { 0 }; //定义数组变量

    point_char = &exam_char;
    point_int = &exam_int;
    point_list = &exam_list;
    point_str = &exam_str;

    printf("size of point_char = %d  size of char = %d\n", sizeof(point_char), sizeof(exam_char)); //显示字符类型指针变量长度和字符变量长度
    printf("size of point_str = %d  size of str[0] = %d\n", sizeof(point_str), sizeof(exam_str[0])); //显示字符串类型指针变量长度和字符串变量长度
    printf("size of point_int = %d  size of int = %d\n", sizeof(point_int), sizeof(exam_int)); //显示int类型指针变量长度和int变量长度
    printf("size of point_list = %d  size of list[0] = %d\n", sizeof(point_list), sizeof(exam_list[0])); //显示数组类型指针变量长度和数组变量长度
    
    return 0;
}

从以上代码输出可以看到,无论指针的类型是什么,指针变量的大小都是8(如果是32位平台一般是4,原因上面分析过,不赘述)而每一种类型的变量长度是不一样的,char类型是1 str类型的每一个元素也是1,而int类型是4,数组的每一个元素长度也是4。好了那么我们的问题就来了,如果我们给我们的指针分别+1 那么他们会得到什么样的结果呢???以下都是代码片段了,如果有人想验证的话,粘贴到上面那一段代码中就可以啦。

printf("char: %p - %p = 1\n", point_char, point_char + 1); //两个指针相差1字节
printf("str: %p - %p = 1\n", point_str, point_str + 1);  //两个指针相差1字节
printf("int: %p - %p = 4\n", point_int, point_int + 1); //两个指针相差4字节
printf("list: %p - %p = 4\n", point_list, point_list + 1); //两个指针相差4字节

从以上代码可以看出,不同类型的代码指针+1的产生的差值是不一样的,而这个差值正好就是常量类型的长度。也就是说,当我们对指针进行操作的时候无论是左右++ 还是左右--(这里注意指针不要越界),每一次指针变化的跨度就正好是一个类型长度,这为我们以后进行指针操作提供了方便,比如我们想用指针去遍历一个数组的时候,只需要给指针进行++就行了。 

那么问题又来了,如果我们将一个char类型指针指向一个int类型变量的地址,他的跨步单位会变吗?请看以下代码。

#include<stdio.h>

int main(void)
{
    char* point_char = NULL;
    int* point_num = NULL;
    int num = 100;
    char text = 'a';

    point_num = &num;
    point_char = &text;
    printf("%d  %d \n", sizeof(int),sizeof(*point_num)); //显示指针指向目标的长度
    printf("%p p+1= %p\n", point_char,point_char + 1); //在这里可以看到,char类型指针每一步跨度是1
    printf("%p p+1= %p\n", point_num, point_num + 1); //int类型指针每一步跨度是4

    point_char = (char*)&num;  //在这里我们把char类型的指针强制转换为int类型
    printf("%d \n", sizeof(*point_char));
    printf("%p p+1= %p\n", point_char, point_char + 1); //在这里可以看到,这个char类型指针虽然指向int类型地址 但它的每一步跨度还是1
         
    return 0;
}

从以上的代码可以看出来,我使用一个char类型的指针,即使我把指针强行匹配给一个int类型的变量,它的跨步单位还是1,也就是char类型的长度。由此可见。如果我们不改变指针定义类型,也就是一开始定义指针时候所指定的类型不改变的话,指针的跨步单位是不会变的,当然我们其实还可以进一步的验证,比如我们下面把point_char这个指针变量重新定义为int类型的,看看他的跨步单位是否会发生变化,如果发生变化,就说明,指针的跨步单位在你定义指针变量类型的那一刻就固定了。无论后面怎么变,只要不改变指针变量的类型,那么指针跨步单位就不会变。 当然,因为和我上面的编码基本雷同,我就不往上贴了,有兴趣的小伙伴可以自己去试验一下。

4、地址指针和值的指针是不同的,这句话听起来好像是废话,但却是我今天思考时间最长的一个困惑。困惑的主要原因是下面的几行代码。

#include<stdio.h>
int main(void)
{
    int a[10] = {1,2,3,};
    printf("%p",a); //输出数组a的地址
    printf("%p",&a[0];  //输出a[0]的地址
    printf("%p",a[0]); //输出a[0]的值的地址
    return 0;
}

刚开始的时候我始终不明白,为什么第一个printf语句没有带取址符&的时候得到的是数组a的内存地址。而第三句printf语句却得到了0的ASCII码。后来我又试了试定义一个常量,然后用带取址符和不带取址符的printf去看结果,还是一样的, 带了取址符就打印变量地址,不带取址符就打印值的ASKII码。

这个问题其实当时在学C的时候就没想明白,一直也没有仔细的琢磨,直到今天下午我收快递的时候发现快递员给错了一个快递我才突然反应过来。 

今天下午,接到某东电话让到小区门口取快递,但是到了那里因为取快递人比较多。而这个快递员又特别认真负责,每一个取快递的人都要核对门牌地址和姓名电话。说实话,等得有点烦,但我仔细想想,这个过程跟我上面的那段代码何其的相似啊,我接到快递员的电话就像计算机接收到printf函数,然后我去取快的时候,我要听快递员叫我名字,就是计算机在验证数据。 最后当信息核对正确以后快递员把快递给我,这就是最后的结果输出。在这个过程中,快递员先是按照快递上的地址找到我家,然后再核对我的个人信息。就是我上面那个疑惑的答案。首先在计算机接收到printf函数时因为函数中有%p这个占位符也就是说我最后要输出一个指针,那么计算机在接下来就会寻找指针信息,如果说我后面给的信息是带取址符的变量名称,那么他就会把变量的指针交给计算机输出。这个有点像我快递上的写的地址门牌号。取址符的作用就是直接把我的门牌号信息告诉快递员,让他根据这个信息进行输出。 而不带取址符就像我没给门牌号,而是直接把我的姓名电话报给了快递员。然后快递员根据这个信息最后把快递给我。这里面之所以不同就是因为我家可能不止我一个人买东西,今天有可能是我,明天有可能是我老婆,虽然都是这个地址,但其实收货人是不同的。如果带了取址符,那么就相当于我在快递上注明只要送到这个地址不管是谁收都行,而如果不带取址符的话就相当于快递虽然是送到这里,但必须是我自己收一样。

扯得有点远,我画了一张图,大概的把上面的意思总结了一下

在这个代码中,可以看到在我们创建一个数组a的时候,我们的程序开辟了一段内存空间,他就像小区的地址,他是从a[0]开始的,长度就是a[]的长度乘以元素个数。如果我们在输出的时候带取址符,就是我们要输出a[0]的门牌号,那么结果就是a[0]的内存地址,同时因为a[0]是数组a的第一个元素,所以a的地址也就是a[0]的地址。那么如果我们在输出的时候不带取址符,就相当于我们要输出a[0]这元素里的值的地址。这里比较绕,我已经尽自己最大的努力取说清楚了, 希望过一段时间自己再来看的时候不会一脸懵逼。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值