02-C語言的指針

02-C語言的指針

目標(biāo)

  • C語言指針釋義
  • 指針用法
  • 指針與數(shù)組
  • 指針與函數(shù)的參數(shù)
  • 二級指針
  • 函數(shù)指針

指針在C中很重要,它可以直接操作內(nèi)存,是C效率的體現(xiàn)之一。

C語言指針概述

什么是指針

C語言中,指針是一個存放地址的變量。

指針能做什么

  • 更直接和效率的在內(nèi)存中修改值。
  • 寫游戲外掛(解決跨進程問題和注入問題即可達(dá)成)。

指針定義

先看一個指針定義的例子:

int a = 10;
int *p = &a; // 將a的地址賦值給給指針p

// 也可這么寫,這種方式更容易理解
int a1 = 10;
int *p; // 定義一個指針p
p = &a1; // 將a1的地址值(十六進制)賦值給p

這里有兩個新概念

  • *符號: *符號代表是一個“指針”定義,這里定義一個叫p的指針。
  • &符號: &符號是一個“取地址符”,&a代表取a的內(nèi)存地址值

再看一個例子

int a = 10;
int *p;
p = &a; // 將a的地址賦值給給指針p
printf("p的地址為:%#x\n", &p);
printf("p存儲的值為:%#x\n",p);
printf("p指向的地址存儲的值為:%d \n",*p);

返回值如下:

p的地址為:0x3bfed4
p存儲的值為:0x3bfee0
p指向的地址存儲的值為:10

第三行的打印,引出了另一個知識點:

  • 在程式的運算中,*p的*號將指定獲取p的存儲地址值指向目標(biāo)內(nèi)存獲取。也相當(dāng)于拿著存儲的地址值后,跳轉(zhuǎn)到這個地址指向的值。簡單來講可以說是一種地址解包。

指針占用大小

指針指向的是地址,地址占用4個字節(jié)。

在指針的角度看數(shù)據(jù)的占用

指針是指向變量占用的首地址的

int占用為4字節(jié),若定義一個int變量i,那么指針指向的是i地址的第一個字節(jié)地址,接下來3個字節(jié)都是int字節(jié)。這總共4個字節(jié)都不會被其他對象所占用。

long long占用8個字節(jié),若定義一個long long變量LL,那么指針指向了LL地址的首字節(jié)地址,接下來7個地址都是這個LL的(共占8個)。

C語言指針的使用

指針的基本運算

我們這里主要討論值運算地址運算,需要有N目運算的基礎(chǔ)。如果不清楚請看上一篇文章的相關(guān)描述。

先看一個例子:

int a = 10;
int *p = &a;
*p = *p + 10;
printf("%#x,%d,%d\n",p,*p,a);

在這里,控制臺打印出來的為:

0x28f9a4,20,20

第一個參數(shù)為16進制地址輸出(每次分配的地址一般都不一樣),第二個是取指針值顯示,第三個值為a內(nèi)存值。

再看看第二個例子:

int a = 10;
int *p = &a;
(*p)++;
printf("%#x,%d,%d\n",p,*p,a);
*p++;
printf("%#x,%d,%d\n",p,*p,a);

運行結(jié)果為:

0x2dfb34,11,11
0x2dfb38,-858993460,11

第一個打印行,運行是 ( *p)++,根據(jù)N目運算優(yōu)先級,先計算的 ( *p),然后再說++,也就是先算出值,再在值上+1。

第二個打印行,運行是 *p++,根據(jù)N木運算優(yōu)先級,先算++,再算 *,這就變成了一個地址++位移,再進行地址值獲取的動作。我們可以看到,這里的第二個參數(shù),取值已經(jīng)不知道取到個什么值了,這也就是指針位移帶來的不可控問題。

這里有個有意思的點:++進行地址自增過后,會根據(jù)數(shù)據(jù)的類型進行占用字節(jié)計算,再位移。比如int占用為4字節(jié),++就會位移4個字節(jié)。

指針數(shù)組與數(shù)組指針

數(shù)組和指針

int array[5];
int *p = array; // 沒有&符號
printf("array地址為%#x \n",array);
printf("p地址為%#x \n",p);

控制臺打印為:

array地址為0x25f7e0
p地址為0x25f7e0

