深度探討C++中的函數重載的用法。本站提示廣大學習愛好者:(深度探討C++中的函數重載的用法)文章只能為提供參考,不一定能成為您想要的結果。以下是深度探討C++中的函數重載的用法正文
C++ 許可統一規模內具有雷同稱號的多個函數的標准。這些函數稱為重載函數,“重載”中對其停止了具體引見。應用重載函數,法式員可以依據參數的類型和數目為函數供給分歧的語義。
例如,采取字符串(或 char *)參數的 print 函數履行的義務與采取“雙精度”類型的參數的函數履行的義務判然不同。重載許可通用定名並使法式員無需創立稱號,例如 print_sz 或 print_d。下表顯示了 C++ 應用函數聲明的哪些部門來辨別統一規模內具有雷同稱號的函數組。
重載留意事項
// 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(); }
後面的代碼演示了文件規模內的 print 函數重載。
默許參數不被視為函數類型的一部門。是以,它不消於選擇重載函數。僅在默許參數上存在差別的兩個函數被視為多個界說而不是重載函數。
不克不及為重載運算符供給默許參數。
參數婚配
選擇重載函數以完成以後規模內的函數聲明與函數挪用中供給的參數的最好婚配。假如找到適合的函數,則挪用該函數。此高低文中的“Suitable”具有以下寄義之一:
編譯器為每一個參數創立一組候選函數。候選函數是如許一種函數,個中的實參可以轉換為形參的類型。
為每一個參數生成一組“最好婚配函數”,而且所選函數是一切集的交集。假如交集包括多個函數,則重載是不明白的並會生成毛病。關於至多一個參數而言,終究選擇的函數一直是比組中的一切其他函數更好的婚配項。假如不是如許(假如沒有清楚的勝者),則函數挪用會生成毛病。
斟酌上面的聲明(針對上面的評論辯論中的標識,將函數標志為 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 );
後面的語句生成兩個集:
F1 = Add( 3, 6 );
後面的函數挪用生成以下集:
// 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 對象的指針也被以為和指向基類型的指針(以重載為目標)分歧。
參數婚配和轉換
當編譯器測驗考試依據函數聲明中的參數婚配現實參數時,假如未找就任何確實婚配項,它可以供給尺度轉換或用戶界說的轉換來獲得准確類型。轉換的運用法式受這些規矩的限制:
不斟酌包括多個用戶界說的轉換的轉換序列。
不斟酌可經由過程刪除中央轉換來延長的轉換序列。
終究的轉換序列(假如有)稱為最好婚配序列。可經由過程多種方法應用尺度轉換將類型 int 的對象轉換為類型unsigned long 的對象(如尺度轉換中所述):
第一個序列(雖然它完成了所需目的)不是最好婚配序列 - 存在一個較短的序列。
下表顯示了一組稱為經常使用轉換的轉換,這些轉換對肯定哪一個序列是最好婚配項有必定的限制。該表前面的列表中評論辯論了經常使用轉換影響序列選擇的實例。
經常使用轉換
應用晉升的婚配。未歸類為僅包括整型晉升、從 float 到 double 的轉換和經常使用轉換的完整婚配的任何序列將被歸類為應用晉升的婚配。雖然比不上完整婚配,但應用晉升的婚配仍優於應用尺度轉換的婚配。
應用尺度轉換的婚配。未歸類為完整婚配或僅包括尺度轉換和經常使用轉換的應用晉升的婚配的序列將歸類為應用尺度轉換的婚配。在此種別中,以下規矩將實用:
從指向派生類的指針到指向直接或直接基類的指針的轉換優於到 void * 或 const void * 的轉換。
從指向派生類的指針到指向基類的指針的轉換會發生一個到直接基類的更好婚配。假定類條理構造以下圖所示。
演示首選轉換的關系圖
從 D* 類型到 C* 類型的轉換優於從 D* 類型到 B* 類型的轉換。異樣,從 D* 類型到 B* 類型的轉換優於從 D* 類型到 A* 類型的轉換。
此統一規矩實用於援用轉換。從 D& 類型到 C& 類型的轉換優於從 D& 類型到 B& 類型的轉換等。
此統一規矩實用於指向成員的指針轉換。從 T D::* 類型到 T C::* 類型的轉換優於從 T D::* 類型到 T B::* 類型的轉換等(個中,T 是該成員的類型)。
後面的規矩僅沿派生的給定途徑運用。斟酌下圖中顯示的關系圖。
演示首選轉換的多繼續關系圖
從 C* 類型到 B* 類型的轉換優於從 C* 類型到 A* 類型的轉換。緣由是它們位於統一個途徑上,且 B* 更加接近。然則,從 C* 類型到 D* 類型的轉換不優於到 A* 類型的轉換;沒有首選項,由於這些轉換遵守分歧的途徑。
應用用戶界說的轉換的婚配。此序列不克不及歸類為完整婚配、應用晉升的婚配或應用尺度轉換的婚配。序列必需僅包括用戶界說的轉換、尺度轉換或要歸類為應用用戶界說的轉換的婚配的經常使用轉換。應用用戶界說的轉換的婚配被以為優於應用省略號的婚配,但比不上應用尺度轉換的婚配。
應用省略號的婚配。與聲明中的省略號婚配的任何序列將歸類為應用省略號的婚配。這被視為最弱婚配。
假如內置晉升或轉換不存在,則用戶界說的轉換將實用。基於將婚配的參數的類型選擇這些轉換。斟酌以下代碼:
// argument_matching1.cpp class UDC { public: operator int() { return 0; } operator long(); }; void Print( int i ) { }; UDC udc; int main() { Print( udc ); }
類 UDC 的可用的用戶界說的轉換來自 int 類型和 long 類型。是以,編譯器會斟酌針對將婚配的對象類型的轉換:UDC。到 int 的轉換已存在且已被選中。
在婚配參數的進程中,尺度轉換可運用於參數和用戶界說的轉換的成果。是以,上面的代碼將實用:
void LogToFile( long l ); ... UDC udc; LogToFile( udc );
在後面的示例中,將挪用用戶界說的轉換 operator long 以將 udc 轉換為類型 long。假如不決義到 long 類型的用戶界說的轉換,則按以下所示持續轉換:應用用戶界說的轉換將 UDC 類型轉換為 int 類型。將運用從 int 類型到 long 類型的尺度轉換以婚配聲明中的參數。
假如須要任何用戶界說的轉換來婚配參數,則在盤算最好婚配時不會應用尺度轉換。即便多個候選函數須要用戶界說的轉換也是如斯;在這類情形下,這些函數被以為是相等的。例如:
// 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 的兩個版本都須要用戶界說的轉換以將類型 int 轉換為類類型參數。能夠的轉換包含:
即便個中的第二個轉換須要尺度轉換和用戶界說的轉換,這兩個轉換仍被視為相等。
留意
用戶界說的轉換被以為是經由過程結構函數的轉換或經由過程初始化的轉換(轉換函數)。在斟酌最好婚配時,兩個辦法被以為是相等的。
參數婚配和 this 指針
處置類成員函數的方法各不雷同,詳細取決於它們能否已被聲明為 static。因為非靜態函數具有供給 this 指針的隱式參數,是以將非靜態函數視為比靜態函數多一個參數;不然,將以雷同的方法聲明這些函數。
這些非靜態成員函數請求隱含的 this 指針與經由過程其挪用函數的對象類型婚配,或許關於重載運算符,它們請求第一個參數與該運算符運用於的對象婚配。
與重載函數中的其他參數分歧,當測驗考試婚配 this 指針參數時,不會引入暫時對象,且不會測驗考試轉換。
當 – > 成員選擇運算符用於拜訪成員函數時,this 指針參數具有 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
處置 –>* 和 .*(指向成員的指針)運算符的左操作數的方法與處置與參數婚配相干的 . 和 –>(成員選擇)運算符的方法雷同。
限制
多個限制治理可接收的重載函數集:
不克不及只依據一個靜態類型和一個非靜態類型來重載成員函數。
typedef 聲明不界說新類型;它們引入現有類型的同義詞。它們不影響重載機制。斟酌以下代碼:
typedef char * PSTR; void Print( char *szToPrint ); void Print( PSTR szToPrint );
後面的兩個函數具有雷同的參數列表。 PSTR 是類型 char * 的同義詞。在成員規模內,此代碼生成毛病。
列舉類型是分歧的類型,而且可用於辨別重載函數。
就辨別重載函數而言,類型“array of”和“pointer to”是等效的。此情形僅實用於單維度數組。是以,以下重載函數會產生抵觸並生成毛病新聞:
void Print( char *szToPrint ); void Print( char szToPrint[] );
關於多維數組,第二個和後續維度被視為類型的一部門。是以,它們可用來辨別重載函數:
void Print( char szToPrint[] ); void Print( char szToPrint[][7] ); void Print( char szToPrint[][9][42] );
聲明婚配
統一規模內具有統一稱號的任何兩個函數聲明都可以援用統一函數或重載的兩個分歧的函數。假如聲明的參數列表包括等效類型的參數(如上一節所述),函數聲明將援用統一函數。不然,它們將援用應用重載選擇的兩個分歧的函數。
須要嚴厲遵照類規模;是以,在基類中聲明的函數與在派生類中聲明的函數不在統一規模內。假如應用與基類中的函數雷同的稱號聲明派生類中的函數,則該派生類函數會隱蔽基類函數,而不是招致重載。
須要嚴厲遵照塊規模;是以,在文件規模中聲明的函數與在當地聲明的函數不在統一規模內。假如在當地聲明的函數與在文件規模中聲明的函數具有雷同稱號,則在當地聲明的函數將隱蔽文件規模內的函數而不是招致重載。例如:
// 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" ); }
後面的代碼顯示函數 func 中的兩個界說。因為 char * 語句,采取 main 類型的參數的界說是 extern 的當地界說。是以,采取 int 類型的參數的界說被隱蔽,而對 func 的第一次挪用失足。
關於重載的成員函數,分歧版本的函數能夠取得分歧的拜訪權限。它們仍被視為在關閉類的規模內,是以是重載函數。請斟酌上面的代碼,個中的成員函數 Deposit 將重載;一個版本是公共的,另外一個版本是公有的。
此示例的目標是供給一個 Account 類,個中須要准確的暗碼來履行存款。應用重載可完成此操作。
請留意,對 Deposit 中的 Account::Deposit 的挪用將挪用公有成員函數。此挪用是准確的,由於 Account::Deposit 是成員函數,因此可以拜訪類的公有成員。
// 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; }