指針是 C 語言的精髓,因為有了指針,C 語言可以和彙編語言比效率。在教學過程中,指針這部分內容常常給學生帶來困惑。下面我來說說指針在 C 及 C++語言中的用法。
上一篇文章,變量的三個要素之一,變量的值包括變量的數據值和變量的地址值。這個地址值也可以由另外的一個變量來存儲,這個變量就需要是指針變量。
指針的定義及相關術語
(1)指針用來存放某個變量的地址的值的變量,這個變量與一般變量不同,它所存放的值是某個變量在內存中的地址的值。
(2)一個指針變量存放了哪個變量的地址,就說這個指針指向了哪個變量。
(3)指針必須初始化或者賦值(指向了變量數據)後,才能進行間接訪問操作
內存空間的訪問方式:
取地址符:&
例:int x;
則&x 表示變量 x 在內存中的起始地址,
x 是整型數據,佔四個字節的內存單元
那麼&x 就可以存放在一個指針變量裡。這就需要定義一個指針變量。
語法格式
存儲類型 數據類型 *指針名=初始地址;
例:int *pi=&x;
說明:
(1)這個語句就定義了 pi 這個指針變量,pi 是指針名,int 是數據類型,是指這個指針變量要存放的是 int 類型變量的地址,其他類型變量的地址不可以給指針變量 pi 賦初值或賦值。
(2)語句雖然沒有給指針指定存儲類型,但是有默認的存儲類型 auto 類型,即存儲類型為自動類型,也就是函數級的自動類型變量,存放在棧空間,由編譯系統管理 pi 變量的所佔用的內存空間。
(3)這條聲明語句中的「」號非常重要,只有有了「」號,才能說明 pi 是指針變量,才能存放另外一個整型變量的地址;
(4)聲明了指針變量 pi,並且給這個變量賦了初值為 x 變量的地址,我們就可以說 pi 指向了 x。(5)定義指針變量的時候可以賦初值,也可以不賦初值,如果賦初值需要注意的是該變量必須在指針初始化之前已聲明過,且變量類型應與指針類型一致。也可以用一個已賦初值的指針去初始化另一 個指針變量。
(6)既然 pi 是一個指針變量,那麼 pi 也會分配內存單元,也佔用內存空間,因此也有地址,&pi 就是指針變量的地址,不管指向什麼類型的變量的指針,所佔用的空間大小是一樣的,都是 4 個字節的內存單元。
(7)如果指針沒有賦初值,那麼在通過使用指針間接它所指向的變量前,一定要賦值,這樣也很好理解,指針沒有指向變量,怎麼能間接訪問這個變量的數據值呢?因此說使用指向前,指針不能為空 。
我們來看這個程序段
#include <iostream>
using namespace std;
int main()
{
int x = 100;//聲明 x 變量,並賦初值 100
int *pi = &x;//聲明 pi 指針變量,並賦值為 x 的地址
cout << x << endl;//輸出 x 變量的數據值
cout << &x << endl;//輸出 x 變量的地址值
cout << *pi << endl;//通過指針間接訪問 x 變量的值
cout << pi << endl;//pi 變量的值,就是 x 的地址
cout << &pi << endl;//pi 變量的地址值
getchar();
return 1;
}執行結果如下圖 1 所示
圖1 程序代碼執行結果分析上面語句可以看出,pi指針保存的是x變量的地址,第2條輸出語句和第4條輸出語句的結果是一樣的,都是x變量的地址00F2FBFC,是8個16進位字符,也就是說是一個32位的長整型數據,表示指向的變量在內存中的編號。對於第3條輸出語句cout << pi << endl;的輸出結果為x變量的值100,在開始學習這裡內容學生會有疑惑,都是因為那個聲明語句int pi = &x;帶來的困惑。在這個聲明語句中,看似pi賦值為&x,怎麼到了輸出pi時,pi就成了x變量的值了呢?因為那個是聲明語句,聲明語句中的「」號,只是表示後面的變量pi是指針類型,只有是指針類型,才可以賦值為另外一個變量的地址,離開了這個聲明語句,那個「*」號,就成了通過指針訪問變量的取內容運算符了。因此cout << *pi << endl;輸出pi指針指向的變量的值。圖1結構中x變量的數據值、x變量的地址、pi變量的數據值、pi變量的地址值,在內存中的模型示意圖,如圖2所示
圖2 pi指針指向x變量的物理內存模型示意圖如果畫pi變量指向x變量的邏輯圖,就很簡單了,示意圖如圖3所示
圖3 pi指針指向x變量的邏輯示意圖兩個格子,分別表示pi變量和x變量,畫一個箭頭,箭尾畫在pi指針變量的小格子裡面,箭頭指向x的小格子,不要指到x變量的小格子裡。這樣就表示pi指針存放的是x變量的地址。
3. 指針變量可以指向不同的數據類型定義指針變量:
1)int *pi;
//pi是一個指向int型變量的指針;
2)float *pl;
//pl是一個指向float型變量的指針;
3) char *pc;
//pc是一個指向char型變量的指針;
4)char (*pa)[3];
//pa是一個指向一維數組的指針,這個一維數組是長度為3的字符數組;
5) char *pm[3];
//pm是一個指針數組,pm是數組名,這個數組的三個元素是字符變量的地址
6)int (pf)();
//pf是一個指向函數的指針,該函數的返回值為int型數值;
//也就是說指針不僅可以指向數據,也可以指向代碼,函數就是代碼段、
//函數名就是函數的地址
7)int * fun();
//函數的返回值是指針,是指向整型變量的指針
8)int **pp;
//pp是一個指向指針的指針,即二級指針。
//要存儲一個指針變量的地址,就需要一個二級指針
//如果pi是執行整型變量的指針
//pp變量就可以存儲pi變量的地址
//一個「」號,*pp表示間接訪問pp指向變量的值,就取得了pi變量的值,
//pi變量是x變量的地址,因此**pp才可以訪問到x變量的值。
9)struct DATE{
int year,month,day;//三個結構體成員
};//定義結構體變量DATE
DATE *pd;//定義指向結構體變量的指針;10)結構體成員的指針類型是自身結構體類型(也是定義鍊表的格式)
struct NODE
{
int data;
struct NODE *next; //結構體成員是指向自身結構的結構體變量的指針
};這種結構體類型也叫自引用類型
11)指向對象的指針class PERSON //定義PERSON類
{
private:
char *name; //姓名 類的成員是指向字符的指針
Int age; //年齡
char gender;//性別
public:
PERSON(char *n,int nl,char xb) //類的構造函數
{
strcpy(name,n);
age=nl;
gender=xb;
}
};
PERSON *pa;//pa是指向PERSON類的指針
4.指針的運算指針有三種運算:賦值運算、算術運算和關係運算。
(1)賦值運算說明:指針名=地址「地址」中存放的數據類型與指針類型必須相符。b. 向指針變量賦的值必須是地址常量或變量,不能是普通整數。但可以賦值為整數0,表示空指針。c. 指針的類型是它所指向變量的類型,而不是指針本身數據值的類型,任何一個指針本身的數據值都是unsigned long int型。d. 允許聲明指向 void 類型的指針。該指針可以被賦予任何類型對象的地址。void vobject;
(2)算術運算
//錯,不能聲明void類型的變量void *pv;
//可以聲明void類型的指針int *pint;int i;void main()
//void類型的函數沒有返回值{pv = &i;//void類型指針指向整型變量pint = (int *)pv;// void指針賦值給int指針需要類型強制轉換:}指針與整數的加減運算
指針 p 加上或減去 n ,其意義是指針當前指向位置的前方或後方第 n 個數據的地址。
這種運算的結果值取決於指針指向的數據類型。
指針加一,減一運算
指向下一個或前一個數據。
例如:y=*px++ 相當於 y=*px;px++
(*和++優先級相同,自右向左運算,但是如果++運算符是後綴,儘管++運算符優先級高,由於它的風格更高,因此它會讓指針先進行取內容運算,再做加1運算)兩個指針相減
兩個指針相差數據的個數。
說明:兩個指針不能做加法運算,因為兩個指針相加沒有意義 。#include <iostream>
using namespace std;
int main()
{
int i = 5;//聲明變量i
int *p = &i;//聲明p指針指向變量i
cout << "p為" << p << endl; //i的地址
cout << "*p為" << (*p)++ << endl; //5
//通過指針p訪問i變量,語句執行結束後,把i變量的值加1
cout << p << endl;//依然是i變量的地址
cout << *p << endl;//i變量的值是6
getchar();
return 1;
}輸出結果如圖4所示。
圖4 程序執行結果圖5 去掉包含p指針的括號後程序執行結果
上面代碼第4行,如果把括號去掉,結果會有很大的不同
如果把cout << "*p為" << (*p)++ << endl;
語句改成
cout << "*p為" << *p++ << endl; //5
輸出的結果依然是5,但是這條語句執行結束後,不是指針間接訪問的變量加1,而是指針變量加1,一個指針加上常量1,那麼指針就指向整數i變量後面的數據了,因此後面的輸出語句cout<<p<<endl;//這個是變量i後面整數的地址
cout<<*p<<endl;
//間接地訪問了i變量後面地址單元裡的內容
輸出結果如圖5所示。指針加減一個常數後,指針值的大小和指針指向數據類型有關, 如圖6所示。
圖6 指針加減一個常數,移動字節數與指向變量的數據類型有關總結:指針加減整數的操作表示空間位置上的移動;但是移動的字節數與指針所指數據類型相關。
(3)關係運算
對float指針加6實際增加了24個字節
對 int指針加5實際增加了20個字節
對char指針減7實際減少了7個字節
對double指針減2實際減少了16個字節兩個指針可以做 <,<=,>,>=,==,!=這樣的關係運算。圖下程序段,
int a[10]={10,20,30,40,50,60,70,80,90,100};
//定義一個長度為10的數組
int *p=&a[0];//p 指針指向第一個數組元素
int *q=&a[9]; //q指針指向第10個數組元素
while(p<=q)//循環條件,兩個指針做關係運算
{
Cout<<*p<<」\t」; //通過指針間接訪問數組中的每個數據元素
p++;
}
5.指針與數組數組名是一個指針常量,一個數組的數組名就是該數組首地址的值。指針常量不同於指針變量,指針常量的值不能改變,但指針變量的值是可以改變的。
說明(或聲明)與賦值
例:int a[10], pa;
pa=&a[0]; 或 pa=a; 兩個賦值語句都是讓pa指針指向數組的第一個元素
通過指針使用用數組元素,經過上述聲明及賦值後:
pa就是a[0],(pa+1)就是a[1],... ,(pa+i)就是a[i].
a[i], *(pa+i), *(a+i), pa[i]都是等效的。都是表示數組下標為i的數組元素,
但是不能寫 a++,因為a是數組首地址是常量。#include <iostream>
using namespace std;
int main()
{
static int a[5]={5,4,3,2,1};
int i,j;
i=a[0]+a[4];
j=*(a+2)+*(a+4);// 等價於 j=a[2]+a[4] 結果為4
cout<<i<<endl<<j;
getchar();
return 1;
}
6.字符指針字符指針是指向字符串的指針。字符指針便於對字符串進行操作。
如:char *p=「abcd」,*q;
q=「mnpq」;
字符指針比較特殊,比如p是字符串「abcd」的首地址,q存放的是「mnpq」字符串的首地址
cout<<*p<<endl;//輸出字符a
cout<<*q<<endl;//輸出字符mcout<<p<<endl;//
7.指針數組
不會輸出地址值,而是輸出從這個地址開始,到』\0』結束的字符串,因此這個語句的輸出結果是「abcd」這個字符串,因為字符串就是以『\0』結束的。同樣cout<<q<<endl;輸出結果是「mnpq」這個字符串。數組的元素是指針的類型
定義格式:類型 *數組名[數組長度]
例:int *pa[2];
//pa就是指針數組,每個數組元素是整型變量的地址#include <iostream>
using namespace std;
int main()
{
int a[3] = { 1,2,3 };
int* pa[3];
pa[0] = &a[0];//pa[0]指向a[0] *pa[0]就是a[0]
pa[1] = &a[1];//pa[1]指向a[1] *pa[1]就是a[1]
*(pa + 2) = a + 2;//等價於pa[2]=&a[2] *pa[2]就是a[2]
cout << *pa[0] << "," << **(pa + 1) << ',' << *pa[2] << endl;//1,2,3
// *(pa+1)就是pa[1],pa[1]指向a[1];因此pa[1]還是一個地址
//**(pa+1)等價於*pa[1]等價於a[1]
getchar();
return 1;
}
8. 指向數組的指針指向一維數組的指針,可以認為是一個二級指針,因為數組可以表示為一級指針,因此它是指向一級指針的指針,故為二級指針。
定義格式:類型 (*指針名)[數組長度]如:int a[2][3]={1,2,3,4,5,6};
int (*pa)[3];
pa=a+1;對於二維數組a,我們可以把它看成是一個一維數組,這個一維數組有兩個數組元素,每個數組元素又可以看成是一個有三個數組元素的一維數組。
對於a來說,a[0]和a[1]是它的兩個數組元素;
但是a[0]和a[1]分別是與三個數組元素的數組名;
a[0]這個數組中的三個數組元素是{a[0][0],a[0][1],a[0][2]}
a[1]這個數組中的三個數組元素是{a[1][0],a[1][1],a[1][2]}
這樣看來,對於 a是一個二級指針就好理解了
a就是a[0],(a+1)就是a[1];
a是指針,通過指針取內容,取到了兩個數組元素a[0]和a[1];
a[0]有是三個數組元素的數組名,a[0]就是a[0][0]同樣是通過指針取內容a[0]就是(a),因為a是a[0],因此**a就是a[0][0],對於二維數組取到數組元素,就是把數組名看成了二級指針。
(a+1)+1呢?還是一個地址,a[1]是地址,地址加上一個常量1,是下一個元素的地址,a[1]是三個數組元素{a[1][0],a[1]以[1],a[1][2]}的首地址,a[1]+1 就是a[1][1]的地址,因此((a+1)+1)就是數組元素a[1][1],以此類推,
((a+i)+j)是數組元素a[i][j]using namespace std;
int main()
{
int a[2][3] = { 1,2,3,4,5,6 };
int(*pa)[3];
pa = a + 1;//a是二級指針,a+1還是二級指針,pa指針指向了{a[1][0],a[1][1],a[1][2]}這三個元素的數組,pa+1或a+1實際移動的字節數是三個整型數據的空間,即12個字節
cout << pa[1][0] << "," << pa[0][0] << "," << *(*pa + 2) << endl;
//pa[1][0]是數組a後面的數據,不是數組a中的數據,因此這個是隨機值
//pa[0][0]就是a[1][0],值是4
//* *(*pa + 2)就是pa[0][2],是a[1][2]這數組元素,值是6
getchar();
return 1;
}
9.指針與函數(1)形參是指針的函數如果定義一個函數,形參定義的是指針,那麼調用的時候,指針形參對應的實參一定是地址,我們把這種調用方式成為傳地址調用。先定義一個函數, 用指針變量作參數, 實現兩個數的交換,然後在主函數中調用它。