指針在C語言中是一塊很重要的內(nèi)容,也是比較難理解的一塊內(nèi)容,我們需要反復理解反復鞏固才可以對其有所了解。之前也分享過指針相關的筆記,但是都比較雜,本篇筆記匯總一下指針相關的內(nèi)容,包含了挺多指針相關的基礎知識點。這篇筆記有點長,可以收藏下來慢慢閱讀。
復雜類型說明
以下這部分內(nèi)容主要來自《讓你不再害怕指針》:
要了解指針,多多少少會出現(xiàn)一些比較復雜的類型,所以,先介紹一下如何完全理解一個復雜類型,要理解復雜類型其實很簡單。
一個類型里會出現(xiàn)很多運算符,他們也像普通的表達式一樣,有優(yōu)先級,其優(yōu)先級和運算優(yōu)先級一樣,所以我總結了一下其原則: 從變量名處起,根據(jù)運算符優(yōu)先級結合,一步一步分析。
下面讓我們先從簡單的類型開始慢慢分析吧:
int p;
這是一個普通的整型變量 。
int *p;
首先從 P處開始,先與*結合,所以說明 P 是一個指針,然后再與 int 結合,說明指針所指向的內(nèi)容的類型為 int 型。所以 P 是一個返回整型數(shù)據(jù)的指針。
int p[3];
首先從 P 處開始,先與[]結合,說明 P 是一個數(shù)組,然后與 int 結合,說明數(shù)組里的元素是整型的,所以 P 是一個由整型數(shù)據(jù)組成的數(shù)組。
int *p[3];
首先從 P 處開始,先與[]結合,因為其優(yōu)先級比 * 高,所以 P 是一個數(shù)組,然后再與 * 結合,說明數(shù)組里的元素是指針類型,然后再與 int 結合,說明指針所指向的內(nèi)容的類型是整型的,所以P 是一個由返回整型數(shù)據(jù)的指針所組成的數(shù)組 。
int (*p)[3];
首先從 P 處開始,先與 * 結合,說明 P 是一個指針然后再與[]結合與"()"這步可以忽略,只是為了改變優(yōu)先級),說明指針所指向的內(nèi)容是一個數(shù)組,然后再與 int 結合,說明數(shù)組里的元素是整型的。所以 P 是一個指向由整型數(shù)據(jù)組成的數(shù)組的指針。
int **p;
首先從 P 開始,先與后再與 * 結合,說明指針所指向的元素是指針,然后再與 int 結合,說明該指針所指向的元素是整型數(shù)據(jù)。由于二級以上的指針極少用在復雜的類型中,所以后面更復雜的類型我們就不考慮多級指針了,最多只考慮一級指針。
int p(int);
從 P 處起,先與()結合,說明 P 是一個函數(shù),然后進入()里分析,說明該函數(shù)有一個整型變量的參數(shù)然后再與外面的 int 結合,說明函數(shù)的返回值是一個整型數(shù)據(jù)。
int (*p)(int);
從 P 處開始,先與指針結合,說明 P 是一個指針,然后與()結合,說明指針指向的是一個函數(shù),然后再與()里的int 結合,說明函數(shù)有一個 int 型的參數(shù),再與最外層的int 結合,說明函數(shù)的返回類型是整型,所以 P 是一個指向有一個整型參數(shù)且返回類型為整型的函數(shù)的指針。
說到這里也就差不多了,我們的任務也就這么多,理解了這幾個類型,其它的類型對我們來說也是小菜了。不過我們一般不會用太復雜的類型,那樣會大大減小程序的可讀性,請慎用,這上面的幾種類型已經(jīng)足夠我們用了。
分析指針的方法
指針是一個特殊的變量, 它里面存儲的數(shù)值被解釋成為內(nèi)存里的一個地址。要搞清一個指針需要搞清指針的四方面的內(nèi)容: 指針的類型、 指針所指向的類型、 指針的值(指針所指向的內(nèi)存區(qū))、 指針本身所占據(jù)的內(nèi)存區(qū)。 讓我們分別說明。
先聲明幾個指針放著做例子:
(1)int *ptr;(2)char*ptr;(3)int **ptr;(4)int (*ptr)[3];(5)int *(*ptr)[4];
1、指針的類型
從語法的角度看, 你只要把指針聲明語句里的指針名字去掉, 剩下的部分就是這個指針的類型。 這是指針本身所具有的類型。 讓我們看看例一中各個指針的類型:
(1)int*ptr;//指針的類型是 int*(2)char*ptr;//指針的類型是 char*(3)int**ptr;//指針的類型是 int**(4)int(*ptr)[3];//指針的類型是 int(*)[3](5)int*(*ptr)[4];//指針的類型是 int*(*)[4]
2、指針所指向的類型
當你通過指針來訪問指針所指向的內(nèi)存區(qū)時, 指針所指向的類型決定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當做什么來看待。
從語法上看, 你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉, 剩下的就是指針所指向的類型。例如:
(1)int*ptr; //指針所指向的類型是 int(2)char*ptr; //指針所指向的的類型是 char(3)int**ptr; //指針所指向的的類型是 int*(4)int(*ptr)[3]; //指針所指向的的類型是 int()[3](5)int*(*ptr)[4]; //指針所指向的的類型是 int*()[4]
在指針的算術運算中, 指針所指向的類型有很大的作用。
3、指針的值
指針的值是指針本身存儲的數(shù)值, 這個值將被編譯器當作一個地址, 而不是一個一般的數(shù)值。 在 32 位程序里, 所有類型的指針的值都是一個 32 位 整數(shù), 因為 32 位程序里內(nèi)存地址全都是 32 位長。
指針所指向的內(nèi)存區(qū)就是從指針的值所代表的那個內(nèi)存地址開始, 長度為 sizeof(指針所指向的類型)的一片內(nèi)存區(qū)。
以后, 我們說一個指針的值是 XX, 就相當于說該指針指向了以 XX 為首地址的一片內(nèi)存區(qū)域; 我們說一個指針指向了某塊內(nèi)存區(qū)域,就相當于說該指針的值是這塊內(nèi)存區(qū)域的首地址。
指針所指向的內(nèi)存區(qū)和指針所指向的類型是兩個完全不同的概念。 在例一中, 指針所指向的類型已經(jīng)有了, 但由于指針還未初始化, 所以它所指向的內(nèi)存區(qū)是不存在的, 或者說是無意義的。
以后, 每遇到一個指針, 都應該問問: 這個指針的類型是什么? 指針指向的類型是什么? 該指針指向了哪里? (重點注意) 。
4、指針本身所占據(jù)的內(nèi)存區(qū)
指針本身占了多大的內(nèi)存? 你只要用函數(shù) sizeof(指針的類型)測一下就知道了。 在 32 位平臺里, 指針本身占據(jù)了 4 個字節(jié)的長度。指針本身占據(jù)的內(nèi)存這個概念在判斷一個指針表達式(后面會解釋) 是否是左值時很有用。
指針的算術運算
指針可以加上或減去一個整數(shù)。 指針的這種運算的意義和通常的數(shù)值的加減運算的意義是不一樣的, 以單元為單位。
這在內(nèi)存上體現(xiàn)為:相對這個指針向后偏移多少個單位或向前偏移了多少個單位,這里的單位與指針變量的類型有關。在32bit環(huán)境下,int類型占4個字節(jié),float占4字節(jié),double類型占8字節(jié),char占1字節(jié)。
【注意】一些處理整數(shù)的操作不能用來處理指針。例如,可以把兩個整數(shù)相乘,但是不能把兩個指針相乘。
示例程序
#include <stdio.h>int main(void){int a = 10, *pa = &a;float b = 6.6, *pb = &b;char c = 'a', *pc = &c;double d = 2.14e9, *pd = &d;//最初的值printf("pa0=%d, pb0=%d, pc0=%d, pd0=%dn", pa, pb, pc, pd);//加法運算pa += 2; pb += 2; pc += 2;pd += 2;printf("pa1=%d, pb1=%d, pc1=%d, pd1=%dn", pa, pb, pc, pd);//減法運算pa -= 1; pb -= 1; pc -= 1;pd -= 1;printf("pa2=%d, pb2=%d, pc2=%d, pd2=%dn", pa, pb, pc, pd);return 0;}
運行結果為:
pa0=6422268, pb0=6422264, pc0=6422263, pd0=6422248pa1=6422276, pb1=6422272, pc1=6422265, pd1=6422264pa2=6422272, pb2=6422268, pc2=6422264, pd2=6422256
解析:
舉例說明pa0→pa1→pa2的過程,其他類似。pa0+2*sizeof(int)=pa1,pa1-1*sizeof(int)=pa2。因為pa為int類型的指針,所以加減運算是以4字節(jié)(即sizeof(int))為單位地址向前向后偏移的。看下圖:

