蘇杭麗,李 燕,童 端
(南京財經大學信息工程學院,江蘇 南京 210023)
指針是C/C++中的重點和難點[1-4],指針的“抽象”及使用的“靈活”讓初學者感到無從下手和無所適從。
作者參考了大量的例題、習題以及學生上機時的各種出錯代碼后發現:如果根據每句代碼的功能、分類成組、以組為單位重新排序,那么每個指針類題目的代碼都符合一個通用的規律,即本文歸納出的“三步法”。“三步法”是一個好用的、通用的、易學的方法,能幫助學生快速掌握指針編程并提高代碼的一次性正確率。
指針在“定義時”并不開辟存儲空間,指針只能“依附”于基本變量或數組所開辟的存儲空間。所以,在定義一個指針變量時需要定義一個“同種數據類型”的非指針量,通過給指針賦該變量所開辟空間的地址,讓指針指向該變量的空間,從而實現指針對此存儲空間的數據操作。也可以這樣理解:非指針量在“定義”時開辟存儲空間,該空間可以采用指針和非指針兩種方式來定位(動態開辟存儲空間除外)。將這一思路整理后再配合畫圖得出本文的指針編程三步法。
這一步需要注意的是:在定義一個指針量時,還需要定義一個同種數據類型的非指針量。非指針量定義時開辟的存儲空間是為指針量作空間準備。所以,指針變量在定義時,是與非指針量“成對”出現的。
給指針賦值就是讓指針指向某一存儲空間。這里的賦初值,就是在指針和非指針量定義后就可以讓指針指向非指針量所開辟的存儲空間。
這一步需要注意的是:這里的給指針賦初值(如指針定義為int *p),是指“p 等于什么”,而不是“*p 等于什么”。p是“地址”;*p在“大多數”情況下是指“值”而非“地址”。
以int 類型為例,說明基本變量x、一維數組a[10]、二維數組b[3][4]相對應的指針定義、指針賦初值,以及x、a[i]、b[i][j]的地址和值的指針表示格式,見表1。
說明:
⑴一維數組指針賦初值有兩種格式pp=a 或pp=&a[0],其中pp=a 是常用格式。不論一維還是二維數組,數組名代表數組的首地址。數組a 的首地址是首元素a[0]的地址&a[0],所以pp=&a[0]等價于pp=a。
⑵二維數組的指針定義有兩種格式,分別是二維指針格式和一維指針格式。
●二維指針格式,如表1 的(*p1)[4],是二維數組指針的常用定義格式。
指針定義時帶有一個與對應二維數組“第二維”相同的下標,任何情況下(如函數的實參、形參)的定義不可省略下標值。這種定義格式,指針與數組完全匹配,指針將按照行、列的二維模式訪問數組元素。
●一維指針格式,如表1 的*p2,這種定義二維數組的指針格式不常用。
*p2 是一維模式的指針定義格式,只能按照一維的模式進行數據訪問。因此,采用一維指針格式指向二維數組,要將二維數組變形為一維(或理解為降維),然后按照一維的模式訪問數組。“二維降為一維”通過給指針賦初值實現,如p2=b[0]或p2=*b,達到降維的目的。然后可以按照一維的模式訪問數組b,如數組任意元素b[i][j]在變形為一維數組后,相對應的元素離首地址p2 的位置為i*4+j,因此,b[i][j]用一維指針表示為*(p2+i*4+j)。
p2=&b[0][0]這種賦值方式,與基本變量和一維數組一樣,直接賦某一個具體元素的地址。
⑶當函數的實參和形參是指針類型,它們之間的參數傳遞與指針賦初值同理。
當實參和形參指針的定義格式相同時,“函數調用時實參的格式和種類”可以參考“指針實參賦初值的格式和種類”。舉例如下:
基本變量:int x,*p;p=&x;函數形如fun(int*q),相對應的調用格式為fun(p)或fun(&x)。
一維數組:

二維數組的二維指針格式:

二維數組的一維指針格式:

如果實參只定義了數組沒定義指針,則函數調用除指針格式外其他的格式依然成立;或者先增加一個與形參格式相同的指針實參,給實參指針賦值、依據實參指針的賦值寫出函數調用所有可能的參數格式,最后再去掉與添加的指針有關的代碼。例如參考文獻[1]第251 頁例8.14,在main 主函數中只定義了二維數組score,沒定義指針格式的實參;函數average 和search 的形參分別是一維指針和二維指針,它們都指向主函數的數組score。
為了理解本題,我們可以在主函數中增加一個與average 函數形參float *p 格式相同的實參float *p2;增加一個與search 函數形參float (*p)[4]格式相同的實參float(*p1)[4]。main函數主要代碼相應改為:


