程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 指針和數組的掌握以及內存的管理

指針和數組的掌握以及內存的管理

編輯:關於C

前言:對於C語言的學習,我記得最初就有人說最難莫過於指針,當初我們的學習呢,又是先學習了數組,然後接觸的就是指針,指針和數組被很多書籍放在一塊進行教學,指針是C/C++中的精華,而難點就在於對指針和數組的掌握以及內存的管理,所以在這裡,我們要對這個問題,進入一些研討:

1 .什麼是指針

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

指針做什麼
int *p;

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

理解指針
int *p;
int a=10;
p=&a;

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

“*”的作用

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

int *p;
int a=10;
p=&a;
*p=20;

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

2.什麼是數組

數組概念

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

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

學過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";
指針形式和下標形式訪問指針

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

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

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

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

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

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

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

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

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

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

在這裡我們要分析:
&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];

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

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

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

這裡寫圖片描述

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

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

函數指針


什麼是函數指針?

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


char *fun3(char *p1,char *p2);

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

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

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

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

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

函數指針的使用
例:
#include
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;
}

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

接下來看下一個例子:

void fun()
{
    printf("CALL Fun!\n");
}
int main()
{
    void (*p)();
    *(int *)&p=(int)fun;
    (*p)();
    return 0;
}

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

(*(void (*)())0)();

這個是什麼呢?

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

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

(*(char * *(*)(char **,char **))0)(char * *,char * *);

是不是看這有些暈呢,咱們繼續一層一層進行分析。
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);

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

函數指針數組指針


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

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved