这是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
可以看出,与上面的多级取址后的值是一致的,因此当:
- 需要拿二级指针时需要对三级指针取一次地址,用 ** 接收:int **back = *num_ppp;
- 需要拿一级指针时需要对三级指针取二次地址,用 * 接收:int *back3 = **num_ppp;
- 需要拿一级指针对应的值时需要对三级指针取三次地址: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中注释的两行也是可以实现重新赋值的方式,在上面三种方式任选其一都是可以的。