如圖:pa1所指向的地址在pa0所指向地址往后8字節(jié)處,pa2指向地址在pa1指向地址往前4字節(jié)處。
從本示例程序中,還可以看出:連續(xù)定義的變量在內(nèi)存的存儲有可能是緊挨著的,有可能是分散著的。
數(shù)組和指針的聯(lián)系
數(shù)組與指針有很密切的聯(lián)系,常見的結合情況有以下三種:
- 數(shù)組指針
- 指針數(shù)組
- 二維數(shù)組指針
1、數(shù)組指針
數(shù)組指針:指向數(shù)組的指針。如:
int arr[] = {0,1,2,3,4};int *p = arr; //也可寫作int *p=&arr[0]
也就是說,p,arr,&arr[0]都是指向數(shù)組的開頭,即第0個元素的地址。
如果一個指針p指向一個數(shù)組arr[]的開頭,那么p+i為數(shù)組第i個元素的地址,即&arr[i],那么*(p+i)為數(shù)組第i個元素的值,即arr[i]。
同理,若指針p指向數(shù)組的第n個元素,那么p+i為第n+1個元素的地址;不管 p 指向了數(shù)組的第幾個元素,p+1 總是指向下一個元素,p-1 也總是指向上一個元素。
下面示例證實了這一點:
#include <stdio.h>int main(void){ int arr[] = {0, 1, 2, 3, 4}; int *p = &arr[3]; //也可以寫作 int *p = arr + 3; printf("%d, %d, %d, %d, %dn", *(p-3), *(p-2), *(p-1), *(p), *(p+1) ); return 0;}
運行結果為:
0, 1, 2, 3, 4
2、指針數(shù)組
指針數(shù)組:數(shù)組中每個元素都是指針。如:
int a=1,b=2,c=3;int *arr[3] = {&a,&b,&c};
示例程序:
#include <stdio.h>int main(void){ int a = 1, b = 2, c = 3; //定義一個指針數(shù)組 int *arr[3] = {&a, &b, &c};//也可以不指定長度,直接寫作 int *parr[] //定義一個指向指針數(shù)組的指針 int **parr = arr; printf("%d, %d, %dn", *arr[0], *arr[1], *arr[2]); printf("%d, %d, %dn", **(parr+0), **(parr+1), **(parr+2)); return 0;}
第一個 printf() 語句中,arr[i] 表示獲取第 i 個元素的值,該元素是一個指針,還需要在前面增加一個 * 才能取得它指向的數(shù)據(jù),也即 *arr[i] 的形式。
第二個 printf() 語句中,parr+i 表示第 i 個元素的地址,*(parr+i) 表示獲取第 i 個元素的值(該元素是一個指針),**(parr+i) 表示獲取第 i 個元素指向的數(shù)據(jù)。
指針數(shù)組還可以和字符串數(shù)組結合使用,請看下面的例子:
#include <stdio.h>int main(void){ char *str[3] = { "hello C", "hello C++", "hello JAVA" }; printf("%sn%sn%sn", str[0], str[1], str[2]); return 0;}
運行結果為:
hello Chello C++hello Java
3、二維數(shù)組指針
二維數(shù)組指針:指向二維數(shù)組的指針。如:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };int (*p)[4] = a;
a [3] [4]表示一個3行4列的二維數(shù)組,其所有元素在內(nèi)存中是連續(xù)存儲的。
請看如下程序:
#include <stdio.h>int main(void){ int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; int i,j; for( i = 0; i < 3; i++ ) { for( j = 0; j < 4; j++ ) { printf("a[%d][%d]=%dn", i, j, &a[i][j]); } } return 0;}
運行結果為:
a[0][0]=6422216a[0][1]=6422220a[0][2]=6422224a[0][3]=6422228a[1][0]=6422232a[1][1]=6422236a[1][2]=6422240a[1][3]=6422244a[2][0]=6422248a[2][1]=6422252a[2][2]=6422256a[2][3]=6422260
可見,每個元素的地址都是相差4個字節(jié),即每個連續(xù)在內(nèi)存中是連續(xù)存儲的。
按照以上定義可歸納出如下4個結論:
(1)p指向數(shù)組a的開頭,也即第1行;p+1前進一行,指向第2行。
(2)*(p+1)表示取第2行元素(一整行元素)。
(3)*(p+1)+1表示第2行第2個元素的地址。
(4)((p+1)+1)表示第2行第2個元素的值。
綜上4點,可得出如下結論:
a+i == p+i *(a+i) == *(p+i)a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j)== *(*(p+i)+j)
以上就是數(shù)組與指針常用的三種結合形式。
指針與數(shù)組的區(qū)別
數(shù)組與指針在多數(shù)情況是可以等價的,比如:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;value=array[0]; //也可寫成: value=*array;value=array[3]; //也可寫成: value=*(array+3);value=array[4]; //也可寫成: value=*(array+4)
但也有不等價的時候,比如如下三種情況:
- 數(shù)組名不可以改變,而指向數(shù)組的指針是可以改變的。
- 字符串指針指向的字符串中的字符是不能改變的,而字符數(shù)組中的字符是可以改變的。
- 求數(shù)組長度時,借用數(shù)組名可求得數(shù)組長度,而借用指針卻得不到數(shù)組長度。
1、區(qū)別一
數(shù)組名的指向不可以改變,而指向數(shù)組的指針是可以改變的。
請看如下代碼:
#include <stdio.h>int main(void){ int a[5] = {0, 1, 2, 3, 4}, *p = a; char i; // 數(shù)組遍歷方式一 for ( i = 0; i < 5; i++ ) { printf("a[%d] = %dn", i, *p++); } // 數(shù)組遍歷方式二 for ( i = 0; i < 5; i++ ) { printf("a[%d] = %dn", i, *a++); } return 0;}
數(shù)組遍歷方式一:使用指針遍歷數(shù)組元素,* p++等價于*(p++),即指針指向的地址每次后移一個單位,然后再取地址上的值。這里的一個單位是sizeof(int)個字節(jié)。
數(shù)組遍歷方式二:使用數(shù)組名自增遍歷數(shù)組元素,編譯出錯,錯誤如下:
error: value required as increment operand
因為數(shù)組名的指向是不可以改變的,使用自增運算符自增就會改變其指向,這是不對的,數(shù)組名只能指向數(shù)組的開頭。但是可以改為如下遍歷方式:
for ( i = 0; i < 5; i++ ){ printf("a[%d] = %dn", i, *(a+i));}
這可以正確遍歷數(shù)組元素。因為*(a+i)與a[i]是等價的。
2、區(qū)別二
字符串指針指向的字符串中的字符是不能改變的,而字符數(shù)組中的字符是可以改變的。
請看如下代碼:
//字符串定義方式一char str[] = "hAppy";//字符串定義方式二char *str = "happy";
字符串定義方式一:字符串中的字符是可以改變的。如可以使用類似str[3]='q'這樣的語句來改變其中的字符。原因就是:這種方式定義的字符串保存在全局數(shù)據(jù)區(qū)或棧區(qū),是可讀寫的。
字符串定義方式二:字符串中的字符是不可以改變的。原因就是:這種方式定義的字符串保存在常量區(qū),是不可修改的。
2、區(qū)別三
求數(shù)組長度時,借用數(shù)組名可求得數(shù)組長度,而借用指針卻得不到數(shù)組長度。
請看如下代碼:
#include <stdio.h>int main(void){ int a[] = {0, 1, 2, 3, 4}, *p = a; char len = 0; // 求數(shù)組長度方式一 printf("方式一:len=%dn",sizeof(a)/sizeof(int)); // 求數(shù)組長度方式二 printf("方式二:len=%dn",sizeof(p)/sizeof(int)); return 0;}
運行結果
方式一:len=5方式二:len=1
求數(shù)組長度方式一:借用數(shù)組名來求數(shù)組長度,可求得數(shù)組有5個元素,正確。
求數(shù)組長度方式二:借用指針求數(shù)組長度,求得長度為1,錯誤。原因是:
p只是一個指向int類型的指針,編譯器不知道其指向的是一個整數(shù)還是指向一個數(shù)組。sizeof(p)求得的是p這個指針變量本身所占用的字節(jié)數(shù),而不是整個數(shù)組占用的字節(jié)數(shù)。
下面還需要注意數(shù)組名的一個問題: 聲明了一個數(shù)組 TYPE array[n] , 則數(shù)組名是一個常量指針, 該指針的值是不能修改的, 即類似 array++的表達式是錯誤的。
指針函數(shù)與函數(shù)指針
函數(shù)、指針這兩個詞結合的順序不同其意義也不同,即指針函數(shù)與函數(shù)指針的意義不同。
1、指針函數(shù)
指針函數(shù)的本質(zhì)是一個函數(shù),其返回值是一個指針。示例如下:
int *pfun(int, int);
由于“*”的優(yōu)先級低于“()”的優(yōu)先級,因而pfun首先和后面的“()”結合,也就意味著,pfun是一個函數(shù)。即:int *(pfun(int, int));
接著再和前面的“*”結合,說明這個函數(shù)的返回值是一個指針。由于前面還有一個int,也就是說,pfun是一個返回值為整型指針的函數(shù)。
指針函數(shù)示例程序如下:
#include <stdio.h>//這是一個指針函數(shù)的聲明int *pfun(int *arr, int n);int main(void){ int array[] = {0, 1, 2, 3, 4}; int len = sizeof(array)/sizeof(array[0]); int *p; int i; //指針函數(shù)的調(diào)用 p = pfun(array, len); for (i = 0; i < len; i++) { printf("array[%d] = %dn", i, *(p+i)); } return 0;}//這是一個指針函數(shù),其返回值為指向整形的指針int *pfun(int *arr, int n){ int *p = arr; return p;}
程序運行結果如下:

