c++中的函數重載、函數重寫、函數重定義


目錄

  一、函數重載

  二、函數重寫

  三、函數重定義


為了更加深刻的理解 函數重載、重寫、重定義,我們可以帶着如下這兩個問題去思考:

1、子類中是否可以定義父類中的同名成員?為什么?

  可以,因為子類與父類的命名空間不同;

2、子類中定義的函數是否可以重載父類中的同名函數?

  不可以,因為函數重載必須在同一個作用域中。


 

一、函數重載(Function Overloading) 

1、什么是函數重載

  在 同一個類中(同一個作用域中/在類的內部),存在一組 函數名相同,函數的參數列表不同(參數的個數、類型、順序),函數有無 virtual 關鍵字都可以,我們把這組函數稱為函數重載。

2、為什么使用函數重載(函數重載的好處)

  由於函數重載可以在同一個作用域內,使用同一個函數名 命名一組功能相似的函數,這樣做減少了函數名的數量,避免了程序員因給函數名命名所帶來的煩惱,從而提高程序的開發的效率。

3、函數重載的條件

  1. 必須在同一作用域下

  2. 函數名相同但是參數列表不同(參數列表的 類型 or 個數 or 順序 不同)

  3. 返回值的類型不會影響重載

  4. const屬性相同

4、函數重載的原理(本質:c++編譯器對同名函數進行重命名)

  編譯器在編譯.cpp文件中當前使用的作用域里的同名函數時,根據函數形參的類型和順序會對函數進行重命名(不同的編譯器在編譯時對函數的重命名標准不一樣);

  但是總的來說,他們都把文件中的同一個函數名進行了重命名

  • 在vs編譯器中:

  根據返回值類型(不起決定性作用)+形參類型和順序(起決定性作用)的規則重命名並記錄在map文件中。

  • 在linux g++ 編譯器中:

  根據函數名字的字符數+形參類型和順序的規則重命名記錄在符號表中;從而產生不同的函數名,當外面的函數被調用時,便是根據這個記錄的結果去尋找符合要求的函數名,進行調用;

  為什么c語言不能實現函數重載?

  編譯器在編譯.c文件時,只會給函數進行簡單的重命名;

  具體的方法是給函數名之前加上”_”;所以加入兩個函數名相同的函數在編譯之后的函數名也照樣相同;調用者會因為不知道到底調用那個而出錯;

 1 #include<stdio.h>
 2 
 3 int Add(int a, int b)
 4 {
 5     return a + b;
 6 }
 7 
 8 
 9 float Add(float a, float b)
10 {
11     return a + b;
12 }
13 
14 void testFunc()
15 {
16     Add(10, 20);
17     Add(20.0f, 30.0f);
18 }
19 
20 int main(int argc, char *argv[])
21 {
22     testFunc();
23 
24     return 0;
25 }
案例分析

1.  將上述代碼保存到.c文件中

  若上述代碼用c編譯器編譯,由於c語言中無函數重載,所以,在程序運行時出錯。

  出錯原因:因為在c語言中,c編譯器只是在函數名的前面加下划線進行簡單的重命名

  為了驗證結果,將上述的代碼稍作修改( float Add(float a, float b) -> float Add1(float a, float b) )。然后用 vs Debug模式編譯.c文件,之后在.map文件中就可以看到結果。

       

  在vs中,map文件生成的步驟設置:工程名右擊—>屬性—->配置屬性—->鏈接器—–>調試—->生成映射文件—>選擇是;

2.  將上述代碼保存到.cpp文件中

  若上述代碼用c++編譯器編譯,由於c++語言支持函數重載,所以程序正常運行;但是,在不同c++編譯器之間對函數重載的機制也是不一樣,接下來分別用vs 和 g++介紹。

(1)用 vs Debug模式編譯.cpp文件,之后就可以在map文件中看到如下結果,

       

  // ‘?’表示名稱開始,‘?’后邊是函數名;“@@YA”表示參數表開始,后邊的3個字符分別表示返回值類型,兩個參數類型;“@Z”表示名稱結束。

(2)在Ubuntu下測試(需要安裝g++編譯器),執行以下指令:

  1)g++ test.cpp   

  2)objdump a.out -t > test.out    // -t是表示生成符號表,最后是將生成的符號表用重定向符號放在test.out文件。

  3)vi test.out

      

  打開test.out文件,就會發現,整形數相加的函數Add(int a,int b)生成的符號表中,Add函數名被記錄為_Z3Addii。

  其中,_Z表示符號表名稱開始, 3代表函數名的字符個數,ii代表參數列表順序中2個形參的類型;

綜述,無論使用何種編譯器,在.cpp文件中,雖然兩個函數的函數名一樣,但是他們在符號表中生成的名稱不一樣,所以是可以編譯通過的。

