深度探究C++中的函數(shù)重載的用法
C++ 允許同一范圍內(nèi)具有相同名稱的多個函數(shù)的規(guī)范。這些函數(shù)稱為重載函數(shù),“重載”中對其進行了詳細介紹。利用重載函數(shù),程序員可以根據(jù)參數(shù)的類型和數(shù)量為函數(shù)提供不同的語義。
例如,采用字符串(或 char *)參數(shù)的 print 函數(shù)執(zhí)行的任務與采用“雙精度”類型的參數(shù)的函數(shù)執(zhí)行的任務截然不同。重載允許通用命名并使程序員無需創(chuàng)建名稱,例如 print_sz 或 print_d。下表顯示了 C++ 使用函數(shù)聲明的哪些部分來區(qū)分同一范圍內(nèi)具有相同名稱的函數(shù)組。
重載注意事項
函數(shù)聲明元素 | 是否用于重載? |
---|---|
函數(shù)返回類型 | No |
參數(shù)的數(shù)量 | 是 |
參數(shù)的類型 | 是 |
省略號存在或缺失 | 是 |
typedef 名稱的使用 | 否 |
未指定的數(shù)組邊界 | 否 |
const 或 volatile(見下文) | 是 |
雖然可以根據(jù)返回類型區(qū)分函數(shù),但是無法在此基礎上對它們進行重載。僅當 Const 或 volatile 在類中用于應用于類的 this 指針(而不是函數(shù)的返回類型)時,它們才用作重載的基礎。換言之,僅當 const 或 volatile 關鍵字遵循聲明中函數(shù)的參數(shù)列表時,重載才適用。
以下示例闡述如何使用重載。
// function_overloading.cpp // compile with: /EHsc #include <iostream> #include <math.h> // Prototype three print functions. int print( char *s ); // Print a string. int print( double dvalue ); // Print a double. int print( double dvalue, int prec ); // Print a double with a // given precision. using namespace std; int main( int argc, char *argv[] ) { const double d = 893094.2987; if( argc < 2 ) { // These calls to print invoke print( char *s ). print( "This program requires one argument." ); print( "The argument specifies the number of" ); print( "digits precision for the second number" ); print( "printed." ); exit(0); } // Invoke print( double dvalue ). print( d ); // Invoke print( double dvalue, int prec ). print( d, atoi( argv[1] ) ); } // Print a string. int print( char *s ) { cout << s << endl; return cout.good(); } // Print a double in default precision. int print( double dvalue ) { cout << dvalue << endl; return cout.good(); } // Print a double in specified precision. // Positive numbers for precision indicate how many digits // precision after the decimal point to show. Negative // numbers for precision indicate where to round the number // to the left of the decimal point. int print( double dvalue, int prec ) { // Use table-lookup for rounding/truncation. static const double rgPow10[] = { 10E-7, 10E-6, 10E-5, 10E-4, 10E-3, 10E-2, 10E-1, 10E0, 10E1, 10E2, 10E3, 10E4, 10E5, 10E6 }; const int iPowZero = 6; // If precision out of range, just print the number. if( prec < -6 || prec > 7 ) return print( dvalue ); // Scale, truncate, then rescale. dvalue = floor( dvalue / rgPow10[iPowZero - prec] ) * rgPow10[iPowZero - prec]; cout << dvalue << endl; return cout.good(); }
前面的代碼演示了文件范圍內(nèi)的 print 函數(shù)重載。
默認參數(shù)不被視為函數(shù)類型的一部分。因此,它不用于選擇重載函數(shù)。僅在默認參數(shù)上存在差異的兩個函數(shù)被視為多個定義而不是重載函數(shù)。
不能為重載運算符提供默認參數(shù)。
參數(shù)匹配
選擇重載函數(shù)以實現(xiàn)當前范圍內(nèi)的函數(shù)聲明與函數(shù)調(diào)用中提供的參數(shù)的最佳匹配。如果找到合適的函數(shù),則調(diào)用該函數(shù)。此上下文中的“Suitable”具有下列含義之一:
- 找到完全匹配項。
- 已執(zhí)行不重要的轉(zhuǎn)換。
- 已執(zhí)行整型提升。
- 已存在到所需參數(shù)類型的標準轉(zhuǎn)換。
- 已存在到所需參數(shù)類型的用戶定義的轉(zhuǎn)換(轉(zhuǎn)換運算符或構造函數(shù))。
- 已找到省略號所表示的參數(shù)。
編譯器為每個參數(shù)創(chuàng)建一組候選函數(shù)。候選函數(shù)是這樣一種函數(shù),其中的實參可以轉(zhuǎn)換為形參的類型。
為每個參數(shù)生成一組“最佳匹配函數(shù)”,并且所選函數(shù)是所有集的交集。如果交集包含多個函數(shù),則重載是不明確的并會生成錯誤。對于至少一個參數(shù)而言,最終選擇的函數(shù)始終是比組中的所有其他函數(shù)更好的匹配項。如果不是這樣(如果沒有清晰的勝者),則函數(shù)調(diào)用會生成錯誤。
考慮下面的聲明(針對下面的討論中的標識,將函數(shù)標記為 Variant 1、Variant 2 和 Variant 3):
Fraction &Add( Fraction &f, long l ); // Variant 1 Fraction &Add( long l, Fraction &f ); // Variant 2 Fraction &Add( Fraction &f, Fraction &f ); // Variant 3 Fraction F1, F2;
請考慮下列語句:
F1 = Add( F2, 23 );
前面的語句生成兩個集:
集 1:其第一個參數(shù)的類型為 Fraction 的候選函數(shù) | 集 2:其第二個參數(shù)可轉(zhuǎn)換為類型 int 的候選函數(shù) |
---|---|
Variant 1 | Variant 1(可使用標準轉(zhuǎn)換將 int 轉(zhuǎn)換為 long) |
Variant 3 |
集 2 中的函數(shù)是具有從實參類型到形參類型的隱式轉(zhuǎn)換的函數(shù),在這些函數(shù)中,有一種函數(shù)的從實參類型到其形參類型的轉(zhuǎn)換的“成本”是最低的。
這兩個集的交集為 Variant 1。不明確的函數(shù)調(diào)用的示例為:
F1 = Add( 3, 6 );
前面的函數(shù)調(diào)用生成以下集:
集 1:其第一個參數(shù)的類型為 int 的候選函數(shù) | 集 2:其第二個參數(shù)的類型為 int 的候選函數(shù) |
---|---|
Variant 2(可使用標準轉(zhuǎn)換將 int 轉(zhuǎn)換為 long) | Variant 1(可使用標準轉(zhuǎn)換將 int 轉(zhuǎn)換為 long) |
請注意,這兩個集之間的交集為空。因此,編譯器會生成錯誤消息。
對于參數(shù)匹配,具有 n 個默認參數(shù)的函數(shù)將視為 n+1 個單獨函數(shù),并且每個函數(shù)均具有不同數(shù)量的參數(shù)。
省略號 (...) 用作通配符;它與任何實參匹配。如果您未極其謹慎地設計重載函數(shù)集,這可能導致產(chǎn)生許多不明確的集。
注意
重載函數(shù)的多義性無法確定,直到遇到函數(shù)調(diào)用。此時,將為函數(shù)調(diào)用中的每個參數(shù)生成集,并且可以確定是否存在明確的重載。這意味著,多義性可保持在您的代碼中,直到它們由特定函數(shù)調(diào)用引發(fā)。
參數(shù)類型差異
重載函數(shù)區(qū)分使用不同的初始值設定項的參數(shù)類型。因此,對于重載而言,給定類型的參數(shù)和對該類型的引用將視為相同。由于它們采用相同的初始值設定項,因此它們被視為是相同的。例如,max( double, double ) 被視為與 max( double &, double & ) 相同。聲明兩個此類函數(shù)會導致錯誤。
出于同一原因,對由 const 或 volatile 修改的類型的函數(shù)參數(shù)(出于重載的目的)的處理方式與基類沒有什么不同。
但是,函數(shù)重載機制可以區(qū)分由 const 和 volatile 限定的引用和對基類型的引用。此方法可以編寫諸如以下內(nèi)容的代碼:
// argument_type_differences.cpp // compile with: /EHsc /W3 // C4521 expected #include <iostream> using namespace std; class Over { public: Over() { cout << "Over default constructor\n"; } Over( Over &o ) { cout << "Over&\n"; } Over( const Over &co ) { cout << "const Over&\n"; } Over( volatile Over &vo ) { cout << "volatile Over&\n"; } }; int main() { Over o1; // Calls default constructor. Over o2( o1 ); // Calls Over( Over& ). const Over o3; // Calls default constructor. Over o4( o3 ); // Calls Over( const Over& ). volatile Over o5; // Calls default constructor. Over o6( o5 ); // Calls Over( volatile Over& ). }
輸出
Over default constructor Over& Over default constructor const Over& Over default constructor volatile Over&
指向 const 和 volatile 對象的指針也被認為和指向基類型的指針(以重載為目的)不同。
參數(shù)匹配和轉(zhuǎn)換
當編譯器嘗試根據(jù)函數(shù)聲明中的參數(shù)匹配實際參數(shù)時,如果未找到任何確切匹配項,它可以提供標準轉(zhuǎn)換或用戶定義的轉(zhuǎn)換來獲取正確類型。轉(zhuǎn)換的應用程序受這些規(guī)則的限制:
不考慮包含多個用戶定義的轉(zhuǎn)換的轉(zhuǎn)換序列。
不考慮可通過刪除中間轉(zhuǎn)換來縮短的轉(zhuǎn)換序列。
最終的轉(zhuǎn)換序列(如果有)稱為最佳匹配序列??赏ㄟ^多種方式使用標準轉(zhuǎn)換將類型 int 的對象轉(zhuǎn)換為類型unsigned long 的對象(如標準轉(zhuǎn)換中所述):
- 從 int 轉(zhuǎn)換為 long,然后從 long 轉(zhuǎn)換為 unsigned long。
- 從 int 轉(zhuǎn)換為 unsigned long。
第一個序列(盡管它實現(xiàn)了所需目標)不是最佳匹配序列 - 存在一個較短的序列。
下表顯示了一組稱為常用轉(zhuǎn)換的轉(zhuǎn)換,這些轉(zhuǎn)換對確定哪個序列是最佳匹配項有一定的限制。該表后面的列表中討論了常用轉(zhuǎn)換影響序列選擇的實例。
常用轉(zhuǎn)換
從類型轉(zhuǎn)換 | 轉(zhuǎn)換為類型 |
---|---|
type-name | type-name & |
type-name & | type-name |
type-name [ ] | type-name* |
type-name ( argument-list ) | ( *type-name ) ( argument-list ) |
type-name | const type-name |
type-name | volatile type-name |
type-name* | const type-name* |
type-name* | volatile type-name* |
在其中嘗試轉(zhuǎn)換的序列如下:
完全匹配。用于調(diào)用函數(shù)的類型與函數(shù)原型中聲明的類型之間的完全匹配始終是最佳匹配。常用轉(zhuǎn)換的序列將歸類為完全匹配。但是,不進行任何轉(zhuǎn)換的序列被視為比進行轉(zhuǎn)換的序列更佳:
- 從指針,到指向 const(type* 指向 consttype*)的指針。
- 從指針,到指向 volatile(type* 指向 volatiletype*)的指針。
- 從引用,到對 const(type & 到 const type &)的引用。
- 從引用,到對 volatile(type & 到 volatile type &)的引用。
使用提升的匹配。未歸類為僅包含整型提升、從 float 到 double 的轉(zhuǎn)換以及常用轉(zhuǎn)換的完全匹配的任何序列將被歸類為使用提升的匹配。盡管比不上完全匹配,但使用提升的匹配仍優(yōu)于使用標準轉(zhuǎn)換的匹配。
使用標準轉(zhuǎn)換的匹配。未歸類為完全匹配或僅包含標準轉(zhuǎn)換和常用轉(zhuǎn)換的使用提升的匹配的序列將歸類為使用標準轉(zhuǎn)換的匹配。在此類別中,以下規(guī)則將適用:
從指向派生類的指針到指向直接或間接基類的指針的轉(zhuǎn)換優(yōu)于到 void * 或 const void * 的轉(zhuǎn)換。
從指向派生類的指針到指向基類的指針的轉(zhuǎn)換會產(chǎn)生一個到直接基類的更好匹配。假定類層次結(jié)構如下圖所示。
演示首選轉(zhuǎn)換的關系圖
從 D* 類型到 C* 類型的轉(zhuǎn)換優(yōu)于從 D* 類型到 B* 類型的轉(zhuǎn)換。同樣,從 D* 類型到 B* 類型的轉(zhuǎn)換優(yōu)于從 D* 類型到 A* 類型的轉(zhuǎn)換。
此同一規(guī)則適用于引用轉(zhuǎn)換。從 D& 類型到 C& 類型的轉(zhuǎn)換優(yōu)于從 D& 類型到 B& 類型的轉(zhuǎn)換等。
此同一規(guī)則適用于指向成員的指針轉(zhuǎn)換。從 T D::* 類型到 T C::* 類型的轉(zhuǎn)換優(yōu)于從 T D::* 類型到 T B::* 類型的轉(zhuǎn)換等(其中,T 是該成員的類型)。
前面的規(guī)則僅沿派生的給定路徑應用??紤]下圖中顯示的關系圖。
演示首選轉(zhuǎn)換的多繼承關系圖
從 C* 類型到 B* 類型的轉(zhuǎn)換優(yōu)于從 C* 類型到 A* 類型的轉(zhuǎn)換。原因是它們位于同一個路徑上,且 B* 更為接近。但是,從 C* 類型到 D* 類型的轉(zhuǎn)換不優(yōu)于到 A* 類型的轉(zhuǎn)換;沒有首選項,因為這些轉(zhuǎn)換遵循不同的路徑。
使用用戶定義的轉(zhuǎn)換的匹配。此序列不能歸類為完全匹配、使用提升的匹配或使用標準轉(zhuǎn)換的匹配。序列必須僅包含用戶定義的轉(zhuǎn)換、標準轉(zhuǎn)換或要歸類為使用用戶定義的轉(zhuǎn)換的匹配的常用轉(zhuǎn)換。使用用戶定義的轉(zhuǎn)換的匹配被認為優(yōu)于使用省略號的匹配,但比不上使用標準轉(zhuǎn)換的匹配。
使用省略號的匹配。與聲明中的省略號匹配的任何序列將歸類為使用省略號的匹配。這被視為最弱匹配。
如果內(nèi)置提升或轉(zhuǎn)換不存在,則用戶定義的轉(zhuǎn)換將適用?;趯⑵ヅ涞膮?shù)的類型選擇這些轉(zhuǎn)換??紤]下列代碼:
// argument_matching1.cpp class UDC { public: operator int() { return 0; } operator long(); }; void Print( int i ) { }; UDC udc; int main() { Print( udc ); }
類 UDC 的可用的用戶定義的轉(zhuǎn)換來自 int 類型和 long 類型。因此,編譯器會考慮針對將匹配的對象類型的轉(zhuǎn)換:UDC。到 int 的轉(zhuǎn)換已存在且已被選中。
在匹配參數(shù)的過程中,標準轉(zhuǎn)換可應用于參數(shù)和用戶定義的轉(zhuǎn)換的結(jié)果。因此,下面的代碼將適用:
void LogToFile( long l ); ... UDC udc; LogToFile( udc );
在前面的示例中,將調(diào)用用戶定義的轉(zhuǎn)換 operator long 以將 udc 轉(zhuǎn)換為類型 long。如果未定義到 long 類型的用戶定義的轉(zhuǎn)換,則按如下所示繼續(xù)轉(zhuǎn)換:使用用戶定義的轉(zhuǎn)換將 UDC 類型轉(zhuǎn)換為 int 類型。將應用從 int 類型到 long 類型的標準轉(zhuǎn)換以匹配聲明中的參數(shù)。
如果需要任何用戶定義的轉(zhuǎn)換來匹配參數(shù),則在計算最佳匹配時不會使用標準轉(zhuǎn)換。即使多個候選函數(shù)需要用戶定義的轉(zhuǎn)換也是如此;在這種情況下,這些函數(shù)被認為是相等的。例如:
// argument_matching2.cpp // C2668 expected class UDC1 { public: UDC1( int ); // User-defined conversion from int. }; class UDC2 { public: UDC2( long ); // User-defined conversion from long. }; void Func( UDC1 ); void Func( UDC2 ); int main() { Func( 1 ); }
Func 的兩個版本都需要用戶定義的轉(zhuǎn)換以將類型 int 轉(zhuǎn)換為類類型參數(shù)??赡艿霓D(zhuǎn)換包括:
- 從 int 類型轉(zhuǎn)換到 UDC1 類型(用戶定義的轉(zhuǎn)換)。
- 從 int 類型轉(zhuǎn)換到 long 類型;然后轉(zhuǎn)換為 UDC2 類型(一個兩步轉(zhuǎn)換)。
即使其中的第二個轉(zhuǎn)換需要標準轉(zhuǎn)換以及用戶定義的轉(zhuǎn)換,這兩個轉(zhuǎn)換仍被視為相等。
注意
用戶定義的轉(zhuǎn)換被認為是通過構造函數(shù)的轉(zhuǎn)換或通過初始化的轉(zhuǎn)換(轉(zhuǎn)換函數(shù))。在考慮最佳匹配時,兩個方法被認為是相等的。
參數(shù)匹配和 this 指針
處理類成員函數(shù)的方式各不相同,具體取決于它們是否已被聲明為 static。由于非靜態(tài)函數(shù)具有提供 this 指針的隱式參數(shù),因此將非靜態(tài)函數(shù)視為比靜態(tài)函數(shù)多一個參數(shù);否則,將以相同的方式聲明這些函數(shù)。
這些非靜態(tài)成員函數(shù)要求隱含的 this 指針與通過其調(diào)用函數(shù)的對象類型匹配,或者對于重載運算符,它們要求第一個參數(shù)與該運算符應用于的對象匹配。
與重載函數(shù)中的其他參數(shù)不同,當嘗試匹配 this 指針參數(shù)時,不會引入臨時對象,且不會嘗試轉(zhuǎn)換。
當 – > 成員選擇運算符用于訪問成員函數(shù)時,this 指針參數(shù)具有 class-name* const 的類型。如果將成員聲明為 const 或 volatile,則類型分別為 const class-name* const 和 volatile class-name * const。
. 成員選擇運算符以相同的方式工作,只不過隱式 & (address-of) 運算符將成為對象名稱的前綴。下面的示例演示了此工作原理:
// Expression encountered in code obj.name // How the compiler treats it (&obj)->name
處理 –>* 和 .*(指向成員的指針)運算符的左操作數(shù)的方式與處理與參數(shù)匹配相關的 . 和 –>(成員選擇)運算符的方式相同。
限制
多個限制管理可接受的重載函數(shù)集:
- 重載函數(shù)集內(nèi)的任意兩個函數(shù)必須具有不同的參數(shù)列表。
- 僅基于返回類型重載具有相同類型的參數(shù)列表的函數(shù)是錯誤的。
不能只根據(jù)一個靜態(tài)類型和一個非靜態(tài)類型來重載成員函數(shù)。
typedef 聲明不定義新類型;它們引入現(xiàn)有類型的同義詞。它們不影響重載機制??紤]下列代碼:
typedef char * PSTR; void Print( char *szToPrint ); void Print( PSTR szToPrint );
前面的兩個函數(shù)具有相同的參數(shù)列表。 PSTR 是類型 char * 的同義詞。在成員范圍內(nèi),此代碼生成錯誤。
枚舉類型是不同的類型,并且可用于區(qū)分重載函數(shù)。
就區(qū)分重載函數(shù)而言,類型“array of”和“pointer to”是等效的。此情況僅適用于單維度數(shù)組。因此,以下重載函數(shù)會發(fā)生沖突并生成錯誤消息:
void Print( char *szToPrint ); void Print( char szToPrint[] );
對于多維數(shù)組,第二個和后續(xù)維度被視為類型的一部分。因此,它們可用來區(qū)分重載函數(shù):
void Print( char szToPrint[] ); void Print( char szToPrint[][7] ); void Print( char szToPrint[][9][42] );
聲明匹配
同一范圍內(nèi)具有同一名稱的任何兩個函數(shù)聲明都可以引用同一函數(shù)或重載的兩個不同的函數(shù)。如果聲明的參數(shù)列表包含等效類型的參數(shù)(如上一節(jié)所述),函數(shù)聲明將引用同一函數(shù)。否則,它們將引用使用重載選擇的兩個不同的函數(shù)。
需要嚴格遵守類范圍;因此,在基類中聲明的函數(shù)與在派生類中聲明的函數(shù)不在同一范圍內(nèi)。如果使用與基類中的函數(shù)相同的名稱聲明派生類中的函數(shù),則該派生類函數(shù)會隱藏基類函數(shù),而不是導致重載。
需要嚴格遵守塊范圍;因此,在文件范圍中聲明的函數(shù)與在本地聲明的函數(shù)不在同一范圍內(nèi)。如果在本地聲明的函數(shù)與在文件范圍中聲明的函數(shù)具有相同名稱,則在本地聲明的函數(shù)將隱藏文件范圍內(nèi)的函數(shù)而不是導致重載。例如:
// declaration_matching1.cpp // compile with: /EHsc #include <iostream> using namespace std; void func( int i ) { cout << "Called file-scoped func : " << i << endl; } void func( char *sz ) { cout << "Called locally declared func : " << sz << endl; } int main() { // Declare func local to main. extern void func( char *sz ); func( 3 ); // C2664 Error. func( int ) is hidden. func( "s" ); }
前面的代碼顯示函數(shù) func 中的兩個定義。由于 char * 語句,采用 main 類型的參數(shù)的定義是 extern 的本地定義。因此,采用 int 類型的參數(shù)的定義被隱藏,而對 func 的第一次調(diào)用出錯。
對于重載的成員函數(shù),不同版本的函數(shù)可能獲得不同的訪問權限。它們?nèi)员灰暈樵诜忾]類的范圍內(nèi),因此是重載函數(shù)。請考慮下面的代碼,其中的成員函數(shù) Deposit 將重載;一個版本是公共的,另一個版本是私有的。
此示例的目的是提供一個 Account 類,其中需要正確的密碼來執(zhí)行存款。使用重載可完成此操作。
請注意,對 Deposit 中的 Account::Deposit 的調(diào)用將調(diào)用私有成員函數(shù)。此調(diào)用是正確的,因為 Account::Deposit 是成員函數(shù),因而可以訪問類的私有成員。
// declaration_matching2.cpp class Account { public: Account() { } double Deposit( double dAmount, char *szPassword ); private: double Deposit( double dAmount ) { return 0.0; } int Validate( char *szPassword ) { return 0; } }; int main() { // Allocate a new object of type Account. Account *pAcct = new Account; // Deposit $57.22. Error: calls a private function. // pAcct->Deposit( 57.22 ); // Deposit $57.22 and supply a password. OK: calls a // public function. pAcct->Deposit( 52.77, "pswd" ); } double Account::Deposit( double dAmount, char *szPassword ) { if ( Validate( szPassword ) ) return Deposit( dAmount ); else return 0.0; }
相關文章
C++結(jié)構體作為函數(shù)參數(shù)傳參的實例代碼
這篇文章主要介紹了C++結(jié)構體作為函數(shù)參數(shù)傳參的實例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12C++入門(命名空間,缺省參數(shù),函數(shù)重載,引用,內(nèi)聯(lián)函數(shù),auto,范圍for)
這篇文章主要介紹了C++入門(命名空間,缺省參數(shù),函數(shù)重載,引用,內(nèi)聯(lián)函數(shù),auto,范圍for),這些基礎知識是學習C++最最基礎需要掌握的知識點,需要的朋友可以參考下2021-05-05C/C++實現(xiàn)動態(tài)數(shù)組的示例詳解
動態(tài)數(shù)組相比于靜態(tài)數(shù)組具有更大的靈活性,因為其大小可以在運行時根據(jù)程序的需要動態(tài)地進行分配和調(diào)整,本文為大家介紹了C++實現(xiàn)動態(tài)數(shù)組的方法,需要的可以參考下2023-08-08