文章目錄
- 前言
- 一、指標是什么?
- 1.1記憶體和地址
- 1.2 * 運算子和 &運算子
- 1.3創建指標變數
- 1.4int* p的解釋
- 二、陣列指標指標陣列
- 2.1 陣列指標和指標陣列的理解
- 2.2 陣列指標和指標陣列的定義方式
- 2.3 代碼分析
- 三、函式指標
- 3.1 函式指標的概念
- 3.2 函式指標的定義
- 3.3 函式指標的使用
- 四、套娃
- 4.1套娃開始
- 4.2 套娃解釋
- 4.3 瘋狂瘋狂套娃
前言
指標可以說是C語言最強大的功能之一,通過指標可以直接訪問記憶體,然而指標也是C語言中非常難掌握的知識點之一,如果可以靈活地使用指標,基本上可以稱得上是編程老手了,
本文通過我對于C語言指標的一些了解,對指標中容易讓人混淆的幾個概念進行一些分析和講解,如有不足之處,還請大家幫忙指出,
一、指標是什么?
C語言里,我們常用的一些資料型別有:char型,int型,float型,double型等,它們分別用來存放字符型、整型、單精度浮點型、雙精度浮點型,和這些資料型別相比,指標實際上也是一種資料型別,不過與一般的資料型別不同的是,指標變數中存放的內容,是另一個變數的地址,因此也可以把指標理解為地址,
C語言一般通過變數來存盤資料,用函式來定義一段可以重復使用的代碼,無論是資料還是函式,最終都要被放到記憶體中,然后CPU通過地址來獲取記憶體當中的代碼和資料然后進行執行,可以說,指標就是C語言的靈魂,
1.1記憶體和地址
正如前文所說,資料和代碼都是存放在記憶體中,而CPU去訪問它們的時候,都要通過地址去進行訪問,
例如我們創建一個變數:int a = 0;(int占2或4個位元組,我們以4位元組為例)
如下圖所示,整個大方格為記憶體的一片空間,每個小格代表一個位元組,我們假設a的地址是0X00FFFFFF(16進制),當cpu需要訪問a的時候,就通過訪問0X00FFFFFF來訪問a,

我們可以做一個比喻,假設你想找某一個你不認識的人,你知道他的名字和住址,請問你是通過名字來找他還是通過地址來找他呢?很明顯,通過地址可以很方便的找到這個人,而通過名字則很難,
1.2 * 運算子和 &運算子
如果把* 和&看做雙目運算子的話,它們分別表示乘和按為與,這里我們不作重點介紹,當它們作單目運算子時,分別表示( * )解參考運算子和 (&)取地址運算子,
&(取地址運算子)很好理解,顧名思義就是取地址,而*(解參考運算子)可能就不太好理解,我們可以把* 理解成取內容,取出某個地址里存放的內容,
1.3創建指標變數
#include<stdio.h>
int main()
{
int a = 0; //創建int型的變數a
a = 10;
int* p = &a; //創建一個int型的指標p
printf("%d", *p); //列印a的值
return 0;
}
這是一段很簡單的代碼,我們先創建了一個整型變數a,并初始化為0,然后將它的值改為10,最后列印在螢屏上,我們主要看第六行代碼: int* p = &a;
首先&a,是取a的地址,然后將它賦給p,所以p當中存放的就是a的地址,而int*則表明p是一個指向整型的指標變數,
1.4int* p的解釋
關于int *p的解釋,很多人會說這是語法規定的,指標變數就應該這么定義,沒有什么要解釋的,下面我將給出一種不同的解釋,
正如前面所說, *是解參考運算子,或者說是取內容,那么我們這么理解int *p:
將 int *p拆分成兩部分,int 和 * p; *p為取p的內容,即取出p中存放的地址的內容,取出這個內容之后,它是int型,總上所述,首先p中存放了地址 ,然后對p進行 *操作后,取出的資料是一個int型的資料,所以說它是int型的指標,
二、陣列指標指標陣列
2.1 陣列指標和指標陣列的理解
可能很多人看到這兩個詞在一起就一臉懵逼,剛開始的時候我也是這樣,分不清什么是陣列指標,什么是指標陣列,后來通過老師的講解才慢慢理解,希望我的理解能夠對大家有幫助,
我們在這兩個詞之間分別加上一個“的”,即陣列的指標和指標的陣列,將前半部分其實是對主語的修飾,我們再舉個例子:它是小明的書,請問這個“它”的本質內容是小明還是書呢?很明顯“它”是一本書,而小明只是對這本書的修飾,表明它的所屬,
類似地,我們再來分析陣列指標和指標陣列,陣列指標即陣列的指標,首先它是一個指標,然后它是屬于陣列的指標,因此陣列指標就是一個指向陣列的指標,
指標陣列即指標的陣列,首先它是一個陣列,然后它是屬于指標的陣列,因此指標陣列就是一個陣列,這個陣列的元素是指標,
2.2 陣列指標和指標陣列的定義方式
先給出陣列指標和指標陣列的定義方式再進行解釋:
int *arr1[10] ————————————————指標陣列
int (*arr2)[10] ————————————————陣列指標
再看一段代碼,我們通過三種方式輸出了陣列arr[10]中的元素
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* arr1[10] = { &arr[0],&arr[1],&arr[2],&arr[3],&arr[4],&arr[5],&arr[6],&arr[7],&arr[8],&arr[9]};
int(*arr2)[10] = &arr;
int i = 0;
//用arr輸出每個元素
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
//用arr1輸出每個元素
for (i = 0; i < 10; i++)
{
printf("%d ", *(arr1[i]));
}
printf("\n");
//用 arr2 輸出每個元素
for (i = 0; i < 10; i++)
{
printf("%d ", *(*arr2 + i));
}
printf("\n");
return 0;
}