由上述分析可知,c編譯器 與 c++編譯器  對函數的重命名規則不一樣;那么,在c++中如何確保將一段c代碼以c編譯器的方式被編譯呢?---- 使用 extern 關鍵字

 1 // 使用方式1
 2 extern "C"
 3 {
 4       // C-Style Compilation
 5 }
 6 
 7 // 使用方式2
 8 //__cplusplus 是 c++ 編譯器內置的標准宏定義
 9 //__cplusplus 的意義:確保C代碼以統一的C方式被編譯成目標文件
10 
11 #ifdef __cplusplus
12 extern "C" {
13 #endif
14 
15 // C-Style Compilation
16 
17 #ifdef __cplusplus
18 }
19 #endif
extern "C" 的使用方式

 參考鏈接:https://blog.csdn.net/qq_37791134/article/details/81502017https://blog.csdn.net/gogogo_sky/article/details/71189499https://blog.csdn.net/fantian_/article/details/80719144

5、函數重載的結論

  1. 函數重載的本質:多個不同的函數;

  2. 函數名和參數列表是唯一的標識;

  3. 函數重載必須發生在同一個作用域中;

  4. c++編譯器 和 c編譯器 對函數重命名的規則不同;

  5. 編譯器決定符號表中函數名被編譯后的最終目標名;

    c++ 編譯器 將函數名和參數列表編譯成目標名;

    c 編譯器將函數名編譯成目標名;

  6. 函數重載是在編譯期間根據參數類型和個數決定函數調用

  7. 函數重載是一種靜態多態;

  (1)多態:用同一個東西表示不同的形態;

  (2)多態分為:靜態多態(編譯時的多態)、動態多態(運行時的多態);

6、編譯器調用函數重載的規則

  1. 將所有同名函數作為候選者;

  2. 嘗試尋找可行的候選者函數

  (1)精確匹配實參;

  (2)通過默認參數能夠匹配實參;

  (3)通過默認類型轉換匹配實參;

  3. 匹配失敗

  (1)最終尋找的候選函數不唯一,則出現二義性,編譯失敗;

  (2)無法匹配所有的候選函數,函數沒定義,編譯失敗;

7、函數重載與默認參數

  當函數重載遇到默認參數時,就會發生二義性;

  代碼如下:  

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class A
 5 {
 6     void func(int a, int b, int c = 0) {}
 7     void func(int a, int b) {}
 8 };
 9 
10 int main()
11 {
12     A a;
13     a.func(1, 2); // 二義性出現
14 
15     return 0;
16 }
函數重載的二義性案例

8、函數重載 與 函數指針

  將重載函數名賦值給函數指針時,

  1. 根據重載規則挑選與函數指針參數列表一致的候選者;

  2. 嚴格匹配候選者的函數類型與函數指針的函數類型;

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 int func(int x)
 5 {
 6     return x;
 7 }
 8 
 9 int func(int a, int b)
10 {
11     return a + b;
12 }
13 
14 int func(const char* s)
15 {
16     return strlen(s);
17 }
18 
19 typedef int(*PFUNC)(int a);
20 
21 
22 int main(int argc, char *argv[])
23 {
24     int c = 0;
25 
26     PFUNC p = func;
27         
28     c = p(1);   
29     
30     printf("c = %d\n", c);    // c = 1
31 
32     return 0;
33 }
函數重載與函數指針

 

二、函數重寫(也稱為覆蓋, Function override)

1、什么是函數重寫

  函數重寫分為 虛函數重寫(會發生多態) 與 非虛函數重寫(重定義的一種形式); 

  函數重寫:也叫做覆蓋。子類重新定義父類中有相同返回值、名稱參數虛函數。函數特征相同。但是具體實現不同,主要是在繼承關系中出現的 。

  注:一般而言,函數重寫 就是 虛函數重寫,為的是實現多態調用; 

2、函數重寫的條件

  1. 函數的返回類型、方法名、參數列表完全相同;

  2. 必須發生在不同的作用域中(基類與派生類中);

  3. 基類中有 virtual 關鍵字聲明,派生類中可有可無,不能有 static (虛函數重寫);

3、函數重寫的意義

  在面向對象的繼承關系中,我們了解到子類可以擁有父類中的所有屬性與行為;但是,有時父類中提供的方法並不能滿足現有的需求,所以,我們必須在子類中重寫父類中已有的方法,來滿足當前的需求。

 

三、函數重定義(也稱為隱藏,Function redefining)

1、什么是函數重定義

  子類重新定義父類中有相同名稱的函數 ( 不包括虛函數重寫 ) 。

2、重定義的表現形式

  1. 必須發生在不同的作用域中(基類與派生類中);

  2. 函數名相同;

  3. 返回值可以不同;

  4. 參數列表不同,此時,無論基類中的同名函數有無 virtual 關鍵字,基類中的同名函數都會被隱藏。

  5. 參數列表相同,此時,基類中的同名函數沒有 virtual 關鍵字,則基類中的同名函數將會被隱藏 --- 非虛函數重寫 。

