摘要:學習C++語言中函數參數傳遞方式的關鍵是給出函數調用過程中內存各段內容的變化圖示。本文針對C++語言中三種函數參數傳遞方式,輔以代碼段內容圖示和堆棧段內容圖示,從機理上詳細解釋了函數參數的傳遞過程。實踐表明,這種圖示說明的方法在教學中取得了非常良好的效果。
關鍵詞:函數調用;參數傳遞;代碼段;堆棧段
1背景
“C++程序設計”是高等學校計算機專業或非計算機專業學生的必修課。對于非計算機專業的學生,C++語言是他們真正學習和使用計算機語言進行編程的關鍵入門,對于以后在其專業應用開發中具有至關重要的作用。即使以后使用其他編程語言進行專業項目的開發,如VB、C和Java語言,C++語言由于其概念的廣泛性和綜合性,也能夠使得他們很快學習并掌握這些編程語言。而對于計算機專業的學生來說,“C++程序設計”是“數據結構”、“算法設計”等核心課程的先修課,同時,“C++程序設計”中涉及的部分硬件知識也是其學習計算機原理的重要基礎。
但是,C++作為入門程序語言課程,對于初學者來說確實難度較大。周立章對自己的教學實踐進行總結,強調分層教學、案例教學和對計算機實驗進行改革的思想[1];李新霞在C++的前驅語言C語言的教學實踐中也表達了類似的思想[2]。因此,案例教學對C++語言來說是必不可少的。
對于大多數學生來說,C++程序設計學習中存在三個難點:(1)函數參數的傳遞;(2)指針變量的使用;(3)虛函數和多態性機制。
函數和類作為C++語言中的兩種基本模塊,分別支持C++語言進行面向過程的開發和面向對象的開發,而不論是何種開發方法,函數都是不可缺少的。一個完整的函數使用過程包括函數定義和函數調用,有時存在函數聲明,而函數調用過程中,在主調函數和被調函數之間發生著數據的交互,表現為函數參數的傳遞和被調函數的返回值。
其中,對于函數參數傳遞方式及相關教學研究,得到了很多關注。馬新將函數參數傳遞方式分為值傳遞方式和地址傳遞方式,并歸納總結了選用何種方式的條件[3];劉志華將函數參數傳遞方式分為簡單變量作參數、指針作參數、引用作參數、數組作參數和字符串作參數共五種方式,并對每一種情況進行了實例描述[4];譚慶將函數參數傳遞方式分為傳普通值調用、傳地址值調用和引用調用三種方式,并對其使用方法進行了總結[5];王萍、譚浩強和陳志泊在其編寫的相應教材中也對C++中函數參數傳遞方式給予了重點關注[6-8]。
本文就函數參數的傳遞方式,利用圖示說明的方法進行研究,旨在搞清各種函數參數傳遞方式的本質,為函數的學習奠定堅實的基礎。
2函數參數的傳遞方式
C++語言中函數參數的傳遞方式分為值傳遞、引用傳遞和指針傳遞。學生之所以不能正確掌握函數參數傳遞的相關內容,主要原因是不能了解函數參數傳遞過程中內存各段相關內容的變化,而解決這一問題的方法是給出函數調用過程中內存各段內容變化的圖示。
2.1內存分段
程序在執行時,內存是分段使用的,可分為代碼段(CS, Code Segment)、數據段(Data Segment)、附加段(ES, Extra Segment)和堆棧段(SS, Stack Segment),如圖1所示。
代碼段中存放程序執行代碼,數據段由靜態數據區和使用new請求分配數據的堆區組成,堆棧段中存放函數執行過程的各種數據,主要包括形式參數、局部變量和主調函數斷點地址。主調函數斷點地址指的是函數調用語句指令后的一條執行指令的地址。堆棧中每個函數的形式參數、局部變量和主調函數斷點地址稱為該函數的活動記錄。
根據馮諾依曼原理,當執行程序時,必須將該程序指令代碼加載到內存的代碼段,同時將第一條指令代碼的地址存入到PC寄存器,然后,每執行一條指令代碼,PC的內容自動加1,如此順序執行代碼段中的指令。而當發生函數調用時,程序的執行發生了流程的轉向。當流程轉向到被調函數時,PC中的內容更新為被調函數第一條指令的地址;而當流程重新回到主調函數時,PC中的內容更新為主調函數的斷點地址。函數調用過程的代碼段圖示說明,如圖2所示。
2.2值傳遞
采用值傳遞(pass-by-value)方式時,在堆棧段中為被調函數的形參列表分配內存,主調函數的實參列表分別賦給形參列表。因此,內存中每個形式參數和實際參數都是不同的變量,只是在發生函數調用的時刻,對應實參和形參變量的值相同而已。值傳遞方式的特點是被調函數對形參的任何操作不會影響主調函數的實參的值。
以下面程序作圖示說明。
int swap(int x, int y)
{
int temp;
temp = x; x = y; y = temp;
cout<<\"x=\"< return temp; } void main() { int a = 10, b = 20; swap(a, b); cout<<\"a=\"< } 當該程序提交給操作系統執行時,首先將程序代碼加載到代碼段,然后根據PC的內容來執行指令。由于PC存儲的內容為main函數中第一條指令的地址,故從該地址處開始順序執行。此時,執行的是main函數,也可以理解為操作系統調用main函數,操作系統相當于主調函數,main函數是被調函數。因此,堆棧段中為main函數分配活動記錄:a和b。而當執行到swap(a,b);語句時,發生了swap函數調用。堆棧段中為swap函數分配活動記錄: main函數斷點地址,x、y和temp,并且將a和b的值分別賦給形參x和y。修改PC的內容為swap函數的第一條指令的地址,程序由此重新開始順序執行。此時,對x和y的任何修改都不能影響到a和b。當swap函數執行結束后,從堆棧中刪除swap函數活動記錄,并且修改PC的內容為main斷點地址,程序由此繼續順序執行。值傳遞過程的堆棧段圖示說明,如圖3所示。 (a) 函數調用前 (b) 函數調用中(c) 函數調用后 圖3值傳遞過程的堆棧段圖示 上述程序的執行的結果是: x=20,y=10 a=10,b=20 2.3指針傳遞 采用指針傳遞(pass-by-pointer)方式時,同樣也需要在堆棧段中為被調函數的形參列表分配內存。但是,由于形參定義為指針類型,從主調函數傳遞過來的是實參列表各變量的地址。因此,指針傳遞方式的特點是被調函數對形參做的任何操作也都影響到主調函數中的實參的值。 以下面程序作圖示說明。 int swap(int* x, int* y) { int temp; temp = *x; *x = *y; *y = temp; cout<<\"*x=\"<<*x<<\",*y=\"<<*y< return temp; } void main() { int a = 10, b = 20; swap(a, b);