首先解釋陣列指標和指標陣列的定義方式:
int *arr1[10] ————————————————指標陣列
int (*arr2)[10] ————————————————陣列指標
看起來指標陣列和陣列指標很像,只是第二個比第一個多了一個括號,我們要看變數名先和哪一個符號結合,如果先和[]結合,那么它就是陣列,如果先和 * 結合,它就是指標,因為[]的優先級比 *高,所以,如果不加括號,變數名就先和[]結合,那它就是陣列,加了括號,括號的優先級更高,因此先和 *結合,它就是指標,所以說區別指標陣列和陣列指標的關鍵就是看變數名先和哪一個符號結合,當弄清楚它先和誰結合之后,把變數名以及和它結合的部分刪掉,剩下的部分就是它的型別,
例如:
int *arr1[10],arr1先和[10]結合,所以它首先是一個陣列,其次這個陣列有10個元素,剩下的部分是int * ,所以陣列的每個元素的型別是int *,即每個元素都是一個整型指標,
int ( *arr2)[10],arr2先和 *結合,所以arr2是一個指標,剩下的部分是 int [10],所以它指向了一個有10個整型元素的陣列,
我們再用 *運算子的方法分析:
int * arr1[10],arr1先和[10]結合,所以它首先是一個陣列,其次這個陣列有10個元素,然后對這個陣列的元素進行 *(取內容操作),最后得到的型別是int,因此arr1是一個有10個元素的陣列,陣列的每個元素是整型指標,
int ( *arr2)[10],對arr2進行 *(取內容操作),得到的是int [10]型別(有10個整型元素的陣列),所以arr2指向了一個有10個整型元素的陣列,
2.3 代碼分析
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* arr1[10] = { &arr[0],&arr[1],&arr[2],&arr[3],&arr[4],&arr[5],&arr[6],&arr[7],&arr[8],&arr[9]};
int(*arr2)[10] = &arr;
arr是一個陣列,每個元素是1,2,3,4,5,6,7,8,9,10;
分別對應:arr[0],arr[1]…arr[9];
arr1也是一個陣列,但它的每個元素是一個指標,
分別指向arr[0],arr[1],arr[2]…arr[9]
arr2是一個指標,它指向了陣列arr

//用arr1輸出每個元素
for (i = 0; i < 10; i++)
{
printf("%d ", *(arr1[i]));
}
printf("\n");
//用 arr2 輸出每個元素
for (i = 0; i < 10; i++)
{
printf("%d ", *(*arr2 + i));
}
printf("\n");
因為arr1中的每個元素分別指向了arr[0],arr[1]…arr[9],引起對arr1[i]進行*(解參考/取內容操作)就得到了arr[i];
因為arr2指向陣列arr,* arr2得到了arr首元素的地址,* arr2 +i得到了arr[i]的地址,
再對* arr2 +i 進行* 操作,就得到了arr[i],
printf("%p\n", arr2); //輸出陣列arr的地址
printf("%p\n", *arr2); //輸出輸出arr首元素的地址

可以看到,陣列arr的地址和arr首元素的地址數值上相同,但是它們的含義不一樣,一個是整個陣列的地址,一個是陣列首元素的地址,(%p是輸出地址)我們再輸出arr2+1,和* arr2 +1以及&arr[1]
printf("%p\n", arr2+1);
printf("%p\n", *arr2+1);
printf("%p\n", &arr[1]); //輸出arr[1]的地址

arr2 +1和*arr2+1差了0x24,也就是36個位元組,正好是9個整型變數的大小,

