Chương 6. Tính đa hình (Polymorphism)
Ầ
TR N MINH THÁI Email: minhthai@itc.edu.vn Website: www.minhthai.edu.vn
Cập nhật: 10 tháng 04 năm 2015
Nội dung
1. Giới thiệu đa hình
2. Phương thức ảo
3.
Lớp trừu tượng
4. Bài tập ví dụ
#2
Giới thiệu [1/6]
• Giả sử có 2 hàm
#3
• double max(double d1, double d2);
• int max(int i1, int i2);
theo danh sách tham số của thông điệp
àMột thông điệp (lời gọi hàm) được hiểu theo các cách khác nhau tùy
đa năng hóa hàm
àĐa hình hàm (cid:0)
Giới thiệu [2/6]
• Đa hình là hiện tượng các đối tượng thuộc các lớp khác nhau có
khả năng hiểu cùng một thông điệp theo các cách khác nhau
Cùng thông điệp “nhảy”, kangaroo và con cóc nhảy theo hai kiểu khác nhau: chúng cùng có hành vi “nhảy” nhưng các hành vi này có nội dung khác nhau
#4
Giới thiệu [3/6]
Đa hình được cài đặt bởi cơ chế overriding
• Nếu một phương thức của lớp cơ sở được định nghĩa lại tại lớp dẫn xuất thì định nghĩa tại lớp cơ sở có thể bị “che” bởi định nghĩa tại lớp dẫn xuất.
• Với overriding, toàn bộ thông điệp (cả tên và tham số) là hoàn toàn giống nhau - điểm khác nhau là lớp đối tượng được nhận thông điệp.
#5
Giới thiệu [4/6]
class A {
class B: public A {
public:
public:
B b; A *pa=&b; pa>Print(); //A::Print()
void Print() {
void Print() {
cout<<"A::Print()";
cout<<"B::Print()";
cout< cout< } } }; #6 !!! Lời gọi đến một phương thức của một đối tượng trỏ /tham
};
chiếu tới được xem như lời gọi đến phương thức chứ không
phải tương ứng với đối tượng đang được trỏ /tham chiếu tới
Kết nối tĩnh (static binding). Hàm thành viên gọi từ con trỏ đối tượng được xác định trước khi chương trình chạy (cid:0) CCircle *pc = new CCircle(50, 30, "Blue",100);
CMyPoint *pp = pc;
pp > Draw(); //draw point ??? #7 Để gọi được phương thức với đối tượng đươc trỏ/tham
chiếu tới
fi Cần phải xác định được kiểu của đối tượng được xem xét tại thời điểm chương trình đang chạy (runtime) fi Kết nối động (dynamic binding) hoặc kết nối trễ (late binding) fi Xác định hàm thành viên nào tương ứng với một lời gọi
hàm thành viên từ con trỏ/tham chiếu đối tượng phụ
thuộc vào cụ thể vào đối tượng mà con trỏ/tham chiếu
chứa địa chỉ #8 class A
{ class B: public A
{ public: public: B b;
A *pa=&b;
pa>Print(); //A::Print() void Print()
{ void Print()
{ cout<<"A::Print()"; cout<<"B::Print()"; cout< cout< } } }; }; ??? Muốn thực hiện Print() của lớp B #9 • Là cơ chế của C++ cho phép cài đặt kết nối động #10 • Phương thức ảo: thêm từ khóa virtual vào trước khai báo phương thức trong lớp fi Gọi được phương thức với đối tượng đươc trỏ/tham chiếu tới • Một khi một phương thức được khai báo là phương thức ảo
tại lớp cơ sở, nó sẽ tự động là phương thức ảo tại mọi lớp
dẫn xuất trực tiếp hoặc gián tiếp #11 Không cần thêm virtual khi khai báo một phương thức ảo trong lớp dẫn xuất (cid:0) class A
{ class B: public A
{ public: B b;
A *pa=&b;
pa>Print(); //B::Print() public: virtual void virtual void Print() Print() { { cout<<"A::Print()"; cout<<"B::Print()"; cout< cout< } } }; }; #12 class CHome
{ public : virtual void Paint() {
} }; class CWoodframe : public
CHome
{ CWoodframe w;
w.Paint ();
CLand a, b;
CStucco c;
CWoodframe d,e; public: class CLand: public CHome
{ public: virtual void Paint()
{ cout <<"Son nha virtual void Paint()
{ go\n" ; cout <<"Son nha } dat\n" ; };class CStucco: public CHome
{ } public: }; CHome *h[5];
h[0] = &a;
h[1] = &b;
h[2] = &c;
h[3] = &d;
h[4] = &e;
int i;
for (i=0;i<5;i++) h[i]>Paint(); virtual void Paint()
{ cout <<"Son nha xay\n" ; } }; #13 • Phương thức ảo chỉ hoạt động thông qua con trỏ/ tham chiếu • Phương thức ảo tồn tại để có hiệu lực nhưng không có thực trong lớp cơ sở, trong lớp dẫn xuất mới định nghĩa rõ ràng #14 Phương thức ảo chỉ được xây dựng khi có kế thừa. Phương thức này sẽ
được gọi thực hiện từ thực thể của lớp dẫn xuất nhưng được mô tả trong lớp
cơ sở (cid:0) • Không thể có tính đa hình khi không có sự kế thừa và phương thức ảo #15 Điều kiện để có tính đa hình là phải có sự kế thừa và phương thức
ảo trong lớp cơ sở fi Cơ chế đa hình được thực hiện dựa vào bảng phương thức ảo
của đối tượng • Bảng chứa địa chỉ của các phương thức ảo • Được TBD khởi tạo một cách ngầm định khi thiết lập đối tượng • TBD gặp đối tượng đầu tiên thuộc lớp có phương thức ảo à
thêm vào mỗi đối tượng của lớp cơ sở và các lớp dẫn xuất
một con trỏ ảo #16 Mỗi lớp chỉ • virtual pointer (vptr) nằm trong bảng phương thức ảo và có nhiệm
vụ quản lý địa chỉ của các phương thức ảo
• Khi đối tượng khác tạo ra, TBD không tạo thêm vptr (cid:0)
có một bảng phương thức ảo lưu các vptr • Nếu lớp có constructor và destructor, vptr sẽ được tạo ra trước khi
gọi thực hiện các phương thức này • Khi thao tác được thực hiện thông qua con trỏ/tham chiếu, hàm có
địa chỉ trong bảng phương thức ảo sẽ được gọi #17 Các đặc trưng • Việc khai báo các phương thức ảo giữa lớp cơ sở và dẫn xuất phải thống nhất với nhau #18 Tất cả các phiên bản của phương thức ảo phải được khai báo cùng kiểu trả về, cùng tên, danh sách các tham số (gọi là cùng giao diện) (cid:0) Điều kiện của kết nối động (cid:0) Các đặc trưng • Không thể là hàm thành viên tĩnh (do bảng phương thức ảo chỉ tạo ra khi đối tượng của lớp được tạo ra) • Có thể được khai báo là friend trong một lớp khác nhưng các hàm friend của lớp thì không thể là phương thức ảo #19 • Không thể khai báo các constructor ảo (do bảng phương thức ảo tạo trước) • Có thể (và rất nên) khai báo destructor là hàm ảo #20 class A
{ class B: public A
{ public: public: B *pb = new B;
A *pa = pb;
delete pa; //A::~A() ~A()
{ ~B()
{ cout<<"~A";
cout< } } }; }; • Việc gọi nhầm destructor không ảnh hưởng đến việc thu hồi
bộ nhớ của đối tượng (trong mọi trường hợp phần bộ nhớ
của đối tượng sẽ được thu hồi chính xác) • Tuy nhiên, nếu không gọi đúng destructor, các đoạn mã dọn
dẹp quan trọng có thể bị bỏ qua (chẳng hạn như giải phóng
các thành viên được cấp phát động) #21 Quy tắc chung: mỗi khi tạo một lớp để được dùng làm lớp cơ sở à khai
báo destructor ảo #22 class A
{ class B: public A
{ public: public: B *pb = new B;
A *pa = pb;
delete pa; //B::~B() virtual ~A()
{ ~B()
{ //A::~A() cout<<"~A";
cout< } } }; }; • Để tránh tình trạng lãng phí bộ nhớ khi xây dựng các đối
tượng. C++ cho phép xây dựng các phương thức ảo không
có phần định nghĩa #23 Hàm thuần túy ảo • Cú pháp fi Thêm “= 0” vào cuối khai báo phương thức • class CHome
{ public : virtual void Paint() = 0;
}; • Không cần định nghĩa phương thức
• Nếu không có “ = 0” và không định nghĩa (cid:0) TBD báo lỗi #24 • Lớp không thể tạo thực thể nào à Lớp không có đối tượng nào nhưng có thể tạo ra biến con trỏ/ tham chiếu của nó • Thực tế, ta thường phân nhóm các đối tượng theo kiểu này o Chim và ếch đều là động vật, nhưng một con động vật là con gì? o Bia và rượu đều là đồ uống, nhưng một thứ đồ uống chính xác là cái gì? #25 • Trong C++, lớp trừu tượng là lớp có chứa ít nhất một phương thức thuần túy ảo à Các lớp dẫn xuất của lớp cơ sở này nếu cũng không cung
cấp định nghĩa cho phương thức này thì cũng trở thành lớp trừu
tượng #26 Nếu một lớp dẫn xuất muốn tạo thực thể, nó phải cung cấp định
nghĩa cho mọi hàm thuần ảo mà nó kế thừa #27 Bằng cách khai báo một số phương thức là thuần ảo, một lớp
trừu tượng có thể “bắt buộc” các lớp con phải cài đặt các
phương thức đó (cid:0) Lớp trừu tượng class CHome
{ public : virtual void Paint() = CHome home; //Error
CHome *h;
//OK 0;
}; Các lớp trừu tượng chỉ có ích khi chúng là các lớp cơ
sở trong cây kế thừa • Các lớp cơ sở trừu tượng có thể được đặt tại các tầng khác nhau của một cây kế thừa • Yêu cầu duy nhất là mỗi lớp trừu tượng phải có ít nhất một lớp dẫn xuất không trừu tượng #28 Một phương thức được xem là ảo khi nó thuộc nhiều lớp trong
hệ thống cây kế thừa và thỏa các yêu cầu sau: • Thuộc các lớp trên cùng của cây kế thừa • Hành động tạo nên phương thức liên quan đến bản chất của lớp • Được truy cập từ lớp cơ sở nhưng các hành động cụ thể lại thuộc lớp dẫn xuất #29 #30 class CPoint2D
{ protected: int x,y; public: CPoint2D(int x=0,int y=0)
{ this>x=x;
this>y=y; }
int GetX() { return x; } return y; } int GetY() {
void Print()
{ class CShape
{ cout<<"("< }; virtual double Area()=0;
virtual double Perimeter()=0;
virtual void Print()=0; }; class CTriangle: public CShape
{ protected: #31 CPoint2D p1,p2,p3; private: double GetLen( CPoint2D p1,CPoint2D p2) ; public: CTriangle(int x1,int y1,int x2,int y2,int x3,int y3);
virtual double Area();
virtual double Perimeter();
virtual void Print(); };
CTriangle::CTriangle(int x1,int y1,int x2,int y2,int x3,int y3): p1(x1,y1),p2(x2,y2),p3(x3,y3) {
}
double CTriangle::GetLen(CPoint2D p1,CPoint2D p2)
{ double a=p1.GetX()p2.GetX();
double b=p1.GetY()p2.GetY();
return sqrt(a*a+b*b); } void CTriangle::Print()
{ #32 cout< }
double CTriangle::Perimeter()
{ double a=GetLen(p1,p2);
double b=GetLen(p2,p3);
double c=GetLen(p3,p1);
return a+b+c; }
double CTriangle::Area()
{ double a=GetLen(p1,p2);
double b=GetLen(p2,p3);
double c=GetLen(p3,p1);
double p=(a+b+c)/2;
return sqrt(p*(pa)*(pb)*(pc)); } #33 class CCircle: public CShape
{ double CCircle::Perimeter()
{ protected: return 3.14159*2*radius; CPoint2D p;
int radius; }
double CCircle::Area()
{ public: return 3.14159*radius*radius; CCircle(int x,int y,int r):p(x,y)
{ radius = r; }
void CCircle::Print()
{ cout< } }; #34 void Output(CShape* s[],int n)
{ for(int i=0;i s[i]>Print();
cout<<" Chu vi="< } } int MyRand(int a,int b)
{ return rand()%(ba+1)+a; } #define MIN 10
#define MAX 80
#define DIM(x) sizeof(x)/sizeof(x[0]) #35 void main()
{ srand((unsigned)time(NULL));
CTriangle t1(MyRand(MIN,MAX),MyRand(MIN,MAX),MyRand(MIN,MAX), MyRand(MIN,MAX),MyRand(MIN,MAX),MyRand(MIN,MAX)); CTriangle t2(MyRand(MIN,MAX),MyRand(MIN,MAX),MyRand(MIN,MAX), MyRand(MIN,MAX),MyRand(MIN,MAX),MyRand(MIN,MAX)); CCircle c1(MyRand(MIN,MAX),MyRand(MIN,MAX),MyRand(MIN,MAX)); CCircle c2(MyRand(MIN,MAX),MyRand(MIN,MAX),MyRand(MIN,MAX)); CShape *s[]={&t1,&c1,&c2,&t2};
int n=DIM(s);
Output(s,n); } Đa năng hóa toán tử với hàm toán tử là phương thức ảo #36 virtual int GetC()
{ class A
{ return 0; protected: int x1; public: }
void Print(char *st)
{ cout< A& A::operator + (A& t)
{ }
virtual A& operator + (A& t);
virtual A& operator = (A& t);
virtual int GetA()
{ x1+=t.GetA();
return *this; return x1; }
A& A::operator = (A& t)
{ }
virtual int GetB()
{ return 0; x1=t.GetA();
return *this; } } class B : public A
{ #37 protected: int x2; A& B::operator + (A& t)
{ public: B(int i,int j):A(i)
{ x1+=t.GetA();
x2+=t.GetB();
return *this; x2=j; } A& B::operator = (A& t)
{ }
virtual A& operator + (A& t);
virtual A& operator = (A& t);
virtual int GetB()
{ return x2; x1=t.GetA();
x2=t.GetB();
return *this; } }
void Print(char *st)
{ cout< } }; class C : public B
{ #38 protected: int x3; A& C::operator + (A& t)
{ public: C(int i,int j,int k):B(i,j)
{ x3=k; x1+=t.GetA();
x2+=t.GetB();
x3+=t.GetC();
return *this; } A& C::operator = (A& t)
{ }
virtual A& operator + (A& t);
virtual A& operator = (A& t);
virtual int GetC()
{ return x3; x1=t.GetA();
x2=t.GetB();
x3=t.GetC();
return *this; }
void Print(char *st)
{ } cout< } }; #39 void main()
{ void AddObject(A& t1,A& t2)
{ t1 = t1+t2; } A a(10);
B b(10,20);
C c(10,20,30);
a.Print("a");
b.Print("b");
c.Print("c");
AddObject(a,b);//a=a+b
a.Print("a");
AddObject(b,c);//b=b+c
b.Print("b");
AddObject(c,a);//c=c+a
c.Print("c");
a=b+c;
a.Print("a");
c=c+a;
c.Print("c"); } #40Giới thiệu [5/6]
Giới thiệu [6/6]
Phương thức ảo – Virtual method [1/14]
Phương thức ảo [2/14]
Phương thức ảo [3/14]
Phương thức ảo [4/14]
Phương thức ảo – Ví dụ [5/14]
Phương thức ảo [6/14]
Phương thức ảo [7/14]
Phương thức ảo [8/14]
Phương thức ảo [9/14]
Phương thức ảo [10/14]
Phương thức ảo [11/14]
Phương thức ảo [12/14]
Phương thức ảo [13/14]
Phương thức ảo [14/14]
Phương thức ảo thuần tuý [1/2]
Pure virtual method
Phương thức ảo thuần tuý [2/2]
Lớp trừu tượng – abstract class [1/5]
Lớp trừu tượng [2/5]
Lớp trừu tượng [3/5]
Lớp trừu tượng [4/5]
Lớp trừu tượng [5/5]
Lớp trừu tượng – Ví dụ 1 [1/10]
Xây dựng các lớp trong cây kế thừa sau để tính chu vi và diện
tích tam giác và đường tròn
Lớp trừu tượng – Ví dụ 1 [2/10]
Lớp trừu tượng – Ví dụ 1 [3/10]
Lớp trừu tượng – Ví dụ 1 [4/10]
Lớp trừu tượng – Ví dụ 1 [5/10]
Perimeter()<<",dien tich=";
cout <Area()<Lớp trừu tượng – Ví dụ 1 [6/10]
Lớp trừu tượng – Ví dụ 2 [7/10]
Lớp trừu tượng – Ví dụ 2 [8/10]
Lớp trừu tượng – Ví dụ 2 [9/10]
Lớp trừu tượng – Ví dụ 2 [10/10]
Q&A