如果函數average的形參定義為二維指針格式,如float (*p)[4],則函數調用為average(score),函數average定義的代碼需要相對應地改為二維模式*(*(p+i)+j)訪問,例如可以如下改變:

同樣,函數search 的形參也可以改為一維指針模式,如float *p,則函數定義的代碼和函數調用也需要進行相對應的改變。
涉及指針的問題采用畫圖的方式解決,更形象也更容易。“三步法”中的畫圖有如下約定:
⑴非指針量的畫圖
用方框“□”表示基本變量或數組元素定義時開辟的存儲空間,如圖1所示。

圖1 基本變量x定義時的圖
當給基本變量賦值時,例如x=5,也就是將5 放在x的方框中。
⑵指針的畫圖
“三步法”中,指針的值用箭頭“指向”來表示。指針在定義時值為null,因此指針在定義時沒有箭頭,如圖2所示。

圖2 指針p和基本變量x定義時的圖
當給指針賦一個變量的地址時,指針箭頭將“指向”變量的空間,如圖3。當給指針賦另一個指針時,如p1=p2,則p1 也將“指向”p2 所指的空間,即兩個指針相等,則它們的“指向”相同。

圖3 指針p賦初值的圖
這里只觀察符號*、[]和&之間的關系,得出:*和[]相當于“一個維度”,二者在符號效果上等價;&相當于消掉“一個維度”。

因為*p 和x 是定義在同一種類型下,所以*p 與x地位等價。當*p 去掉一個維度*變為p,x 也消掉一個維度變為&x,所以p=&x在符號關系上是等價的。
⑵一維數組定義和賦初值的符號關系

*pp 與a[]在定義時等價,*pp 與a[]分別消掉一個維度,得到pp=a。
在&a[]中,&的作用是消掉一個維度,[]表示一個維度,所以&a[]和a 在符號上是等價的,所以pp=&a[]與pp=a等價。
⑶二維數組定義和賦初值的符號關系

(*p1)[]與b[][]在定義時等價,二者分別消掉兩個維度,(*p1)[]變為p1,b[][]變為b,所以p1=b在符號關系上是等價的。

b[][]和*p2在定義時等價,b[][]消掉一個維度變為b[],*p2消掉一個維度變為p2,所以p2=b[]在符號關系上是等價的。
*與[]都相當于一個維度,所以*b 與b[]在符號上是等價的,所以p2=*b與p2=b[]等價。
&相當于抵消一個維度,所以&b[][]相當于b[],所以p2=&b[][]與p2=b[]等價符號關系僅為了幫助記憶指針的不同賦值格式,從這個角度考慮問題沒有理論依據,僅供參考和討論。
現以參考文獻[1]第224 頁例8.3 為例,輸入a 和b兩個整數,按先大后小的順序輸出a和b。要求用函數處理,而且用指針類型的數據作函數參數。
⑴代碼對比
參考文獻[1]例8.3的代碼標行號后如下:

將上面的代碼,按照“三步法”的步驟拎出各步的代碼行:第4、5行屬于第一步,指針與非指針的“成對”定義;第8、9行屬于第二步,指針賦初值。
按照“三步法”的順序,將上述代碼重新排序標行號后,得到的“三步法”代碼為:

“三步法”的前兩步“成對定義”和“賦初值”看起來非常簡單,但它是指針編程通用的入口核心代碼,讓初學者拿到題目就知道該從哪里入手、該定義哪些指針、什么時候給指針賦初值、初值應該賦什么,可以有效解決無從下手并避免沒有給指針賦初值就開始各種指針操作等各種各樣的問題。
⑵畫圖對比
按“三步法”的畫圖約定,“三步法”代碼行畫圖如圖4所示。

圖4 三步法代碼畫圖
swap函數執行結束后回到main主函數繼續執行,第9 行輸出a、b 的值,由圖4 可以看出,此時a、b 的值分別是9和5,*pointer_1、*pointer_2也是9和5。
參考文獻[1]在第225 頁給出本題的圖示8.5。多種方法對比學習有助于開闊思維、更好地掌握知識。
“三步法”中,指針的“定義”和“賦初值”保證了指針操作“開端”的正確,第三步的“畫圖”能形象地演示指針“后續”的各種操作,因此,“三步法”對指針編程整個流程的有序、完整和正確有一定的保證,大大提高了代碼的一次性正確率。
“三步法”有步驟、每一步有具體的要求和細節上的說明,明確了從哪里入手、每一步該做什么、該怎么做,是值得一試的指針編程方法。