可以運行,可以看到,在指針p賦值的時候,array沒有寫&符號。其實這里傳達(dá)的概念就是:C中,數(shù)組名就是數(shù)組的首地址

如果強制給array用&符號,將會編譯出錯。

定義數(shù)組array,那么array就是一個數(shù)組的首地址,那么可以對指針做地址位移操作,如下:

int array[5] = { 1, 2, 3, 4, 5 };
int *p = array + 4; // 偏移4個位置
printf("array地址為%#x \n", array);
printf("x值為:%d,地址為:%#x \n", *p, &p);
int *p2 = array + 5;
printf("x2值為:%d,地址為:%#x \n", *p2, &p2); // 越界

控制臺打印為:

array地址為0x54fb6c
x值為:5,地址為:0x54fb60
x2值為:-858993460,地址為:0x54fb54

array的首地址為0x54fb6c,在首位偏移4個位置(4*4=16)到了0x54fb60,其中存的值為數(shù)組5,最后我們對array偏移5個地址,但是數(shù)組上沒有那么多,所以越界了,指到一個不知道什么值的地址上了。

根據(jù)以上分析,我們可以得出結(jié)論:

int array[5] = { 1, 2, 3, 4, 5 };
int *p = array;
int i = 2; // 定義數(shù)組的偏移量
// p + i = &array[i] = array + i; // 我們可以認(rèn)為這三個是一個等式

p + i = &array[i] = array + i; 我們可以認(rèn)為這三個是一個等式

數(shù)組的內(nèi)存值改變:

int array[5]; // 未定義數(shù)組值,默認(rèn)每個item都為0
int x = 0;
for (int *p = array; p < array + 5; p++){
    *p = ++x; // 修改指針的值,直接給內(nèi)存地址賦值
}
    
for (int i = 0; i < 5; i++){
    printf("%d \n", array[i]);
}

控制臺輸出:

1
2
3
4
5

我們利用p指針不停的循環(huán)自增,給數(shù)組的內(nèi)存地址重新賦值,內(nèi)存地址的值改變,改變了數(shù)組指向的內(nèi)容。

指針數(shù)組

指針指向數(shù)組的首地址,如下就是一個指針數(shù)組:

char *name[] = {"leon","tony","cherry","mango"}; 

根據(jù)N目運算符優(yōu)先級說明,[]為1級,*為2級,那么先進行name[]操作,再進行了指針 *操作,最后進行了賦值

定義一個char類型的數(shù)組,指針指向了這個數(shù)組的首地址。

其實這就是與如下代碼一個道理,我們在數(shù)組與指針小節(jié)也見過這個定義:

char name[];
char *p = name;

指針p指向了name數(shù)組的首地址。

數(shù)組指針

用指針組成的數(shù)組。

char (*name)[] = {"leon","tony","cherry","mango"};

根據(jù)N目云算法優(yōu)先級說明,[]與()都是1級運算符,1級運算符遵守從左到右的計算規(guī)則,那么是先算( *name),再操作( *name)[],再進行賦值。

定義一個name指針,再把這個name指針定義成數(shù)組,每一個item指向一個item內(nèi)存地址。

指針數(shù)組與數(shù)組指針的區(qū)別

指針數(shù)組是一個指向數(shù)組的首地址,數(shù)組指針是一個由指針構(gòu)成的數(shù)組。

指針與函數(shù)的參數(shù)

函數(shù)的參數(shù)

java的值傳遞和引用傳遞

  • java中,將對象當(dāng)做參數(shù)傳給方法,如果方法中對對象做了點啥的話,會改變這個對象的值,這會影響到方法外的對象。——這就是引用。
  • Java中,將基本變量當(dāng)做參數(shù)傳給方法,在方法中怎么折騰都不會影響方法外的、作為參數(shù)傳遞的變量。——這就是值傳遞。

C中是這樣么?

  • C中,給函數(shù)(方法)傳遞的參數(shù)是值大多情況下都是值傳遞。

這么說來,C在這方面處理的比Java好啊。

如果要達(dá)到跟Java一樣的引用效果,如何做呢?

看看以下例子能否做到:

void swap(int *a, int *b){
    int *temp;
    temp = a; 
    a = b; // 指針交換
    b = temp;
}

int _tmain(int argc, _TCHAR* argv[]){
    int a = 10;
    int b = 20;
    swap(&a, &b);
    printf("a:%d,b:%d \n", a, b);
    system("pause");
    return 0;
}