主函數(shù)中,把一個數(shù)組的首地址與數(shù)組長度作為實參傳入指針函數(shù)pfun里,把指針函數(shù)的返回值(即指向數(shù)組的指針)賦給整形指針p。最后使用指針p來遍歷數(shù)組元素并打印輸出。
2、函數(shù)指針
函數(shù)指針其本質(zhì)是一個指針變量,該指針變量指向一個函數(shù)。C程序在編譯時,每一個函數(shù)都有一個入口地址,該入口地址就是函數(shù)指針所指向的地址。函數(shù)指針示例:
/*聲明一個函數(shù)指針 */int (*fptr) (int, int); /* 函數(shù)指針指向函數(shù)func */fptr = func; // 或者fptr = &func;
func是一個函數(shù)名,那么func與&func都表示的是函數(shù)的入口地址。同樣的,在函數(shù)的調(diào)用中可以使用:方式一:func(),也可以使用方式二:(*fun)()。這兩種調(diào)用方式是等價的,只是我們平時大多都習慣用方式一的調(diào)用方法。
至于為什么func與&func的含義相同,《嵌入式linux上的C語言編程實踐》這本書中有如下解釋:
對于函數(shù)func來說,函數(shù)的名稱就是函數(shù)代碼區(qū)的常量,對它取地址(&func)可以得到函數(shù)代碼區(qū)的地址,同時,func本身也可以視為函數(shù)代碼區(qū)的地址。因此,函數(shù)名稱和對其取地址其含義是相同的。
函數(shù)指針示例程序如下:
#include <stdio.h>int add(int a, int b);int main(void){ int (*fptr)(int, int); //定義一個函數(shù)指針 int res; fptr = add; //函數(shù)指針fptr指向函數(shù)add /* 通過函數(shù)指針調(diào)用函數(shù) */ res = (*fptr)(1,2); //等價于res = fptr(1,2); printf("a + b = %dn", res); return 0;}int add(int a, int b){ return a + b;}
程序運行結果如下:

以上就是關于指針函數(shù)與函數(shù)指針的簡單區(qū)分。其中,函數(shù)指針廣泛應用于嵌入式軟件開發(fā)中,其常用的兩個用途:調(diào)用函數(shù)和做函數(shù)的參數(shù)。
以上就是本次的分享,如有錯誤,歡迎指出!謝謝