美文网首页ndk
NDK系列02——指针的学习

NDK系列02——指针的学习

作者: JackDaddy | 来源:发表于2021-08-15 12:06 被阅读0次

        这是NDK系列的第二章,将会学习 内存地址与指针 相关的知识。主要分为以下几个内容:

    • C语言中的基本数据类型
    • C语言中的基本函数
    • 内存地址与指针
    • 数组与数组指针
    • 数组指针操作的几种方式

    一、C 中的基本数据类型

        学习一门新的语言都绕不过基本的数据类型,跟 Java 类似,Java 中有的基本数据类型,C 中也基本都有。下面用一个例子来说明:

    #include <stdio.h>
    
    int main() {
        printf("Hello, World!\n");
    
        int intNum = 100;
        float floatNum = 150;
        double doubleNum = 200;
        long longNum  = 3;
        char charNum = 'c';
    
        printf("intNum 的值为: %d\n", intNum);
        printf("floatNum 的值为: %f\n", floatNum);
        printf("doubleNum 的值为: %lf\n", doubleNum);
        printf("longNum 的值为: %ld\n", longNum);
        printf("charNum 的值为: %c\n", charNum);
    
        return 0;
    }
    

    以下是运行结果:

    Hello, World!
    intNum 的值为: 100
    floatNum 的值为: 150.000000
    doubleNum 的值为: 200.000000
    longNum 的值为: 3
    charNum 的值为: c
    Process finished with exit code 0
    

    以上列举出来的就是日常经常用的基本数据类型,跟在 Java 中的基本数据类型基本一致,较为特殊的是字符串,这点在后面的文章再慢慢介绍。现在我们只需知道:

    • 单个字符用 char 声明,同时用单引号 ' ' 包裹
    • 字符串时用 char* 声明,同时用双引号 " " 包裹
      同时从打印的日志中可以看到引用时变量时需要使用占位符,这一点也是跟 Java 不一样的点,不过我相信各位多敲几遍肯定就会熟悉了的。

    二、C语言中的基本函数

    2.1 函数使用方式:

        Java 中有一个个的方法相对应的 C 语言就是一个一个的 函数。在 Java 中 方法可以写在类中的任意地方,只要是在类里面即可。而 C 语言不同的是,C 语言的函数必须写在主函数之前,或者跟声明头文件类似,需提前声明才可以在主函数中调用。接下来用一个例子来说明一下:

    //第一种写法:
    
    #include <stdio.h>
    
    void sampleMethod() {
        printf("我是一个 C 函数,快来调用我吧");
    }
    
    int main() {
    
        sampleMethod();
    
        return 0;
    }
    =================华丽的分割线=======================
    // 第二种写法
    #include <stdio.h>
    void sampleMethod();
    
    int main() {
    
        sampleMethod();
    
        return 0;
    }
    
    void sampleMethod() {
        printf("我是一个 C 函数,快来调用我吧");
    }
    

    运行结果:

    我是一个 C 函数,快来调用我吧
    Process finished with exit code 0
    

    可以看到这两种方式都可以正确运行,都是 C 语言中使用函数的方式。

    2.2 函数的传参

    上面两个demo 中都是没有传参的函数,参考 Java 中的传参方法,C 语言中的传参函数也是在函数名的括号内进行传参。

    void sampleMethod(int a, int b) {
        printf("我是一个 C 函数,快来调用我吧");
    }
    

    三、内存地址与指针

    3.1 简介

        所谓内存地址指的就是一个对象在内存中开辟的地址,在 Java 的学习中,会经常遇到一个简单的需求:向一个方法中传入两个数,在方法中对这两个数进行进行交换。
    那么遇到这个需求我们的思路:

    • 1 定义一个临时变量
    • 2 将其中一个值赋值给临时变量
    • 3 将另一个值赋值给已赋值的变量
    • 4 最后将临时变量赋值给第二个值。
      看完可能有点懵,直接上代码就清楚了:
    //交换两个数
    public void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
    }
    

    那么在 C 语言中当我们也需要对两个数进行交换时,采用相同的方法,能否达到我们的要求呢?用一个demo来试验一下:

    #include <stdio.h>
    
    void testSwap(int a, int b) {
        printf("a 的值为:%d\n", a);
        printf("b 的值为:%d\n", b);
        int temp = a;
        a = b;
        b = temp;
    }
    
    int main() {
    
      testSwap(10, 20);
      printf("a 的值为:%d\n", a);
      printf("b 的值为:%d\n", b);
    
      return 0;
    }
    

    运行结果为:

    a 的值为:10
    b 的值为:20
    交换结束后 a 的值为:10
    交换结束后 b 的值为:20
    
    Process finished with exit code 0
    

    从运行结果可以看出,上面这种方法没有把 a 和 b 两个值进行交换,这是为什么呢?这就要请出今天的重头戏了——内存地址。
    上面已经说过内存地址是一个对象在内存中开辟的地址,在上面的 demo 中,传进去的只是 a 和 b 的值,而其内存地址对应的值并未发生改变。所以我们应该要传的是 a 和 b 对应的内存地址,通过内存地址交换对应的值,这才是正确做法:

    #include <stdio.h>
    
    void swap(int* a, int* b) {
        printf("a 的值为:%d\n", *a);
        printf("b 的值为:%d\n", *b);
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    
    int main() {
    
      int a = 10;
      int b = 20;
      swap(&a, &b);
      printf("a 的值为:%d\n", a);
      printf("b 的值为:%d\n", b);
    
      return 0;
    }
    

    运行结果为:

    a 的值为:10
    b 的值为:20
    交换结束后 a 的值为:20
    交换结束后 b 的值为:10
    
    Process finished with exit code 0
    

    从上面的 demo 中可以看到 a 与 b 已经成功进行交换。因此,有如下结论:

    • 在函数传参中的类型后面加 * 代表该参数传入的是一个地址
    • 通过 & 符号获取对象其内存地址
    • 通过 * 获取该内存地址对应的值
    3.2多级指针

        了解完指针与内存地址,我们来学习多一个概念 多级指针,上面我们看到的一个对象对应一个内存地址,那么内存地址又对应有一个内存地址(听起来好像套娃)用一张图来表示就是:

    多级指针
    在 C 语言中,所谓的多级指针最多只有3级,用下面的demo来说明一下:
    int main() {
        /**
         * 多级指针
         */
        int num = 999;
        int *num_p = &num;
        printf("num 的值为:%d\n", num);
        printf("num 的地址为:%p\n", &num);
        printf("num_p 的值为:%p\n", num_p);
        printf("num 地址对应的值为:%d\n", *num_p);
        //二级指针
        int **num_pp = &num_p;
        printf("num_pp (二级指针)的值为:%p\n", num_pp);
        //三级指针
        int *** num_ppp = &num_pp;
        printf("num_ppp (三级指针)的值为:%p\n", num_ppp);
    
        return 0;
    }
    

    打印结果为:

    num 的值为:999
    num 的地址为:000000000061FE00
    num_p 的值为:000000000061FE00
    num 地址对应的值为:999
    num_pp (二级指针)的值为:000000000061FDF8
    num_ppp (三级指针)的值为:000000000061FDF0
    

    可以看到如果是二级指针时,则在 变量前 加多一个 * 。
    前面说过可以根据地址拿到对应地址的值。那么对于多级指针类似地也可以得到对应的值:

    int **back = *num_ppp;
    printf("二级指针num_pp 的值为:%p\n", back);
    
    int *back3 = **num_ppp;
    printf("num_p 的值为:%p\n", back3);
    
    int back4 = ***num_ppp;
    printf("num 的值为:%d\n", back4);
    

    打印的结果为:

    二级指针num_pp 的值为:000000000061FDF8
    num_p 的值为:000000000061FE00
    num 的值为:999
    

    可以看出,与上面的多级取址后的值是一致的,因此当:

    1. 需要拿二级指针时需要对三级指针取一次地址,用 ** 接收:int **back = *num_ppp;
    2. 需要拿一级指针时需要对三级指针取二次地址,用 * 接收:int *back3 = **num_ppp;
    3. 需要拿一级指针对应的值时需要对三级指针取三次地址:int back4 = ***num_ppp;

    四、数组与数组指针

    4.1数组 & 数组指针

        在 C 语言中,可以认为 数组就是指针,指针就是数组,去阅读一些外国大佬写的源码可以发现,通常用指针去声明个数组。下面用个简单的demo来说明:

    int main() {
    
        /**
         * 数组与数组指针
         */
    
        int arr[] = {1, 2, 3, 4};
        printf("arr 的值为:%p\n", arr);
        printf("arr地址 的值为:%p\n", &arr);
        printf("arr首元素的地址 的值为:%p\n", &arr[0]);
    
        /**
         * 所以可以看出 数组 == 内存地址
         */
        int *arr_p = arr;
        printf("arr_p 的值为:%p\n", arr_p);
        //数组第一个元素
        printf("arr_p 对应的值为:%d\n", *arr_p);
    
    return 0;
    }
    

    打印的日志为:

    arr 的值为:000000000061FDE0
    arr地址 的值为:000000000061FDE0
    arr首元素的地址 的值为:000000000061FDE0
    arr_p 的值为:000000000061FDE0
    arr_p 对应的值为:1
    

    因此作为初学者,我们可以简单的认为:

    • 数组的值 = 数组的地址 = 数组首元素的地址
    • 由于可以用指针去接收这个数组,因此 指针 = 内存地址
    4.2 指针移动

    那么既然可以用指针去接收这个数组,那么当指针挪动时,获取到的值也就是这个数组相对应的值:

         //指针移动
        arr_p++;
        //数组第二个元素
        printf("arr_p 对应的值为:%d\n", *arr_p);
        //指针挪动2
        arr_p += 2;//挪动指针指向4
        printf("arr_p 对应的值为:%d\n", *arr_p);
        //挪动指针指向1
        arr_p -= 3;
        printf("arr_p 对应的值为:%d\n", *arr_p);
    

    打印的结果为:

    arr_p 对应的值为:2
    arr_p 对应的值为:4
    arr_p 对应的值为:1
    

    在 Java 中对于这个数组的长度我们都知道是4,那在 C 语言中这个数组的长度是多少呢?在C语言中,想要获取 数组的长度需要用到 sizeof 这个 api,这里我们打印一下 上面的数组(arr 以及 int 类型)的长度是多少:

        printf("sizeof arr 的值为:%llu\n", sizeof arr);
        printf("sizeof int 的值为:%llu\n", sizeof(int));
    

    打印的结果为:

    sizeof arr 的值为:16
    sizeof int 的值为:4
    

    从打印的日志可以看到 arr 数组的长度为 16,int 类型的长度为 4。 这是为什么呢?这是因为 arr 是个int 类型的数组,因此 长度是 arr 长度 = arr数组 长度 x 类型长度 = 4 x 4 = 16。

    4.3 数组 & 遍历数组

    那么如何通过指针遍历数组呢?我们来看下面这个demo:

    /**
         * 通过指针遍历数组
         */
        //其他平台较严格
        // int i = 0;
        for (int i = 0; i < sizeof arr / sizeof(int); ++i) {
            printf("数组下标为 %d 的值为:%d\n", i, *(arr + i));
            /**
             * 从这里可以看到由于是 int 型数组,int 的内存大小为4.因此地址都是挪动4个位置
             *
             */
            printf("数组下标为 %d 的地址为:%p\n", i, (arr + i));
        }
    

    注意这里,其他编译器(visiion studio)比较严格的话,不能同上面代码一样,需要把 int i = 0; 这一句单独写出来。
    打印的结果为:

    数组下标为 0 的值为:1
    数组下标为 0 的地址为:000000000061FDE0
    数组下标为 1 的值为:2
    数组下标为 1 的地址为:000000000061FDE4
    数组下标为 2 的值为:3
    数组下标为 2 的地址为:000000000061FDE8
    数组下标为 3 的值为:4
    数组下标为 3 的地址为:000000000061FDEC
    
    4.4 循环给数组赋值
        for (int i = 0; i < sizeof arr / sizeof(int); ++i) {
    //        * &(arr[i]) = i+100;
    //        arr[i] = i+100
            *(arr_p + i) = (i + 200);
            printf("数组下标为 %d 的值重新赋值为:%d\n", i, arr[i]);
        }
    

    打印的日志为:

    数组下标为 0 的值重新赋值为:200
    数组下标为 1 的值重新赋值为:201
    数组下标为 2 的值重新赋值为:202
    数组下标为 3 的值重新赋值为:203
    

    在这个demo中注释的两行也是可以实现重新赋值的方式,在上面三种方式任选其一都是可以的。

    相关文章

      网友评论

        本文标题:NDK系列02——指针的学习

        本文链接:https://www.haomeiwen.com/subject/hgobmltx.html