在 C++ 程式設計中,函數參數的處理機制直接影響程式的正確性與效能。本文將深入探討三大核心主題:參數求值順序的不確定性、預設參數值的設定規則,以及陣列作為函數參數時的三種傳遞方式。掌握這些知識,能幫助開發者避開常見陷阱,編寫更健壯的程式碼。
C++ 標準並未規定函數調用時實參的求值順序。編譯器為了優化程式碼,可能採用從左至右、從右至左,或其他任意順序。對於無副作用的運算式,這不會造成影響;但若實參包含副作用運算符(如 ++、--、賦值運算),則可能導致未定義行為。
int x = 10;
int z = add_int(++x, x + 5); // 未定義行為!
若編譯器先求值 ++x(x 變 11),再求值 x + 5(結果 16),則傳入 (11, 16);若順序相反,則傳入 (10, 15) 後 x 才變 11。不同編譯器、甚至同一編譯器不同優化等級下,結果可能不同。
-Wsequence-point)C++ 允許在函數宣告或定義時為參數指定預設值。規則如下:
// 宣告時指定預設值
void drawRect(int x, int y, int w = 100, int h = 50, Color c = Color::Black);
// 呼叫時可省略尾部參數
drawRect(10, 20); // 相當於 drawRect(10, 20, 100, 50, Color::Black)
drawRect(10, 20, 200); // 相當於 drawRect(10, 20, 200, 50, Color::Black)
drawRect(10, 20, 200, 100, Color::Red); // 全部指定
預設值僅出現在宣告中(通常在標頭檔),定義時不應重複。虛函數的預設值由靜態類型決定,而非動態類型,這是常見錯誤來源。
C++ 中陣列名在傳遞時會退化為指向首元素的指標,但可透過不同語法達成不同語意:
void func(int arr[]); // 相當於 int* arr
void func(int arr[10]); // 大小被忽略,仍為 int* arr
int a[10];
func(a); // 共用同一塊記憶體,函數內修改會影響原陣列
void func(int* ptr); // 語意更明確
func(a); // 傳遞首元素位址
func(&a[3]); // 也可傳遞子陣列起始位址
typedef int IntArray[8]; // 先定義陣列類型
void func(IntArray& arr, int n); // 形參為陣列引用
int a[8] = {1,2,3,4,5,6,7,8};
func(a, 8); // 傳遞整個陣列別名,保留大小,sizeof(arr) 可得正確大小
此方式在 C++ 中較常用,因為能保留型別安全與大小資訊,避免指標退化帶來的問題。
理解參數求值順序的不確定性、善用預設參數簡化介面、正確選擇陣列傳遞方式,是編寫高品質 C++ 程式的關鍵基礎。建議開發者養成「實參無副作用」、「優先使用引用或標準容器」的習慣,減少錯誤機會。