Để có thể hiểu được phần này bạn cần hiểu rõ về cách sử dụng con trỏ và thừa kế giữa các lớp. Nếu có vài biểu thức nào có vẻ lạ lùng với bạn, bạn có thể xem lại các phần sau: int a::b(c) {}; // Các lớp (Bài 4.1) a->b // Con trỏ và đối tượng (Bài 4.2) class a: public b; // Quan hệ giữa các lớp(Bài 4.3) |
Con trỏ tới lớp cơ sở
Một trong những lợi thế lớn của việc thừa kế các lớp là
một con trỏ trỏ tới một lớp được thừa kế là tương thích về kiểu với một con trỏ trỏ tới lớp cơ sở của nó. Bài này sẽ đề cập đầy đủ đến việc tận dụng tính năng mạnh mẽ này của C++. Ví dụ, chúng ta sẽ viết lại chương trình của chúng ta về hình chữ nhật và hình tam giác trong chương trước để xem xét tính năng này:
// con trỏ tới lớp cơ sở#include <iostream.h>class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } };class CRectangle: public CPolygon { public: int area (void) { return (width ° height); } };class CTriangle: public CPolygon { public: int area (void) { return (width ° height / 2); } };int main () { CRectangle rect; CTriangle trgl; CPolygon ° ppoly1 = ▭ CPolygon ° ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); cout << rect.area() << endl; cout << sqre.area() << endl; return 0;} | 2010 |
Hàm
main tạo hai con trỏ trỏ tới hai đối tượng của lớp
CPolygon, đó là
°ppoly1 và
°ppoly2. Chúng được gán cho địa chỉ của
rect và
trgl, đây là các đối tượng thuộc lớp thừa kế từ
CPolygon nên đó là những phép gán hợp lệ.
Sự hạn chế duy nhất khi sử dụng
°ppoly1 và
°ppoly2 thay vì
rect và
trgl là cả
°ppoly1 và
°ppoly2 đều có kiểu là
CPolygon° và vì vậy chúng ta chỉ có thể tham chiếu đến các thành viên mà
CRectangle và
CTriangle được thừa kế từ
CPolygon. Vì nguyên nhân đó chúng ta không thể gọi đến thành viên
area() khi dùng
°ppoly1 và
°ppoly2.
Để các con trỏ đó có thể truy xuất đến
area() như là một thành viên hợp lệ, cần phải khai báo thành viên này trong lớp cơ sở chứ không chỉ trong các lớp thừa kế.
Các thành viên ảo
Nếu muốn khai báo một phần tử trong một lớp mà chúng ta muốn định nghĩa lại nó trong các lớp thừa kế thì chúng ta phải đặt trước nó từ khoá
virtual để việc sử dụng con trỏ tới các đối tượng thuộc lớp này là thích hợp.
Hãy xem ví dụ sau:
// các thành viên ảo#include <iostream.h>class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) { return (0); } };class CRectangle: public CPolygon { public: int area (void) { return (width ° height); } };class CTriangle: public CPolygon { public: int area (void) { return (width ° height / 2); } };int main () { CRectangle rect; CTriangle trgl; CPolygon poly; CPolygon ° ppoly1 = ▭ CPolygon ° ppoly2 = &trgl; CPolygon ° ppoly3 = &poly; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly3->set_values (4,5); cout << ppoly1->area() << endl; cout << ppoly2->area() << endl; cout << ppoly3->area() << endl; return 0;} | 20100 |
Bây giờ cả ba lớp (
CPolygon,
CRectangle và
CTriangle) đều có cùng các thành viên:
width,
height,
set_values() và
area().
area() được định nghĩa là
virtual vì nó sẽ được định nghĩa lại trong các lớp thừa kế. Bạn có thể kiểm tra lại rằng nếu bạn bỏ từ khoá đó và thực hiện chương trình thì kết quả sẽ là
0 cho cả 3 đa giác thay vì
20,10,0. Nguyên nhân là do thay vì gọi hàm
area() tương ứng với mỗi đối tượng (
CRectangle::area(),
CTriangle::area() và
CPolygon::area()),
CPolygon::area() sẽ được gọi cho tất cả thông qua một con trỏ tới
CPolygon.
Trừu tượng hoá lớp cơ sở
Các lớp trừu trượng là một cái gì đó rất giống với lớp lớp
CPolygon trong ví dụ trước của chúng ta. Sự khác biệt duy nhất là trong ví dụ đó chúng ta đã định nghĩa hàm
area() cho các đối tượng thuộc lớp
CPolygon (giống như đối tượng
poly), trong khi ở trong một lớp trừu tượng cơ sở chúng ta có thể bỏ qua việc định nghĩa hàm này bằng cách thêm
=0(bằng không) vào phần khai báo hàm.
Lớp
CPolygon có thể được định nghĩa như sau:
// abstract class CPoligonclass CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; };
Hãy chú ý cách chúng ta thêm
=0 vào
virtual int area (void) thay vì định nghĩa đầy đủ cho hàm. Kiểu hàm này có tên là là
pure virtual function (hàm ảo thuần tuý) và tất cả các lớp chứa bất kì một hàm ảo thuần tuý nào đều được coi là lớp trừu tượng cơ sở.
Sự khác biệt lớn của một lớp trừu tượng cơ sở là không thể tạo được các đối tượng thuộc lớp. Nhưng chúng ta có thể tạo các con trỏ trỏ đến chúng. Vì vậy một khai báo như sau:
CPolygon poly;
sẽ là không hợp lệ cho lớp trừu tượng cơ sở được khai báo ở trên. Tuy nhiên con trỏ:
CPolygon ° ppoly1;CPolygon ° ppoly2
là hoàn toàn hợp lệ. Có điều này vì hàm trừu tượng thuần tuý mà nó có không được định nghĩa và không thể toạ được một đối tượng nếu như chưa định nghĩa tất cả các thành viên của nó. Tuy nhiên một con trỏ trỏ tới một đối tượng thuộc lớp thừa kế mà hàm này đã được định nghĩa là hoàn toàn hợp lệ.
Dưới đây chúng ta có một ví dụ đầy đủ:
// các thành viên ảo.#include <iostream.h>class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; };class CRectangle: public CPolygon { public: int area (void) { return (width ° height); } };class CTriangle: public CPolygon { public: int area (void) { return (width ° height / 2); } };int main () { CRectangle rect; CTriangle trgl; CPolygon ° ppoly1 = ▭ CPolygon ° ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); cout << ppoly1->area() << endl; cout << ppoly2->area() << endl; return 0;} | 2010 |
Nếu bạn xem lại chương trình bạn sẽ thấy rằng chúng ta tham chiếu đến các đối tượng thuộc các lớp khác nhau nhưng chỉ sử dụng một kiểu con trỏ duy nhất. Điều này là cực kì hữu dụng, bây giờ chúng ta có thể tạo một hàm thành viên của
CPolygon có khả năng in ra màn hình kết quả của hàm
area() mà không phụ thuộc vào lớp được thừa kế là lớp nào.
// ejemplo miembros virtuales#include <iostream.h>class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; void printarea (void) { cout << this->area() << endl; } };class CRectangle: public CPolygon { public: int area (void) { return (width ° height); } };class CTriangle: public CPolygon { public: int area (void) { return (width ° height / 2); } };int main () { CRectangle rect; CTriangle trgl; CPolygon ° ppoly1 = ▭ CPolygon ° ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly1->printarea(); ppoly2->printarea(); return 0;} | 2010 |
Hãy nhớ rằng
this biểu diễn một con trỏ trỏ đến đối tượng đang được thực hiện.
Các lớp trừu tượng và các thành viên ảo cung cấp cho C++ tính năng đa hình khiến cho việc lập trình hướng đối tượng trở thành một công cụ hữu dụng. Tất nhiên chúng ta đã thấy cách đơn giản nhất để sử dụng những tính năng này, nhưng hãy tưởng tượng nếu những tính năng này được áp dụng cho các mảng các đối tượng hay các đối tượng được cấp phát thông qua bộ nhớ động.