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

C語言指針總結

編輯:關於C

C語言中的精華是什麼,那當然是指針,是C語言的難點部分。C是對底層操作非常方便的語言,而底層操作中用到最多的就是指針,

這成就了優秀的C程序的效率幾乎和匯編語言程序一樣高的功績。

本文介紹C指針的一些基礎和高級知識。關鍵好是多寫代碼,這樣才能更好的理解C的精華--指針。

1. 指針的概念

指針是一種數據類型,與其它的數據類型不同的是指針是一種“用來存放地址值的”變量。

首先搞懂這幾個概念:

指針類型、指針指向數據類型、指針指向的內存區、指針在內存中占空間大小.

如:int *pn;

指針類型: 去掉變量名,所剩就是指針類型,可知 pn 的指針類型為 int * ;

指針所指數據類型: 去掉變量名及 *, int 即為指針指向數據的類型;

指針指向的內存區:指針指向數據類型為多大,那麼指針指向的內存區大小就為多少。

指針在內存中所占空間大小:在 32位的系統上, 指針在內存中始終是一個占了4個字節的數據類型。

再介紹下C裡面特殊類型的指針,void指針:

void的意思就是“無值”或“無類型”。void指針一般稱為“通用指針”或“泛指針”, 使用void指針可以很容易地把void指針轉換成其它數據類型的指針.

分析指針,可以從變量名處起,根據運算符優先級結合,一步一步分析.

下面舉例分析:

int p;//這是一個普通的整型變量

int *p;//首先從P處開始,先與*結合,所以說明P是一個指針,然後再與int結合,說明指針所指向的內容的類型為int型.所以P是一個返回整型數據的指針

int p[3];//首先從P處開始,先與[]結合,說明P是一個數組,然後與int結合,說明數組裡的元素是整型的,所以P是一個由整型數據組成的數組

int *p[3];//首先從P處開始,先與[]結合,因為其優先級比*高,所以P是一個數組,然後再與*結合,說明數組裡的元素是指針類型,然後再與int結合,說明指針

所指向的內容的類型是整型的,所以是一個由返回整型數據的指針所組成的數組

int (*p)[3];//首先從P處開始,先與*結合,說明P是一個指針然後再與[]結合(與"()"這步可以忽略,只是為了改變優先級),說明指針所指向的內容是一個數組,

然後再與int結合,說明數組裡的元素是整型的.所以P是一個指向由整型數據組成的數組的指針

int **p;//首先從P開始,先與*結合,說明P是一個指針,然後再與*結合,說明指針所指向的元素是指針,然後再與int結合,說明該指針所指向的元素是整型數據.

所以P是一個返回指向整型數據的指針的指針

int p(int);//從P處起,先與()結合,說明P是一個函數,然後進入()裡分析,說明該函數有一個整型變量的參數然後再與外面的int結合,說明函數的返回值是一個

整型數據.所以P是一個有整型參數且返回類型為整型的函數

int (*p)(int);//從P處開始,先與指針結合,說明P是一個指針,然後與()結合,說明指針指向的是一個函數,然後再與()裡的int結合,說明函數有一個int型的參數,

再與最外層的int結合,說明函數的返回類型是整型,所以P是一個指向有一個整型參數且返回類型為整型的函數的指針

int *(*p(int))[3];//從P開始,先與()結合,說明P是一個函數,然後進入()裡面,與int結合,說明函數有一個整型變量參數,然後再與外面的*結合,說明函數返回的

是一個指針,,然後到最外面一層,先與[]結合,說明返回的指針指向的是一個數組,然後再與*結合,說明數組裡的元素是指針,然後再與int結合,說明指針指向的

內容是整型數據.所以P是一個參數為一個整數且返回一個指向由整型指針變量組成的數組的指針變量的函數

2.指針的算術運算

有效的指針運算包括相同類型指針之間的賦值運算、指針同整數之間的加法或減法運算、指向相同數組中元素的兩個指針間的減法或比較運算、將指針賦

值為0或指針與0之間的比較運算。其它形式的指針運算都是非法的,例如兩個指針間的加法、乘法等。

