函數參數列表中,**若某參數有默認值,其右側所有參數必須有默認值**。
void func(int a, int b = 10, int c = 20); // ✅ 合法 void func(int a = 10, int b, int c = 20); // ❌ 非法:b 無默認值卻在有默認值的 a 右側
默認參數只能出現一次:要麼在聲明中,要麼在定義中,**不能同時出現**。
// 1. 聲明有默認值,定義無 ✅ 推薦(頭文件使用者可見默認值)
// foo.h
char* left(const char* str, int n = 1);
// foo.cpp
char* left(const char* str, int n) { ... } // 定義不寫默認值
// 2. 定義有默認值,聲明無 ✅ 合法但不推薦(調用者看不到默認值)
// foo.h
char* left(const char* str, int n);
// foo.cpp
char* left(const char* str, int n = 1) { ... }
// 3. 聲明、定義都寫默認值 ❌ 編譯錯誤:重複默認參數
// foo.h
char* left(const char* str, int n = 1);
// foo.cpp
char* left(const char* str, int n = 1) { ... } // Error
若一組重載函數(含默認參數)能匹配相同實參個數的調用,將導致編譯錯誤。
void func(int a); // #1 void func(int a, int b = 4); // #2 void func(int a = 3, int b = 4); // #3 func(7); // ❌ 二義性:可匹配 #1, #2(a=7,b=4), #3(a=7,b=4) func(20,30); // ❌ 二義性:可匹配 #2(a=20,b=30), #3(a=20,b=30)
void func(int); void func(double, double=1.0); 無二義性func1(int); func2(int, int=4);| 場景 | 建議 |
|---|---|
| 庫接口(頭文件) | 聲明中給默認值,定義中不寫;便於調用者閱讀 |
| 內部實現函數 | 可在定義中給默認值,減少頭文件修改 |
| 需多種參數組合 | 優先用「參數對象」模式:struct Opt { int a=1, b=2; }; void func(Opt o={});
|
| 避免二義性 | 不混用重載與默認參數;用不同函數名或參數對象 |
| 虛函數 | 默認參數靜態綁定(按靜態類型決定),慎用!派生類覆蓋時默認值不生效 |
class Base {
public:
virtual void foo(int x = 10) { cout << "Base: " << x << endl; }
};
class Derived : public Base {
public:
void foo(int x = 20) override { cout << "Derived: " << x << endl; }
};
int main() {
Derived d;
Base* p = &d;
p->foo(); // 輸出 "Derived: 10" 而非 20!默認值按 Base* 靜態類型取 10
}
**結論**:虛函數**不要使用默認參數**,或在所有覆蓋中保持一致。
默認參數是語法糖,便利性與風險並存。核心記住三條鐵律:
複雜參數組合場景,優先考慮「參數對象/建造者模式」,類型安全且可擴展。