Tương ứng bội và phương thức ảo - Pdf 29

Chương 6
Tương ứng bội và phương thức ảo
Tương ứng bội và phương thức ảo là công cụ mạnh của C++
cho phép tổ chức quản lý các đối tượng khác nhau theo cùng một lược
đồ. Một khái niệm khác liên quan là: lớp cơ sở trừu tượng. Chương
này sẽ trình bầy cách sử dụng các công cụ trên để xây dựng chương
trình quản lý nhiều đối tượng khác nhau theo một lược đồ thống nhất.
§
1. Phương thức tĩnh
1.1. Lời gọi tới phương thức tĩnh
Như đã biết một lớp dẫn xuất được thừa kế các phương thức của
các lớp cơ sở tiền bối của nó. Ví dụ lớp A là cơ sở của B, lớp B lại là
cơ sở của C, thì C có 2 lớp cơ sở tiền bối là B và A. Lớp C được thừa
kế các phương thức của A và B. Các phương thức mà chúng ta vẫn
nói là các phương thức tĩnh. Để tìm hiểu thêm về cách gọi tới các
phương thức tĩnh, ta xét ví dụ về các lớp A, B và C như sau:
class A
{
public:
void xuat()
{
cout << "\n Lop A " ;
}
};
class B:public A
{
public:
void xuat()
{
cout << "\n Lop B " ;
}

p = &a ;
q = &b ;
r = &c ;
Chúng ta tiếp tục xét các lời gọi phương thức từ các con trỏ p, q, r:
p->xuat();
q->xuat();
r->xuat();
và hãy lý giải xem phương thức nào (trong các phương thức A::xuat,
B::xuat và C::xuat) được gọi. Câu trả lời như sau:
Cả 3 câu lệnh trên đều gọi tới phương thức A::xuat() , vì các con
trỏ p, q và r đều có kiểu A.
Như vậy có thể tóm lược cách thức gọi các phương thức tĩnh như
sau:
Quy tắc gọi phương thức tĩnh: Lời gọi tới phương thức tĩnh bao
giờ cũng xác định rõ phương thức nào (trong số các phương thức
trùng tên của các lớp có quan hệ thừa kế) được gọi:
1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào, thì phương
thức của lớp đó sẽ được gọi.
2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì phương
thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ của đối tượng
nào.
1.2. Ví dụ
Xét 4 lớp A, B, C và D. Lớp B và C có chung lớp cơ sở A. Lớp D
dẫn xuất từ C. Cả 4 lớp đều có phương thức xuat(). Xét hàm:
void hien(A *p)
{
p->xuat();
}
Không cần biết tới địa chỉ của đối tượng nào sẽ truyền cho đối con
trỏ p, lời gọi trong hàm luôn luôn gọi tới phương thức A::xuat() vì con

int getN()
{
return n;
}
};
class B:public A
{
public:
B():A()
{
}
B(int n1):A(n1)
{
}
void xuat()
{
cout << "\nLop B: "<<getN();
}
};
class C:public A
{
public:
C():A()
{
}
C(int n1):A(n1)
{
}
void xuat()
{

hien(&b);
hien(&c);
hien(&d);
getch();
}
§
2. Sự hạn chế của phương thức tĩnh
Ví dụ sau cho thấy sự hạn chế của phương thức tĩnh trong việc sử
dụng tính thừa kế để phát triển chương trình.
Giả sử cần xây dựng chương trình quản lý thí sinh. Mỗi thí sinh
đưa vào ba thuộc tính: Họ tên, số báo danh và tổng điểm. Chương
trình gồm ba chức năng: Nhập dữ liệu thí sinh, in dữ liệu thí sinh ra
máy in và xem - in (in họ tên ra màn hình, sau đó lựa chọn hoặc in
hoặc không). Chương trình dưới đây sử dụng lớp TS (Thí sinh) đáp
ứng được yêu cầu đặt ra.
//CT6-02
// Han che phuong thuc tinh
// Lop TS
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
#include <ctype.h>
class TS
{
private:
char ht[25];
int sobd;
float td;
public:
void nhap()

cin >> n;
for (i=1; i<=n; ++i)
t[i].nhap();
for (i=1; i<=n; ++i)
t[i].xem_in();
getch();
}
Giả sử Nhà trường muốn quản lý thêm địa chỉ của thí sinh. Vì sự
thay đổi ở đây là không nhiều, nên chúng ta không đả động đến lớp
TS mà xây dựng lớp mới TS2 dẫn xuất từ lớp TS. Trong lớp TS2 đưa
thêm thuộc tính dc (địa chỉ) và các phương thức nhap, in. Cụ thể lớp
TS2 được định nghĩa như sau:
class TS2:public TS
{
private:
char dc[30] ; // Dia chi
public:
void nhap()
{
TS::nhap();
cout << "Dia chi: " ;
fflush(stdin); gets(dc);
}
void in()
{
TS::in();
fprintf(stdprn,"\nDia chi: %s", dc);
}
};
Trong lớp TS2 không xây dựng lại phương thức xem_in, mà sẽ

}
void xem_in()
{
int ch;
cout << "\nHo ten: " << ht ;
cout << "\nCo in khong? - C/K" ;
ch = toupper(getch());
if (ch=='C')
this->in(); //Goi den TS::in() (Vi this la con tro
//kieu TS)
}
} ;
class TS2:public TS
{
private:
char dc[30] ; // Dia chi
public:
void nhap()
{
TS::nhap();
cout << "Dia chi: " ;
fflush(stdin); gets(dc);
}
void in()
{
TS::in();
fprintf(stdprn,"\nDia chi: %s", dc);
}
};
void main()

(bấm phím C), thì câu lệnh:
this->in() ;
sẽ được thực hiện. Mặc dù địa chỉ của t[i] (là đối tượng của lớp TS2)
được truyền cho con trỏ this, thế nhưng câu lệnh này luôn luôn gọi
tới phương thức TS::in(), vì con trỏ this ở đây có kiểu TS và vì in() là
phương thức tĩnh. Kết quả là không in được địa chỉ của thí sinh.
Như vậy việc sử dụng các phương thức tĩnh in() (trong các lớp TS
và TS2) đã không đáp ứng được yêu cầu phát triển chương trình. Có
một giải pháp rất đơn giản là: Định nghĩa các phương thức in() trong
các lớp TS và TS2 như các phương thức ảo (virtual).
§
3. Phương thức ảo và tương ứng bội
3.1. Cách định nghĩa phương thức ảo
Giả sử A là lớp cơ sở, các lớp B, C, D dẫn xuất (trực tiếp hoặc dán
tiếp) từ A. Giả sử trong 4 lớp trên đều có các phương thức trùng dòng
tiêu đề (trùng kiểu, trùng tên, trùng các đối). Để định nghĩa các
phương thức này là các phương thức ảo, ta chỉ cần:
+ Hoặc thêm từ khoá virtual vào dòng tiêu đề của phương thức bên
trong định nghĩa lớp cơ sở A.
+ Hoặc thêm từ khoá virtual vào dòng tiêu đề bên trong định nghĩa
của tất cả các lớp A, B, C và D.
Ví dụ:
Cách 1:
class A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp A” ;
};

virtual void hien_thi()
{
cout << “\n Đây là lớp A” ;
};
} ;
class B : public A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp B” ;
};
} ;
class C : public B
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp C” ;
};
} ;
class D : public A
{
...
virtual void hien_thi()
{
cout << “\n Đây là lớp D” ;
};
} ;
Chú ý: Từ khoá virtual không được đặt bên ngoài định nghĩa lớp.

1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào, thì phương
thức của lớp đó sẽ được gọi.
2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp nào, thì phương
thức của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ của đối tượng
nào.
3.2.2. Quy tắc gọi phương thức ảo
Phương thức ảo chỉ khác phương thức tĩnh khi được gọi từ một
con trỏ (trường hợp 2 nêu trong mục 3.2.1). Lời gọi tới phương thức
ảo từ một con trỏ chưa cho biết rõ phương thức nào (trong số các
phương thức ảo trùng tên của các lớp có quan hệ thừa kế) sẽ được gọi.
Điều này phụ thuộc vào đối tượng cụ thể mà con trỏ đang trỏ tới: Con
trỏ đang trỏ tới đối tượng của lớp nào thì phương thức của lớp đó sẽ
được gọi.
Ví dụ A, B, C và D là các lớp đã định nghĩa trong 3.1. Ta khai báo
một con trỏ kiểu A và 4 đối tượng:
A *p ; // p là con trỏ kiểu A
A a ; // a là biến đối tượng kiểu A
B b ; // b là biến đối tượng kiểu B
C c ; // c là biến đối tượng kiểu C
D d ; // d là biến đối tượng kiểu D
Xét lời gọi tới các phương thức ảo hien_thi sau:
p = &a; // p trỏ tới đối tượng a của lớp A
p->hien_thi() ; // Gọi tới A::hien_thi()
p = &b; // p trỏ tới đối tượng b của lớp B
p->hien_thi() ; // Gọi tới B::hien_thi()
p = &c; // p trỏ tới đối tượng c của lớp C
p->hien_thi() ; // Gọi tới C::hien_thi()
p = &d; // p trỏ tới đối tượng d của lớp D
p->hien_thi() ; // Gọi tới D::hien_thi()
3.3. Tương ứng bội

+ liên kết với A::hien_thi() , nếu p chứa địa chỉ đối tượng lớp A
+ liên kết với B::hien_thi() , nếu p chứa địa chỉ đối tượng lớp B
+ liên kết với C::hien_thi() , nếu p chứa địa chỉ đối tượng lớp C
+ liên kết với D::hien_thi() , nếu p chứa địa chỉ đối tượng lớp D
Như vậy một lời gọi (xuất phát từ con trỏ) tới phương thức ảo
không liên kết với một phương thức cố định, mà tuỳ thuộc vào nội
dung con trỏ. Đó là sự liên kết động và phương thức được liên kết
(được gọi) thay đổi mỗi khi có sự thay đổi nội dung con trỏ trong quá
trình chạy chương trình.
3.5. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở
+ Như đã nói trong
§
1, C++ cho phép gán địa chỉ đối tượng của
một lớp dẫn xuất cho con trỏ của lớp cơ sở. Như vậy các phép gán sau
(xem 3.2) là đúng:
A *p ; // p là con trỏ kiểu A
A a ; // a là biến đối tượng kiểu A
B b ; // b là biến đối tượng kiểu B
C c ; // c là biến đối tượng kiểu C
D d ; // d là biến đối tượng kiểu D
p = &a; // p và a cùng lớp A
p = &b; // p là con trỏ lớp cơ sở, b là đối tượng lớp dẫn xuất
p = &c; // p là con trỏ lớp cơ sở, c là đối tượng lớp dẫn xuất
p = &d; // p là con trỏ lớp cơ sở, d là đối tượng lớp dẫn xuất
+ Tuy nhiên cần chú ý là: Không cho phép gán địa chỉ đối tượng
của lớp cở sở cho con trỏ của lớp dẫn xuất. Như vậy ví dụ sau là sai:
B *q ;
A a ;
q = &a;
Sai vì: Gán địa chỉ đối tượng của lớp cơ sở A cho con trỏ của lớp


Nhờ tải bản gốc

Tài liệu, ebook tham khảo khác

Music ♫

Copyright: Tài liệu đại học © DMCA.com Protection Status