
????這是NDK系列的第二章,將會(huì)學(xué)習(xí) 內(nèi)存地址與指針 相關(guān)的知識(shí)。主要分為以下幾個(gè)內(nèi)容:
- C語(yǔ)言中的基本數(shù)據(jù)類(lèi)型
- C語(yǔ)言中的基本函數(shù)
- 內(nèi)存地址與指針
- 數(shù)組與數(shù)組指針
- 數(shù)組指針操作的幾種方式
一、C 中的基本數(shù)據(jù)類(lèi)型
????學(xué)習(xí)一門(mén)新的語(yǔ)言都繞不過(guò)基本的數(shù)據(jù)類(lèi)型,跟 Java 類(lèi)似,Java 中有的基本數(shù)據(jù)類(lèi)型,C 中也基本都有。下面用一個(gè)例子來(lái)說(shuō)明:
#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;
}
以下是運(yùn)行結(jié)果:
Hello, World!
intNum 的值為: 100
floatNum 的值為: 150.000000
doubleNum 的值為: 200.000000
longNum 的值為: 3
charNum 的值為: c
Process finished with exit code 0
以上列舉出來(lái)的就是日常經(jīng)常用的基本數(shù)據(jù)類(lèi)型,跟在 Java 中的基本數(shù)據(jù)類(lèi)型基本一致,較為特殊的是字符串,這點(diǎn)在后面的文章再慢慢介紹。現(xiàn)在我們只需知道:
- 單個(gè)字符用 char 聲明,同時(shí)用單引號(hào) ' ' 包裹
- 字符串時(shí)用 char* 聲明,同時(shí)用雙引號(hào) " " 包裹
同時(shí)從打印的日志中可以看到引用時(shí)變量時(shí)需要使用占位符,這一點(diǎn)也是跟 Java 不一樣的點(diǎn),不過(guò)我相信各位多敲幾遍肯定就會(huì)熟悉了的。
二、C語(yǔ)言中的基本函數(shù)
2.1 函數(shù)使用方式:
????Java 中有一個(gè)個(gè)的方法相對(duì)應(yīng)的 C 語(yǔ)言就是一個(gè)一個(gè)的 函數(shù)。在 Java 中 方法可以寫(xiě)在類(lèi)中的任意地方,只要是在類(lèi)里面即可。而 C 語(yǔ)言不同的是,C 語(yǔ)言的函數(shù)必須寫(xiě)在主函數(shù)之前,或者跟聲明頭文件類(lèi)似,需提前聲明才可以在主函數(shù)中調(diào)用。接下來(lái)用一個(gè)例子來(lái)說(shuō)明一下:
//第一種寫(xiě)法:
#include <stdio.h>
void sampleMethod() {
printf("我是一個(gè) C 函數(shù),快來(lái)調(diào)用我吧");
}
int main() {
sampleMethod();
return 0;
}
=================華麗的分割線=======================
// 第二種寫(xiě)法
#include <stdio.h>
void sampleMethod();
int main() {
sampleMethod();
return 0;
}
void sampleMethod() {
printf("我是一個(gè) C 函數(shù),快來(lái)調(diào)用我吧");
}
運(yùn)行結(jié)果:
我是一個(gè) C 函數(shù),快來(lái)調(diào)用我吧
Process finished with exit code 0
可以看到這兩種方式都可以正確運(yùn)行,都是 C 語(yǔ)言中使用函數(shù)的方式。
2.2 函數(shù)的傳參
上面兩個(gè)demo 中都是沒(méi)有傳參的函數(shù),參考 Java 中的傳參方法,C 語(yǔ)言中的傳參函數(shù)也是在函數(shù)名的括號(hào)內(nèi)進(jìn)行傳參。
void sampleMethod(int a, int b) {
printf("我是一個(gè) C 函數(shù),快來(lái)調(diào)用我吧");
}
三、內(nèi)存地址與指針
3.1 簡(jiǎn)介
????所謂內(nèi)存地址指的就是一個(gè)對(duì)象在內(nèi)存中開(kāi)辟的地址,在 Java 的學(xué)習(xí)中,會(huì)經(jīng)常遇到一個(gè)簡(jiǎn)單的需求:向一個(gè)方法中傳入兩個(gè)數(shù),在方法中對(duì)這兩個(gè)數(shù)進(jìn)行進(jìn)行交換。
那么遇到這個(gè)需求我們的思路:
- 1 定義一個(gè)臨時(shí)變量
- 2 將其中一個(gè)值賦值給臨時(shí)變量
- 3 將另一個(gè)值賦值給已賦值的變量
- 4 最后將臨時(shí)變量賦值給第二個(gè)值。
看完可能有點(diǎn)懵,直接上代碼就清楚了:
//交換兩個(gè)數(shù)
public void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
那么在 C 語(yǔ)言中當(dāng)我們也需要對(duì)兩個(gè)數(shù)進(jìn)行交換時(shí),采用相同的方法,能否達(dá)到我們的要求呢?用一個(gè)demo來(lái)試驗(yàn)一下:
#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;
}
運(yùn)行結(jié)果為:
a 的值為:10
b 的值為:20
交換結(jié)束后 a 的值為:10
交換結(jié)束后 b 的值為:20
Process finished with exit code 0
從運(yùn)行結(jié)果可以看出,上面這種方法沒(méi)有把 a 和 b 兩個(gè)值進(jìn)行交換,這是為什么呢?這就要請(qǐng)出今天的重頭戲了——內(nèi)存地址。
上面已經(jīng)說(shuō)過(guò)內(nèi)存地址是一個(gè)對(duì)象在內(nèi)存中開(kāi)辟的地址,在上面的 demo 中,傳進(jìn)去的只是 a 和 b 的值,而其內(nèi)存地址對(duì)應(yīng)的值并未發(fā)生改變。所以我們應(yīng)該要傳的是 a 和 b 對(duì)應(yīng)的內(nèi)存地址,通過(guò)內(nèi)存地址交換對(duì)應(yīng)的值,這才是正確做法:
#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;
}
運(yùn)行結(jié)果為:
a 的值為:10
b 的值為:20
交換結(jié)束后 a 的值為:20
交換結(jié)束后 b 的值為:10
Process finished with exit code 0
從上面的 demo 中可以看到 a 與 b 已經(jīng)成功進(jìn)行交換。因此,有如下結(jié)論:
- 在函數(shù)傳參中的類(lèi)型后面加 * 代表該參數(shù)傳入的是一個(gè)地址
- 通過(guò) & 符號(hào)獲取對(duì)象其內(nèi)存地址
- 通過(guò) * 獲取該內(nèi)存地址對(duì)應(yīng)的值
3.2多級(jí)指針
????了解完指針與內(nèi)存地址,我們來(lái)學(xué)習(xí)多一個(gè)概念 多級(jí)指針,上面我們看到的一個(gè)對(duì)象對(duì)應(yīng)一個(gè)內(nèi)存地址,那么內(nèi)存地址又對(duì)應(yīng)有一個(gè)內(nèi)存地址(聽(tīng)起來(lái)好像套娃)用一張圖來(lái)表示就是:

