新聞中心
指針在C語言中是一塊很重要的內(nèi)容,也是比較難理解的一塊內(nèi)容,我們需要反復學習反復鞏固才可以對其有所了解。

之前也分享過指針相關(guān)的筆記,但是都比較雜,本篇筆記匯總一下指針相關(guān)的內(nèi)容,包含了挺多指針相關(guān)的基礎(chǔ)知識點。筆記有點長,可以收藏下來慢慢閱讀。
復雜類型說明
以下這部分內(nèi)容主要來自《讓你不再害怕指針》:
要了解指針,多多少少會出現(xiàn)一些比較復雜的類型,所以,先介紹一下如何完全理解一個復雜類型,要理解復雜類型其實很簡單。
一個類型里會出現(xiàn)很多運算符,他們也像普通的表達式一樣,有優(yōu)先級,其優(yōu)先級和運算優(yōu)先級一樣,所以我總結(jié)了一下其原則: 從變量名處起,根據(jù)運算符優(yōu)先級結(jié)合,一步一步分析。
下面讓我們先從簡單的類型開始慢慢分析吧:
int p;
這是一個普通的整型變量 。
int *p;
首先從 P處開始,先與* 結(jié)合,所以說明 P 是一個指針。
然后再與 int 結(jié)合,說明指針所指向的內(nèi)容的類型為 int 型。所以 P 是一個返回整型數(shù)據(jù)的指針。
int p[3];
首先從 P 處開始,先與[]結(jié)合,說明 P 是一個數(shù)組,然后與 int 結(jié)合,說明數(shù)組里的元素是整型的,所以 P 是一個由整型數(shù)據(jù)組成的數(shù)組。
int *p[3];
首先從 P 處開始,先與[]結(jié)合,因為其優(yōu)先級比 * 高。所以 P 是一個數(shù)組,然后再與 * 結(jié)合,說明數(shù)組里的元素是指針類型。
然后再與 int 結(jié)合,說明指針所指向的內(nèi)容的類型是整型的,所以P 是一個由返回整型數(shù)據(jù)的指針所組成的數(shù)組 。
int (*p)[3];
首先從 P 處開始,先與 * 結(jié)合,說明 P 是一個指針然后再與[]結(jié)合與"()"這步可以忽略,只是為了改變優(yōu)先級)。
說明指針所指向的內(nèi)容是一個數(shù)組,然后再與 int 結(jié)合,說明數(shù)組里的元素是整型的。所以 P 是一個指向由整型數(shù)據(jù)組成的數(shù)組的指針。
int **p;
首先從 P 開始,先與后再與 * 結(jié)合,說明指針所指向的元素是指針,然后再與 int 結(jié)合,說明該指針所指向的元素是整型數(shù)據(jù)。
由于二級指針以上的指針極少用在復雜的類型中,所以后面更復雜的類型我們就不考慮多級指針了,最多只考慮一級指針。
int p(int);
從 P 處起,先與()結(jié)合,說明 P 是一個函數(shù),然后進入()里分析,說明該函數(shù)有一個整型變量的參數(shù)然后再與外面的 int 結(jié)合,說明函數(shù)的返回值是一個整型數(shù)據(jù)。
int (*p)(int);
從 P 處開始,先與指針結(jié)合,說明 P 是一個指針,然后與()結(jié)合,說明指針指向的是一個函數(shù),然后再與()里的int 結(jié)合,說明函數(shù)有一個 int 型的參數(shù),再與最外層的int 結(jié)合。
說明函數(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]
在指針的算術(shù)運算中, 指針所指向的類型有很大的作用。
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ù)。指針的這種運算的意義和通常的數(shù)值的加減運算的意義是不一樣的, 以單元為單位。
這在內(nèi)存上體現(xiàn)為:相對這個指針向后偏移多少個單位或向前偏移了多少個單位,這里的單位與指針變量的類型有關(guān)。
在32bit環(huán)境下,int類型占4個字節(jié),float占4字節(jié),double類型占8字節(jié),char占1字節(jié)。
【注意】一些處理整數(shù)的操作不能用來處理指針。例如,可以把兩個整數(shù)相乘,但是不能把兩個指針相乘。
示例程序
#include
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=%d\n", pa, pb, pc, pd);
//加法運算
pa += 2;
pb += 2;
pc += 2;
pd += 2;
printf("pa1=%d, pb1=%d, pc1=%d, pd1=%d\n", pa, pb, pc, pd);
//減法運算
pa -= 1;
pb -= 1;
pc -= 1;
pd -= 1;
printf("pa2=%d, pb2=%d, pc2=%d, pd2=%d\n", pa, pb, pc, pd);
return0;
}
運行結(jié)果為:
pa0=6422268, pb0=6422264, pc0=6422263, pd0=6422248
pa1=6422276, pb1=6422272, pc1=6422265, pd1=6422264
pa2=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))為單位地址向前向后偏移的??聪聢D:
如圖:pa1所指向的地址在pa0所指向地址往后8字節(jié)處,pa2指向地址在pa1指向地址往前4字節(jié)處。
從本示例程序中,還可以看出:連續(xù)定義的變量在內(nèi)存的存儲有可能是緊挨著的,有可能是分散著的。
數(shù)組和指針的聯(lián)系
數(shù)組與指針有很密切的聯(lián)系,常見的結(jié)合情況有以下三種:
- 數(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 也總是指向上一個元素。
下面示例證實了這一點:
運行結(jié)果為:
0, 1, 2, 3, 4
2、指針數(shù)組
指針數(shù)組:數(shù)組中每個元素都是指針。如:
int a=1,b=2,c=3;
int *arr[3] = {&a,&b,&c};
示例程序:
#include
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, %d\n", *arr[0], *arr[1], *arr[2]);
printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));
return0;
}
第一個 printf() 語句中,arr[i] 表示獲取第 i 個元素的值,該元素是一個指針,還需要在前面增加一個 * 才能取得它指向的數(shù)據(jù),也即 *arr[i] 的形式。
第二個 printf() 語句中,parr+i 表示第 i 個元素的地址,*(parr+i) 表示獲取第 i 個元素的值(該元素是一個指針),**(parr+i) 表示獲取第 i 個元素指向的數(shù)據(jù)。
指針數(shù)組還可以和字符串數(shù)組結(jié)合使用,請看下面的例子:
#include
int main(void)
{
char *str[3] =
{
"hello C",
"hello C++",
"hello Java"
};
printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
return0;
}
運行結(jié)果為:
hello C
hello 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
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]=%d\n", i, j, &a[i][j]);
}
}
return0;
}
運行結(jié)果為:
a[0][0]=6422216
a[0][1]=6422220
a[0][2]=6422224
a[0][3]=6422228
a[1][0]=6422232
a[1][1]=6422236
a[1][2]=6422240
a[1][3]=6422244
a[2][0]=6422248
a[2][1]=6422252
a[2][2]=6422256
a[2][3]=6422260
可見,每個元素的地址都是相差4個字節(jié),即每個連續(xù)在內(nèi)存中是連續(xù)存儲的。
按照以上定義可歸納出如下4個結(jié)論:
(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點,可得出如下結(jié)論:
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ù)組與指針常用的三種結(jié)合形式。
指針與數(shù)組的區(qū)別
數(shù)組與指針在多數(shù)情況是可以等價的,比如:
intarray[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
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] = %d\n", i, *p++);
}
// 數(shù)組遍歷方式二
for ( i = 0; i < 5; i++ )
{
printf("a[%d] = %d\n", i, *a++);
}
return0;
}
數(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] = %d\n", 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ū),是不可修改的。
3、區(qū)別三
求數(shù)組長度時,借用數(shù)組名可求得數(shù)組長度,而借用指針卻得不到數(shù)組長度。
請看如下代碼:
#include
int main(void)
{
int a[] = {0, 1, 2, 3, 4}, *p = a;
char len = 0;
// 求數(shù)組長度方式一
printf("方式一:len=%d\n",sizeof(a)/sizeof(int));
// 求數(shù)組長度方式二
printf("方式二:len=%d\n",sizeof(p)/sizeof(int));
return0;
}
運行結(jié)果
方式一: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ù)、指針這兩個詞結(jié)合的順序不同其意義也不同,即指針函數(shù)與函數(shù)指針的意義不同。
1、指針函數(shù)
指針函數(shù)的本質(zhì)是一個函數(shù),其返回值是一個指針。示例如下:
int *pfun(int, int);
由于“*”的優(yōu)先級低于“()”的優(yōu)先級,因而pfun首先和后面的“()”結(jié)合,也就意味著,pfun是一個函數(shù)。即:int *(pfun(int, int));
接著再和前面的“*”結(jié)合,說明這個函數(shù)的返回值是一個指針。由于前面還有一個int,也就是說,pfun是一個返回值為整型指針的函數(shù)。
指針函數(shù)示例程序如下:
#include
//這是一個指針函數(shù)的聲明
int *pfun(int *arr, int n);
int main(void)
{
intarray[] = {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] = %d\n", i, *(p+i));
}
return0;
}
//這是一個指針函數(shù),其返回值為指向整形的指針
int *pfun(int *arr, int n)
{
int *p = arr;
return p;
}
程序運行結(jié)果如下:
主函數(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
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 = %d\n", res);
return0;
}
int add(int a, int b)
{
return a + b;
}
程序運行結(jié)果如下:
其中,函數(shù)指針廣泛應用于嵌入式軟件開發(fā)中,其常用的兩個用途:調(diào)用函數(shù)和做函數(shù)的參數(shù)。
以上就是關(guān)于指針的的一些基礎(chǔ)知識總結(jié),如有錯誤歡迎指出!謝謝
當前題目:遇到指針別害怕!先把這篇筆記看一遍~
本文鏈接:http://www.dlmjj.cn/article/cdggshd.html


咨詢
建站咨詢
