C指針



1 .什麼是指針

在探究之前我們要弄清楚指針的概念,

  • 指針做什麼
int *p;
  • 1

學過C的人都應該知道,這定義了一個指針,在這裏p到底是個什麼東西呢?
其實,p也就是一個變量,而對於變量,就可以理解爲一個左值,會開闢一塊內存空間,然後在這塊空間中儲存內容。

  • 理解指針
int *p;
int a=10;
p=&a;
  • 1
  • 2
  • 3

在這幾句話,也不難理解,正因爲像咱們在上面說的。開闢好了p這塊空間,所以咱們現在就要在這塊空間中,存放內容,而這個內容,在這裏&a,也就是a的地址了。
如果你還不能理解,那麼我用圖來給你解釋:
這裏寫圖片描述
我想這樣你應該會理解指針的作用,即存放地址。對於上面那個例子,p這塊存放地址的空間我們叫做指針變量,p中存放的內存地址處的內存我們就稱爲p所指向的內存,通常我們也會說p指向了a。

  • “*”的作用

    對於上面的理解,我們留下一個問題,就是“* ”,在定義一個指針時,我們需要加“ *”,而在使用時,我們也會加“ *”。
    比如下面這段程序:

int *p;
int a=10;
p=&a;
*p=20;
  • 1
  • 2
  • 3
  • 4

“* ”我們就可以理解爲,當一個基本的數據類型加上它時,這樣就會構成一個數據類型的指針,這個指針我們要記住,大小永遠是4個字節。而當我們寫成 p 這樣的形式時,我們在這裏,稱呼 *爲解引用。就好像你現在手中會有一把鑰匙,可以去通過指針來改變它所指向的那塊空間的內容。

2.什麼是數組

  • 數組概念

數組,我們通常會理解爲一組類型相同元素的集合。
比如:

int arr[]={1,2,3,4,5};
  • 1

學過C的人應該都知道,這意思就是定義了一個數組,其中包含了5個int類型的元素。
這裏寫圖片描述
當定義數組時,編譯器會根據元素的個數和元素類型確定大小開闢空間,比如,上面的arr,我們就會分配20字節大小個空間。在這裏,這塊空間是不能再次改變的。

  • 亂亂的&arr和&arr[0]
    對於數組,我們一定要清楚&arr和&arr[0]這兩個概念,&arr,這裏說的就是對整個數組取地址。而&arr[0]在這裏所說的也就是對數組的首個元素的地址。&arr和&arr[0]這兩個值是相等的,雖然他們相等,但是意義是不一樣的。這個的區別就好比陝西省政府與西安市政府,他們的所在地都一樣,都是在西安,但是意義不一樣。
  • 數組名的左值右值
    左值和右值,簡單說就是可以出現在“=”右邊的就是右值,可以出現在“=”左邊的就是左值。左值,他要是一塊可以被修改的空間,右值,所說的值一塊空間中所帶的內容。

    接下來我們要探討一個問題了,數組名可以做左值嗎?右值呢?

    當arr作爲右值時,我們要清楚,arr就是代表首元素的地址,在這裏的arr就相當於arr[0]。
    而當arr作爲左值呢,在這我們就要根據左值右值的概念考慮了,左值必須是一塊可以修改的空間,而這裏,arr待變的是arr數組首元素的地址,地址不能被修改,所以,arr是不能作爲左值的。

3.指針數組之間的聯繫

在我當初學習指針和數組時,總會覺得他們之間有種關係,並且好像彼此之間是一回事,在這裏,我要告訴你,指針和數組之間是沒有任何關係的!!!

很多人容易把這兩個概念弄混淆很大的原因就是應爲在訪問時,指針和數組他們總是可以達到一樣的目的。
例如:

char *p="abcdef";
  • 1
  • 指針形式和下標形式訪問指針

對於上面的的指針變量p,根據前面咱們分析的可以知道,p是一塊4個字節大小的空間,裏面存放了首字符的地址。
比如我們需要訪問’c’:

指針形式:*(p+2);這個意思就是先取出p的地址,然後給它按照字符型進行偏移2次,然後把所指向的地址進行解引用,訪問其中所存放的內容。

數組下標形式:p[2];這裏編譯器會把下標形式解析成爲指針形式的操作。這裏的意思就是先取出p中存儲的地址值,再加上其中括號中2個元素的偏移,得出新地址,然後再取出內容。

這兩種方法最後使得我們所得到的結果都是一樣的,兩種方式本質上都是以指針爲形式進行訪問。

  • 指針形式和下邊形式訪問數組
char arr[]="123456";
  • 1

在這裏我們需要訪問’3’

指針形式:*(arr+2);這裏就是得到arr的首字符地址,然後偏移兩個字符,然後得到所指向地址,然後解引用,得到其中的內容。

數組下標形式:arr[2];這裏編譯器會把下標形式解析成爲指針形式的操作。首先得到arr首元素的地址,然後偏移兩個字符,得到新的地址,再取出內容。

經過了上述兩個過程的分析,我們應該都知道指針和數組就是兩個不一樣的東西,他們只是可以”以指針的形式“和”以數組下標形式“來進行訪問。

  • 區分a與&a
    首先我們通過牛客網的一道題來進行分析:
#include<stdio.h>
int main()
{
    int a[5]={1,2,3,4,5};
    int *ptr=(int *)(&a+1);
    printf("%d,%d,*(a+1),*(ptr-1)");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在這裏我們要分析:
&a+1:這個說的就是取了a的地址,然後向後偏移一個int,也就是到了這個數組最後一個位置的下一個地址。
(int )(&a+1):這個意思是將上述的地址強制轉化爲int 類型的地址。
所以執行完第二句話後,這裏的ptr中存放的就是a這個數組的下一個數組的首地址。
這裏寫圖片描述
*(a+1):這個的意思參考上面咱們分析的,所以也就可以得到這個的結果就是數組第二個元素。
所以,最終輸出的結果也就是2和5;
這裏我們依然強調一下&a說的是數組的地址,對他+1指的是偏移整個數組。
a這裏代表的是a[0],首元素的地址。

4.指針數組和數組指針

指針數組:這裏強調的是數組,然後數組中的元素都是指針,數組的大小是通過數組本身來決定。
數組指針:這裏強調的是指針,就是說它還是一個指針變量,只不過他指向數組,至於他所指向的數組多大,這個就不知道了。

int *p1[5];
int (*p2)[5];
  • 1
  • 2

在這裏我總結下來,指針數組和數組指針主要的區別主要是看優先級結合。”()”的優先級是最高的,下來是”[ ]”,再下來是”*“;

int* p1[5]:我們可以理解爲,先於[10]結合,就是一個數組p1,然後是和”int*“結合,就表示數組中的內容全部都是int*類型的,所以這就是一個指針數組,數組中包含的是10個指向int類型數據的指針。所以他就是一個指針數組。

int (*p2)[5]:這裏首先優先級最高的是”()“,所以先結合”()“,就是強調了這是個指針,然後和”[10]”進行結合,所以p2就是一個指向一個包含10個int大小的數組的指針。所以他就是一個數組指針。

這裏寫圖片描述

5.函數指針,函數指針數組和函數指針數組指針

通過剛纔對於指針數組和數組指針的理解,接下來我們來探討一下函數數組和函數指針。

函數指針


  • 什麼是函數指針?

函數指針:顧名思義,這裏強調的依然是指針,函數指針也就是說函數的指針,他是一個指向函數的指針。


char *fun3(char *p1,char *p2);
  • 1
  • 2

這個我想大家再熟悉不過了,函數爲fun3,兩個char* 類型的形參 p1,p2,函數返回值char *。

char * *fun2(char *p1,char *p2);
  • 1

這裏函數爲fun2,兩個char 類型的形參 p1,p2,函數返回值char **

char *(*fun1)(char * p1,char *p2);
  • 1

在這,我們就可以用優先級來進行判斷,它先於()結合,可以知道fun1是一個指針,然後它指向的是一個函數。而這個函數呢,就是一個有兩個char* 類型的形參 p1,p2,函數返回值char *的函數。

  • 函數指針的使用
    例:
#include<stdio.h>
int max(int x,int y)
{
    return (x>y? x:y);
}
int main()
{
    int (*ptr)(int, int);
    int a, b, c;
    ptr = max;
    scanf("%d%d", &a, &b);
    c = (*ptr)(a,b);
    printf("a=%d, b=%d, max=%d", a, b, c);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在這一段程序中,ptr是指向函數的指針變量,所以可把函數max()賦給ptr作爲ptr的值,即把max()的入口地址賦給ptr,以後就可以用ptr來調用該函數。

接下來看下一個例子:

void fun()
{
    printf("CALL Fun!\n");
}
int main()
{
    void (*p)();
    *(int *)&p=(int)fun;
    (*p)();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

分析:
void (*p)():這一句說的就是我在這裏定義了一個指針變量p,p用來指向函數,函數的返回值和參數都是void類型的。
*(int )&p=(int)fun:在這,就是說將fun函數的地址強制轉換成int類型,然後賦給了p指針變量。
(*p)():表示對函數fun進行調用。
所以我們要清楚,函數指針還是一個指針,裏面存放的是函數的首地址,然後我們通過首地址來調用函數。

(*(void (*)())0)();
  • 1

這個是什麼呢?

對於這個,其實咱們也可以通過分析進行解決。
void (*)():這個是一個void類型的一個函數指針,這個函數返回值爲空,並且參數也爲空。
(void (*)())0:這個就是將0強制轉換成這個函數指針的類型,也就是說一個函數保存在首地址爲0的一段區域。
(* ( void (*)())0):這是取得首地址爲0的的內容,就是保存在首地址爲0的的函數。
(* (void (*)())0)():這是最終對這個函數進行調用,因爲函數的參數爲空,所以調用時參數也是空的。

接下來我們再進行分析一個類似的:

(*(char * *(*)(char **,char **))0)(char * *,char * *);
  • 1

是不是看這有些暈呢,咱們繼續一層一層進行分析。
char * * ( * )(char * * ,char * * ):這個所說的就是一個函數指針,這個函數指針指向返回值爲char * * * ,參數爲兩個char * *的參數。
(char * * ( * )(char * * ,char * * ))0:這是將0強制轉換爲這個函數指針的類型,也就是說一個函數保存在首地址爲0的一段區域。
( * (char * * ( * )(char * * ,char * * ))0 ):這是取得首地址爲0的的內容,就是保存在首地址爲0的的函數。
( * (char * * ( * )(char * * ,char * * ))0)(char * * ,char * * ):這是最終對這個函數進行調用因爲函數的參數爲(char * * ,char * * ),所以調用也是給(char * * ,char * * )參數。

函數指針數組


函數指針數組:我們也可以參考前面的分析方法,它是一個數組,這個數組中的元素就是我們前面所提到的函數指針。

char *(* p[3])(char *p);
  • 1

上面這個就是一個函數指針數組,首先它是有3個元素的一個數組,裏面存放的是指向函數的指針,這些指針指向一些返回值類型爲指向字符的指針。
在這裏的關鍵你要知道這個是數組,數組中的元素是函數指針。

函數指針數組指針


到這裏,我想許多人看見以後肯定非常暈了,一層套一層的,其實,沒什麼複雜的,萬變不離其宗,咱們還是一層一層來進行分析。
首先給出一個函數指針數組指針

char *(*(*p[3])(char *p);
  • 1

在這裏,首先按照優先級從個最裏面看,最裏面的(*p)這是一個指針。
(*( *p)[3])這個說了這個指針是指向含有三個元素的數組的指針。
char*( *(*p)[3])(char *p)這個說了這個數組中存放的元素是3個函數指針,這些指針指向一些返回值爲指向字符的指針。然後這個函數的參數是指向字符的指針。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章