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
Trang 3Giới thiệu [1/6]
Giả sử có 2 hàm
double max(double d1, double d2);
int max(int i1, int i2);
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 theo danh sách tham số của thông điệp
Đa hình hàm đa năng hóa hàm
Trang 5Giớ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.
Trang 6Giới thiệu [4/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
class A
{
public:
void Print() {
cout<<"A::Print()";
cout<<endl;
} };
class B: public A {
public:
void Print() {
cout<<"B::Print()";
cout<<endl;
} };
B b;
A *pa=&b;
pa->Print(); //A::Print()
Trang 8Giới thiệu [6/6]
Để gọi được phương thức với đối tượng đươc trỏ/tham chiếu tới
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)
Kết nối động (dynamic binding) hoặc kết nối trễ (late binding)
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ỉ
Trang 9Phương thức ảo – Virtual method [1/14]
??? Muốn thực hiện Print() của lớp B
class A
{
public:
void Print() {
cout<<"A::Print()";
cout<<endl;
} };
class B: public A {
public:
void Print() {
cout<<"B::Print()";
cout<<endl;
} };
B b;
A *pa=&b;
pa->Print(); //A::Print()
Trang 10Phương thức ảo [2/14]
• Là cơ chế của C++ cho phép cài đặt kết nối động
Gọi được phương thức với đối tượng đươc trỏ/tham chiếu tới
• 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
Trang 11Phương thức ảo [3/14]
• 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
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
Trang 12{ cout<<"A::Print()";
cout<<endl;
} };
class B: public A {
public:
virtual void Print()
{ cout<<"B::Print()";
cout<<endl;
} };
B b;
A *pa=&b;
pa->Print(); //B::Print()
Trang 13Phương thức ảo – Ví dụ [5/14]
class CHome {
public :
virtual void Paint()
{ } };
class CWoodframe : public CHome
class CStucco: public CHome
class CLand: public CHome {
CWoodframe w; w.Paint ();
CLand a, b;
CStucco c;
CWoodframe d,e;
CHome *h[5]; h[0] = &a;
Trang 14Phương thức ảo [6/14]
• 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
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ở
Trang 16Phương thức ảo [8/14]
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
Trang 17Phương thức ảo [9/14]
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 Mỗi lớp chỉ 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
Trang 18số (gọi là cùng giao diện)
Điều kiện của kết nối động
Trang 20cout<<endl;
}};
class B: public A{
public:
~B(){
cout<<"~B";
cout<<endl;
}};
B *pb = new B;
A *pa = pb;
delete pa; //A::~A()
Trang 21Phương thức ảo [13/14]
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)
Trang 22cout<<endl;
}};
class B: public A{
public:
~B(){
cout<<"~B";
cout<<endl;
}};
B *pb = new B;
A *pa = pb;
delete pa; //B::~B()
//A::~A()
Trang 23Phương thức ảo thuần tuý [1/2]
Pure virtual method
• Để 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
Hàm thuần túy ảo
• Cú pháp
Thêm “ = 0 ” vào cuối khai báo phương thức
Trang 24Phương thức ảo thuần tuý [2/2]
Không cần định nghĩa phương thức
Nếu không có “ = 0” và không định nghĩa TBD báo lỗi
Trang 25Lớp trừu tượng – abstract class [1/5]
• 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ì?
Trang 27Lớp trừu tượng [3/5]
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
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 đó
Trang 29Lớp trừu tượng [5/5]
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
Trang 30Lớ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
class CPoint2D{
int GetY() { return y; }
void Print(){
cout<<"("<<x<<","<<y<<")";
}};
virtual void Print()=0;};
Trang 31virtual 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);
}
Trang 32Lớp trừu tượng – Ví dụ 1 [3/10]
void CTriangle::Print(){
Trang 33Perimeter() ;
virtual void Print();
};
double CCircle::Perimeter() {
return 3.14159*2*radius;
}double CCircle::Area() {
return 3.14159*radius*radius;
}void CCircle::Print(){
cout<<endl<<"[Duong tron: tam=";p.Print(), cout<<",ban kinh=";
cout<<radius<<"]"<<endl;
}
Trang 34int MyRand(int a,int b){
Trang 35CTriangle 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);
}
Trang 36}virtual A& operator + (A& t);
virtual A& operator = (A& t);
virtual int GetA(){
return x1;
}virtual int GetB(){
cout<<st<<":x1="<<x1<<endl;
}}; //end of class A
A& A::operator + (A& t){
x1+=t.GetA();
return *this;
}A& A::operator = (A& t){
x1=t.GetA();
return *this;
}
Trang 37virtual A& operator = (A& t);
virtual int GetB(){
return x2;
}void Print(char *st){
cout<<st<<":x1="<<x1;
cout<<",x2="<<x2<<endl;
}};
A& B::operator + (A& t){
Trang 38virtual A& operator = (A& t);
virtual int GetC(){
return x3;
}void Print(char *st){
cout<<st<<":x1="<<x1<<",x2=";
cout<<x2<<",x3="<<x3<<endl;
}};
A& C::operator + (A& t){
Trang 39Lớp trừu tượng – Ví dụ 2 [10/10]
void main(){
AddObject(b,c);//b=b+cb.Print("b");
AddObject(c,a);//c=c+ac.Print("c");
Trang 40Q&A