深析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];所以这里是存放数组指针的数组;



    相关文章
    |
    8天前
    |
    存储 C语言
    【C语言基础】一篇文章搞懂指针的基本使用
    本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
    |
    11天前
    |
    存储 人工智能 C语言
    C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
    本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
    |
    17天前
    |
    存储 安全 C语言
    C语言 二级指针应用场景
    本文介绍了二级指针在 C 语言中的应用,
    |
    1月前
    |
    存储 编译器 C语言
    【C语言篇】深入理解指针2
    代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
    |
    1月前
    |
    存储 程序员 编译器
    【C语言篇】深入理解指针1
    assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
    |
    1月前
    |
    存储 搜索推荐 C语言
    C语言中的指针函数:深入探索与应用
    C语言中的指针函数:深入探索与应用
    |
    1月前
    |
    C语言
    【C语言】指针速览
    【C语言】指针速览
    16 0
    |
    1月前
    |
    C语言
    【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
    【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
    |
    1月前
    |
    C语言
    【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
    【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
    |
    3月前
    |
    C语言
    指针进阶(C语言终)
    指针进阶(C语言终)