• C语言——指针初阶详解


     🐒博客名:平凡的小苏

     

    📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情

    目录

    1. 指针是什么

     2. 指针和指针类型

    2.1指针类型的第一个意义

     2.2指针类型的第二个意义

    3. 野指针

    3.1 野指针成因

    3.2 如何规避野指针

    4. 指针运算

    4.1 指针+-整数

    4.2 指针-指针

    4.3 指针的关系运算 

     5. 指针和数组

    6. 二级指针  

    7. 指针数组


    1. 指针是什么

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

    指针变量
    我们可以通过 & (取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个
    变量就是指针变量
    1. #include <stdio.h>
    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);
    那么 32 根地址线产生的地址就会是:
    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. 指针和指针类型

    我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
    准确的说:有的。
    我们给指针变量相应的类型:
    1. char  *pc = NULL;
    2. int   *pi = NULL;
    3. short *ps = NULL;
    4. long  *pl = NULL;
    5. float *pf = NULL;
    6. double *pd = NULL;

    2.1指针类型的第一个意义

    代码

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 0x11223344;
    5. int* pa = &a;
    6. *pa = 0;
    7. return 0;
    8. }

    我们来进行调试观看它的内存变化

     我们可以看到存放的的确是11223344

    当我们用整型int来存放a的地址的时候,这时候将他解引用将它修改为0时,修改的是四个字节所以都变为零 

    当我们改为char和short类型将会如何变化呢?

    1. //演示实例
    2. #include
    3. int main()
    4. {
    5. int n = 0x11223344;
    6. char *pc = (char *)&n;
    7. int *pi = &n;
    8. *pc = 0;   //重点在调试的过程中观察内存的变化。
    9. *pi = 0;   //重点在调试的过程中观察内存的变化。
    10. return 0;
    11. }

    我们可以看到char类型的指针为一个字节,只能修改一个字节的值 

    总结:

    指针类型决定了,指针解引用操作的时候,一次性访问几个字节,访问权限的大小

    如果是char*的指针解引用访问1个字节

    那么short*解引用则访问2个字节

     2.2指针类型的第二个意义

    代码演示:

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

    总结:

    指针类型决定指针的步长(指针+1跳过几个字节)

    字符指针类型+1,跳过1个字节

    整型指针+1,跳过4个字节

    当我们想要一个一个字节访问则可以用char类型的指针

    如果想要4个字节的访问,则用int类型的指针

    3. 野指针

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

    3.1 野指针成因

    1. 指针未初始化

    代码演示:

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

    这里编译器都不允许我们通过 

    2. 指针越界访问

    代码演示:

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

     

     这个时候就是越界访问了,访问的空间是不属于我们的

    例子:

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

     这里我们调用函数,函数里面讲a取地址返回,但这个做法是错误的,因为函数一旦返回,a的生命周期就结束了,a的空间就已经释放掉了,这时候我们进行解引用p的话就属于非法访问了。但是第一次打印结果是对的,是因为编译器做了保存,假如先打印一个hehe,再打印解引用p那么打印出来的值就不是10了。

    3.2 如何规避野指针

    1. 指针初始化
    2. 小心指针越界
    3. 指针指向空间释放,及时置 NULL
    4. 避免返回局部变量的地址
    5. 指针使用之前检查有效性
    代码演示:
    1. #include <stdio.h>
    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 my_strlen(char *s)
    2. {
    3.       char *p = s;
    4.       while(*p != '\0' )
    5.              p++;
    6.       return p-s;
    7. }

    注:

    指针减指针前提:两个指针要指向同一块空间

    指针减指针得到的是两个指针之间的元素个数

    4.3 指针的关系运算 

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

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

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

    这里当vp已经指向下标为0时,vp还需要继续向前走一位,相当于向前越界了,应该避免这种写法 

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

     标准规定:

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

     5. 指针和数组

    1.指针和数组时不同的对象

    指针是一种变量,是存放地址的,大小是4/8字节的

    数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决元素个数和元素的类型的

    2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针来访问数组

    我们看一个例子:

    1. #include
    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;
    8. }

    可见数组名和数组首元素的地址是一样的。
    结论: 数组名表示的是数组首元素的地址
    那么这样写代码是可行的:
    1. int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    2. int *p = arr;//p存放的是数组首元素的地址
    既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
    例如:
    1. #include <stdio.h>
    2. int main()
    3. {
    4.    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    5.    int *p = arr; //指针存放数组首元素的地址
    6.    int sz = sizeof(arr)/sizeof(arr[0]);
    7.    for(i=0; i<sz; i++)
    8.   {
    9.        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
    10.   }
    11.    return 0;
    12. }

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

    6. 二级指针  

    指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
    代码演示二级指针
    1. #include<stdio.h>
    2. int main()
    3. {
    4. int a = 10;
    5. int* p = &a;
    6. int** pa = &p;
    7. return 0;
    8. }

    、画图演示

     解读:首先解读指针,*pa表示是一个指针变量,用来存放a得地址,而a的类型是int,则*pa的前面是int。*ppa指的是指针变量,用来存放pa的地址,而pa的类型是int*,则*ppa的前面为int*。也就是二级指针,以此类推,可以三级指针,但是三级指针很少见。

    对于二级指针的运算有:
    *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;

    7. 指针数组

    指针数组是指针还是数组?
    答案:是数组。是存放指针的数组。
    数组我们已经知道整形数组,字符数组。
    代码举例:
    1. int main()
    2. {
    3. int a = 10;
    4. int b = 20;
    5. int c = 30;
    6. int d = 40;
    7. int e = 50;
    8. int* arr[5] = {&a, &b, &c, &d, &e};
    9. int i = 0;
    10. for (i = 0; i < 5; i++)
    11. {
    12. printf("%d ", *(arr[i]));
    13. }
    14. return 0;
    15. }

    解读:int*arr[5]是一个指针数组,存放的各个变量的地址,当我们需要遍历的时候,需要进行解引用,这里只为讲语法,用法很牵强! 并且该数组的的地址不一定连续

    写一个比较有意义的指针数组,利用一维数组来模拟二维数组

    1. #include<stdio.h>
    2. int main()
    3. {
    4. //假设我想模拟出一个34列的数组
    5. int a[] = { 1,2,3,4 };
    6. int b[] = { 2,3,4,5 };
    7. int c[] = { 3,4,5,6 };
    8. int* arr[3] = { a, b, c };
    9. int i = 0;
    10. for (i = 0; i < 3; i++)
    11. {
    12. int j = 0;
    13. for (j = 0; j < 4; j++)
    14. {
    15. printf("%d ", arr[i][j]);
    16. }
    17. printf("\n");
    18. }
    19. return 0;
    20. }

     

    解读:数组名表示数组首元素的地址,arr[i][j]可以写成*(arr[i]+j),意思是arr[i]找到数组a的首元素地址,然后加 j 找到 数组里的各个元素,因为是地址,所以需要进行解引用

     

  • 相关阅读:
    HTML旅游景点网页作业制作——旅游中国11个页面(HTML+CSS+JavaScript)
    适配器模式详解和实现(设计模式 四)
    php+vue3实现点选验证码
    LeetCode 剑指 Offer 10- I. 斐波那契数列
    springcloud3:支付模块和订单模块的编写
    RISC-V 编译环境搭建:riscv-gnu-toolchain 和 riscv-tools
    RabbitMQ可靠发布-SpringBoot
    航空发动机轴承数据集 | 写论文再也不用担心没数据集啦!
    STM32产品介绍
    SQL Server 技术100问?
  • 原文地址:https://blog.csdn.net/VHhhbb/article/details/127943366