第6章 指 針
指針是C語言的精華部分,通過利用指針,我們能很好地利用內存資源,使其發揮最大的效率。有了指針技術,我們可以描述復雜的數據結構,對字符串的處理可以更靈活,對數組的處理更方便,使程序的書寫簡潔,高效,清爽。但由于指針對初學者來說,難于理解和把握,需要一定的計算機硬件的知識做基礎,這就需要多做多練,多上機動手,才能在實踐中盡快把握,成為C的高手。
6.1 指針與指針變量
6.2 指針變量的定義與引用
6.2.1 指針變量的定義
6.2.2 指針變量的引用
6.3 指針運算符與指針表達式
6.3.1 指針運算符與指針表達式
6.3.2 指針變量作函數的參數
6.4 指針與數組
6.4.1 指針與一維數組
6.4.2 指針與二維數組
6.4.3 數組指針作函數的參數
6.1 指針與指針變量
過去,我們在編程中定義或說明變量,編譯系統就為已定義的變量分配相應的內存單元,也就是說,每個變量在內存會有固定的位置,有具體的地址。由于變量的數據類型不同,它所占的內存單元數也不相同。若我們在程序中做定義為:
int a=1, b=2;
float x=3.4, y = 4 . 5 ;
double m=3.124;
char ch1='a', ch2='b';
讓我們先看一下編譯系統是怎樣為變量分配內存的。變量a , b是整型變量,在內存各占2個字節;x , y是實型,各占4個字節;m是雙精度實型,占8個字節; ch1 , ch2是字符型,各占1個字節。由于計算機內存是按字節編址的,設變量的存放從內存2000單元開始存放,則編譯系統對變量在內存的安放情況為圖6 - 1所示。

變量在內存中按照數據類型的不同,占內存的大小也不同,都有具體的內存單元地址,如變量a 在內存的地址是200 0,占據兩個字節后,變量b的內存地址就為2002,變量m的內存地址為2012等。對內存中變量的訪問,過去用
scanf("%d%d%f",&a,&b,&x) 表示將數據輸入變量的地址所指示的內存單元。那么,訪問變量,首先應找到其在內存的地址,或者說,一個地址唯一指向一個內存變量,我們稱這個地址為變量的指針。假如將變量的地址保存在內存的特定區域,用變量來存放這些地址,這樣的變量就是指針變量,通過指針對所指向變量的訪問,也就是一種對變量的“間接訪問”。
設一組指針變量pa、pb、px、py、pm、pch1、pch2,分別指向上述的變量a、b、x、y、m、ch1、ch2,指針變量也同樣被存放在內存,二者的關系如圖6 - 2所示:在圖6 - 2中,左部所示的內存存放了指針變量的值,該值給出的是所指變量的地址,通過該地址,就可以對右部描述的變量進行訪問。如指針變量pa的值為2000,是變量a在內存的地址。因此, p a就指向變量a。變量的地址就是指針,存放指針的變量就是指針變量。

6.2 指針變量的定義與引用
6.2.1 指針變量的定義
在C程序中,存放地址的指針變量需專門定義;
int *ptr1;
float *ptr2;
char *ptr3;
表示定義了三個指針變量ptr1、ptr2、ptr3。ptr1可以指向一個整型變量, ptr2可以指向一個實型變量,ptr3可以指向一個字符型變量,換句話說, ptr1、ptr2、ptr3可以分別存放整型變量的地址、實型變量的地址、字符型變量的地址。
定義了指針變量,我們才可以寫入指向某種數據類型的變量的地址,或者說是為指針變量賦初值:
int *ptr1,m= 3;
float *ptr2, f=4.5;
char *ptr3, ch='a';
ptr1 = &m ;
ptr2 = &f ;
ptr3 = &ch ;
上述賦值語句ptr1 = &m表示將變量m的地址賦給指針變量ptr1,此時ptr1就指向m。三條賦值語句產生的效果是ptr1指向m;ptr2指向f;ptr3指向ch 。用示意圖6 - 3描述如下:

需要說明的是,指針變量可以指向任何類型的變量,當定義指針變量時,指針變量的值是隨機的,不能確定它具體的指向,必須為其賦值,才有意義。
6.2.2 指針變量的引用
利用指針變量,是提供對變量的一種間接訪問形式。對指針變量的引用形式為:
*指針變量
其含義是指針變量所指向的值。
[例6-1] 用指針變量進行輸入、輸出。
main( )
{
int *p,m;
scanf("%d" , &m);
p = &m ; / *指針p指向變量m * /
/* p是對指針所指的變量的引用形式,與此m意義相同* /
}
運行程序:
3
3
上述程序可修改為:
main( )
{
int *p,m;
p = &m ;
scanf("%d" , p); /* p是變量m的地址,可以替換& m * /
printf("%d", m);
}
運行效果完全相同。請思考一下若將程序修改為如下形式:
main( )
{
int *p,m;
scanf("%d" , p);
p = &m ;
printf("%d", m);
}
會產生什么樣的結果呢?事實上,若定義了變量以及指向該變量的指針為:
int a,*p;
若p=&a; 則稱p指向變量a,或者說p具有了變量a的地址。在以后的程序處理中,凡是可
以寫&a的地方,就可以替換成指針的表示p,a就可以替換成為*p。
6.3 指針運算符與指針表達式
6.3.1 指針運算符與指針表達式
在C中有兩個關于指針的運算符:
? &運算符: 取地址運算符,& m即是變量m的地址。
? *運算符:指針運算符, * ptr表示其所指向的變量。
[例6-2] 從鍵盤輸入兩個整數,按由大到小的順序輸出。
main( )
{
int *p1,*p2,a,b,t; / * 定義指針變量與整型變量* /
scanf("%d , %d" , &a , &b);
p1 = &a; / *使指針變量指向整型變量* /
p2 = &b;
if (*p1 < *p2)
{ / *交換指針變量指向的整型變量* /
t = *p1;
*p1 = *p2;
*p2 = t;
}
printf("%d, %d " , a , b);
}
在程序中,當執行賦值操作p1 = &a和p2 = &b后,指針實實在在地指向了變量a與b,這時引用指針*p1與*p2,就代表了變量a與b。
運行程序:
3 , 4
在程序運行過程中,指針與所指的變量之間的關系如圖6 - 4所示:

當指針被賦值后,其在內存的安放如a ),當數據比較后進行交換,這時,指針變量與所指向的變量的關系如b )所示,在程序的運行過程中,指針變量與所指向的變量其指向始終沒變。
下面對程序做修改。
[例6 - 3 ]
main( )
{
int *p1,*p2,a,b,*t;
scanf("%d , %d" , &a , &b);
p1 = &a;
p2 = &b;
if(*p1 < *p2)
{ / *指針交換指向* /
t = p1;
p1 = p2;
p2 = t;
}
printf("%d, %d ", *p1 , *p2);
}
程序的運行結果完全相同,但程序在運行過程中,實際存放在內存中的數據沒有移動,而是將指向該變量的指針交換了指向。其示意如圖6 - 5:

當指針交換指向后, p 1和p 2由原來指向的變量a和b改變為指向變量b和a,這樣一來, * p 1
就表示變量b,而* p 2就表示變量a。在上述程序中,無論在何時,只要指針與所指向的變量滿
足p = & a;我們就可以對變量a 以指針的形式來表示。此時p等效于& a,* p等效于變量a 。
6.3.2 指針變量作函數的參數
函數的參數可以是我們在前面學過的簡單數據類型,也可以是指針類型。使用指針類型做函數的參數,實際向函數傳遞的是變量的地址。由于子程序中獲得了所傳遞變量的地址,在該地址空間的數據當子程序調用結束后被物理地保留下來。
[例6-4] 利用指針變量作為函數的參數,用子程序的方法再次實現上述功能。
main( )
{
void chang(); / *函數聲明* /
int *p1,*p2,a,b,*t;
scanf("%d, %d", &a, &b);
p1 = &a;
p2 = &b;
chang(p1 , p2); / *子程序調用* /
printf("%d, %d " , *p1, *p2);
return 0;
}
void chang(int *pt1,int *pt2)
{ / *子程序實現將兩數值調整為由大到小* /
int t;
if (*pt1<*pt2) / *交換內存變量的值* /
{
t=*pt1; *pt1=*pt2; * pt2 = t;
}
return;
}
由于在調用子程序時,實際參數是指針變量,形式參數也是指針變量,實參與形參相結合,傳值調用將指針變量傳遞給形式參數pt1和pt2。但此時傳值傳遞的是變量地址,使得在子程序中pt1和pt2具有了p1和p2的值,指向了與調用程序相同的內存變量,并對其在內存存放的數據進行了交換,其效果與[例6 - 2 ]相同。
思考下面的程序,是否也能達到相同的效果呢?
main( )
{
void chang();
int *p1,*p2,a,b,*t;
scanf("%d,%d", &a, &b);
p1 = &a;
p2 = &b;
chang(p1, p2);
printf("%d, %d " , *p1, *p2);
}
void chang(int *pt1,int *pt2)
{
int *t;
if (*pt1<*pt2)
{
t=pt1; pt1=pt2; p t 2 = t ;
}
return;
}
程序運行結束,并未達到預期的結果,輸出與輸入完全相同。其原因是對子程序來說,函數內部進行指針相互交換指向,而在內存存放的數據并未移動,子程序調用結束后,main( )函數中p 1和p 2保持原指向,結果與輸入相同。
6.4 指針與數組
變量在內存存放是有地址的,數組在內存存放也同樣具有地址。對數組來說,數組名就是數組在內存安放的首地址。指針變量是用于存放變量的地址,可以指向變量,當然也可存放數組的首址或數組元素的地址,這就是說,指針變量可以指向數組或數組元素,對數組而言,數組和數組元素的引用,也同樣可以使用指針變量。下面就分別介紹指針與不同類型的數組。
6.4.1 指針與一維數組
假設我們定義一個一維數組,該數組在內存會有系統分配的一個存儲空間,其數組的名字就是數組在內存的首地址。若再定義一個指針變量,并將數組的首址傳給指針變量,則該指針就指向了這個一維數組。我們說數組名是數組的首地址,也就是數組的指針。而定義的指針變量就是指向該數組的指針變量。對一維數組的引用,既可以用傳統的數組元素的下標法,也可使用指針的表示方法。
int a[10] , *ptr; /* 定義數組與指針變量* /
做賦值操作:ptr=a; 或ptr = &a[0];
則ptr就得到了數組的首址。其中, a是數組的首地址, &a[0]是數組元素a[0]的地址,由于a[0]的地址就是數組的首地址,所以,兩條賦值操作效果完全相同。指針變量ptr就是指向數組a的指針變量。
若ptr指向了一維數組,現在看一下C規定指針對數組的表示方法:
1) ptr+n與a + n表示數組元素a[n]的地址,即&a[n] 。對整個a數組來說,共有10個元素, n的取值為0~9,則數組元素的地址就可以表示為ptr + 0~ptr + 9或a + 0~a + 9,與&a[0] ~&a[9]保持一致。
2) 知道了數組元素的地址表示方法, *(ptr + n)和*(a+n)就表示為數組的各元素即等效于a[n]。
3) 指向數組的指針變量也可用數組的下標形式表示為ptr[n],其效果相當于*(ptr + n)。
[例6-5] /*以下標法輸入輸出數組各元素。
下面從鍵盤輸入10個數,以數組的不同引用形式輸出數組各元素的值。
# include <stdio.h>
main( )
{
int n,a[10],*ptr=a;
for(n = 0; n<=9; n++)
scanf("%d" , &a[n]);
printf("1------output! ");
for(n = 0; n<=9; n++)
printf("%4d", a[n]);
printf(" ");
}
運行程序:
1 2 3 4 5 6 7 8 9 0
1------output!
1 2 3 4 5 6 7 8 9 0
[例6-6] 采用指針變量表示的地址法輸入輸出數組各元素。
#include<stdio.h>
main( )
{
int n,a[10],*ptr=a; / *定義時對指針變量初始化* /
for(n = 0; n<=9; n++)
scanf("%d", ptr + n);
printf("2------output! ");
for(n=0; n<=9; n++)
printf("%4d", *(ptr+n));
printf(" ");
}
運行程序:
1 2 3 4 5 6 7 8 9 0
2------output!
1 2 3 4 5 6 7 8 9 0
[例6-7] 采用數組名表示的地址法輸入輸出數組各元素。
main( )
{
int n,a[10],*ptr=a;
for(n = 0; n < = 9; n ++)
scanf("%d", a+n);
printf("3------output! ");
for(n = 0; n<=9; n++)
printf("%4d", *(a+n));
printf(" ");
}
運行程序:
1 2 3 4 5 6 7 8 9 0
3------output!
1 2 3 4 5 6 7 8 9 0
[例6-8] 用指針表示的下標法輸入輸出數組各元素。
main( )
{
int n,a[10],*ptr=a;
for(n = 0; n<=9; n++)
scanf("%d", &ptr[n]);
printf("4------output! ");
for(n = 0; n<=9; n++)
printf("%4d", ptr[n]);
printf(" ");
}
運行程序:
1 2 3 4 5 6 7 8 9 0
4----output!
1 2 3 4 5 6 7 8 9 0
[例6-9] 利用指針法輸入輸出數組各元素
main( )
{
int n,a[10],*ptr=a;
for(n = 0; n<=9; n++)
scanf("%d", ptr++);
printf("5------output! ");
ptr = a; / *指針變量重新指向數組首址* /
for(n = 0; n<=9; n++)
printf("%4d", *ptr++);
printf(" ");
}
運行程序:
1 2 3 4 5 6 7 8 9 0
5-----output!
1 2 3 4 5 6 7 8 9 0
在程序中要注重*ptr++所表示的含義。*ptr表示指針所指向的變量; ptr++表示指針所指向的變量地址加1個變量所占字節數,具體地說,若指向整型變量,則指針值加2,若指向實型,則加4,依此類推。而printf(“%4d”, *ptr+ +)中,*ptr++所起作用為先輸出指針指向的變量的值,然后指針變量加1。循環結束后,指針變量指向如圖6 - 6所示:

指針變量的值在循環結束后,指向數組的尾部的后面。假設元素a[9]的地址為1000,整型占2字節,則ptr的值就為100 2。請思考下面的程序段:
main( )
{
int n,a[10],*ptr=a;
for(n = 0; n<=9; n++)
scanf("%d", ptr++);
printf("4------output! ");
for(n = 0; n<=9; n++)
printf("%4d", *ptr++);
printf(" ");
}
程序與例6 - 9相比,只少了賦值語句p t r = a;程序的運行結果還相同嗎?
6.4.2 指針與二維數組
定義一個二維數組:
int a[3][4];
表示二維數組有三行四列共1 2個元素,在內存中按行存放,存放形式為圖6 - 7:

其中a是二維數組的首地址, &a[0][0]既可以看作數組0行0列的首地址,同樣還可以看作是二維數組的首地址, a [0]是第0行的首地址,當然也是數組的首地址。同理a[n]就是第n行的首址;&a[n][m]就是數組元素a[n][m]的地址。
既然二維數組每行的首地址都可以用a[n]來表示,我們就可以把二維數組看成是由n行一維數組構成,將每行的首地址傳遞給指針變量,行中的其余元素均可以由指針來表示。圖6 - 8給出了指針與二維數組的關系。
我們定義的二維數組其元素類型為整型,每個元素在內存占兩個字節,若假定二維數組從1000單元開始存放,則以按行存放的原則,數組元素在內存的存放地址為1000~1022。
用地址法來表示數組各元素的地址。對元素a[1][2],&a[1][2]是其地址, a[1] + 2也是其地址。分析a[1] + 1與a [1] + 2的地址關系,它們地址的差并非整數1,而是一個數組元素的所占位置2,原因是每個數組元素占兩個字節。
對0行首地址與1行首地址a與a + 1來說,地址的差同樣也并非整數1,是一行,四個元素占的字節數8。由于數組元素在內存的連續存放。給指向整型變量的指針傳遞數組的首地址,則該指針指向二維數組。
int *ptr, a[3][4];
若賦值: p t r = a;則用ptr++ 就能訪問數組的各元素。
[例6-10] 用地址法輸入輸出二維數組各元素。
#include <stdio.h>
main( )
{
int a[3][4];
int i,j;
for(i = 0; i<3; i++)
for(j = 0; j<4; j++)
scanf("%d",a[i]+j); / *地址法* /
for(i = 0; i<3; i++)
{
for(j = 0; j<4; j++)
printf("%4d",*(a[i]+j)); /* *(a[i]+j) 是地址法所表示的數組元素* /
printf(" ");
}
}
運行程序:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4
5 6 7 8
9 1 0 1 1 1 2
[例6 - 11] 用指針法輸入輸出二維數組各元素。
#include<stdio.h>
main( )
{
int a[3][4],*ptr;
int i,j;
ptr = a[0];
for(i = 0; i<3; i++)
for(j = 0; j< 4; j++)
scanf("%d", ptr++); / *指針的表示方法* /
ptr = a[0];
for(i = 0; i<3; i++)
{
for(j = 0; j<4; j++)
printf("%4d", *ptr++);
printf(" ");
}
}
運行程序:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4
5 6 7 8
9 1 0 1 1 1 2
對指針法而言,程序可以把二維數組看作展開的一維數組:
main( )
{
int a[3][4],*ptr;
int i,j;
ptr = a[0];
for(i = 0; i< 3; i++)
for(j = 0; j < 4; j++)
scanf("%d", ptr++); / * 指針的表示方法* /
ptr = a[0];
for(i = 0; i < 12;i++)
printf("%4d", *ptr++);
printf(" ");
}
運行程序:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 1 0 1 1 1 2
6.4.3 數組指針作函數的參數
學習了指向一維和二維數組指針變量的定義和正確引用后,我們現在學習用指針變量作函數的參數。
[例6-12] 調用子程序,實現求解一維數組中的最大元素。
我們首先假設一維數組中下標為0的元素是最大和用指針變量指向該元素。后續元素與該元素一一比較,若找到更大的元素,就替換。子程序的形式參數為一維數組,實際參數是指向一維數組的指針。
#include <stdio.h>
main( )
{
int sub_max(); / * 函數聲明* /
int n,a[10],*ptr=a; / *定義變量,并使指針指向數組* /
int max;
for(n = 0; n < = i - 1; n++) / *輸入數據* /
scanf("%d", &a[n]);
max = sub_max(ptr, 10); / * 函數調用,其實參是指針* /
printf("max = %d ", max);
}
int sub_max(b,i) / * 函數定義,其形參為數組* /
int b[],i;
{
int temp,j;
temp = b[0];
for(j = 1; j < = 9; j++)
if(temp<b[j]) temp=b[j];
return temp;
}
程序的main( )函數部分,定義數組a 共有1 0個元素,由于將其首地址傳給了ptr,則指針變量ptr 就指向了數組,調用子程序,再將此地址傳遞給子程序的形式參數b,這樣一來,b數組在內存與a 數組具有相同地址,即在內存完全重合。在子程序中對數組b 的操作,與操作數組a 意義相同。其內存中虛實結合的示意如圖6 - 9所示。
main( )函數完成數據的輸入,調用子程序并輸出運行結果。sub_max( )函數完成對數組元素找最大的過程。在子程序內數組元素的表示采用下標法。運行程序:
1 3 5 7 9 2 4 6 8 0
max = 9