3、關於同名覆蓋的結論(歸納:基類與派生類中存在同名成員;--- 同名覆蓋

  1. 子類將隱藏父類中的同名成員;

  2. 父類中的同名成員依然存在於子類中;

  3. 可以通過作用域分辨符(::)訪問被隱藏的父類中的同名成員;

  4. 不可以直接通過子類對象訪問父類成員;

   注:同名覆蓋規則適用於類的成員變量與成員函數;

  相關代碼展示:

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 class Parent
 7 {
 8 public:
 9     int mi;
10     
11     Parent()
12     {
13         cout << "Parent() : " << "&mi = " << &mi << endl;
14     }
15 };
16 
17 class Child : public Parent
18 {
19 public:
20     int mi;
21     
22     Child()
23     {
24         cout << "Child() : " << "&mi = " << &mi << endl;
25     }
26 };
27 
28 int main()
29 {
30     Child c;
31     
32     c.mi = 100;    
33         
34     c.Parent::mi = 1000;
35     
36     cout << "&c.mi = " << &c.mi << endl;
37     cout << "c.mi = " << c.mi << endl;
38     
39     cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
40     cout << "c.Parent::mi = " << c.Parent::mi << endl;
41     
42     return 0;
43 }
44 
45 /**
46 * Parent() : &mi = 0x7ffe98191450
47 * Child() : &mi = 0x7ffe98191454
48 * &c.mi = 0x7ffe98191454
49 * c.mi = 100
50 * &c.Parent::mi = 0x7ffe98191450
51 * c.Parent::mi = 1000
52 */
同名成員變量案例
 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 class Parent
 7 {
 8 public:
 9     int mi;
10     
11     void add(int v)
12     {
13         mi += v;
14     }
15     
16     void add(int a, int b)
17     {
18         mi += (a + b);
19     }
20 };
21 
22 class Child : public Parent
23 {
24 public:
25     int mi;
26     
27     void add(int v)
28     {
29         mi += v;
30     }
31     
32     void add(int a, int b)
33     {
34         mi += (a + b);
35     }
36     
37     void add(int x, int y, int z)
38     {
39         mi += (x + y + z);
40     }
41 };
42 
43 int main()
44 {
45     Child c;
46     
47     c.mi = 100;        
48     c.Parent::mi = 1000;
49     
50     cout << "c.mi = " << c.mi << endl;    
51     cout << "c.Parent::mi = " << c.Parent::mi << endl;
52     
53     c.add(1);
54     c.add(2, 3);
55     c.add(4, 5, 6);
56     c.Parent::add(10);
57     c.Parent::add(11, 12);
58     
59     cout << "c.mi = " << c.mi << endl;    
60     cout << "c.Parent::mi = " << c.Parent::mi << endl;
61     
62     return 0;
63 }
64 /**
65 * c.mi = 100
66 * c.Parent::mi = 1000
67 * c.mi = 121
68 * c.Parent::mi = 1033
69 */
重定義案例
 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 class Parent
 7 {
 8 public:
 9     int mi;
10     
11     virtual void add(int v)
12     {
13         mi += v;
14     }
15 };
16 
17 class Child : public Parent
18 {
19 public:
20     int mi;
21     
22     virtual void add(int v)
23     {
24         mi += v;
25     }
26     
27     void add(int a, int b)
28     {
29         mi += (a + b);
30     }
31 };
32 
33 int main()
34 {
35     Child c;
36     Parent &p = c;  // 父類引用指向子類對象,多態發生
37     
38     c.mi = 100;     
39     c.Parent::mi = 1000;
40     
41     cout << "c.mi = " << c.mi << endl;   
42     cout << "c.Parent::mi = " << c.Parent::mi << endl;
43     
44     c.add(1);
45     c.add(2, 3);
46     p.add(100);     // 實際調用的是子類中 add(int v) 函數
47     c.Parent::add(10);
48      
49     cout << "c.mi = " << c.mi << endl;   // c.mi = 1 + 2 + 3 + 100
50     cout << "c.Parent::mi = " << c.Parent::mi << endl; // c.Parent::mi = 1000 + 10
51     
52     return 0;
53 }
54 /**
55 * c.mi = 100
56 * c.Parent::mi = 1000
57 * c.mi = 206
58 * c.Parent::mi = 1010
59 */
重寫案例


本節總結:

1、 重載 必須在 一個類之間, 而 重寫、重定義 是在 2個類 之間

2、 重載是在 編譯期間 根據參數類型和個數決定函數調用; 多態(虛函數重寫)是在 運行期間 根據具體對象的類型決定函數調用

3、 發生重寫、重定義后,遵循 同名覆蓋 規則;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM