深析C语言的灵魂 -- 指针(1)

简介: 深析C语言的灵魂 -- 指针(1)

一、指针基础知识

在开始我们指针的进阶内容之前,我们先来回顾一下与指针相关的基础知识:

1、什么是指针

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

2、指针变量的大小


    在32位的机器上,地址由32个0/1组成二进制序列组成,所以地址需要用4个字节的空间来存储,则一个指针变量的大小就应该是4个字节。

在64位机器上,地址由64个0/1组成二进制序列组成,所以地址需要用8个字节的空间来存储,则一个指针变量的大小就应该是8个字节。

总结:在 X86 (32位平台) 环境下,一个指针变量的大小 (一个地址的大小) 是四个字节;在 X64 (64位平台) 环境下,一个指针变量的大小 (一个地址的大小) 是八个字节

3、指针类型的意义

    1. 指针的类型决定了指针进行解引用操作时的访问权限 (解引用时能向后访问几个字节的空间) ;
    2. 指针的类型决定了指针 ± 整数时候的步长 (+1跳过几个字节) ;

    4、指针的运算

    1. 指针 ± 整数:指针移动整数个元素的大小;
    2. 指针 - 指针:得到指针之间元素的个数;
    3. 指针的关系运算:比较两个地址的大小;

    5、野指针的成因及规避方法

    野指针的成因

    1. 指针未初始化;
    2. 指针越界访问;
    3. 指针指向的空间被释放;

    野指针的规避方法

    1. 使用已初始化的指针;
    2. 小心指针越界;
    3. 当指针指向的空间被释放的同时把该指针置为 NULL;
    4. 避免返回局部变量的地址 (离开该变量的生命周期该变量就会被销毁);
    5. 指针使用之前检查其有效性;

    二、指针进阶知识

    1、字符指针

    什么是字符指针

    顾名思义,字符指针就是用来存放字符地址的指针。

    字符指针的两种使用方法

    第一种:

    int main()
    {
        char ch = 'w';
        char *pc = &ch;
        *pc = 'w';
        return 0;
    }

    第二种:

    int main()
    {
        const char* pstr = "hello world";  //这里是把一个字符串放到pstr指针变量里了吗?
        printf("%s\n", pstr);
        return 0;
    }

    第一种使用方法很简单,这里我不再赘述;难点是第二种使用方法:在第二个例子中,我们并不是把 “hello world” 这整个字符串放到 pstr 指针变量中,而且 pstr 是指针变量,只能存放四个字节的内容,也存不下这整个字符串;


    其实我们是把 “hello world” 这个字符串中首字符的地址,即 ‘h’ 的地址放入 pstr 中,然后我们可以以 %s 的形式把整个字符串打印出来;

    2020062310470442.png

    20200623104134875.png

    同时,“hello world” 这样的字符串被我们称为常量字符串,它是存储在字符常量区的,我们可以通过 pstr 来访问它,但是不能修改它的内容 (因为它是常量),所以这里我们用 const 关键字来修饰 char*,防止有人误该 “hello world” 中的内容。

    2020062310470442.png

    笔试题练习

    下面程序的输出结果是什么?

    int main()
    {
      char str1[] = "hello world";
      char str2[] = "hello world";
      const char* str3 = "hello world";
      const char* str4 = "hello world";
      if (str1 == str2)
        printf("str1 and str2 are same\n");
      else
        printf("str1 and str2 are not same\n");
      if (str3 == str4)
        printf("str3 and str4 are same\n");
      else
        printf("str3 and str4 are not same\n");
      return 0;
    }

    2020062310470442.png

    解析

    str1 和 str2 是数组,数组空间在栈区上开辟,所以操作系统会给 str1 和 str2 分别分配一块空间,并把空间里的内容初始化为 “hello world”,同时数组名代表首元素地址,所以 str1 != str2;


    对于 str3 和 str4 来说,由于 “hello world” 存放在字符常量区,所以 “hello world” 只会存在一份,只需要让它们同时指向 “hello world” 的空间即可,所以 str3 和 str4 其实存放的都是字符常量区中 “hello world” 中 字符 ‘h’ 的地址,所以 str3 == str4;

    2、指针数组

    指针数组是什么

    顾名思义,指针数组是一个数组,而且是用来存放指针变量的数组;所以指针数组就是存放指针的数组。

    指针数组的定义

    int* arr[10];
    # arr的类型:int* [10]   //去掉变量名剩下的就是变量类型
    # arr先和[10]结合,表示arr是一个数组,数组里面有10个元素,每个元素的类型是int*
    char* str[10];
    # str的类型 char* [10]   //去掉变量名剩下的就是变量类型
    # str先和[10]结合,表示str是一个数组,数组里面有10个元素,每个元素的类型是char*

    指针数组的使用

    int main()
    {
      int a = 10;
      int b = 20;
      int c = 30;
      int* arr[3] = { &a, &b, &c };
      int i = 0;
      for (i = 0; i < 3; i++)
      {
        *(arr[i]) = i;
      }
      printf("%d %d %d\n", a, b, c);
      return 0;
    }

    2020062310470442.png

    3、数组指针

    指针数组是什么

    顾名思义,数组指针就是一个指针,这个指针指向的是一个数组;所以数组指针就是指向数组的指针。

    数组指针的定义

    int (*arr)[10];
    # arr的类型:int (*)[10]  //去掉变量名剩下的就是变量类型
    # arr首先和*结合,表示它是一个指针,然后和[10]结合,表示它指向的是一个数组,数组里面有10个元素,每个元素的类型是int;
    char* (*arr)[10];
    # arr的类型:int* (*)[10]  //去掉变量名剩下的就是变量类型
    # arr首先和*结合,表示它是一个指针,然后和[10]结合,表示它指向的是一个数组,数组里面有10个元素,每个元素的类型是char*;

    数组名和&数组名的区别

    #include <stdio.h>
    int main()
    {
      int arr[10] = { 0 };
      printf("arr = %p\n", arr);
      printf("&arr= %p\n", &arr);
      printf("arr+1 = %p\n", arr + 1);
      printf("&arr+1= %p\n", &arr + 1);
      return 0;
    }

    2020062310470442.png

    如上所示:数组名和&数组名所得到的地址的起始位置是相同的,但是数组名加1跳过的是一个整形,即4个字节;而&数组名加1跳过的是一个数组,即40个字节;

    所以:数组名表示首元素的地址,+1 跳过一个数组元素;而&数组名表表示整个数组的地址,+1 跳过整个数组。

    数组指针的使用

    int main()
    {
      int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
      int(*p)[10] = &arr;  //把整个数组的地址赋值给数组指针变量p
      int i = 0;
      for (i = 0; i < 10; i++)
      {
        //*p找到整个数组,而数组名代表整个数组,所以*p相当于得到数组名,
        //而数组名又表示首元素的地址,所以*p最终的效果是得到数组首元素的地址
        //首元素的地址 +i 再解引用得到数组的各个元素
        printf("%d ", *(*p) + i);  
      }
      return 0;
    }

    2020062310470442.png

    虽然上面的使用是正确的,但是我们通常不这样用,因为我们可以直接用 arr[i] 来得到数组的每个元素;数组指针通常用于二维数组:

    void print_arr(int(*arr)[5], int row, int col)
    {
        int i = 0;
        int j = 0;
        for (i = 0; i < row; i++)
        {
            for (j = 0; j < col; j++)
            {
                //arr[i] 相当于 *(arr+i)
                //所以 arr[i][j] 相当于 *(*(arr+i)+j)
                //arr[i] 找到二维数组具体的某一行,而行号代表那一行,同时行号又表示那一行首元素的地址
                //所以 arr[i][j] 就可以找到二维数组中具体某一行的具体某一个元素
                printf("%d ", arr[i][j]);
            }
            printf("\n");
        }
    }
    int main()
    {
        int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
        //数组名arr,表示首元素的地址
        //但是二维数组的首元素是二维数组的第一行
        //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
        //一维数组的地址用数组指针来接收
        print_arr(arr, 3, 5);
        return 0;
    }

    2020062310470442.png

    练习题

    下面的代码分别表示什么意思?

    int arr[5];
    # arr和[5]结合,表示arr是一个数组,数组里面有5个元素,每个元素的类型是int;所以这里表示正常的一维整形数组;
    int *parr1[10];
    # parr1和[10]结合,表示parr1是一个数组,数组里面有10个元素,每个元素的类型是int*;所以这里表示指针数组;
    int (*parr2)[10];
    # parr2首先和*结合,表示它是一个指针,然后和[10]结合,表示它指向一个数组,数组里面有10个元素,每个元素的类型是int;所以这里表示数组指针;
    int (*parr3[10])[5];
    # parr3和[10]结合,表示这是一个数组,数组里面有10个元素,每个元素的类型是int (*)[5];所以这里是存放数组指针的数组;



    相关文章
    |
    25天前
    |
    C语言
    【c语言】指针就该这么学(1)
    本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
    45 0
    |
    24天前
    |
    C语言
    【c语言】指针就该这么学(3)
    本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
    15 2
    |
    25天前
    |
    C语言
    如何避免 C 语言中的野指针问题?
    在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
    |
    25天前
    |
    C语言
    C语言:哪些情况下会出现野指针
    C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
    |
    1月前
    |
    存储 C语言
    C语言32位或64位平台下指针的大小
    在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
    |
    30天前
    |
    存储 算法 C语言
    C语言:什么是指针数组,它有什么用
    指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
    |
    1月前
    |
    存储 C语言
    C语言指针与指针变量的区别指针
    指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
    |
    1月前
    |
    C语言
    C语言指针(3)
    C语言指针(3)
    11 1
    |
    1月前
    |
    C语言
    C语言指针(2)
    C语言指针(2)
    13 1
    |
    24天前
    |
    编译器 C语言
    【c语言】指针就该这么学(2)
    本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
    17 0