在 C 語(yǔ)言中,所謂的多級(jí)指針最多只有3級(jí),用下面的demo來(lái)說(shuō)明一下:
int main() {
/**
* 多級(jí)指針
*/
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 地址對(duì)應(yīng)的值為:%d\n", *num_p);
//二級(jí)指針
int **num_pp = &num_p;
printf("num_pp (二級(jí)指針)的值為:%p\n", num_pp);
//三級(jí)指針
int *** num_ppp = &num_pp;
printf("num_ppp (三級(jí)指針)的值為:%p\n", num_ppp);
return 0;
}
打印結(jié)果為:
num 的值為:999
num 的地址為:000000000061FE00
num_p 的值為:000000000061FE00
num 地址對(duì)應(yīng)的值為:999
num_pp (二級(jí)指針)的值為:000000000061FDF8
num_ppp (三級(jí)指針)的值為:000000000061FDF0
可以看到如果是二級(jí)指針時(shí),則在 變量前 加多一個(gè) * 。
前面說(shuō)過(guò)可以根據(jù)地址拿到對(duì)應(yīng)地址的值。那么對(duì)于多級(jí)指針類(lèi)似地也可以得到對(duì)應(yīng)的值:
int **back = *num_ppp;
printf("二級(jí)指針num_pp 的值為:%p\n", back);
int *back3 = **num_ppp;
printf("num_p 的值為:%p\n", back3);
int back4 = ***num_ppp;
printf("num 的值為:%d\n", back4);
打印的結(jié)果為:
二級(jí)指針num_pp 的值為:000000000061FDF8
num_p 的值為:000000000061FE00
num 的值為:999
可以看出,與上面的多級(jí)取址后的值是一致的,因此當(dāng):
- 需要拿二級(jí)指針時(shí)需要對(duì)三級(jí)指針取一次地址,用 ** 接收:int **back = *num_ppp;
- 需要拿一級(jí)指針時(shí)需要對(duì)三級(jí)指針取二次地址,用 * 接收:int *back3 = **num_ppp;
- 需要拿一級(jí)指針對(duì)應(yīng)的值時(shí)需要對(duì)三級(jí)指針取三次地址:int back4 = ***num_ppp;
四、數(shù)組與數(shù)組指針
4.1數(shù)組 & 數(shù)組指針
????在 C 語(yǔ)言中,可以認(rèn)為 數(shù)組就是指針,指針就是數(shù)組,去閱讀一些外國(guó)大佬寫(xiě)的源碼可以發(fā)現(xiàn),通常用指針去聲明個(gè)數(shù)組。下面用個(gè)簡(jiǎn)單的demo來(lái)說(shuō)明:
int main() {
/**
* 數(shù)組與數(shù)組指針
*/
int arr[] = {1, 2, 3, 4};
printf("arr 的值為:%p\n", arr);
printf("arr地址 的值為:%p\n", &arr);
printf("arr首元素的地址 的值為:%p\n", &arr[0]);
/**
* 所以可以看出 數(shù)組 == 內(nèi)存地址
*/
int *arr_p = arr;
printf("arr_p 的值為:%p\n", arr_p);
//數(shù)組第一個(gè)元素
printf("arr_p 對(duì)應(yīng)的值為:%d\n", *arr_p);
return 0;
}
打印的日志為:
arr 的值為:000000000061FDE0
arr地址 的值為:000000000061FDE0
arr首元素的地址 的值為:000000000061FDE0
arr_p 的值為:000000000061FDE0
arr_p 對(duì)應(yīng)的值為:1
因此作為初學(xué)者,我們可以簡(jiǎn)單的認(rèn)為:
- 數(shù)組的值 = 數(shù)組的地址 = 數(shù)組首元素的地址
- 由于可以用指針去接收這個(gè)數(shù)組,因此 指針 = 內(nèi)存地址
4.2 指針移動(dòng)
那么既然可以用指針去接收這個(gè)數(shù)組,那么當(dāng)指針挪動(dòng)時(shí),獲取到的值也就是這個(gè)數(shù)組相對(duì)應(yīng)的值:
//指針移動(dòng)
arr_p++;
//數(shù)組第二個(gè)元素
printf("arr_p 對(duì)應(yīng)的值為:%d\n", *arr_p);
//指針挪動(dòng)2
arr_p += 2;//挪動(dòng)指針指向4
printf("arr_p 對(duì)應(yīng)的值為:%d\n", *arr_p);
//挪動(dòng)指針指向1
arr_p -= 3;
printf("arr_p 對(duì)應(yīng)的值為:%d\n", *arr_p);
打印的結(jié)果為:
arr_p 對(duì)應(yīng)的值為:2
arr_p 對(duì)應(yīng)的值為:4
arr_p 對(duì)應(yīng)的值為:1
在 Java 中對(duì)于這個(gè)數(shù)組的長(zhǎng)度我們都知道是4,那在 C 語(yǔ)言中這個(gè)數(shù)組的長(zhǎng)度是多少呢?在C語(yǔ)言中,想要獲取 數(shù)組的長(zhǎng)度需要用到 sizeof 這個(gè) api,這里我們打印一下 上面的數(shù)組(arr 以及 int 類(lèi)型)的長(zhǎng)度是多少:
printf("sizeof arr 的值為:%llu\n", sizeof arr);
printf("sizeof int 的值為:%llu\n", sizeof(int));
打印的結(jié)果為:
sizeof arr 的值為:16
sizeof int 的值為:4
從打印的日志可以看到 arr 數(shù)組的長(zhǎng)度為 16,int 類(lèi)型的長(zhǎng)度為 4。 這是為什么呢?這是因?yàn)?arr 是個(gè)int 類(lèi)型的數(shù)組,因此 長(zhǎng)度是 arr 長(zhǎng)度 = arr數(shù)組 長(zhǎng)度 x 類(lèi)型長(zhǎng)度 = 4 x 4 = 16。
4.3 數(shù)組 & 遍歷數(shù)組
那么如何通過(guò)指針遍歷數(shù)組呢?我們來(lái)看下面這個(gè)demo:
/**
* 通過(guò)指針遍歷數(shù)組
*/
//其他平臺(tái)較嚴(yán)格
// int i = 0;
for (int i = 0; i < sizeof arr / sizeof(int); ++i) {
printf("數(shù)組下標(biāo)為 %d 的值為:%d\n", i, *(arr + i));
/**
* 從這里可以看到由于是 int 型數(shù)組,int 的內(nèi)存大小為4.因此地址都是挪動(dòng)4個(gè)位置
*
*/
printf("數(shù)組下標(biāo)為 %d 的地址為:%p\n", i, (arr + i));
}
注意這里,其他編譯器(visiion studio)比較嚴(yán)格的話,不能同上面代碼一樣,需要把 int i = 0; 這一句單獨(dú)寫(xiě)出來(lái)。
打印的結(jié)果為:
數(shù)組下標(biāo)為 0 的值為:1
數(shù)組下標(biāo)為 0 的地址為:000000000061FDE0
數(shù)組下標(biāo)為 1 的值為:2
數(shù)組下標(biāo)為 1 的地址為:000000000061FDE4
數(shù)組下標(biāo)為 2 的值為:3
數(shù)組下標(biāo)為 2 的地址為:000000000061FDE8
數(shù)組下標(biāo)為 3 的值為:4
數(shù)組下標(biāo)為 3 的地址為:000000000061FDEC
4.4 循環(huán)給數(shù)組賦值
for (int i = 0; i < sizeof arr / sizeof(int); ++i) {
// * &(arr[i]) = i+100;
// arr[i] = i+100
*(arr_p + i) = (i + 200);
printf("數(shù)組下標(biāo)為 %d 的值重新賦值為:%d\n", i, arr[i]);
}
打印的日志為:
數(shù)組下標(biāo)為 0 的值重新賦值為:200
數(shù)組下標(biāo)為 1 的值重新賦值為:201
數(shù)組下標(biāo)為 2 的值重新賦值為:202
數(shù)組下標(biāo)為 3 的值重新賦值為:203
在這個(gè)demo中注釋的兩行也是可以實(shí)現(xiàn)重新賦值的方式,在上面三種方式任選其一都是可以的。