• (初阶)指针


    好长时间没有更新博客了,博主前段时间考虑了自己的学习路线,还是想要去考个研究生,以后会一直更新的。

    本篇文章简单地讲解一下指针的一些基础知识,大招还会放在后面。

    目录

    1. 指针是什么?

    2. 指针和指针类型  

    2.1 指针+-整数 

    2.2 指针的解引用 

    3. 野指针  

    3.1 野指针成因 

    3.2 如何规避野指针 

    4. 指针运算

    4.1 指针+-整数

    4.2 指针-指针

    4.3 指针的关系运算

    5. 指针和数组

    6. 二级指针 

    7. 指针数组


    1. 指针是什么?

    1. 指针是内存中一个最小单元的编号,也就是地址
    2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

    1. #include
    2. int main()
    3. {
    4. int a = 10;//在内存中开辟一块空间
    5. int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    6.    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
    7. 中,p就是一个之指针变量。
    8. return 0;
    9. }

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

    那这里的问题是:
    一个小的单元到底是多大?(1个字节)
    如何编址?
    经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
    对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0); 

    00000000 00000000 00000000 00000000
    00000000 00000000 00000000 00000001
    ...
    11111111 11111111 11111111 11111111

    所以这里就有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个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
    总结:
    指针是用来存放地址的,地址是唯一标示一块地址空间的。
    指针的大小在32位平台是4个字节,在64位平台是8个字节

    2. 指针和指针类型  

    这里我们在讨论一下:指针的类型
    我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
    准确的说:有的。 这里我们给上一些不同类型的指针

    char  *pc = NULL;
    int  *pi = NULL;
    short *ps = NULL;
    long  *pl = NULL;
    float *pf = NULL;
    double *pd = NULL;

    char* 类型的指针是为了存放 char 类型变量的地址。
    short* 类型的指针是为了存放 short 类型变量的地址。
    int* 类型的指针是为了存放 int 类型变量的地址

    那指针类型的意义是什么?

    2.1 指针+-整数 

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

    总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

    2.2 指针的解引用 

    1. int main()
    2. {
    3. int n = 0x11223344;
    4. int* pi = &n;
    5. *pi = 0;
    6. char* pa = &n;
    7. *pa = 0;
    8. return 0;
    9. }

    1. int main()
    2. {
    3. int a = 10;
    4. int*pa = &a;
    5. char* pc = &a;
    6. printf("%p\n", pa);
    7. printf("%p\n", pc);
    8. printf("%p\n", pa+1);
    9. printf("%p\n", pc+1);
    10. return 0;
    11. }

    总结:
    指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
    比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

    3. 野指针  

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

    3.1 野指针成因 

    1. 指针未初始化

    这个指针指向的地址是不可知的,也许还不属于你。所以将20放进去不对。

    1. #include
    2. int main()
    3. {
    4. int *p;//局部变量指针未初始化,默认为随机值
    5.    *p = 20;
    6. return 0;
    7. }

     2. 指针越界访问

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

    3. 指针指向的空间释放
    这里放在动态内存开辟的时候讲解,这里可以简单提一嘴。

    1. int* test()
    2. {
    3. int a = 10;
    4. printf("%d\n", a);
    5. return &a;
    6. }
    7. int main()
    8. {
    9. int* p = test();
    10. *p = 100;
    11. return 0;
    12. }

    test函数调用后,虽然把a的地址交给了p,但是a这个变量的地址还给操作系统了,这样p指针指向的又是一片未知的区域了。

    3.2 如何规避野指针 

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

    1. #include
    2. int main()
    3. {
    4.   int *p = NULL;
    5.   //....
    6.   int a = 10;
    7.   p = &a;
    8.   if(p != NULL)
    9.   {
    10.     *p = 20;
    11.   }
    12.   return 0;
    13. }

    4. 指针运算

    4.1 指针+-整数

    1. #define N_VALUES 5
    2. float values[N_VALUES];
    3. float *vp;
    4. //指针+-整数;指针的关系运算
    5. for (vp = &values[0]; vp < &values[N_VALUES];)
    6. {
    7.   *vp++ = 0;
    8. }

    4.2 指针-指针

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

     

    指针-指针,前提是两个指针必须指向同一片空间,它们得到的值的绝对值是两个指针之间的元素个数。但是char ch [5],int arr[6] , &arr[3] - &ch[4] 这种写法是错误的.

    实现strlen函数,运用到的例子。

    1. int my_strlen(char* str)
    2. {
    3. char* start = str;
    4. while (*str)
    5. {
    6. str++;
    7. }
    8. return str - start;
    9. }
    10. int main()
    11. {
    12. char arr[] = "abcdef";
    13. int len = my_strlen(arr);
    14. printf("%d\n", len);
    15. return 0;
    16. }

    4.3 指针的关系运算

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

    代码简化, 这将代码修改如下:

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

    实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行

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

    5. 指针和数组

    数组名表示的是数组首元素的地址。 

    但是有2个例外:
    sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小
    &数组名,数组名表示整个数组,取出的是整个数组的地址 

    既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能

    1. int main()
    2. {
    3. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    4. // 0 1 2 3 4 5 6 7 8 9
    5. int* p = arr;
    6. int sz = sizeof(arr) / sizeof(arr[0]);
    7. int i = 0;
    8. for (i = 0; i < sz; i++)
    9. {
    10. printf("%d ", *(p + i));
    11. }
    12. //printf("%p\n", arr);//
    13. //printf("%p\n", &arr[0]);
    14. //printf("%d\n", sizeof(arr));
    15. return 0;
    16. }

    所以 p+i 其实计算的是数组 arr 下标为i的地址。那我们就可以直接通过指针来访问数组。

    6. 二级指针 

    指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
    这就是 二级指针。

    对于二级指针的运算有:
    *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .

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

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

    1. **ppa = 30;
    2. //等价于*pa = 30;
    3. //等价于a = 30;
    4. int arr1[5];
    5. char arr2[6];

    7. 指针数组

    指针数组是指针还是数组?
    答:是数组。是存放指针的数组 

    1. int main()
    2. {
    3. int data1[] = { 1,2,3,4,5 };
    4. int data2[] = { 2,3,4,5,6 };
    5. int data3[] = { 3,4,5,6,7 };
    6. //arr就是一个指针数组
    7. int* arr[3] = { data1 ,data2, data3 };
    8. int i = 0;
    9. for (i = 0; i < 3; i++)
    10. {
    11. int j = 0;
    12. for (j = 0; j < 5; j++)
    13. {
    14. printf("%d ", arr[i][j]);
    15. }
    16. printf("\n");
    17. }
    18. return 0;
    19. }

  • 相关阅读:
    MySQL 练习<3>
    老司机带你用python从另外一个角度看市场需求~
    xcode iOS 在app文件中开启访问 Document Directory
    计算机毕业设计(附源码)python智能仓储进出货管理系统
    GB28181,sdk,设备集成和平台测试
    RabbitMQ的常见工作模式
    虹科分享 | 冻干机搁板温度均匀性(分布验证)的测量方法
    沙盘游戏咨询感悟
    Latex IEEE模板导入中文问题
    好看的html网站维护源码
  • 原文地址:https://blog.csdn.net/m0_63562631/article/details/126161111