NDK系列02——指针的学习


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

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

一、C 中的基本数据类型

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

#include 

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 

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

int main() {

    sampleMethod();

    return 0;
}
=================华丽的分割线=======================
// 第二种写法
#include 
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 

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 

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 = #
    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——指针的学习)