• 【C语言】从零开始理解初级指针


    创作不易,欢迎  关注  点赞  收藏  留言
    水平有限,不足之处,望友人指出
    博主介绍:一枚不知所措的大学生
    博客首页:Gredot
    所属专栏《C语言》

    前言:

      指针是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。今天我们就指针这一概念深入解析指针到底是什么。

    一、内存和地址

      我们从int开始,首先我们知道C语言定义变量的方式:为 数据类型  变量名  赋值符  值;

    int a = 0
    • 1

    这局代码是什么意思呢?向内存申请四个(部分编译器是两个)字节的空间,来存储变量a的值,这里a,只是标识符,并不会进行存储,在内存中存储的其实是变量a的值,我们通过vs的调试可以看到变量a的内存结构。
    在这里插入图片描述
    我们在内存窗口使用&a就可看见a的值,可以看见a的值的确是0。那左边的十六进制数又是什么呢?没错,这就是内存中的地址。内存地址只是内存中的一个编号,代表了一块空间。现在我们再来理解这句代码:

    	int a = 0
    • 1

    向内存申请4个字节,0x0000007E206FF634 - 0x0000007E206FF637这四个字节,并且将值赋为0。

    注:&a取出的是变量a的四个字节中第一个字节的地址。

    到这里,我们已经知道了内存,和地址。那么地址到底是怎么产生的?日常我们说32位/64位平台说的是什么呢?这里的32位/64位指的就是地址线的数量,32位平台有32根地址线,64位平台有64根地址线。地址线通过高低电压就可以产生32/64个2进制序列,这样通过地址线我们就可以产生232/264个地址编号,作为内存单元的编号。只要我们理解了内存和地址,那么理解指针就不再困难了。

    二、指针概念

      我们再来看指针的概念,用来存储变量地址的变量,如

    	int a = 0;
    	int* p = &a;
    
    • 1
    • 2
    1. 指针是内存中一个最小单元的编号,也就是地址
    2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
      这里 a是变量,p是指针变量,p中存的是变量a的地址,由于平台不一样指针变量的大小不一样,在32位平台下(32个比特位)为4个字节,64位平台下(64个比特位)为8个字节。
      在这里插入图片描述
      通过监视窗口,我们可以看见&a,和指针变量p的值相同,都是变量a的地址,而指针p的地址不同于p的值。总结:指针变量类比普通变量,向内存申请4/8个字节存放普通变量的地址。
    #include
    int main()
    {
     int a = 10;//在内存中申请4个字节的空间
     int *p = &a;//使用&操作符,取出a的地址存入指针变量中作为指针变量的值
        //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中
        //p就是一个之指针变量。
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    三、指针和指针类型
      这里我们讨论一下指针类型的意义,我们再来看这样一段代码:

    int a = 0;
    int* p = &a;
    
    • 1
    • 2

    这里的int*就是指针类型,是一种整型指针,那么指针类型有何实际意义呢?
    类比整型指针,那么肯定有字符指针,单精度浮点型指针……

    char  *p1 = &a;
    int   *p2 = &b;
    short *p3 = &c;
    long  *p4 = &d;
    float *p5 = &e;
    double *p6 = &f;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    接下来我们通过两个例子,来探索指针类型的意义:

    1.指针加减整数

    #include 
    int main()
    {
    	int n = 10;
    	char* p1 = (char*)&n;
    	int* p2 = &n;
    
    	printf("%p\n", &n);
    
    	printf("%p\n", p1);
    	printf("%p\n", p1 + 1);
    
    	printf("%p\n", p2);
    	printf("%p\n", p2 + 1);
    	return  0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    总结:指针的类型决定了指针向前或者向后走一步有多少字节

    2.指针的解引用

    #include 
    int main()
    {
     int n = 0x11223344;
     char *pc = (char *)&n;
     int *pi = &n;
     *pc = 0; 
     *pi = 0; 
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述

    总结:指针的类型决定了,对指针解引用的时候有多大的访问权限(能操作几个字节)

    三、野指针

    1.概念

       指针指向的位置是不可知的,我们把这种随机的、不正确的、没有明确限制的指针称为野指针。

    2.成因

       那么为什么会产生这种随机的、不正确的、没有明确限制的指针呢?
    我们来探讨也只真的集中成因

    1)指针未初始化

    #include 
    int main()
    { 
     int *p;
     *p = 10;
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    局部变量指针未初始化,默认为随机值,此时指针的指向是随机的。

    2). 指针越界访问

    #include
    int main()
    {
    	int arr[10] = {0};
        int *p = arr;
        int i = 0;
        for(i=0; i<=11; i++)
       {
            *(p++) = i;
       }
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当指针指向的范围超出数组arr的范围时,数组后面的内存空间是位置的,此时p也是野指针。

    3)指针指向的空间释放

    #include
    int * Gre()
    {
    	int a = 2;
    	return &a;
    }
    int main()
    {
    	int p* =Gre();
    	printf("%d",*p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    函数调用结束后,函数内的变量a的空间销毁,即a的地址销毁,所以Gre函数无法成功的返回变量a的地址,导致指针变量p的指向未定义,此时p也是野指针。

    3.如何规避

    对于如何规避野指针,这里给出以下五条建议:

    1. 指针初始化
    2. 小心指针越界
    3. 指针指向空间释放,及时置NULL
    4. 避免返回局部变量的地址
    5. 指针使用之前检查指针有效性

    四、指针运算

    1.指针加减整数

    这点我们在探索指针类型的意义是已做过探讨,这里直接给出结论:
    指针加减整数跳过的是指针类型大小的空间,例如:
    char * 类型的指针加一就向后走一个字节
    int * 类型的指针加一就向后走四个字节

    2.指针-指针

    指针减指针(地址减地址)时,两个指针必须指向同一块内存空间,指针减指针的结果得出的时两个指针之间的元素个数。
    例题:使用指针减指针模拟实现strlen函数

    int Srlen(char *s)
    {
         char *p = s;
          while(*p != '\0' )
                 p++;
          return p-s;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里的指针p指向字符串的首地址,指针s指向字符串的末尾( ’ \ 0 ’ )。

    2.指针的关系运算

    使用指针的关系运算时,两个指针必须指向同一块内存空间。两个指针是否相等,是判断两个指针的指向是否相同,而大于或小于则是比较两个指针在内存中的指向谁更靠前。
    看两段代码

    for(vp = &values[N_VALUES]; vp > &values[0];)
    {
        *--vp = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
    {
        *vp = 0;
    }
    
    • 1
    • 2
    • 3
    • 4

    很显然,第二段代码的可读性优于系一段代码,两段代码的功能都是将数组归零。但是,第二段代码中指针最后会指向数组首元素之前的那个元素。C语言标准规定:

    允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

    五、指针和数组

    1.数组名

    #include 
    int main()
    {
     int arr[10] = {1,2,3,4,5,6,7,8,9,0};
        printf("%p\n", arr);
        printf("%p\n", &arr[0]);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    可见,数组名就是首元素的地址,这点在前面介绍数组时有介绍到,这里再次印证。

    2.指针访问数组

    #include 
    int main()
    {
        int arr[] = {1,2,3,4,5,6,7,8,9,0};
        int *p = arr; //指针存放数组首元素的地址
        int sz = sizeof(arr)/sizeof(arr[0]);
        for(int i=0; i<sz; i++)
       {
            printf("&arr[%d] = %p   p+%d = %p\n", i, &arr[i], i, p+i);
       }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    所以 p+i 其实计算的是数组 arr 下标为i的地址,我们就可运用指针对数组进行灵活的访问了。

    3.指针数组

    指针数组是指针还是数组呢?答案是数组,指针数组使用来存放指针的数组。让我们再次回顾数组的定义,一组相同数据类型元素的集合。

    int*  arr1[] = {&a,&b,&c};
    char* arr2[] = {&d,&e,&f};
    
    • 1
    • 2

    指针数组的每一个元素都是指针(地址),而且指针类型相同。

    六、二级指针

    这里先总结一下,指针就是地址,地址就是指针。指针变量是用来存放变量的地址的。再次体会这样两句代码:

    int a = 10;
    int * p = &a;
    
    • 1
    • 2

    创建变量a,为a开辟空间(4字节),赋值10;
    创建指针变量p,为指针变量p开辟空间(4/8字节),赋值a的地址;
    哦,原来创建指针变量也要在内存中开辟空间(地址),那我们用什么来存指针变量的地址呢?
    我们使用二级指针来存一级指针的地址。来看这样一段代码:

    int a = 10;
    int *pa = &a;
    int**ppa = &p;
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    a的地址存放在指针变量pa中,一级指针变量pa的地址存放在二级指针变量ppa中,pa是一级指针,ppa是二级指针
    类比一级指针,通过对二级指针解引用既可以访问一级指针,也可以访问变量。

    int b = 20;
    *ppa = &b;//等价于 pa = &b
    
    • 1
    • 2

    *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa

    **ppa = 30;
    //等价于*pa = 30;
    //等价于a = 30;
    
    • 1
    • 2
    • 3

    结语:通过对本篇的学习,你可以了解初级指针,加深对指针理解。
    ps:如果本篇对你有帮助别忘了关注、点赞、收藏支持一下哦,你的支持是我创作的动力。
    在这里插入图片描述

  • 相关阅读:
    1.2 w字+!Java IO 基础知识系统总结 | JavaGuide
    处理 List、Set、Map 的相互转换问题
    音视频 ffmpeg命令视频录制(Windows)
    9--RNN
    【python】基于pandas的EXCEL合并方法
    容器化技术最佳实践1--容器化技术简介与Docker入门
    索引的基础使用
    Node.js学习记录
    jdk版本与class文件格式major版本对应关系
    【实习】vue input下拉及搜索功能
  • 原文地址:https://blog.csdn.net/qq_75209065/article/details/127976065