控制臺輸出:

a:10,b:20

沒有改變,這說明:形參的修改,無法帶來實參的修改。

  • 形參:方法定義的參數(shù)
  • 實參:調(diào)用方法時,注入進方法的參數(shù)。

樓上的代碼,只是將a,b的地址copy給了形參(形參叫a,b但是不是實參的a,b,只是一個copy),可以理解成copy為a1,b1,雖然他們指向的地址也是a,b的地址,但是將這份a,b的copy指針a1,b1地址進行切換,是不會影響真正a,b的。

真的沒有辦法了么?再看看下面的方法:

void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b; // 指針指向值交換
    *b = temp;
}

int _tmain(int argc, _TCHAR* argv[]){
    int a = 10;
    int b = 20;
    swap(&a, &b);
    printf("a:%d,b:%d \n", a, b);
    system("pause");
    return 0;
}

控制臺返回值為:

a:20,b:10

看起來成功了,這次又發(fā)生了什么呢?

在新的swap方法中,a,b形參是copy實參a,b的地址的,這個地址值是不會有差別的,所以他用了一個 *號對地址指向的內(nèi)存值進行直接修改。 =。=

二級指針

什么是二級指針?

一個指向一級指針的指針。它存儲的內(nèi)容是一個一級指針的地址。

簡單來講,就是在一個指針的基礎(chǔ)上,再包一層指針。

舉個例子,下例中p2就是一個二級指針。

int a = 10;
int *p = &a; // 一級指針,p存放的是a的地址
int **p2 = &p; // 二級指針,在p指針基礎(chǔ)上再包一層指針
printf("a的地址:%#x \n", &a); // 打印p存儲的值(也就是a的地址)
printf("p存儲的值:%#x \n", p); // 打印p存儲的值(也就是a的地址)
printf("p2存儲的值:%#x \n", p2); // 打印p2存儲的地址
printf("p2存儲的值,再用*解一次指針包:%#x \n", *p2); // 打印*p2,也就是p存儲的值(也就是16進制地址)
printf("p2存儲的值,用*解2次指針包:%#d \n", **p2); // 打印a存儲的值(10進制int)

控制臺打印為:

a的地址:0x3efc54
p存儲的值:0x3efc54
p2存儲的值:0x3efc48
p2存儲的值,再用解一次指針包:0x3efc54
p2存儲的值,用
解2次指針包:10

同理,三級指針也來了

……接上例代碼
int ***p3 = &p2; // 三級指針來了
printf("p2存儲的值,用*解3次指針包:%#d \n", ***p3); // 打印a存儲的值(10進制int)

這多級指針概念復(fù)雜感覺一點都不簡潔,有什么用?

在NDK開發(fā)中,大量使用多級指針,所以一定要介紹。

函數(shù)指針

什么是函數(shù)指針?一個指向函數(shù)的指針。

函數(shù)指針定義是什么樣子的?如下:

// 語法: 返回類型 (*指針)(參數(shù)1,參數(shù)2,……);
int (*calc)(int a, int b);

上例中,根據(jù)注釋的語法說明,我們就這么定義了一個函數(shù)指針。

C中的函數(shù),在C平臺是一種什么樣的存在呢?看看下面的例子:

/*完整代碼*/

int impl(int a, int b){
    return a + b;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int (*calc)(int a, int b);
    calc = impl; 
    int x = calc(1,2);
    printf("calc(1,2)得到值為:%d \n",x);
    printf("impl函數(shù)打印為:%#x \n", impl);
    printf("calc函數(shù)打印為:%#x \n", calc);
    system("pause");

    return 0;
}

控制臺輸出:

calc(1,2)得到值為:3
impl函數(shù)打印為:0xa61028
calc函數(shù)打印為:0xa61028

定義calc是一個函數(shù)指針,打印impl函數(shù)名,也是一個地址!這說明什么?函數(shù)名跟數(shù)組名一樣都是一個指針!

這就是我們的函數(shù)指針

目標(biāo)小結(jié)

  • [x] C語言指針釋義
  • [x] 指針用法
  • [x] 指針與數(shù)組
  • [x] 指針與函數(shù)的參數(shù)
  • [x] 二級指針
  • [x] 函數(shù)指針
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容