• 【C语言】你还不会指针吗?不妨来一起攻克指针这个难点


      💘作者:你我皆为凡人

     💘博客主页:你我皆为凡人的博客

     💘名言警句:时间不会为任何人停留,而事物与人,无时不刻也在变化着。每一个人,也都在不停向前!

     💘觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!

     💘系列作品:

     💘牛客网习题练习直达

     💘C语言编程刷题篇

     💘经典题型系列

     💘练C语言刷C语言习题直达网站(无需下载):

            C语言刷题习题大全

     

    目录

    文章目录

    🙈 前言

    💫指针是什么

    💫指针和指针类型

    💛指针+-整数

    💫野指针

    💛野指针成因

    ☯ 没有初始化

     ☯ 指针越界访问

     ☯ 指针指向的空间释放

    💛如何规避野指针

    💫指针运算

    💛指针+-整数

    💛指针-指针

    💛指针的关系运算

    💫指针和数组

    💫二级指针

    💫指针数组

    💞指针习题练习

     💞题外话

    🙈 总结


    文章目录

    🙈 前言

    本篇文章给大家介绍了C语言初阶中的一大难点,指针方面的各种知识点,让处于新手阶段的你更容易理解指针,在以后的日子里对指针的应用如鱼得水,更加透彻


    提示:以下是本篇文章正文内容,下面案例可供参考

    💫指针是什么

    指针是什么?
    指针理解的2个要点:
    1. 指针是内存中一个最小单元的编号,也就是地址
    2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
    总结:
    指针就是地址,口语中说的指针通常指的是指针变量

     

     从上图可以看到,我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址存放到一个变量中,这个变量就是指针变量

    总结:

    指针变量,用来存放地址的变量(存放在指针中的值都被当成地址处理)

    那么我们来思考两个问题:

    一个小的单元到底是多大?(1个字节)

    如何编址?

    在经过仔细的计算和权衡发现一个字节给对应的一个地址比较合适,其他的都太大

    对于32位机器,假设有32跟地址线,假设没跟地址线在寻址的时候产生高电平和低电平假设1或者0,那么32根地址线产生的地址就会是:

     一共会产生2的32次方个地址

    如果每个地址为一个字节,我们可以给(2^32Byte == 2^32/1024KB ==

    2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB 4G的空闲进行编址
    相同,如果64位机器,假设给64根地址线,原理一样
    那么这样我们就明白:
    在32位机器上,地址是32个0或者1组成的二进制序列,地址得用4个字节的空间来存储,所以一个指针变量的大小应该是4个字节
    在64位机器上,地址是64个0或者1组成的二进制序列,一个指针变量的大小是8个字节,才能存放一个地址
    总结:
    指针是用来存放地址的,地址是唯一一块地址空间的
    指针的大小在32位平台是4个字节,在64位平台是8个字节

    💫指针和指针类型

    我们知道变量有不同的类型,整形,浮点型等,那么指针有没有类型呢?

    准确来说是有的

    1. int main()
    2. {
    3. int num = 10;
    4. ? p = #
    5. return 0;
    6. }

     要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢?

    我们给指针变量相应的类型: 

    1. int main()
    2. {
    3. int num = 10;
    4. int* pc = #
    5. char* pi = NULL;
    6. short* ps = NULL;
    7. long* pl = NULL;
    8. float* pf = NULL;
    9. double* pd = NULL;
    10. return 0;
    11. }

     这里可以看到,指针的定义方式是: 类型 + *

    其实:
    char* 类型的指针是为了存放 char 类型变量的地址。
    short* 类型的指针是为了存放 short 类型变量的地址。
    int* 类型的指针是为了存放 int 类型变量的地址。
    那指针类型的意义是什么?

    💛指针+-整数

     我们可以看到上图中 int和char类型的地址,pa与pc都存放的是a的地址,都一样

    按理说pa与pc加一减一都一样,但是pa+1加了4个字节,而pc+1加了1个字节,这件事指针类型的意义

    指针的类型决定了指针加一减一操作时跳过几个字节(指针的类型决定指针向前或者向后走一步有多大)

    而解引用有多大的权限在于指针类型,如果char*的指针解引用可以访问1个字节,而int*的指针的解引用可以访问4个字节

    💫野指针

    概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

    💛野指针成因

    ☯ 没有初始化

    1. int main()
    2. {
    3. int* p;//p没有初始化,就意味着没有明确的指向
    4. //一个局部变量不初始化的话,放的是随机值:0xcccccccc
    5. *p = 10;//非法访问内存,p就是野指针
    6. return 0;
    7. }

     ☯ 指针越界访问

    1. int main()
    2. {
    3. int arr[10] = { 0 };
    4. int* p = arr;
    5. int i = 0;
    6. for (i = 0; i <= 10; i++)
    7. {
    8. *p = i; //当指针指向的范围超出数组arr范围时,p就是野指针
    9. p++;
    10. }
    11. return 0;
    12. }

     ☯ 指针指向的空间释放

    1. int* test()
    2. {
    3. int a = 10;
    4. return &a;
    5. }
    6. int main()
    7. {
    8. int*p = test();
    9. return 0;
    10. }

     上图中,当调用test时,返回的是一个a的地址,a是10,那么p指针可以接收这个地址,但是可以找回这个10吗?答案是不可以的,因为在test函数内部,局部变量a为10,出了这块儿空间就开始销毁了,虽然p还记的这块儿地址,但是却找不回来了,因为已经不属于p了,此时就是野指针

    💛如何规避野指针

    1. 指针初始化
    2. 小心指针越界
    3. 指针指向空间释放即使置NULL
    4. 避免返回局部变量的地址
    5. 指针使用之前检查有效性
    1. int main()
    2. {
    3. int a = 10;
    4. int b = 0;
    5. int* p = &a;
    6. *p = 20;
    7. int* p2 = NULL;
    8. //*p2 = 100; err
    9. return 0;
    10. if (p2 != NULL)
    11. {
    12. *p2 = 100;
    13. }
    14. return 0;
    15. }

    💫指针运算

    💛指针+-整数

    1. #define N_VALUES 5
    2. int main()
    3. {
    4. float values[N_VALUES];
    5. float* vp;
    6. for (vp = &values[0]; vp < &values[N_VALUES];)
    7. {
    8. *vp++ = 0;
    9. }
    10. }

     定义values为5,起初定义vp为野指针,在for循环内定义vp为values第一个地址,开始解引用后置加加,*vp++可以分解为  *vp = 0,然后++,当vp等于5的地址时就停下来,这样就形成了指针+整数的形式,同理,相减也一样

    💛指针-指针

    1. int main()
    2. {
    3. int arr[10] = { 0 };
    4. printf("%d\n", &arr[9] - &arr[0]);
    5. return 0;
    6. }

     

     我们发现最后的结果是9,为什么是9呢?下标9与0之间有10个元素,减去一个等于9,那么反应了指针与指针相减的绝对值得到的指针和指针之间元素的个数

    不是所有的指针都能相减,指向同一块儿空间的2个指针才能相减,不然没有意义

    💛指针的关系运算

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

     上面第一段代码是结合了指针之间的比较与相减,如第一段代码看起来比较难理解,经过第二段的简化,那么是不是用第二种就比较好,也好理解,实则不是这样的,应该避免第二种写法,因为标准并不保证它可行 ,第一个是先是vp等于5,第二个是直接为4,然后大于等于第一个,一个是从后面越界的比较,一个是前面越界的进行比较

    标准规定:

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

    💫指针和数组

    数组:是一组相同类型元素的集合

    指针变量:是一个变量,存放的是地址

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

     

     可以看到数组名和数组首元素的地址是一样的,在前面数组的时候我们就知道数组名表示的是数组首元素的地址,除了sizeof【arr】与&arr

    那么下面这种就可以,吧数组名当成地址放到一个指针中,我们使用指针来访问也可以

    1. int main()
    2. {
    3. int arr[10] = { 0 };
    4. //arr是首元素的地址
    5. //&arr[0]
    6. int* p = arr;
    7. //通过指针来访问数组
    8. int sz = sizeof(arr) / sizeof(arr[0]);
    9. int i = 0;
    10. for (i = 0; i < sz; i++)
    11. {
    12. printf("%p ---- %p\n", &arr[i], p + 1);
    13. }
    14. return 0;
    15. }

     

     可以看到,p+i其实计算的就是数组arr下标为i的地址

    那么我们也可以直接通过指针来访问数组:

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

    💫二级指针

    指针变量也是变量,而是个变量就会有地址,那么指针变量的地址存放在哪里呢?

    1. int main()
    2. {
    3. int a = 10;
    4. int* pa = &a;//pa是一个指针变量,一级指针变量
    5. int** ppa = &pa;//ppa是一个二级指针变量
    6. return 0;
    7. }

     a的地址存放到pa中,pa解引用可以找到a,而pa的地址放到ppa中,ppa解引用可以找到pa,pa是一级指针,而ppa是二级指针

     

    1. *pa = 20;
    2. **ppa = 30;

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

    **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

     第一个说明a是int整型变量

    第二个*是说明pa是指针,类型是int整型

    第三个*说明ppa是指针,类型是int*类型

    💫指针数组

    指针数组到底是指针呢还是数组呢?

    答案是数组,存放指针的数组就是指针数组

    1. int main()
    2. {
    3. int a = 10;
    4. int b = 20;
    5. int c = 30;
    6. int* pa = &a;
    7. int* pb = &b;
    8. int* pc = &c;
    9. //parr就是存放指针的数组
    10. //指针数组
    11. int* parr[10] = { &a,&b,&c };
    12. int i = 0;
    13. for (i = 0; i < 3; i++)
    14. {
    15. printf("%d ", *(parr[i]));
    16. }
    17. return 0;
    18. }

     如上图取出a,b,c的地址放到指针中,如果数量过多显得很麻烦,所以可以定义一个指针数组来存放地址,用遍历的方式打印出来解引用是通过每个地址来找到值

    1. int main()
    2. {
    3. int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
    4. int i = 0;
    5. int j = 0;
    6. for (i = 0; i < 3; i++)
    7. {
    8. for (j = 0; j < 4; j++)
    9. {
    10. printf("%d ", arr[i][j]);
    11. }
    12. }
    13. return 0;
    14. }

     我们以前遍历一个二维数组是这样遍历的,那么现在我们还可以按照下面这种方法,把一维数组像二维数组一样串起来放到指针数组中

    1. int main()
    2. {
    3. int arr1[4] = { 1,2,3,4 };
    4. int arr2[4] = { 2,3,4,5 };
    5. int arr3[4] = { 3,4,5,6 };
    6. int* arr4[3] = { arr1,arr2,arr3 };
    7. int i = 0;
    8. for (i = 0; i < 3; i++)
    9. {
    10. int j = 0;
    11. for (j = 0; j < 4; j++)
    12. {
    13. printf("%d ", arr4[i][j]);
    14. }
    15. printf("\n");
    16. }
    17. return 0;
    18. }

     

    💞指针习题练习

    看完这些不具体操作操作那么是不可以的,可以点击上方直达去练习一些有关指针的习题,也可以随便看一看C语言的一些习题,练习练习选择题和编程题,让自己的知识得到巩固,直接点入标题就可直达,另外想获得大厂内推资格也可以去看看:

    大厂内推

     💞题外话

    大家不想看可以跳过这一段,本人三年被脸上痘印折磨的死去活来,想过很多办法,后来用对了产品慢慢改善的变好,也是大学生创业搞的一个小产品,质量很好,大家有同样的困扰可以来询问我

     微信号:lpy16128227

    🙈 总结

    这篇文章主要讲解了关于指针运用的各种知识点,让新手不在畏惧指针,图文并茂,让大家可以更好的掌握关于指针这一知识点,但是光理解是不够的,看完还需要巩固所学的知识点,练习一些关于指针的习题,这样才可以学以致用

  • 相关阅读:
    2475. 数组中不等三元组的数目-快速排序+遍历求和
    智能健身动作识别:PP-TinyPose打造AI虚拟健身教练!
    100 余个网页设计优化案例(用户体验、交互优化等方面)
    Ansys Zemax / Ansys Speos | 如何使用Ansys光学解决方案设计和分析 HUD系统
    嵌入式学习笔记(54)S5PV210的ADC控制器
    基于PHP+MySQL公积金在线办理系统的设计与实现
    浅谈spring的@Scheduled定时任务(演示)
    单商户商城系统功能拆解19—订单管理
    Linux基本指令(一)
    2024年软考考试和报名时间表,请接收~
  • 原文地址:https://blog.csdn.net/weixin_45659943/article/details/124828684