c語(yǔ)言相比其他高級(jí)語(yǔ)言來(lái)說(shuō)饿肺,更接近于對(duì)計(jì)算機(jī)硬件的操作,而指針的應(yīng)用更是為我們對(duì)硬件的操作插上了翅膀卷中,所以指針是嵌入式編程不可少的一部分矛双,在一定意義上說(shuō),指針是c語(yǔ)言的精髓蟆豫。
歡迎加入嵌入式學(xué)習(xí)群:559601187
一议忽、 什么是指針
在計(jì)算機(jī)中,數(shù)據(jù)時(shí)存放在內(nèi)存中的十减,而內(nèi)存其實(shí)就是一組有序字節(jié)組成的數(shù)組栈幸,一般以一個(gè)字節(jié)為一個(gè)內(nèi)存單元,每個(gè)字節(jié)都有唯一的地址嫉称。cpu通過(guò)尋址的方式去查找內(nèi)存中某個(gè)變量的位置侦镇,我們知道定義變量就是向CPU申請(qǐng)一個(gè)某一類型的空間,這個(gè)空間也有自己的地址织阅,同樣地址也需要一種類型去存儲(chǔ)壳繁,C語(yǔ)言規(guī)定用指針類型的變量去存儲(chǔ)地址類型。記住一點(diǎn):指針就是地址荔棉,指針變量時(shí)存放地址類型的變量闹炉。
二、指針變量的定義
2.1 聲明并初始化一個(gè)指針
可以保存地址值的變量稱為指針變量润樱,指針變量定義如下:
數(shù)據(jù)類型 * 變量名
這里的數(shù)據(jù)類型為基本數(shù)據(jù)類型渣触、構(gòu)造類型,指針變量的聲明比普通變量的聲明多了一個(gè)' * ',運(yùn)算符' * '就是間接引用或間接尋址壹若。例如:
int *p; // 聲明一個(gè) int 類型的指針 p
char *p // 聲明一個(gè) char 類型的指針 p
int *arr[10] // 聲明一個(gè)指針數(shù)組嗅钻,該數(shù)組有10個(gè)元素,其中每個(gè)元素都是一個(gè)指向 int 類型對(duì)象的指針
int (*arr)[10] // 聲明一個(gè)數(shù)組指針店展,該指針指向一個(gè) int 類型的一維數(shù)組
int **p; // 聲明一個(gè)指針 p 养篓,該指針指向一個(gè) int 類型的指針
在上面的聲明中:p就是一個(gè)指針變量,里面存著一個(gè)地址赂蕴。
這里要注意**指針在使用前一定要初始化柳弄,否則就會(huì)指針就會(huì)變成野指針。初始化有3種方式:
/* 方法1:使指針指向現(xiàn)有的內(nèi)存 */
int x = 1;
int *p = &x; // 指針 p 被初始化概说,指向變量 x 碧注,其中取地址符 & 用于產(chǎn)生操作數(shù)內(nèi)存地址
/* 方法2:動(dòng)態(tài)分配內(nèi)存給指針 */
int *p;
p = (int *)malloc(sizeof(int) * 10); // malloc 函數(shù)用于動(dòng)態(tài)分配內(nèi)存
free(p); // free 函數(shù)用于釋放一塊已經(jīng)分配的內(nèi)存,常與 malloc 函數(shù)一起使用糖赔,要使用這兩個(gè)函數(shù)需要頭文件 stdlib.h
/*方法3:定義為NULL */
int *p=NULL;
指針的初始化就是給指針一個(gè)合理的指向萍丐,讓程序知道往哪指,上述NULL是一個(gè)特殊的指針變量放典,相當(dāng)于0碉纺。地址為0的內(nèi)存一般都不允許訪問(wèn)船万,但是內(nèi)存地址為0有一個(gè)重要的意義,它表明指針指向不指向一個(gè)可訪問(wèn)的內(nèi)存地址骨田。
2.2 指針的調(diào)用
訪問(wèn)內(nèi)存空間,一般分為直接訪問(wèn)和間接訪問(wèn)声怔。
如果知道內(nèi)存空間的名字态贤,可通過(guò)名字訪問(wèn)該空間,稱為直接訪問(wèn)醋火。由于變量即代表有名字的內(nèi)存單元悠汽,故通。過(guò)變量名操作變量芥驳,也就是通過(guò)名字直接訪問(wèn)該變量對(duì)應(yīng)的內(nèi)存單元柿冲。
如果知道內(nèi)存空間的地址,也可以通過(guò)該地址間接訪問(wèn)該空間兆旬。對(duì)內(nèi)存空間的訪問(wèn)操作一般指的是存假抄、取操作,即向內(nèi)存空間中存入數(shù)據(jù)和從內(nèi)存空間中讀取數(shù)據(jù)丽猬。
在 C 語(yǔ)言中宿饱,可以使用間接訪問(wèn)符(取內(nèi)容訪問(wèn)符)*來(lái)訪問(wèn)指針?biāo)赶虻目臻g,例如:
int *p,a=3;//p中保存變量a對(duì)應(yīng)內(nèi)存單元的地址
p=&a;
在該地址 p 前面加上間接訪問(wèn)符 *,即代表該地址對(duì)應(yīng)的內(nèi)存單元脚祟。而變量 a 也對(duì)應(yīng)該內(nèi)存單元谬以,故 *p 就相當(dāng)于 a。
printf("a=%d\n",a); //通過(guò)名字由桌,直接訪問(wèn)變量a空間(讀取)
printf("a=%d\n",*p); //通過(guò)地址为黎,間接訪問(wèn)變量a空間(讀取)
*p=6;//等價(jià)于a=6;間接訪問(wèn)a對(duì)應(yīng)空間(存)
2.3 野指針
一般我們把沒有合法指向的指針?lè)Q為“野”指針行您。因?yàn)椤耙啊敝羔橂S機(jī)指向一塊空間铭乾,該空間中存儲(chǔ)的可能是其他程序的數(shù)據(jù)甚至是系統(tǒng)數(shù)據(jù),故不能對(duì)“野”指針?biāo)赶虻目臻g進(jìn)行存取操作邑雅,否則輕者會(huì)引起程序崩潰片橡,嚴(yán)重的可能導(dǎo)致整個(gè)系統(tǒng)崩潰。例如:
int *pi,a; //pi未初始化淮野,無(wú)合法指向捧书,為“野”指針
*pi=3; //運(yùn)行時(shí)錯(cuò)誤!不能對(duì)”野”指針指向的空間做存入操作骤星。該語(yǔ)句試圖把 3 存入“野”指針pi所指的隨機(jī)空間中经瓷,會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
a=*pi; //運(yùn)行時(shí)錯(cuò)誤洞难!不能對(duì)”野”指針指向的空間取操作舆吮。該語(yǔ)句試圖從“野”指針pi所指的空間中取出數(shù)據(jù),然后賦給變量a同樣會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
正確的應(yīng)該是:
pi=&a;//讓pi有合法的指向色冀,pi指向a變量對(duì)應(yīng)的空間
*pi=3;//把3間接存入pi所指向的變量a對(duì)應(yīng)的空間
三潭袱、指針的運(yùn)算
c指針的算術(shù)運(yùn)算只限兩種形式:
(1)指針+/-整數(shù)
可以對(duì)指針變量加減整數(shù),例如對(duì)指針變量p進(jìn)行p++,p--,p+i等操作锋恬,所得結(jié)果任然是一個(gè)指針屯换,只是指針p所指的內(nèi)存地址前進(jìn)或后退了i個(gè)操作數(shù)。
在上圖中假設(shè)p指向內(nèi)存10000008与学,p是一個(gè)int型指針可以看出對(duì)p進(jìn)行加減所指向的內(nèi)存彤悔。下面進(jìn)行一個(gè)例子來(lái)說(shuō)明指針變量自增自減運(yùn)算:
#include<stdio.h>
#include<string.h>
void main(void)
{
char str[]="hello";
char *p=str;
strcpy(str,"hello");
printf("str=%s\n",str);
printf("%c\n",*p);//打印結(jié)果為'h'
p=str;
strcpy(str,"hello");
printf("str=%s\n",str);
printf("%c\n",*p++);//打印結(jié)果為'h'
p=str;
strcpy(str,"hello");
printf("str=%s\n",str);
printf("%c\n",(*p)++);//打印結(jié)果為'h'
p=str;
strcpy(str,"hello");
printf("str=%s\n",str);
printf("%c\n",*(p++));//打印結(jié)果為'h'
p=str;
strcpy(str,"hello");
printf("str=%s\n",str);
printf("%c\n",++*p);//打印結(jié)果為'i'
p=str;
strcpy(str,"hello");
printf("str=%s\n",str);
printf("%c\n",*++p);//打印結(jié)果為'e'
}
對(duì)于指針++p和 p++來(lái)說(shuō)是依據(jù)就近原則運(yùn)算的,而對(duì)y=p++則相當(dāng)于y=p;p++;這里如果加上括號(hào)則為y=(p)++則相當(dāng)于y=p;(*p)++;
(2)指針-指針
只有當(dāng)兩個(gè)指針都指向同一個(gè)數(shù)組中的元素時(shí)索守,才允許從一個(gè)指針減去另一個(gè)指針晕窑。兩個(gè)指針相減的結(jié)果的類型是 ptrdiff_t,它是一種有符號(hào)整數(shù)類型卵佛。減法運(yùn)算的值是兩個(gè)指針在內(nèi)存中的距離(以數(shù)組元素的長(zhǎng)度為單位杨赤,而不是以字節(jié)為單位),因?yàn)闇p法運(yùn)算的結(jié)果將除以數(shù)組元素類型的長(zhǎng)度级遭。舉個(gè)例子:
#include "stdio.h"
int main(){
int a[10] = {1,2,3,4,5,6,7,8,9,0};
int sub;
int *p1 = &a[2];
int *p2 = &a[8];
sub = p2-p1;
printf("%d\n",sub); // 輸出結(jié)果為 6
return 0;
}
(3)指針的比較
指針在一定條件下可以比較望拖,這里的一定條件指兩個(gè)指針指向同一個(gè)對(duì)象才有意義,例如兩個(gè)指針變量p挫鸽,q指向同一數(shù)組说敏,則<,>,>=,<=,== 等關(guān)系運(yùn)算符都能正常運(yùn)行。若q==p為真丢郊,則表示p和q為同一元素盔沫;若p<q為真,則表示p所指向的數(shù)組元素在q所指向的數(shù)組元素之前枫匾。
四架诞、指針和數(shù)組
指針和數(shù)組的關(guān)系十分密切。在前面的文章(c語(yǔ)言二維指針數(shù)組詳解)中我們推算到a[i]=(a+i)*干茉,一般來(lái)說(shuō)通過(guò)數(shù)組完成的工作都可以用指針來(lái)完成谴忧,但是使用數(shù)組更容易理解。
4.1 一維數(shù)組與指針
一維數(shù)組的數(shù)組名表示該數(shù)組的首地址角虫,c語(yǔ)言中指針變量加1表示跳過(guò)該指針變量所指類型占用的空間大小沾谓。如果指針變量指向數(shù)組,那么指針加1表示指向數(shù)組的下一個(gè)元素戳鹅。
int *p;//聲明一個(gè)int類型的指針變量
int a[5];//聲明一個(gè)int型數(shù)組
p=a;//數(shù)組名表示數(shù)組首地址均驶,把數(shù)組首地址賦給指針變量藻雌,p指向數(shù)組的第0個(gè)元素a[0]
在上面的程序中赁炎,數(shù)組名等價(jià)于數(shù)組的首地址,即&a[0]拱燃。
訪問(wèn)數(shù)組的元素有三種方式:
(1)直接訪問(wèn): 數(shù)組名[下標(biāo)]的形式
int a[5]={1,2,3,4,5};
int b=0;
b=a[3];//b=4,直接使用數(shù)組下標(biāo)訪問(wèn)數(shù)組元素
(2)間接訪問(wèn):*(數(shù)組名+i)的形式
int a[5]={1,2,3,4,5};
int b=0;
b=*(a+3);//b=4腾它,直接使用*(數(shù)組名+i)訪問(wèn)
(3)間接訪問(wèn):*(指針變量)的形式
int a[5]={1,2,3,4,5};
int b=0;
int *p=a;
b=*(p+3);//b=4跑筝,直接使用指針間接訪問(wèn)數(shù)組元素
【例 1】通過(guò)指針變量實(shí)現(xiàn)對(duì)數(shù)組元素的輸入和輸出操作。
實(shí)例代碼為:
#include <stdio.h>
#define N 10
int main (void)
{
int *p,a[N],i;
p=a; //p初始指向a[0]
printf("Input the array:\n");
for(i=0;i<N;i++) //用整型變量i控制循環(huán)次數(shù)
scanf ("%d",p++); //指針P表示地址携狭,不能寫成&P
printf ("the array is :\n");
for(p=a;p<a+N;p++) //用p的移動(dòng)范圍控制循環(huán)次數(shù)
printf("%d\t", *p);
return 0;
}
4.2 二維數(shù)組與指針
二維數(shù)組實(shí)際上還是一維數(shù)組继蜡,它的存儲(chǔ)結(jié)構(gòu)仍是順序存儲(chǔ),即二維數(shù)組中的元素在內(nèi)存中的存儲(chǔ)地址是連續(xù)的逛腿,所以可以用指針變量訪問(wèn)數(shù)組的各個(gè)元素。具體的解釋請(qǐng)看(c語(yǔ)言二維指針數(shù)組詳解)
*(a[i] + j) <--> *(*(a + i) + j) <-->*&a[i][j]<-->a[i][j]
4.3 指針數(shù)組
無(wú)論是指針數(shù)組還是數(shù)組指針都看后面兩個(gè)字區(qū)分仅颇,后兩個(gè)字為數(shù)組单默,那么它是一個(gè)存放指針元素的數(shù)組。指針數(shù)組定義:
數(shù)據(jù)類型 *數(shù)組名[數(shù)組大小]
在上面的聲明中,由于[]的優(yōu)先級(jí)高于*忘瓦,所以先形成數(shù)組搁廓。
4.4 數(shù)組指針
同樣看后面兩個(gè)字知道它是一個(gè)指針,指向數(shù)組耕皮。聲明一個(gè)數(shù)組指針?lè)椒ㄈ缦拢?/p>
//數(shù)據(jù)類型 (* 數(shù)組名)[元素個(gè)數(shù)]
int (*p)[5];//聲明一個(gè)數(shù)組指針p境蜕,它指向含有5個(gè)int類型元素的二維數(shù)組
上面p指向二維數(shù)組,它指向二維數(shù)組的每一行
二維數(shù)組 a[M][N] 分解為一維數(shù)組元素 a[0]凌停、a[1]粱年、…、a[M-1] 之后罚拟,其每一行 a[i] 均是一個(gè)含 N 個(gè)元素的一維數(shù)組台诗。如果使用指向一維數(shù)組的指針來(lái)指向二維數(shù)組的每一行,通過(guò)該指針可以較方便地訪問(wèn)二維數(shù)組中的元素赐俗。
使用數(shù)組指針訪問(wèn)二維數(shù)組中的元素拉队。
#define M 3
#define N 4
int a[M][N],i,j;
int (*p)[N]=a; // 等價(jià)于兩條語(yǔ)句 int (*p)[N] ; p=a;
以上語(yǔ)句定義了 M 行 N 列的二維整型數(shù)組 a 及指向一維數(shù)組(大小為 N)的指針變量 p,并初始化為二維數(shù)組名 a阻逮,即初始指向二維數(shù)組的 0 行粱快。
i 行首地址與 i 行首元素地址的區(qū)別如下。
- i 行首元素的地址叔扼,是相對(duì)于 i 行首元素 a[i][0] 來(lái)說(shuō)的事哭,把這種具體元素的地址,稱為一級(jí)地址或一級(jí)指針币励,其值加 1表 示跳過(guò)一個(gè)數(shù)組元素慷蠕,即變?yōu)?a[i][1] 的地址。
- i 行首地址是相對(duì)于 i 行這一整行來(lái)說(shuō)的食呻,不是具體某個(gè)元素的地址流炕,是二級(jí)地址澎现,其值加 1 表示跳過(guò) 1 行元素對(duì)應(yīng)的空間。
- 對(duì)二級(jí)指針(某行的地址)做取內(nèi)容操作即變成一級(jí)指針(某行首元素的地址)每辟。
兩者的變換關(guān)系為:
*(i 行首地址)=i 行首元素地址
0 行首地址:p + 0 <--> a + 0
1 行首地址:p + 1 <--> a + 1
...
i 行首地址:p + i <--> a + i
i 行 0 列元素地址:*(p + i) +0 <—> *(a + i) +0 <—>&a[i][0]
i 行 1 列元素地址:* (p + i) +1 <--> *(a + i) +1 <—>&a[i][1]
...
i 行 j 列元素地址:* (p + i) + j <--> * (a + i) + j <--> &a[i][j]
i 行 j 列對(duì)應(yīng)元素:* (* (p + i) + j) <--> * (* (a + i) + j) <--> a[i][j]
由此可見剑辫,當(dāng)定義一個(gè)指向一維數(shù)組的指針 p,并初始化為二維數(shù)組名 a 時(shí)渠欺,即 p=a;妹蔽, 用該指針訪問(wèn)元素 a[i][j] 的兩種形式 ((p + i) + j) 與 ((a + i) + j) 非常相似,僅把 a 替換成了 p 而已挠将。
由于數(shù)組指針指向的是一整行胳岂,故數(shù)組指針每加 1 表示跳過(guò)一行,而二維字符數(shù)組中每一行均代表一個(gè)串舔稀,因此在二維字符數(shù)組中運(yùn)用數(shù)組指針能較便捷地對(duì)各個(gè)串進(jìn)行操作乳丰。
五、指針和函數(shù)
c語(yǔ)言函數(shù)參數(shù)傳遞有兩種方式:值傳遞和地址傳遞内贮。本節(jié)主要討論下地址傳遞产园,傳遞地址能夠改變主調(diào)函數(shù)對(duì)象中的值。
5.1指針函數(shù)
有時(shí)函數(shù)調(diào)用結(jié)束后夜郁,需要函數(shù)返回給調(diào)用者某個(gè)地址即指針類型什燕,以便于后續(xù)操作,這種函數(shù)返回類型為指針類型的函數(shù)竞端,通常稱為指針函數(shù)屎即。
指針函數(shù)的定義格式為:
類型*函數(shù)名(形參列表)
{
... /*函數(shù)體*/
}
5.2函數(shù)指針
C語(yǔ)言中,函數(shù)本身不是變量婶熬,但是可以定義指向函數(shù)的指針剑勾,也稱作函數(shù)指針,函數(shù)指針指向函數(shù)的入口地址赵颅。這種類型的指針可以被賦值虽另、存放在數(shù)組中、傳遞給函數(shù)以及作為函數(shù)的返回值等等饺谬。 聲明一個(gè)函數(shù)指針的方法如下:
返回值類型 (* 指針變量名)([形參列表]);
int (*pointer)(int *,int *); // 聲明一個(gè)函數(shù)指針
上述代碼聲明了一個(gè)函數(shù)指針 pointer 捂刺,該指針指向一個(gè)函數(shù),函數(shù)具有兩個(gè) int * 類型的參數(shù)募寨,且返回值類型為 int族展。
六、指針與字符串
6.1常量字符串與指針
常量字符串返回來(lái)的就是一個(gè)存放該字符串的首地址拔鹰。注意:常量字符串不能改變仪缸,即使通過(guò)指針也不能改變,因?yàn)樗娣旁谖淖殖A繀^(qū)列肢。假設(shè)字符串常量 "abcd" 表示一個(gè)指針恰画,那么該指針指向字符 'a'宾茂,表達(dá)式 "abcd"+1,是在指針 "abcd" 值的基礎(chǔ)上加 1拴还,故也是一個(gè)指針跨晴,指向字符串中第二個(gè)字符的指針常量。同理片林,"abcd"+3 表示指向第 4 個(gè)字符 'd' 的指針常量端盆。
我們還可以這樣定義:
char *p="hello";
【例1·】如下代碼段通過(guò)指針變量依次遍歷輸出所指串中每個(gè)字符
#include<stdio.h>
int main (void)
{
//初始指向首字符
//間接訪問(wèn)所指字符 //pc依次指向后面的字符
char *pc="hello,world!";
while (*pc! = '\0')
{
putchar(*pc);
pc++;
}
return 0;
}
6.2 變量字符串
我們想要存放可以改變的字符串,可以放在字符數(shù)組中费封。例如:
char str[]="i love china"
這里的str內(nèi)容可以改變焕妙,通過(guò)指針或者下標(biāo)都可以去操作其內(nèi)容。
七弓摘、總結(jié)
到這里指針的相關(guān)內(nèi)容已經(jīng)講解完了访敌,記住以下幾點(diǎn):
- 指針就是地址,指針變量存放的是地址類型的變量
- 定義指針和調(diào)用指針的*作用不一樣
- 在指針變量前每加一個(gè)* 表示取一次內(nèi)容衣盾,類似[],在調(diào)用時(shí)每加一個(gè)*表示取一次內(nèi)容
- 指針變量一定要初始化,c語(yǔ)言不允許野指針出現(xiàn)
- 指針指向字符串的首地址
- 注意辨別指針的自增自減操作
- 了解指針數(shù)組和數(shù)組指針爷抓、指針函數(shù)和函數(shù)指針的應(yīng)用
本文章僅供學(xué)習(xí)交流用禁止用作商業(yè)用途势决,文中內(nèi)容來(lái)水枂編輯,如需轉(zhuǎn)載請(qǐng)告知蓝撇,謝謝合作
微信公眾號(hào):zhjj0729
微博:文藝to青年