C++解析(24):抽象類和介面、多重繼承
阿新 • • 發佈:2018-12-09
0.目錄
1.抽象類和介面
2.被遺棄的多重繼承
3.小結
1.抽象類和介面
1.1 抽象類
面向物件中的抽象類:
- 可用於表示現實世界中的抽象概念
- 是一種只能定義型別,而不能產生物件的類
- 只能被繼承並重寫相關函式
- 直接特徵是相關函式沒有完整的實現
Shape是現實世界中各種圖形的抽象概念,因此:
- 程式中必須能夠反映抽象的圖形
- 程式中通過抽象類表示圖形的概念
- 抽象類不能建立物件,只能用於繼承
1.2 純虛擬函式
抽象類與純虛擬函式:
- C++語言中沒有抽象類的概念
- C++中通過純虛擬函式實現抽象類
- 純虛擬函式是指只定義原型的成員函式
- 一個C++類中存在純虛擬函式就成為了抽象類
純虛擬函式的語法規則:
示例——抽象類:
#include <iostream> using namespace std; class Shape { public: virtual double area() = 0; }; class Rect : public Shape { int ma; int mb; public: Rect(int a, int b) { ma = a; mb = b; } double area() { return ma * mb; } }; class Circle : public Shape { int mr; public: Circle(int r) { mr = r; } double area() { return 3.14 * mr * mr; } }; void area(Shape* p) { double r = p->area(); cout << "r = " << r << endl; } int main() { Rect rect(1, 2); Circle circle(10); area(&rect); area(&circle); return 0; }
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
r = 2
r = 314
- 抽象類只能用作父類被繼承
- 子類必須實現純虛擬函式的具體功能
- 純虛擬函式被實現後成為虛擬函式
- 如果子類沒有實現純虛擬函式,則子類成為抽象類
1.3 介面
滿足下面條件的C++類則稱為介面:
- 類中沒有定義任何的成員變數
- 所有的成員函式都是公有的
- 所有的成員函式都是純虛擬函式
- 介面是一種特殊的抽象類
示例——介面:
#include <iostream> using namespace std; class Channel { public: virtual bool open() = 0; virtual void close() = 0; virtual bool send(char* buf, int len) = 0; virtual int receive(char* buf, int len) = 0; }; int main() { return 0; }
C++中沒有真正的介面,但是C++的後續語言Java、C#直接支援介面的概念!
2.被遺棄的多重繼承
2.1 C++中的多重繼承
C++支援編寫多重繼承的程式碼:
- 一個子類可以擁有多個父類
- 子類擁有所有父類的成員變數
- 子類繼承所有父類的成員函式
- 子類物件可以當作任意父類物件使用
多重繼承的語法規則:
2.2 多重繼承的問題一
示例——多重繼承的問題一:
#include <iostream>
using namespace std;
class BaseA
{
int ma;
public:
BaseA(int a) { ma = a; }
int getA() { return ma; }
};
class BaseB
{
int mb;
public:
BaseB(int b) { mb = b; }
int getB() { return mb; }
};
class Derived : public BaseA, public BaseB
{
int mc;
public:
Derived(int a, int b, int c) : BaseA(a), BaseB(b)
{
mc = c;
}
int getC() { return mc; }
void print()
{
cout << "ma = " << getA() << ", "
<< "mb = " << getB() << ", "
<< "mc = " << mc << endl;
}
};
int main()
{
cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // 12
Derived d(1, 2, 3);
d.print();
cout << "d.getA() = " << d.getA() << endl;
cout << "d.getB() = " << d.getB() << endl;
cout << "d.getC() = " << d.getC() << endl;
cout << endl;
BaseA* pa = &d;
BaseB* pb = &d;
cout << "pa->getA() = " << pa->getA() << endl;
cout << "pb->getB() = " << pb->getB() << endl;
cout << endl;
void* paa = pa;
void* pbb = pb;
if( paa == pbb )
{
cout << "Pointer to the same object!" << endl;
}
else
{
cout << "Error" << endl;
}
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "paa = " << paa << endl;
cout << "pbb = " << pbb << endl;
return 0;
}
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
sizeof(Derived) = 12
ma = 1, mb = 2, mc = 3
d.getA() = 1
d.getB() = 2
d.getC() = 3
pa->getA() = 1
pb->getB() = 2
Error
pa = 0x7ffc9f641dc0
pb = 0x7ffc9f641dc4
paa = 0x7ffc9f641dc0
pbb = 0x7ffc9f641dc4
通過多重繼承得到的物件可能擁有“不同的地址”!!
解決方案:無
(其實pa和pb還是指向了同一個物件,但是指向的是同一個物件的不同位置,打個比方就是pa指向了這個物件的腦袋,pb指向了這個物件的胸口。。。)
2.3 多重繼承的問題二
多重繼承可能產生冗餘的成員:
示例——多重繼承的問題二:
#include <iostream>
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ", "
<< "Age = " << m_age << endl;
}
};
class Teacher : public People
{
public:
Teacher(string name, int age) : People(name, age) { }
};
class Student : public People
{
public:
Student(string name, int age) : People(name, age) { }
};
class Doctor : public Teacher, public Student
{
public:
Doctor(string name, int age) : Teacher(name + "1", age + 10), Student(name + "2", age + 1) { }
};
int main()
{
Doctor d("Bob", 33);
//d.print();
d.Teacher::print();
d.Student::print();
return 0;
}
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Name = Bob1, Age = 43
Name = Bob2, Age = 34
當多重繼承關係出現閉合時將產生資料冗餘的問題!!!!
解決方案:虛繼承
- 虛繼承能夠解決資料冗餘問題
- 中間層父類不再關心頂層父類的初始化
- 最終子類必須直接呼叫頂層父類的建構函式
示例——使用虛繼承解決資料冗餘:
#include <iostream>
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ", "
<< "Age = " << m_age << endl;
}
};
class Teacher : virtual public People
{
public:
Teacher(string name, int age) : People(name, age) { }
};
class Student : virtual public People
{
public:
Student(string name, int age) : People(name, age) { }
};
class Doctor : public Teacher, public Student
{
public:
Doctor(string name, int age) : Teacher(name+"1", age), Student(name+"2", age), People(name+"3", age) { }
};
int main()
{
Doctor d("Delphi", 33);
d.print();
d.Teacher::print();
d.Student::print();
return 0;
}
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
Name = Delphi3, Age = 33
Name = Delphi3, Age = 33
Name = Delphi3, Age = 33
問題:
當架構設計中需要繼承時,無法確定使用直接繼承還是虛繼承!!
2.4 多重繼承的問題三
多重繼承可能產生多個虛擬函式表
示例——多重繼承的問題三:
#include <iostream>
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbb = (BaseB*)pa;
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "Using pa to call funcA()..." << endl;
pa->funcA();
cout << "Using pb to call funcB()..." << endl;
pb->funcB();
cout << "Using pbb to call funcB()..." << endl;
pbb->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbb = " << pbb << endl;
return 0;
}
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
sizeof(d) = 16
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB::funcB()
Using pbb to call funcB()...
BaseA::funcA()
pa = 0x7fffd5157c20
pb = 0x7fffd5157c28
pbb = 0x7fffd5157c20
需要進行強制型別轉換時,C++中推薦使用新式型別轉換關鍵字!!
解決方案:dynamic_cast
示例——使用新式型別轉換dynamic_cast關鍵字:
#include <iostream>
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbb = (BaseB*)pa; // oops!!
BaseB* pbc = dynamic_cast<BaseB*>(pa);
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "Using pa to call funcA()..." << endl;
pa->funcA();
cout << "Using pb to call funcB()..." << endl;
pb->funcB();
cout << "Using pbb to call funcB()..." << endl;
pbb->funcB();
cout << "Using pbc to call funcB()..." << endl;
pbc->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbb = " << pbb << endl;
cout << "pbc = " << pbc << endl;
return 0;
}
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
sizeof(d) = 16
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB::funcB()
Using pbb to call funcB()...
BaseA::funcA()
Using pbc to call funcB()...
BaseB::funcB()
pa = 0x7ffcc2c27ff0
pb = 0x7ffcc2c27ff8
pbb = 0x7ffcc2c27ff0
pbc = 0x7ffcc2c27ff8
2.5 正確的使用多重繼承
工程開發中的“多重繼承”方式:
- 單繼承某個類 + 實現(多個)介面
示例——正確的多繼承方式:
#include <iostream>
using namespace std;
class Base
{
protected:
int mi;
public:
Base(int i) { mi = i; }
int getI() { return mi; }
bool equal(Base* obj)
{
return (this == obj);
}
};
class Interface1
{
public:
virtual void add(int i) = 0;
virtual void minus(int i) = 0;
};
class Interface2
{
public:
virtual void multiply(int i) = 0;
virtual void divide(int i) = 0;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
Derived(int i) : Base(i) { }
void add(int i) { mi += i; }
void minus(int i) { mi -= i; }
void multiply(int i) { mi *= i; }
void divide(int i)
{
if( i != 0 ) { mi /= i; }
}
};
int main()
{
Derived d(100);
Derived* p = &d;
Interface1* pInt1 = &d;
Interface2* pInt2 = &d;
cout << "p->getI() = " << p->getI() << endl; // 100
pInt1->add(10);
pInt2->divide(11);
pInt1->minus(5);
pInt2->multiply(8);
cout << "p->getI() = " << p->getI() << endl; // 40
cout << endl;
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
return 0;
}
執行結果為:
[[email protected] Desktop]# g++ test.cpp
[[email protected] Desktop]# ./a.out
p->getI() = 100
p->getI() = 40
pInt1 == p : 1
pInt2 == p : 1
一些有用的工程建議:
- 先繼承自一個父類,然後實現多個介面
- 父類中提供equal()成員函式
- equal()成員函式用於判斷指標是否指向當前物件
- 與多重繼承相關的強制型別轉換用dynamic_cast完成
3.小結
- 抽象類用於描述現實世界中的抽象概念
- 抽象類只能被繼承不能建立物件
- C++中沒有抽象類的概念
- C++中通過純虛擬函式實現抽象類
- 類中只存在純虛擬函式的時成為介面
- 介面是一種特殊的抽象類
- C++支援多重繼承的程式設計方式
- 多重繼承容易帶來問題
- 可能出現“同一個物件的地址不同”的情況
- 虛繼承可以解決資料冗餘的問題
- 虛繼承的使得架構設計可能出現問題
- 多繼承中可能出現多個虛擬函式表指標
- 與多重繼承相關的強制型別轉換用dynamic_cast完成
- 工程開發中採用“單繼承多介面”的方式使用多繼承
- 父類提供成員函式用於判斷指標是否指向當前物件