父子間的同名沖突
首先來看一段代碼:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
};
class Child : public Parent
{
public:
int mi;
};
int main()
{
Child c;
//這里的mi是Parent中的還是Child中的呢?
c.mi = 100;
return 0;
}
編譯通過,說明子類可以定義和父類相同的同名成員。
- 子類可以定義父類中的同名成員
- 子類中的成員將隱藏父類中的同名成員
- 父類中的同名成員依然存在于子類中
- 通過作用域分辨符( : : )訪問父類中的同名成員
- 訪問父類中的同名成員
Child c;
//子類中的mi
c.mi = 100;
//父類中的mi
c.Parent::mi = 1000;
再來看一個例子:
#include <iostream>
#include <string>
using namespace std;
//定義一個命名空間A
namespace A
{
int g_i = 0;
}
//定義一個命名空間B
namespace B
{
int g_i = 1;
}
class Parent
{
public:
int mi;
Parent()
{
cout << "Parent() : " << "&mi = " << &mi << endl;
}
};
class Child : public Parent
{
public:
int mi;
Child()
{
cout << "Child() : " << "&mi = " << &mi << endl;
}
};
int main()
{
Child c;
//向子類的mi成員賦值100
c.mi = 100;
//通過作用域向父類的mi成員賦值1000
c.Parent::mi = 1000;
//打印子類中mi的地址
cout << "&c.mi = " << &c.mi << endl;
//打印子類中mi的內(nèi)容
cout << "c.mi = " << c.mi << endl;
//打印父類中mi的地址
cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
//打印父類中mi的內(nèi)容
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
輸出結(jié)果為:
Parent() : &mi = 0x7fff57f57a90
Child() : &mi = 0x7fff57f57a94
&c.mi = 0x7fff57f57a94
c.mi = 100
&c.Parent::mi = 0x7fff57f57a90
c.Parent::mi = 1000
- 類中的成員函數(shù)可以進行重載
- 重載函數(shù)的本質(zhì)為多個不同的函數(shù)
- 函數(shù)名和參數(shù)列表是唯一的標(biāo)識
- 函數(shù)重載必須發(fā)生在同一個作用域中
- 所以父子之間的同名成員不構(gòu)成重載
比如像這樣:
class Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
void add(int x, int y, int z)
{
mi += (x + y + z);
}
};
代碼 Parent類 和 Child類 中有同名的函數(shù)add ,但是兩個類之間不構(gòu)成重載,只有Parent類中多個add函數(shù)構(gòu)成重載。
- 子類中的函數(shù)將隱藏父類的同名函數(shù)
- 子類無法重載父類中的成員函數(shù)
- 使用作用域分辨符訪問父類中的同名函數(shù)
- 子類可以定義父類中完全相同的成員函數(shù)
父子間的賦值兼容
- 子類對象可以當(dāng)作父類對象使用(兼容性)
- 子類對象可以直接賦值給父類對象
- 子類對象可以直接初始化父類對象
- 父類指針可以直接指向子類對象
- 父類引用可以直接引用子類對象
舉個例子:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
};
int main()
{
Parent p;
Child c;
//子類對象可以直接賦值給父類對象
p = c;
//子類對象可以直接初始化父類對象
Parent p1(c);
//父類引用可以直接引用子類對象
Parent& rp = c;
//父類指針可以直接指向子類對象
Parent* pp = &c;
return 0;
}
在main函數(shù)中進行上述幾條的操作都沒有出現(xiàn)編譯出錯?,F(xiàn)在進行這樣操作:
rp.mi = 100;
rp.add(5);
rp.add(10, 10);
發(fā)現(xiàn)可以編譯通過,并沒有出現(xiàn)同名覆蓋的問題。但是如果這樣操作:
pp->mv = 1000;
pp->add(1, 10, 100);
運行以后就會報錯,報錯信息如下:
48-1.cpp:51:10: error: no member named 'mv' in 'Parent'
pp->mv = 1000;
~~ ^
48-1.cpp:52:10: error: no matching member function for call to 'add'
pp->add(1, 10, 100);
~~~~^~~
48-1.cpp:16:10: note: candidate function not viable: requires 2 arguments, but 3
were provided
void add(int a, int b)
^
48-1.cpp:11:10: note: candidate function not viable: requires single argument
'i', but 3 arguments were provided
void add(int i)
^
2 errors generated.
信息提示沒有找到帶有3個參數(shù)的add函數(shù)。為什么呢?
- 當(dāng)使用父類指針(引用)指向子類對象時
- 子類對象退化為父類對象
- 只能訪問父類中定義的成員
- 可以直接訪問被子類覆蓋的同名成員
特殊的同名函數(shù)
- 子類中可以沖定義父類中已經(jīng)存在的成員函數(shù)
- 這種沖定義發(fā)生在繼承中,叫做函數(shù)重寫
- 函數(shù)重寫是同名覆蓋的一種特殊情況
例如:
class Parent
{
public:
void print()
{
cout << "I'm Parent." << endl;
}
};
//函數(shù)重寫
class Child : public Parent
{
public:
void print()
{
cout << "I'm Child" << endl;
}
};
假如函數(shù)重寫和賦值兼容同時出現(xiàn)呢? 就像這樣:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
void print()
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
void print()
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p)
{
p->print();
}
int main()
{
Parent p;
Child c;
how_to_print(&p); // Expected to print: I'm Parent.
how_to_print(&c); // Expected to print: I'm Child.
return 0;
}
預(yù)期輸出是 I'm Parent. 和 I'm Child. 。但是實際輸出:
I'm Parent.
I'm Parent.
- 問題分析
- 編譯期間,編譯器只能根據(jù)指針的類型判斷所指向的對象
- 根據(jù)賦值兼容,編譯器認(rèn)為父類指針指向的是父類對象
- 因此,編譯結(jié)果只可能是調(diào)用父類中定義的同名函數(shù)
在編譯 void how_to_print(Parent* p) 這個函數(shù)時,編譯器不可能知道指針p究竟指向了什么,但是編譯器沒有理由報錯。于是,編譯器認(rèn)為最安全的做法是調(diào)用父類的print函數(shù),因為父類和子類肯定都有相同的print函數(shù)。
多態(tài)的概念和意義
- 函數(shù)重寫回顧
- 父類中被重寫的函數(shù)依然會繼承給子類
- 子類中重寫的函數(shù)將覆蓋父類中的函數(shù)
- 通過作用域分辨符( : : )可以訪問到父類中的函數(shù)
就像這樣:
Child c;
Parent* p = &c;
c.Parent::print(); //從父類中繼承
c.print(); //從子類中重寫
p->print(); //父類中定義
雖然程序邏輯是這樣,但并不是我們所期望的。面向?qū)ο笾衅谕男袨椋?/p>
- 根據(jù)
實際的對象類型判斷如何調(diào)用重寫函數(shù) -
父類指針(引用)指向-
父類對象則調(diào)用父類中定義的函數(shù) -
子類對象則調(diào)用子類中定義的重寫函數(shù)
-
這里就引出了面向?qū)ο笾械?多態(tài) 的概念:
- 根據(jù)實際的
對象類型決定函數(shù)調(diào)用的具體目標(biāo) - 同樣的
調(diào)用語句在實際運行時有多種不同的表現(xiàn)形態(tài)
例如:
p->print();
p指向父類對象時,會執(zhí)行
void print()
{
cout << "I'm Parent" << end;
}
p指向子類對象時,會執(zhí)行
void print()
{
cout << "I'm Child" << endl;
}
- C++語言直接支持多態(tài)的概念
- 通過使用
virtual關(guān)鍵字對多態(tài)進行支持 - 被
virtual聲明的函數(shù)被重寫后具有多態(tài)特性 - 被
virtual聲明的函數(shù)叫做虛函數(shù)
- 通過使用
舉個例子:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
//用 virtual 關(guān)鍵字修飾,則具有多態(tài)特性
virtual void print()
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout << "I'm Child." << endl;
}
};
void how_to_print(Parent* p)
{
// 展現(xiàn)多態(tài)的行為
p->print();
}
int main()
{
Parent p;
Child c;
how_to_print(&p); // Expected to print: I'm Parent.
how_to_print(&c); // Expected to print: I'm Child.
return 0;
}
執(zhí)行結(jié)果為:
I'm Parent.
I'm Child.
- 多態(tài)的意義
- 在程序運行過程中展現(xiàn)出動態(tài)的特性
- 函數(shù)重寫必須多態(tài)實現(xiàn),否則沒有意義
- 多態(tài)是面向?qū)ο蠼M件化程序設(shè)計的基礎(chǔ)特性
靜態(tài)聯(lián)編和動態(tài)聯(lián)編
- 理論中的概念
- 靜態(tài)聯(lián)編
- 在程序的編譯期間就能確定具體的函數(shù)調(diào)用。 如:函數(shù)重載
- 動態(tài)聯(lián)編
- 在程序?qū)嶋H運行后才能確定具體的函數(shù)調(diào)用。如:函數(shù)重寫
- 靜態(tài)聯(lián)編
舉個例子:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
//函數(shù)重載 并且用 virtual 關(guān)鍵字修飾
virtual void func()
{
cout << "void func()" << endl;
}
//函數(shù)重載 并且用 virtual 關(guān)鍵字修飾
virtual void func(int i)
{
cout << "void func(int i) : " << i << endl;
}
//函數(shù)重載 并且用 virtual 關(guān)鍵字修飾
virtual void func(int i, int j)
{
cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
}
};
class Child : public Parent
{
public:
//函數(shù)重載
void func(int i, int j)
{
cout << "void func(int i, int j) : " << i + j << endl;
}
//函數(shù)重載
void func(int i, int j, int k)
{
cout << "void func(int i, int j, int k) : " << i + j + k << endl;
}
};
void run(Parent* p)
{
p->func(1, 2); // 展現(xiàn)多態(tài)的特性
// 動態(tài)聯(lián)編
}
int main()
{
Parent p;
p.func(); // 靜態(tài)聯(lián)編
p.func(1); // 靜態(tài)聯(lián)編
p.func(1, 2); // 靜態(tài)聯(lián)編
cout << endl;
Child c;
c.func(1, 2); // 靜態(tài)聯(lián)編
cout << endl;
run(&p);
run(&c);
return 0;
}
運行結(jié)果為:
void func()
void func(int i) : 1
void func(int i, int j) : (1, 2)
void func(int i, int j) : 3
void func(int i, int j) : (1, 2)
void func(int i, int j) : 3
小結(jié)
- 子類可以定義父類的
同名成員,定義時子類中的成員將隱藏父類中的同名成員 -
子類和父類中的函數(shù)不能構(gòu)成重載關(guān)系 - 使用
作用域分辨符可以訪問父類中的同名成員 -
子類對象可以當(dāng)做父類對象使用 -
父類指針可以正確的指向子類對象 -
父類引用可以正確的代表子類對象 - 子類中可以重寫父類中的
成員函數(shù) - 函數(shù)重寫只可能發(fā)生在
父類與子類之間 - 多態(tài)是根據(jù)
實際對象的類型確定調(diào)用的具體函數(shù) -
virtual關(guān)鍵字是C++中支持多態(tài)的唯一方式 - 被重寫的
虛函數(shù)可表現(xiàn)出多態(tài)的特性