• C语言 —— 指针


    目录

    1. 指针是什么?

    2. 指针和指针类型的关系

    2.1 指针的解引用

    2.2 指针+-整数

    3. 野指针

    3.1 野指针成因

    1. 指针未初始化

    2. 指针越界访问

    3. 指针指向的空间释放

    3.2 如何规避野指针

    4. 指针运算

    4.1 指针+-整数

    4.2 指针-指针

    指针-指针的使用

    4.3 指针的关系运算

    5. 指针和数组

    6. 二级指针

    7. 指针数组


    1. 指针是什么?

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

    总结:指针就是地址,口语中说的指针通常指的是指针变量。


    我们在初识C语言中知道, 内存是一块很大的空间, 这么大的内存空间如何管理呢?

    其实我们是把内存切割成一个个的内存单元, 一个内存单元大小是1byte.

    对于要点的理解:

    • 第一点

    • 第二点

    当写下以下代码时:

    int a = 10;//a是整型变量,占用4个字节的内存空间

    意味着要在内存中找4个空间存放a.


    指针=指针变量, 指针变量中存放地址, 通过这个地址可以找到一个内存单元.

    总结:

    • 指针变量是用来存放地址的,地址是唯一标示一块地址空间的。
    • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

    注意下面所使用的环境是x86的环境, 也就是32位的环境. (如果改成x64就是64位的环境)

    我们来看一下指针的大小.

    1. int main()
    2. {
    3. char* pc = NULL;
    4. short* ps = NULL;
    5. int* pi = NULL;
    6. double* pd = NULL;
    7. //sizeof 返回的值的类型是无符号整型 unsigned int
    8. printf("%zu\n", sizeof(pc));
    9. printf("%zu\n", sizeof(ps));
    10. printf("%zu\n", sizeof(pi));
    11. printf("%zu\n", sizeof(pd));
    12. return 0;
    13. }

    执行结果:

    可以看到, 不同类型指针的大小都是4个字节.

    既然这样, 不管是创建什么类型的指针, 在32位平台是4个字节,在64位平台是8个字节, 那么为什么还要区分不同类型的指针出来? 而不是直接使用一个统一的指针类型?

    C语言并没有使用统一的指针类型, 说明每一种类型的指针都是有意义的, 并不多余.

    接下来我们来探讨指针类型的意义.

    2. 指针和指针类型的关系

    2.1 指针的解引用

    我们来看这段代码的效果:

    1. int a = 0x11223344;
    2. int* pa = &a;
    3. *pa = 0;

    通过调试可以看到:

    如果把代码改为:

    1. int a = 0x11223344;
    2. char* pc = (char*)&a;//a原本应该是int*, 而非要赋给char*, 此时进行强转
    3. *pc = 0;

    pc是否有能力存下a的地址? 有, pc也是指针变量, 在x86环境下的大小也为4个字节.

    我们来调试查看:

    所以可以看到, a的地址是可以存到pc中.

    但是, 使用的时候会有差异, 即*pc.

    注: 上图为重新调试的截图, 所以会和前面的图有地址差异.

    可以看到, *pc所修改的内容只有一个字节, 因为pc的类型是char*指针, 所以解引用时只访问了一个字节.

    而前面使用int*pc是访问了4个字节.


    综上, 我们有以下结论.

    指针类型决定了指针在被解引用的时候访问几个字节
    如果是int*的指针,解引用访问4个字节
    如果是char*的指针,解引用访问1个字节
    (还可以推广到其他类型)
    这便是指针类型的第一个意义.再来看第二个意义.

    2.2 指针+-整数

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

    执行结果:

    所以我们得出以下结论:

    指针的类型决定了指针+-1操作的时候,跳过几个字节, 它决定了指针的步长.

    3. 野指针

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

    3.1 野指针成因

    1. 指针未初始化

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

    2. 指针越界访问

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

    3. 指针指向的空间释放

    1. int* test()
    2. {
    3. int a = 10;
    4. return &a;
    5. }
    6. int main()
    7. {
    8. int*p = test();
    9. printf("haha\n");
    10. printf("abcdef\n");
    11. if (p != NULL)
    12. {
    13. printf("%d\n", *p);
    14. }
    15. return 0;
    16. }

    3.2 如何规避野指针

    1. 明确的给指针初始化

    2. 小心指针越界

    3. 指针指向空间释放即使置NULL

    4. 避免返回局部变量的地址

    5. 指针使用之前检查有效性

    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 arr[10] = { 0 };
    2. printf("%d\n", &arr[0] - &arr[9]);//9

    注意: 不是所有的指针都能相减, 指向同一块空间的2个指针才能相减.

    下面写法是错误的:

    1. int arr[10] = { 0 };
    2. char ch[5] = {0};
    3. printf("%d\n", &ch[0] - &arr[5]);//err

    指针-指针的使用

    使用指针-指针的方式求字符串长度.

    显然的, 只需要知道第一个字符的地址和\0的地址就可以知道字符串长度, 两个指针相减即可.
    1. #include
    2. #include
    3. int my_strlen(char* str)
    4. {
    5. char* start = str;
    6. while (*str != '\0')
    7. {
    8. str++;
    9. }
    10. return (str - start);
    11. }
    12. int main()
    13. {
    14. int len = my_strlen("abcdef");
    15. printf("%d\n", len);
    16. return 0;
    17. }

    4.3 指针的关系运算

    1. #define N_VALUES 5
    2. float values[N_VALUES];
    3. float *vp;
    4. for(vp = &values[N_VALUES]; vp > &values[0];)
    5. {
    6. *--vp = 0;
    7. }

    代码简化:

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

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

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

    5. 指针和数组

    我们知道:

    数组:一组相同类型元素的集合
    指针变量:是一个变量,存放的是地址
    数组名表示的是数组首元素的地址(两种情况除外)
    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("%d ", *(p + i));
    13. }
    14. for (i = 0; i < sz; i++)
    15. {
    16. printf("%p ----- %p\n", &arr[i], p + i);
    17. }
    18. return 0;
    19. }

    6. 二级指针

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. int* pa = &a;//pa是一个指针变量,一级指针变量
    6. //*pa = 20;
    7. printf("%d\n", a);
    8. //pa是变量, 它在内存中也会有自己的内存空间, 那么就能&pa.
    9. int** ppa = &pa;//ppa是一个二级指针变量
    10. **ppa = 20;
    11. return 0;
    12. }
    一级指针变量就是如果通过pa找a的话, 找一次就可以了.

    二级指针变量是用来存放级指针变量的地址的.

    7. 指针数组

    指针数组是数组, 是存放指针的数组。

    我们知道, 创建比较多的相同类型的变量可以使用数组, 那么假设对于这些变量都有对应的指针去存放它们的地址, 是否也能用类似的方式, 去创建一个类型都为指针的数组? 显然是可以的, 也就是说C语言是有这种语法存在的.

    1. int a = 10;
    2. int b = 20;
    3. int c = 30;
    4. int* pa = &a;
    5. int* pb = &b;
    6. int* pc = &c;
    1. int arr[10];
    2. int* parr[10] = {&a, &b, &c};
    1. int i = 0;
    2. for (i = 0; i < 3; i++)//0 1 2
    3. {
    4. printf("%d ", *(parr[i]));
    5. }

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

  • 相关阅读:
    FPGA之旅设计99例之第十三例-----FPGA在OLED上显示DHT11数据
    linux系统iptables的操作
    docker的联合文件系统(UnionFS)
    创业路上前行
    Open Winding-PMSM-开绕组永磁同步电机基本介绍
    【ChatGPT】ChatGPT发展历史
    方法引用第四版
    JS------DAY2
    做斗音都要经历的几个时期,你目前处于哪个阶段呢?
    在SpringBoot下,tomcat的运行模式:BIO、NIO、APR
  • 原文地址:https://blog.csdn.net/YeZh1Yong_Cr/article/details/133838270