指針可以進行加或減運算,例如:

pnew=pold + n

一個指針 pold加(減)一個整數 n後,結果是一個新的指針pnew,其中pnew和 pold的類型相同及其所指向的類型也相同,pnew的值將比 pold的值增加(減少)

了n乘sizeof(pold所指向的類型)個字節。即pnew=pold+n

即 pnew表示指針pold當前指向的對象之後第n個對象(pold所指類型的對象)的地址。

3. 運算符&和*

&是取地址運算符,*是...書上叫做“間接運算符”(c++叫解引用)。

&n的運算結果是一個指針,指針的類型是n的類型加個*,指針所指向的類型是n的類型,指針所指向的地址,那就是n的地址。

*p 的結果是 p所指向的東西。*p,它的類型是 p指向的類型,它所占用的地址是p所指向的地址。

int n=1;

p=&n;//&n的結果是一個指針,類型是int*,指向的類型是int,指向的地址是n的地址。

4. 指針表達式

一個表達式的最後結果如果是一個指針,那麼這個表達式就叫指針表達式。

char *str[32];

char **pstr=str;//如果把str看作指針的話,str也是指針表達式

char *str;

str=*pstr;//*pstr是指針表達式

str=*(pstr+1);//*(pstr+1)是指針表達式

由於指針表達式的結果是一個指針,所以指針表達式也具有指針所具有的四個要素:指針的類型,指針所指向的類型,

指針指向的內存區,指針自身占據的內存。

5. 數組和指針的關系

數組的數組名其實可以看作一個指針。

定義一個數組int A[]={1,2, 3};

則數A就有了兩重含義:

第一,它代表整個數組,它的類型是 int [3];

第二,它是一個常量指針,該指針的類型是int*,該指針指向的類型是 int,也就是數組單元的類型,該指針指向的內存區就是數組第0號單元,

該指針自己占有單獨的內存區,注意它和數組第0號單元占據的內存區是不同的。該指針的值是不能修改的,即類似 A++的表達式是錯誤的。

此外數組在作為函數參數時,數組名將蛻化為指針。

例如:

void fun(char arr[12]);

在編譯時編譯器會當成是:

void fun(char *arr);//你在這個函數中使用sizeof(arr)得到的值是4

6. 指針和結構類型的關系

struct My
{
    int a;
    int b;
};

假設我們定義了上面的結構體,同時定義結構體的結構對象並初始化,struct My ss={10,20};
那麼我們如何通過指針ptr來訪問ss的三個成員變量呢?
答案就是,我們先定義一個指向結構對象ss的指針,struct MyStruct *ptr=&ss;然後,使用指向運算符->便可實現對結構對象ss成員的訪問。
ptr->a;//或者可以這們(*ptr).a,建議使用前者
ptr->b;

7. 指針和函數的關系

可以把一個指針聲明成為一個指向函數的指針,從而通過函數指針調用函數。

例如:

int fun(char *,int);
int (*pfun)(char *,int);
pfun=fun;
int a=(*pfun)("abc", 3);

例中,定義了一個指向函數fun的指針pfun,把pfun作為函數的形參。把指針表達式作為實參,從而實現了對函數fun的調用。

在這裡不得不說,C裡面的回調函數。

回調函數:就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,

我們就說這是回調函數。

使用回調函數原因:

可以把調用者與被調用者分開。調用者不關心誰是被調用者,所有它需知道的,只是存在一個具有某種特定原型、某些限制條件(如返回值為int)的被

調用函數。

舉例:

typedef int (*Fun)(char *p); // 為回調函數命名,類型命名為Fun,參數為char *p

int Afun(void *p)
{    // 方法 Afun,格式符合Fun 的格式
    printf("CallBack:%s!\n", (char*)p);
    return 0;
}

void call(char *p, int (*f)(void *))
{
    f(p);//回調
}
int main()
{
    call("123", Afun);
    return 0;
}

8. 指針類型轉換

