c語言相比其他高級語言來說,更接近于對計算機硬件的操作,而指針的應用更是為我們對硬件的操作插上了翅膀,所以指針是嵌入式編程不可少的一部分,在一定意義上說,指針是c語言的精髓。
一、 什么是指針
在計算機中,數據時存放在內存中的,而內存其實就是一組有序字節組成的數組,一般以一個字節為一個內存單元,每個字節都有唯一的地址。cpu通過尋址的方式去查找內存中某個變量的位置,我們知道定義變量就是向CPU申請一個某一類型的空間,這個空間也有自己的地址,同樣地址也需要一種類型去存儲,C語言規定用指針類型的變量去存儲地址類型。記住一點:指針就是地址,指針變量時存放地址類型的變量。
二、指針變量的定義
2.1 聲明并初始化一個指針
可以保存地址值的變量稱為指針變量,指針變量定義如下:
數據類型 * 變量名
這里的數據類型為基本數據類型、構造類型,指針變量的聲明比普通變量的聲明多了一個’ * ‘,運算符’ * '就是間接引用或間接尋址。例如:
int *p; // 聲明一個 int 類型的指針 p
char *p // 聲明一個 char 類型的指針 p
int *arr[10] // 聲明一個指針數組,該數組有10個元素,其中每個元素都是一個指向 int 類型對象的指針
int (*arr)[10] // 聲明一個數組指針,該指針指向一個 int 類型的一維數組
int **p; // 聲明一個指針 p ,該指針指向一個 int 類型的指針
在上面的聲明中:p就是一個指針變量,里面存著一個地址。
這里要注意**指針在使用前一定要初始化,否則就會指針就會變成野指針。初始化有3種方式:
/* 方法1:使指針指向現有的內存 */
int x = 1;
int *p = &x; // 指針 p 被初始化,指向變量 x ,其中取地址符 & 用于產生操作數內存地址/* 方法2:動態分配內存給指針 */
int *p;
p = (int *)malloc(sizeof(int) * 10); // malloc 函數用于動態分配內存
free(p); // free 函數用于釋放一塊已經分配的內存,常與 malloc 函數一起使用,要使用這兩個函數需要頭文件 stdlib.h/*方法3:定義為NULL */
int *p=NULL;
指針的初始化就是給指針一個合理的指向,讓程序知道往哪指,上述NULL是一個特殊的指針變量,相當于0。地址為0的內存一般都不允許訪問,但是內存地址為0有一個重要的意義,它表明指針指向不指向一個可訪問的內存地址。
2.2 指針的調用
訪問內存空間,一般分為直接訪問和間接訪問。
如果知道內存空間的名字,可通過名字訪問該空間,稱為直接訪問。由于變量即代表有名字的內存單元,故通。過變量名操作變量,也就是通過名字直接訪問該變量對應的內存單元。
如果知道內存空間的地址,也可以通過該地址間接訪問該空間。對內存空間的訪問操作一般指的是存、取操作,即向內存空間中存入數據和從內存空間中讀取數據。
在 C 語言中,可以使用間接訪問符(取內容訪問符)*來訪問指針所指向的空間,例如:
int *p,a=3;//p中保存變量a對應內存單元的地址
p=&a;
在該地址 p 前面加上間接訪問符 *,即代表該地址對應的內存單元。而變量 a 也對應該內存單元,故 *p 就相當于 a。
printf("a=%d\n",a); //通過名字,直接訪問變量a空間(讀取)
printf("a=%d\n",*p); //通過地址,間接訪問變量a空間(讀取)
*p=6;//等價于a=6;間接訪問a對應空間(存)
2.3 野指針
一般我們把沒有合法指向的指針稱為“野”指針。因為“野”指針隨機指向一塊空間,該空間中存儲的可能是其他程序的數據甚至是系統數據,故不能對“野”指針所指向的空間進行存取操作,否則輕者會引起程序崩潰,嚴重的可能導致整個系統崩潰。例如:
int *pi,a; //pi未初始化,無合法指向,為“野”指針
*pi=3; //運行時錯誤!不能對”野”指針指向的空間做存入操作。該語句試圖把 3 存入“野”指針pi所指的隨機空間中,會產生運行時錯誤。
a=*pi; //運行時錯誤!不能對”野”指針指向的空間取操作。該語句試圖從“野”指針pi所指的空間中取出數據,然后賦給變量a同樣會產生運行時錯誤。
正確的應該是:
pi=&a;//讓pi有合法的指向,pi指向a變量對應的空間
*pi=3;//把3間接存入pi所指向的變量a對應的空間
三、指針的運算
c指針的算術運算只限兩種形式:
(1)指針+/-整數
可以對指針變量加減整數,例如對指針變量p進行p++,p–,p+i等操作,所得結果任然是一個指針,只是指針p所指的內存地址前進或后退了i個操作數。
在上圖中假設p指向內存10000008,p是一個int型指針可以看出對p進行加減所指向的內存。下面進行一個例子來說明指針變量自增自減運算:
#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);//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*p++);//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",(*p)++);//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*(p++));//打印結果為'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",++*p);//打印結果為'i'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*++p);//打印結果為'e'}
對于指針++*p和 *p++來說是依據就近原則運算的,而對y=*p++則相當于y=*p;p++;這里如果加上括號則為y=(*p)++則相當于y=*p;(*p)++;
(2)指針-指針
只有當兩個指針都指向同一個數組中的元素時,才允許從一個指針減去另一個指針。兩個指針相減的結果的類型是 ptrdiff_t,它是一種有符號整數類型。減法運算的值是兩個指針在內存中的距離(以數組元素的長度為單位,而不是以字節為單位),因為減法運算的結果將除以數組元素類型的長度。舉個例子:
#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); // 輸出結果為 6return 0;
}
(3)指針的比較
指針在一定條件下可以比較,這里的一定條件指兩個指針指向同一個對象才有意義,例如兩個指針變量p,q指向同一數組,則<,>,>=,<=,== 等關系運算符都能正常運行。若q==p為真,則表示p和q為同一元素;若p<q為真,則表示p所指向的數組元素在q所指向的數組元素之前。
四、指針和數組
指針和數組的關系十分密切。在前面的文章(c語言二維指針數組詳解)中我們推算到a[i]=*(a+i),一般來說通過數組完成的工作都可以用指針來完成,但是使用數組更容易理解。
4.1 一維數組與指針
一維數組的數組名表示該數組的首地址,c語言中指針變量加1表示跳過該指針變量所指類型占用的空間大小。如果指針變量指向數組,那么指針加1表示指向數組的下一個元素。
int *p;//聲明一個int類型的指針變量
int a[5];//聲明一個int型數組
p=a;//數組名表示數組首地址,把數組首地址賦給指針變量,p指向數組的第0個元素a[0]
在上面的程序中,數組名等價于數組的首地址,即&a[0]。
訪問數組的元素有三種方式:
(1)直接訪問: 數組名[下標]的形式
int a[5]={1,2,3,4,5};
int b=0;
b=a[3];//b=4,直接使用數組下標訪問數組元素
(2)間接訪問:*(數組名+i)的形式
int a[5]={1,2,3,4,5};
int b=0;
b=*(a+3);//b=4,直接使用*(數組名+i)訪問
(3)間接訪問:*(指針變量)的形式
int a[5]={1,2,3,4,5};
int b=0;
int *p=a;
b=*(p+3);//b=4,直接使用指針間接訪問數組元素
【例 1】通過指針變量實現對數組元素的輸入和輸出操作。
實例代碼為:
#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控制循環次數scanf ("%d",p++); //指針P表示地址,不能寫成&Pprintf ("the array is :\n");for(p=a;p<a+N;p++) //用p的移動范圍控制循環次數printf("%d\t", *p);return 0;
}
4.2 二維數組與指針
二維數組實際上還是一維數組,它的存儲結構仍是順序存儲,即二維數組中的元素在內存中的存儲地址是連續的,所以可以用指針變量訪問數組的各個元素。具體的解釋請看(c語言二維指針數組詳解)
*(a[i] + j) <--> *(*(a + i) + j) <-->*&a[i][j]<-->a[i][j]
4.3 指針數組
無論是指針數組還是數組指針都看后面兩個字區分,后兩個字為數組,那么它是一個存放指針元素的數組。指針數組定義:
數據類型 *數組名[數組大小]
在上面的聲明中,由于[]的優先級高于*,所以先形成數組。
4.4 數組指針
同樣看后面兩個字知道它是一個指針,指向數組。聲明一個數組指針方法如下:
//數據類型 (* 數組名)[元素個數]
int (*p)[5];//聲明一個數組指針p,它指向含有5個int類型元素的二維數組
上面p指向二維數組,它指向二維數組的每一行
二維數組 a[M][N] 分解為一維數組元素 a[0]、a[1]、…、a[M-1] 之后,其每一行 a[i] 均是一個含 N 個元素的一維數組。如果使用指向一維數組的指針來指向二維數組的每一行,通過該指針可以較方便地訪問二維數組中的元素。
使用數組指針訪問二維數組中的元素。
#define M 3
#define N 4
int a[M][N],i,j;
int (*p)[N]=a; // 等價于兩條語句 int (*p)[N] ; p=a;
以上語句定義了 M 行 N 列的二維整型數組 a 及指向一維數組(大小為 N)的指針變量 p,并初始化為二維數組名 a,即初始指向二維數組的 0 行。
i 行首地址與 i 行首元素地址的區別如下。
- i 行首元素的地址,是相對于 i 行首元素 a[i][0] 來說的,把這種具體元素的地址,稱為一級地址或一級指針,其值加 1表 示跳過一個數組元素,即變為 a[i][1] 的地址。
- i 行首地址是相對于 i 行這一整行來說的,不是具體某個元素的地址,是二級地址,其值加 1 表示跳過 1 行元素對應的空間。
- 對二級指針(某行的地址)做取內容操作即變成一級指針(某行首元素的地址)。
兩者的變換關系為:
*(i 行首地址)=i 行首元素地址0 行首地址:p + 0 <--> a + 0
1 行首地址:p + 1 <--> a + 1
...
i 行首地址:p + i <--> a + ii 行 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 列對應元素:* (* (p + i) + j) <--> * (* (a + i) + j) <--> a[i][j]
由此可見,當定義一個指向一維數組的指針 p,并初始化為二維數組名 a 時,即 p=a;, 用該指針訪問元素 a[i][j] 的兩種形式 ((p + i) + j) 與 ((a + i) + j) 非常相似,僅把 a 替換成了 p 而已。
由于數組指針指向的是一整行,故數組指針每加 1 表示跳過一行,而二維字符數組中每一行均代表一個串,因此在二維字符數組中運用數組指針能較便捷地對各個串進行操作。
五、指針和函數
c語言函數參數傳遞有兩種方式:值傳遞和地址傳遞。本節主要討論下地址傳遞,傳遞地址能夠改變主調函數對象中的值。
5.1指針函數
有時函數調用結束后,需要函數返回給調用者某個地址即指針類型,以便于后續操作,這種函數返回類型為指針類型的函數,通常稱為指針函數。
指針函數的定義格式為:
類型*函數名(形參列表)
{... /*函數體*/
}
5.2函數指針
C語言中,函數本身不是變量,但是可以定義指向函數的指針,也稱作函數指針,函數指針指向函數的入口地址。這種類型的指針可以被賦值、存放在數組中、傳遞給函數以及作為函數的返回值等等。 聲明一個函數指針的方法如下:
返回值類型 (* 指針變量名)([形參列表]);int (*pointer)(int *,int *); // 聲明一個函數指針
上述代碼聲明了一個函數指針 pointer ,該指針指向一個函數,函數具有兩個 int * 類型的參數,且返回值類型為 int。
六、指針與字符串
6.1常量字符串與指針
常量字符串返回來的就是一個存放該字符串的首地址。**注意:**常量字符串不能改變,即使通過指針也不能改變,因為它存放在文字常量區。假設字符串常量 “abcd” 表示一個指針,那么該指針指向字符 ‘a’,表達式 “abcd”+1,是在指針 “abcd” 值的基礎上加 1,故也是一個指針,指向字符串中第二個字符的指針常量。同理,“abcd”+3 表示指向第 4 個字符 ‘d’ 的指針常量。
我們還可以這樣定義:
char *p="hello";
【例1·】如下代碼段通過指針變量依次遍歷輸出所指串中每個字符
#include<stdio.h>
int main (void)
{//初始指向首字符//間接訪問所指字符 //pc依次指向后面的字符char *pc="hello,world!";while (*pc! = '\0'){putchar(*pc);pc++;
}return 0;
}
6.2 變量字符串
我們想要存放可以改變的字符串,可以放在字符數組中。例如:
char str[]="i love china"
這里的str內容可以改變,通過指針或者下標都可以去操作其內容。
七、總結
到這里指針的相關內容已經講解完了,記住以下幾點:
- 指針就是地址,指針變量存放的是地址類型的變量
- 定義指針和調用指針的*作用不一樣
- 在指針變量前每加一個* 表示取一次內容,類似[],在調用時每加一個*表示取一次內容
- 指針變量一定要初始化,c語言不允許野指針出現
- 指針指向字符串的首地址
- 注意辨別指針的自增自減操作
- 了解指針數組和數組指針、指針函數和函數指針的應用
本文章僅供學習交流用禁止用作商業用途,文中內容來水枂編輯,如需轉載請告知,謝謝合作
微信公眾號:zhjj0729
微博:文藝to青年