一、在c++裏,什麼是子類型呢? ios
當一個已知類型S,它至少提供了另外一個類型T的行爲,他還能夠包含本身的行爲,這時則稱類型S是類型T的子類型。子類型的概念涉及到行爲共享,即代碼重用的問題,他與繼承有着密切的聯繫。 c++
值得注意的是,共有繼承能夠實現子類型。爲何這麼說呢?由於當一個類的繼承關係爲共有繼承時,基類的公有成員和保護成員做爲派生類的成員時,他們都保持了原有的訪問權限,既,派生類至少提供了基類的行爲。因此說共有繼承能夠實現子類型。 程序員
來一個例子: 函數
class A spa
{ 指針
public: 調試
void print() const 對象
{cout << "A :: print() called.\n" ;} 繼承
}; get
class B:public A
{
public:
void f()
{}
};
void f1 (const A &r)
{
r.print();
}
void main ()
{
B b;
f1(b);
}
執行程序會輸出結果:
A :: print() called.
這就是子類型的應用,注意:子類型的關係是不可逆的。
二、類型適應
類型適應是指兩種類型之間的關係。例如,B類型適應於A類型是指B類型的對象可以用於A類型的對象所可以使用的場合。子類型與類型適應是一致的,A類型是B類型的子類型,那麼B類型必將適應於A類型(派生類對象能夠用於基類對象所使用的場合,派生類對象的指針和引用也適應於基類對象的指針和引用。)
子類型的重要性在於減輕了程序員編寫代碼的負擔,同時也提升了代碼的重用率。由於一個函數能夠用於某類型的對象,則它能夠用於該類型的子類型的對象,這樣就沒必要爲處理這些子類型的對象去重載該函數
三、賦值兼容規則
一般在共有繼承方式下,派生類是基類的子類型。這時派生類對象與基類對象之間的一些關係的規則稱爲賦值兼容規則。
在公有繼承方式下,賦值兼容該規則規定:
(1) 派生類對象能夠賦值給基類對象
eg: a = b;
(2) 派生類對象的地址值能夠賦值給基類的對象指針
eg: pa = &b;(pa 是對象指針)
(3) 派生類對象能夠用來給基類對象引用初始化。
eg: A &a = b;
使用上述規則要注意兩點:
(1) 具備派生類公有繼承基類的條件
(2) 上述三個規定不可逆
在來一個例子討論一下:
#include <iostream>
using namespace std;
class A
{
public:
A()
{a = 0;}
A(int i)
{ a = i; }
void print()
{cout << a << endl;}
int geta()
{return a;}
private:
int a;
};
class B:public A
{
public:
B()
{ b = 0;}
B(int i, int j):A(i),b(j)
{}
void print()
{
A::print();
cout << b << endl;
}
private:
int b;
};
void fun(A &d)
{
cout << d.geta() * 10 << endl;
}
void main()
{
B bb(9,5);
A aa(5);
aa = bb;
aa.print();
A *pa = new A(8);
B *pb = new B(1,2);
pa = pb;
pa -> print();
fun(bb);
}
輸出結果
9
1
90
關於不可逆的問題咱們不去討論,下面咱們來改變程序,看看它是怎麼運行的
拋出問題,pa = pb 這句話執行後,到底pa 指向的是pb的地址仍是pa 的呢?若是是pb的,pa是否能夠調用pb的方法?
咱們來斷點調試:
能夠看到,執行到這一步時,pb 和pa的地址值是不一樣的,下面推動斷點
能夠看到,pa 的地址值已經和pb的地址值相同了,那麼能夠能夠用pa調用B 類的公有成員呢?
咱們在class B裏寫一個函數
void abc()
{
cout << "能夠越界嗎?" << endl ;
}
在main 函數里加上一句話
pa -> abc();
結果編譯器提示有錯誤:
說明abc()不是A類的成員,雖然pa和pb的地址是相同的,可是是不能越界的。咱們在void fun(A &d)里加入一句話,使其變成:
void fun(A &d)
{
cout << d.geta() * 10 << endl;
d.print();
}
從新運行,咱們來看一下結果:
9
1
90
9
結果代表了,它並無調用B類的
void print()
{
A::print();
cout << b << endl;
}
而是調用了A類的void print() 函數,這是爲何呢?這裏涉及到了靜態聯編和動態聯編的有關知識, 在之後的文章中將會介紹。
拋出問題,根據賦值兼容該規則中的第三條規則:派生類對象能夠用來給基類對象引用初始化,那麼若是這樣行麼?
void fun(A d) //將 & 去掉後的結果怎樣?
{
cout << d.geta() * 10 << endl;
}
輸出結果
9
1
90
說明這麼寫徹底是能夠的,那麼程序是怎麼調用的呢?難道是運用了拷貝構造函數?咱們在class A的定義裏補充以下一個拷貝構造函數來證明咱們的猜測
A(A &aa)
{
a = aa.a;
cout << "A的拷貝" << endl;
}
再次執行程序:
9
1
A的拷貝
90
能夠將‘&’在加上,發現沒有調用拷貝構造函數,這裏更加清晰的知道了’&’的做用,既,它就是變量的另外一個別稱,不會調用拷貝構造函數,因此用引用的執行的效率也會加快