當我們初始化一個指針或給一個指針賦值時,賦值號的左邊是一個指針,賦值號的右邊是一個指針表達式,這就要求兩邊的類型一致,所指向的類型也一致,

如果不一致的話,需要進行強制類型轉換。語法格式是:(TYPE *)p;

這樣強制類型轉換的結果是一個新指針,該新指針的類型是TYPE *,它指向的類型是TYPE,它指向的地址就是原指針指向的地址。要注意的是,原來的指針

p的一切屬性都沒有被修改。另外,一個函數如果使用了指針作為形參,那麼在函數調用語句的實參和形參的結合過程中,也必須保證類型一致,否則需要強

制轉換。

9.指針應用

int (*a)[10] 和 int *a[10] 什麼區別

int *a[10] :數組指針。數組a裡存放的是10個int型指針

int (*a)[10] :a是指針,指向一個數組。此數組有10個int型元素


int *a[10]

先找到聲明符a,然後向右看,有[]說明a是個數組,再向左看,是int *,說明數組中的每個元素是int *。所以這是一個存放int指針的數組。

int(*a)[10]

先找到聲明符a,被括號括著,先看括號內的(優先級高),然後向右看,沒有,向左看,是*,說明a是個指針,什麼指針?在看括號外面的,先向右看,有[] 是個數組,

說明a是個指向數組的指針,再向左看,是int,說明數組的每個元素是int。所以,這是一個指向存放int的數組的指針。

例:

int   *a[10];
int   (*b)[10];

printf("*a[10]:   %d\n ",   sizeof(a));
printf("(*b)[10]:   %d\n ",   sizeof(b));

結果是:

*a[10]: 40 //說明a是一個數組名

(*b)[10]: 4 //說明b是一個指針

10.右左法:應用分析指針的復雜申明

上面第9 說明了指針的簡單應用,下面介紹復雜的應用。

右左法則:

“The right-left rule:Start reading the declaration from the innermost parentheses, go right, andthen go left. When you encounter parentheses, the direction

should be reversed.Once everything in the parentheses has been parsed, jump out of it. Continue tillthe whole declaration has been parsed.”

翻譯後為:

首先從最裡面的圓括號看起,然後往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號裡面所有的東西,就跳出圓括號。重復這個過

程直到整個聲明解析完畢。

對於這句話應該是從未定義的標識符開始閱讀,而不是從括號讀起,之所以是未定義的標識符,是因為一個聲明裡面可能有多個標識符,但未定義的標識符只會有

一個。

如:

Int (*f)(int n)

確定括號的含義是分析這個聲明的重要步驟,這個聲明有兩個括號,每個括號作用不同。

首先找到未定義的標識符f,它向右是括號,然後向左,是 *, 即 f是個指針。然後向右為(int n), 這個括號為函數調用操作符。所以f是個指向函數的指針,然後再向左,

碰到int 即為“這個函數的返回類型為整型”,把上面的解析連起來即 f為一個包含參數int n的函數指針,它所指向的函數返回一個整型值。

int (*f[5])(int*p);

f右邊是一個[]運算符,說明f是一個具有5個元素的數組,f的左邊有一個*,說明f的元素是指針,要注意這裡的*不是修飾 f的,而是修飾f[5]的,原因是[]運算符優先級比*高,

f先跟[]結合,因此*修飾的是f[5]。跳出這個括號,看右邊,也是一對圓括號,說明f數組的元素是函數類型的指針,它所指向的函數具有int*類型的形參,返回值類型為int。

int(*(*func)[5])(int *p);

func被一個圓括號包含,左邊又有一個*,那麼func是一個指針,跳出括號,右邊是一個[]運算符號,說明func是一個指向數組的指針,現在往左看,左邊有一個*號,說明

這個數組的元素是指針,再跳出括號,右邊又有一個括號,說明這個數組的元素是指向函數的指針。總結一下,就是:func是一個指向數組的指針,這個數組的元素是函

數指針,這些指針指向具有int*形參,返回值為int類型的函數。


參考:

http://harborwan.blog.sohu.com/19248423.html

http://blog.csdn.net/megaboy/article/details/482771

<>


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