當對arr2加1時,會跳過整個陣列,而對*arr2+1只會越過
三、函式指標
3.1 函式指標的概念
當我們弄清楚了陣列指標和指標陣列之后,再來理解函式指標,和前面類似,函式指標就是函式的指標,首先它是一個指標,然后它指向了一個函式,
3.2 函式指標的定義
一個函式的定義是用 回傳型別 + 函式名 +(函式引數)構成,那么函式指標是如何定義呢?
int Fun(int x, int y)
{
return 0;
}
Fun就是一個函式,它有兩個整型引數,回傳型別是int
int* Fun1(int x, int y)
{
return NULL;
}
Fun1也是一個函式,它有兩個整型引數,回傳型別是int *;
因為()優先級比較高,所以Fun1先和(int x,int y)結合,表明它是一個函式,回傳型別是int *;
那么我們如何定義一個函式指標呢,由于 *比()優先級低,為了讓函式名先和 *結合,我們應該給函式名和 *加一個括號,即:
int Fun(int x, int y)
{
return 0;
}
int main()
{
int (*p)(int x, int y) = &Fun;
return 0;
}
p先和 *結合,因此p是一個指標,將 *p洗掉,剩下的int (int x, int y),就是它所指向的變數的型別,即一個有兩個整型引數,回傳型別為int的函式,
我們也可以用另一種解釋方法,對p進行 *(解參考/取內容)操作,得到了一個int (int x, int y)的函式,所以p就是一個指向有兩個int型引數,回傳型別為int的函式,
3.3 函式指標的使用
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 3;
int b = 2;
int sum1 = 0;
int sum2 = 0;
int sum3 = 0;
int (*p1)(int x, int y);
int (*p2)(int x, int y);
sum1 = Add(a, b);
printf("%d\n", sum1);
p1 = &Add;
sum2 = p1(a, b);
printf("%d\n", sum2);
p2 = Add;
sum3 = (*p2)(a,b);
printf("%d\n", sum3);
return 0;
}

對函式指標的賦值,可以對函式名&,然后賦值給函式指標,即p1 = &Add;
也可以直接將函式名賦值給函式指標,即p2 = Add;
通過函式指標呼叫函式時,同樣有兩種方式:(* p1)(a,b)或p2(a,b),
即:指標名(引數);或(* 指標名)(引數)
四、套娃
當我們理解了陣列指標、指標陣列以及函式指標后,我們就可以開始瘋狂套娃了
4.1套娃開始
1、一個整型數: int a;
2、一個指向整型數的指標 int *p;
3、一個指向指標的指標 int **p;
4、一個有10個整型數的陣列: int arr[10];
5、一個有10個指標的陣列,該指標指向一個整型數 : int *arr[10];
6、一個指向陣列的指標,該陣列有10個整型元素:int ( *arr)[10];
7、一個指向陣列的指標,該陣列有10個指向整型數的指標:int *( *arr)[10];
9、一個指向函式的指標,該函式有一個整型引數并回傳一個整型數: int ( *p)(int );
10、一個指向函式的指標,該函式有一個整型引數并回傳一個整型指標:int * (*p)(int)
11、一個有10個指標的陣列,該指標指向一個函式,該函式有一個整型引數并回傳一個整型:
int ( *arr[10])(int);
12、一個有10個指標的陣列,該指標指向一個函式,該函式有一個整型引數并回傳一個整型指標:
int* ( *arr[10])(int);
4.2 套娃解釋
8和11比較復雜,故只解釋8和11,
首先看8:
arr先和[10]結合,表明arr是一個陣列,該陣列有10個元素,把arr[10]洗掉,剩下的部分為:
int ( *)[10]即為陣列元素的型別,每個元素是一個陣列指標,該指標指向了一個有10個整型元素的陣列,
11:arr先和[10]結合,表明arr是一個陣列,該陣列有10個元素;剩下int ( *)(int),剩下的部分是函式指標的型別,因此arr陣列的每個元素是一個函式指標,指向了一個有一個整型引數,回傳值是int型別的函式,
4.3 瘋狂瘋狂套娃
( *(void( *)(void))0)();是不是一臉懵逼
我們先將這個運算式通過加上空格,讓它變得更加清晰
( * ?(?void( *) (void) ) 0 ) () ;
這樣是不是更加清楚了一點?
再加上顏色:
( * ?(?void( *) (void) ) 0 ) ()
( * ?(? void( *) (void) ) 0 ) ()
藍色部分void( *)(void)是一個函式指標型別,該函式沒有引數,回傳型別為void,我們將其用“型別”代替:
即void( *)(void)=型別
那么原式變為:( * ?(? 型別 ) 0 ) ()
(型別)0,就是強制型別轉換,將0強制型別轉換成一個函式指標,將(型別)0,替換為函式指標
原式變為( *函式指標)(),就是呼叫該函式
因此
( *(void( *)(void))0)();是先將0強制型別轉換為一個函式指標,該指標指向一個無引數,回傳型別為void型別的函式,然后再對這個函式進行呼叫,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/277518.html
標籤:其他
