• Không có kết quả nào được tìm thấy

Phương thức ảo và tính đa hình

N/A
N/A
Protected

Academic year: 2022

Chia sẻ "Phương thức ảo và tính đa hình"

Copied!
1
0
0

Loading.... (view fulltext now)

Văn bản

(1)

Chương 5

Phương thức ảo và tính đa hình

5.1 Bài toán quản lý một danh sách các đối tượng khác kiểu

- Giả sử ta cần quản lý một danh sách các đối tượng có kiểu có thể khác nhau, ta cần giải quyết hai vấn đề: Cách lưu trữ và thao tác xử lý.

- Xét trường hợp cụ thể, các đối tượng có thể là người, sinh viên hoặc công nhân.

- Về lưu trữ: Ta có thể dùng union, trong trường hợp này mỗi đối tượng phải có kích thước chứa được đối tượng có kích thước lớn nhất. Điều này gây lãng phí không gian lưu trữ. Một cách thay thế là lưu trữ đối tượng bằng đúng kích thước của nó và dùng một danh sách (mảng, dslk,...) các con trỏ để quản lý các đối tượng.

- Về thao tác, phải thoả yêu cầu đa hình: Thao tác có hoạt động khác nhau ứng với các loại đối tượng khác nhau. Có hai cách giải quyết là vùng chọn kiểu và phương thức ảo.

5.2 Dùng vùng chọn kiểu

 Về lưu trữ: Ta sẽ dùng một mảng các con trỏ đến lớp cơ sở để có thể trỏ đến các đối tượng thuộc lớp con.

 Xét lớp Người và các lớp kế thừa sinh viên và công nhân. Thao tác ta quan tâm là xuat. Ta cần bảo đảm thao tác xuất áp dụng cho lớp sinh viên và lớp công nhân khác nhau.

class Nguoi {

protected:

char *HoTen;

int NamSinh;

public:

Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = strdup(ht);}

~Nguoi() {delete [] HoTen;}

void An() const { cout << HoTen << " an 3 chen com";}

void Ngu() const { cout << HoTen << " ngu ngay 8 tieng";}

void Xuat() const { cout << "Nguoi, ho ten: " << HoTen

<< " sinh " << NamSinh; } };

class SinhVien : public Nguoi {

protected:

char *MaSo;

(2)

public:

SinhVien(char *n, char *ms, int ns) : Nguoi(n,ns) { MaSo = strdup(ms);}

~SinhVien() {delete [] MaSo;}

void Xuat() const { cout << "Sinh vien " << HoTen << ", ma so " << MaSo;}

};

class NuSinh : public SinhVien {

public:

NuSinh(char *ht, char *ms, int ns) : SinhVien(ht,ms,ns) {}

void An() const { cout << HoTen << " ma so " << MaSo <<

" an 2 to pho";}

};

class CongNhan : public Nguoi {

protected:

double MucLuong;

public:

CongNhan(char *n, double ml, int ns) : Nguoi(n,ns), MucLuong(ml) { }

void Xuat() const { cout << "Cong nhan, ten " << HoTen

<< " muc luong: " << MucLuong;}

};

void XuatDs(int n, Nguoi *an[]) {

for (int i = 0; i < n; i++) {

an[i]->Xuat();

cout << "\n";

} }

const int N = 4;

void main() {

Nguoi *a[N];

a[0] = new SinhVien("Vien Van Sinh", ”200001234", 1982);

a[1] = new NuSinh("Le Thi Ha Dong", ”200001235", 1984);

a[2] = new CongNhan("Tran Nhan Cong", 1000000, 1984);

a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);

XuatDs(4,a);

(3)

 Xuất liệu cho đoạn chương trình trên như sau:

Nguoi, ho ten: Vien Van Sinh sinh 1982 Nguoi, ho ten: Le Thi Ha Dong sinh 1984 Nguoi, ho ten: Tran Nhan Cong sinh 1984 Nguoi, ho ten: Nguyen Thanh Nhan sinh 1960

 Tất cả mọi đối tượng đều được quan điểm như người vì thao tác được thực hiện thông qua con trỏ đến lớp Người.

 Để bảo đảm xuất liệu tương ứng với đối tượng, phải có cách nhận diện đối tượng, ta thêm một vùng dữ liệu vào lớp cơ sở để nhận diện, vùng này có giá trị phụ thuộc vào loại của đối tượng và được gọi là vùng chọn kiểu.

 Các đối tượng thuộc lớp người có cùng giá trị cho vùng chọn kiểu, các đối tượng thuộc lớp sinh viên có giá trị của vùng chọn kiểu khác của lớp người.

class Nguoi {

public:

enum LOAI {NGUOI, SV, CN};

protected:

char *HoTen;

int NamSinh;

public:

LOAI pl;

Nguoi(char *ht, int ns):NamSinh(ns), pl(NGUOI) {HoTen = strdup(ht);}

~Nguoi() {delete [] HoTen;}

void An() const { cout << HoTen << " an 3 chen com";}

void Ngu() const { cout << HoTen << " ngu ngay 8 tieng";}

void Xuat() const { cout << "Nguoi, ho ten: " << HoTen

<< " sinh " << NamSinh; } };

class SinhVien : public Nguoi {

protected:

char *MaSo;

public:

SinhVien(char *n, char *ms, int ns) : Nguoi(n,ns) { MaSo = strdup(ms); pl = SV;}

~SinhVien() {delete [] MaSo;}

void Xuat() const { cout << "Sinh vien " << HoTen << ", ma so " << MaSo;}

};

class NuSinh : public SinhVien

{

(4)

public:

NuSinh(char *ht, char *ms, int ns) : SinhVien(ht,ms,ns) {}

void An() const { cout << HoTen << " ma so " << MaSo <<

" an 2 to pho";}

};

class CongNhan : public Nguoi {

protected:

double MucLuong;

public:

CongNhan(char *n, double ml, int ns) : Nguoi(n,ns), MucLuong(ml) { pl = CN;}

void Xuat() const { cout << "Cong nhan, ten " << HoTen

<< " muc luong: " << MucLuong;}

};

 Khi thao tác ta phải căn cứ vào giá trị của vùng chọn kiểu để “ép kiểu” phù hợp.

void XuatDs(int n, Nguoi *an[]) {

for (int i = 0; i < n; i++) {

switch(an[i]->pl) {

case Nguoi::SV:

((SinhVien *)an[i])->Xuat();

break;

case Nguoi::CN:

((CongNhan *)an[i])->Xuat();

break;

default:

an[i]->Xuat();

break;

}

cout << "\n";

} }

const int N = 4;

void main() {

Nguoi *a[N];

a[0] = new SinhVien("Vien Van Sinh", "200001234",

1982);

(5)

a[2] = new CongNhan("Tran Nhan Cong", 1000000, 1984);

a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);

XuatDs(4,a);

}

 Xuất liệu của đoạn chương trình trên sẽ là:

Sinh vien Vien Van Sinh, ma so 200001234 Sinh vien Le Thi Ha Dong, ma so 200001235

Cong nhan, ten Tran Nhan Cong muc luong: 1000000 Nguoi, ho ten: Nguyen Thanh Nhan sinh 1960

 Cách tiếp cận trên giải quyết được vấn để: Lưu trữ được các đối tượng khác kiểu nhau và thao tác khác nhau tương ứng với đối tượng. Tuy nhiên nó có các nhược điểm sau:

– Dài dòng với nhiều switch, case.

– Dễ sai sót, khó sửa vì trình biên dịch bị cơ chế ép kiểu che mắt.

– Khó nâng cấp ví dụ thêm một loại đối tượng mới, đặc biệt khi chương trình lớn.

 Các nhược điểm trên có thể được khắc phục nhờ phương thức ảo.

5.3 Phương thức ảo

 Con trỏ thuộc lớp cơ sở có thể trỏ đến lớp con:

Nguoi* pn = new SinhVien(“Le Vien Sinh”, 200001234, 1982);

 Ta mong muốn thông qua con trỏ thuộc lớp cơ sở có thể truy xuất hàm thành phần được định nghĩa lại ở lớp con:

pn->Xuat(); // Mong muon: goi Xuat cua lop sinh // vien, thuc te: goi Xuat cua lop // Nguoi

 Phương thức ảo cho phép giải quyết vấn đề. Ta qui định một hàm thành phần là phương thức ảo bằng cách thêm từ khoá virtual vào trước khai báo hàm.

 Trong ví dụ trên, ta thêm từ khoá virtual vào trước khai báo của hàm xuat.

class Nguoi {

protected:

char *HoTen;

int NamSinh;

public:

Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = strdup(ht);}

~Nguoi() {delete [] HoTen;}

void An() const { cout << HoTen << " an 3 chen com";}

void Ngu() const { cout << HoTen << " ngu ngay 8 tieng";}

virtual void Xuat() const { cout << "Nguoi, ho ten: "

(6)

<< HoTen << " sinh " << NamSinh; } };

class SinhVien : public Nguoi {

protected:

char *MaSo;

public:

SinhVien(char *n, char *ms, int ns) : Nguoi(n,ns) { MaSo = strdup(ms);}

~SinhVien() {delete [] MaSo;}

void Xuat() const { cout << "Sinh vien " << HoTen << ", ma so " << MaSo;}

};

class NuSinh : public SinhVien {

public:

NuSinh(char *ht, char *ms, int ns) : SinhVien(ht,ms,ns) {}

void An() const { cout << HoTen << " ma so " << MaSo <<

" an 2 to pho";}

};

class CongNhan : public Nguoi {

protected:

double MucLuong;

public:

CongNhan(char *n, double ml, int ns) : Nguoi(n,ns), MucLuong(ml) { }

void Xuat() const { cout << "Cong nhan, ten " << HoTen

<< " muc luong: " << MucLuong;}

};

void XuatDs(int n, Nguoi *an[]) {

for (int i = 0; i < n; i++) {

an[i]->Xuat();

cout << "\n";

} }

const int N = 4;

void main()

(7)

Nguoi *a[N];

a[0] = new SinhVien("Vien Van Sinh", "200001234", 1982);

a[1] = new NuSinh("Le Thi Ha Dong", "200001235", 1984);

a[2] = new CongNhan("Tran Nhan Cong", 1000000, 1984);

a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);

XuatDs(4,a);

}

 Phương thức ảo xuat được khai báo ở lớp Nguoi cho phép sử dụng con trỏ đến lớp cơ sở (Nguoi) nhưng trỏ đến một đối tượng thuộc lớp con (Sinh viên, công nhân) gọi đúng thao tác ở lớp con:

Nguoi *pn;

pn = new SinhVien("Vien Van Sinh", "200001234", 1982);

pn->Xuat(); // Goi thao tac xuat cua lop Sinh vien

 Con trỏ pn thuộc lớp Nguoi nhưng trỏ đến đối tượng sinh viên, vì vậy pn-

>Xuat() thực hiện thao tác xuất của lớp sinh viên.

 Trở lại ví dụ trên, khi i a[i] lần lượt trỏ đến các đối tượng thuộc các loại khác nhau, thao tác tương ứng với lớp sẽ được gọi.

 Dùng phương thức ảo khắc phục được các nhược điểm của cách tiếp cận dùng vùng chọn kiểu:

 Thao tác đơn giản không phải dùng switch/case vì vậy khó sai, dễ sửa.

 Dùng phương thức ảo, ta dễ dàng nâng cấp sửa chữa. Việc thêm một loại đối tượng mới rất đơn giản, ta không cần phải sửa đổi thao tác xử lý (hàm XuatDs). Qui trình thêm chỉ là xây dựng lớp con kế thừa từ lớp cơ sở hoặc các lớp con đã có và định nghĩa lại phương thức (ảo) ở lớp mới tạo nếu cần

class CaSi : public Nguoi {

protected:

double CatXe;

public:

CaSi(char *ht, double cx, int ns) : Nguoi(ht,ns), CatXe(cx) {}

void Xuat() const { cout << "Ca si, " << HoTen << " co cat xe " << CatXe;}

};

void XuatDs(int n, Nguoi *an[]) {

for (int i = 0; i < n; i++) {

an[i]->Xuat();

cout << "\n";

}

(8)

}

 Hàm XuatDs không thay đổi, nhưng nó có thể hoạt động cho các loại đối tượng ca sĩ thuộc lớp mới ra đời.

 Có thể xem như thao tác XuatDs được viết trước cho các lớp con cháu chưa ra đời.

Các lưu ý khi sử dụng phương thức ảo

 Phương thức ảo chỉ hoạt động thông qua con trỏ.

 Muốn một hàm trở thành phương thức ảo có hai cách: Khai báo với từ khoá virtual hoặc hàm tương ứng ở lớp cơ sở đã là phương thức ảo.

 Phương thức ảo chỉ hoạt động nếu các hàm ở lớp cơ sở và lớp con có nghi thức giao tiếp giống hệt nhau.

 Nếu ở lớp con định nghĩa lại phương thức ảo thì sẽ gọi phương thức ở lớp cơ sở (gần nhất có định nghĩa).

Ví dụ thêm về phương thức ảo

Ví dụ sau đây cho phép quản lý một danh sách các động vật.

1: //Listing mamal.cpp: Multiple virtual functions called in turn 2:

3: #include <iostream.h>

4:

5: class Mammal 6: {

7: public:

8: Mammal():itsAge(1) { } 9: ~Mammal() { }

10: virtual void Speak() const { cout << "Mammal speak!\n"; } 11: protected:

12: int itsAge;

13: };

14:

15: class Dog : public Mammal 16: {

17: public:

18: void Speak()const { cout << "Woof!\n"; } 19: };

20:

21:

22: class Cat : public Mammal 23: {

24: public:

25: void Speak()const { cout << "Meow!\n"; } 26: };

27:

28:

29: class Horse : public Mammal 30: {

31: public:

32: void Speak()const { cout << "Winnie!\n"; } 33: };

(9)

35: class Pig : public Mammal 36: {

37: public:

38: void Speak()const { cout << "Oink!\n"; } 39: };

40:

41: int main() 42: {

43: Mammal* theArray[5];

44: Mammal* ptr;

45: int choice, i;

46: for ( i = 0; i<5; i++) 47: {

48: cout << "(1)dog (2)cat (3)horse (4)pig: ";

49: cin >> choice;

50: switch (choice) 51: {

52: case 1: ptr = new Dog;

53: break;

54: case 2: ptr = new Cat;

55: break;

56: case 3: ptr = new Horse;

57: break;

58: case 4: ptr = new Pig;

59: break;

60: default: ptr = new Mammal;

61: break;

62: }

63: theArray[i] = ptr;

64: }

65: for (i=0;i<5;i++)

66: theArray[i]->Speak();

67: return 0;

68: } Output:

(1)dog (2)cat (3)horse (4)pig: 1 (1)dog (2)cat (3)horse (4)pig: 2 (1)dog (2)cat (3)horse (4)pig: 3 (1)dog (2)cat (3)horse (4)pig: 4 (1)dog (2)cat (3)horse (4)pig: 5 Woof!

Meow!

Winnie!

Oink!

Mammal speak!

Cơ chế thực hiện phương thức ảo

 Khi gọi một thao tác, khả năng chọn đúng phiên bản tuỳ theo đối tượng để thực hiện thông qua con trỏ đến lớp cơ sở được gọi là tính đa hình (polymorphisms).

 Cơ chế đa hình được thực hiện nhờ ở mỗi đối tượng có thêm một bảng phương thức ảo. Bảng này chứa địa chỉ của các phương thức ảo và nó được trình biên dịch khởi tạo một cách ngầm định khi thiết lập đối tượng.

 Khi thao tác được thực hiện thông qua con trỏ, hàm có địa chỉ trong bảng

phương thức ảo sẽ được gọi.

(10)

 Trong ví dụ trên, mỗi đối tượng thuộc lớp cơ sở Người có bảng phương thức ảo có một phần tử là địa chỉ hàm Nguoi::Xuat. Mỗi đối tượng thuộc lớp SinhVien có bảng tương tự nhưng nội dung là địa chỉ của hàm SinhVien::Xuat.

 Trong ví dụ 2, mỗi đối tượng thuộc các lớp Mamal, Dog, Cat, Horse, Pig đều có bảng phương thức ảo với hai phần tử, địa chỉ của hàm Speak và của hàm Move.

Phương thức huỷ bỏ ảo

 Trong ví dụ quản lý danh sách các đối tượng thuộc các lớp Nguoi, SinhVien, CongNhan, … Thao tác dọn dẹp đối tượng là cần thiết.

const int N = 4;

void main() {

Nguoi *a[N];

a[0] = new SinhVien("Vien Van Sinh", "20001234", 1982);

a[1] = new NuSinh("Le Thi Ha Dong", "20001235", 1984);

a[2] = new CongNhan("Tran Nan Cong", 1000000, 1984);

a[3] = new Nguoi("Nguyen Thanh Nhan", 1960);

XuatDs(4,a);

for (int i = 0; i < 4; i++) delete a[i];

}

 Thông qua con trỏ thuộc lớp cơ sở Nguoi, chỉ có phương thức huỷ bỏ của lớp Nguoi được gọi.

 Để bảo đảm việc dọn dẹp là đầy đủ, ta dùng phương thức huỷ bỏ ảo.

class Nguoi {

protected:

char *HoTen;

int NamSinh;

public:

Nguoi(char *ht, int ns):NamSinh(ns) {HoTen = strdup(ht);}

virtual ~Nguoi() {delete [] HoTen;}

virtual void Xuat(ostream &os) const { os << "Nguoi, ho ten: " << HoTen << " sinh " << NamSinh; }

void Xuat() const { Xuat(cout); } };

5.4 Phương thức thiết lập ảo

 C++ không cung cấp cơ chế thiết lập đối tượng có khả năng đa hình theo cơ chế hàm thành phần ảo.

 Tuy nhiên ta có thể “thu xếp” để có thể tạo đối tượng theo nghĩa “ảo”. Xem ví dụ pttl_ao.cpp sau đây, hàm clone cho phép tạo đối tượng tùy theo lớp.

1: // Virtual copy constructor

(11)

4:

5: class Mammal 6: {

7: public:

8: Mammal():itsAge(1) { cout << "Mammal constructor...\n"; } 9: ~Mammal() { cout << "Mammal destructor...\n"; }

10: Mammal (const Mammal & rhs);

11: virtual void Speak() const { cout << "Mammal speak!\n"; } 12: virtual Mammal* Clone() { return new Mammal(*this); } 13: int GetAge()const { return itsAge; }

14: protected:

15: int itsAge;

16: };

17:

18: Mammal::Mammal (const Mammal & rhs):itsAge(rhs.GetAge()) 19: {

20: cout << "Mammal Copy Constructor...\n";

21: } 22:

23: class Dog : public Mammal 24: {

25: public:

26: Dog() { cout << "Dog constructor...\n"; } 27: ~Dog() { cout << "Dog destructor...\n"; } 28: Dog (const Dog & rhs);

29: void Speak()const { cout << "Woof!\n"; }

30: virtual Mammal* Clone() { return new Dog(*this); } 31: };

32:

33: Dog::Dog(const Dog & rhs):

34: Mammal(rhs) 35: {

36: cout << "Dog copy constructor...\n";

37: } 38:

39: class Cat : public Mammal 40: {

41: public:

42: Cat() { cout << "Cat constructor...\n"; } 43: ~Cat() { cout << "Cat destructor...\n"; } 44: Cat (const Cat &);

45: void Speak()const { cout << "Meow!\n"; }

46: virtual Mammal* Clone() { return new Cat(*this); } 47: };

48:

49: Cat::Cat(const Cat & rhs):

50: Mammal(rhs) 51: {

52: cout << "Cat copy constructor...\n";

53: } 54:

55: enum ANIMALS { MAMMAL, DOG, CAT};

56: const int NumAnimalTypes = 3;

57: int main() 58: {

59: Mammal *theArray[NumAnimalTypes];

60: Mammal* ptr;

(12)

61: int choice, i;

62: for ( i = 0; i<NumAnimalTypes; i++) 63: {

64: cout << "(1)dog (2)cat (3)Mammal: ";

65: cin >> choice;

66: switch (choice) 67: {

68: case DOG: ptr = new Dog;

69: break;

70: case CAT: ptr = new Cat;

71: break;

72: default: ptr = new Mammal;

73: break;

74: }

75: theArray[i] = ptr;

76: }

77: Mammal *OtherArray[NumAnimalTypes];

78: for (i=0;i<NumAnimalTypes;i++) 79: {

80: theArray[i]->Speak();

81: OtherArray[i] = theArray[i]->Clone();

82: }

83: for (i=0;i<NumAnimalTypes;i++) 84: OtherArray[i]->Speak();

25: return 0;

86: }

1: (1)dog (2)cat (3)Mammal: 1 2: Mammal constructor...

3: Dog constructor...

4: (1)dog (2)cat (3)Mammal: 2 5: Mammal constructor...

6: Cat constructor...

7: (1)dog (2)cat (3)Mammal: 3 8: Mammal constructor...

9: Woof!

10: Mammal Copy Constructor...

11: Dog copy constructor...

12: Meow!

13: Mammal Copy Constructor...

14: Cat copy constructor...

15: Mammal speak!

16: Mammal Copy Constructor...

17: Woof!

18: Meow!

19: Mammal speak!

 Phương thức thiết lập ảo cũng có thể được hiện thực bằng cách dùng hàm thành phần tĩnh để tạo đối tượng.

enum FILETYPE {UNKNOWN, BMP, GIF, JPG};

class CGBmp;

typedef CGBmp * CGBmpPtr;

class CGBmp

(13)

protected:

// ...

public:

virtual ~CGBmp(){Release();}

virtual void Release() = 0;

static CGBmpPtr NewBmp(const String &pathName);

virtual void Draw() const;

};

class CBmp : public CGBmp {

//...

public:

CBmp(const String &pathName);

void Release();

void Draw() const;

//...

};

class CGif: public CGBmp {

//..

public:

CGif(const String &pathName);

void Release();

void Draw() const;

//...

};

class CJpg: public CGBmp {

//..

public:

CJpg(const String &pathName);

void Release();

void Draw() const;

//...

};

FILETYPE GetFileType(const String &pathName) {

String FileExt = GetFileExt(pathName);

if (FileExt == ".DIB" || FileExt == ".BMP") return BMP;

else if (FileExt == ".JPG") return JPG;

else if (FileExt == ".GIF") return GIF;

else return UNKNOWN;

}

CGBmpPtr CGBmp::NewBmp(const String &pathName) {

switch (GetFileType(pathName)) {

case BMP:

return new CBmp(pathName);

(14)

case GIF:

return new CGif(pathName);

case JPG:

return new CJpg(pathName);

default:

return NULL;

} }

void main() {

CGBmpPtr p = CGBmp::NewBmp("Lena.jpg");

p->Draw();

}

Đoạn chương trình tạo một đối tượng ảnh thuộc lớp CJpg và gọi đúng phương thức Draw cho đối tượng đó.

5.5 Phương thức ảo thuần tuý và lớp cơ sở trừu tượng

 Lớp cơ sở trừu tượng là lớp cơ sở không có đối tượng nào thuộc chính nó. Một đối tượng thuộc lớp cơ sở trừu tượng phải thuộc một trong các lớp con.

 Xét các lớp Circle, Rectangle, Square kế thừa từ lớp Shape, xem chương trình nguồn pta_tt.cpp sau đây.

1: // Shape classes, minh hoa phuong thuc ao thuan tuy.

2:

3: #include <iostream.h>

4:

5: enum BOOL { FALSE, TRUE };

6:

7: class Shape 8: {

9: public:

10: Shape(){}

11: ~Shape(){}

12: virtual long GetArea() { return -1; } // error 13: virtual long GetPerim() { return -1; }

14: virtual void Draw() {}

15: private:

16: };

17:

18: class Circle : public Shape 19: {

20: public:

21: Circle(int radius):itsRadius(radius){}

22: ~Circle(){}

23: long GetArea() { return 3 * itsRadius * itsRadius; } 24: long GetPerim() { return 9 * itsRadius; }

25: void Draw();

26: private:

27: int itsRadius;

28: int itsCircumference;

29: };

30:

(15)

32: {

33: cout << "Circle drawing routine here!\n";

34: } 35:

36:

37: class Rectangle : public Shape 38: {

39: public:

40: Rectangle(int len, int width):

41: itsLength(len), itsWidth(width){}

42: ~Rectangle(){}

43: virtual long GetArea() { return itsLength * itsWidth; } 44: virtual long GetPerim() {return 2*itsLength + 2*itsWidth; } 45: virtual int GetLength() { return itsLength; }

46: virtual int GetWidth() { return itsWidth; } 47: virtual void Draw();

48: private:

49: int itsWidth;

50: int itsLength;

51: };

52:

53: void Rectangle::Draw() 54: {

55: for (int i = 0; i<itsLength; i++) 56: {

57: for (int j = 0; j<itsWidth; j++) 58: cout << "x ";

59:

60: cout << "\n";

61: } 62: } 63:

64: class Square : public Rectangle 65: {

66: public:

67: Square(int len);

68: Square(int len, int width);

69: ~Square(){}

70: long GetPerim() {return 4 * GetLength();}

71: };

72:

73: Square::Square(int len):

74: Rectangle(len,len) 75: {}

76:

77: Square::Square(int len, int width):

78: Rectangle(len,width) 79:

80: {

81: if (GetLength() != GetWidth())

82: cout << "Error, not a square... a Rectangle??\n";

83: } 84:

85: int main() 86: {

87: int choice;

88: BOOL fQuit = FALSE;

(16)

89: Shape * sp;

90:

91: while (1) 92: {

93: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";

94: cin >> choice;

95:

96: switch (choice) 97: {

98: case 1: sp = new Circle(5);

99: break;

100: case 2: sp = new Rectangle(4,6);

101: break;

102: case 3: sp = new Square(5);

103: break;

104: default: fQuit = TRUE;

105: break;

106: }

107: if (fQuit) 108: break;

109:

110: sp->Draw();

111: cout << "\n";

112: }

113: return 0;

114: }

Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2 x x x x x x

x x x x x x x x x x x x x x x x x x

(1)Circle (2)Rectangle (3)Square (0)Quit:3 x x x x x

x x x x x x x x x x x x x x x x x x x x

(1)Circle (2)Rectangle (3)Square (0)Quit:0

Trong ví dụ trên, các hàm trong lớp Shape có nội dung nhưng nội dung không có ý nghĩa. Đồng thời ta luôn luôn có thể tạo được đối tượng thuộc lớp Shape, điều này không đúng với tư tưởng của phương pháp luận hướng đối tượng.

 Ta có thể thay thế cho nội dung không có ý nghĩa bằng phương thức ảo thuần tuý. Phương thức ảo thuần tuý là phương thức ảo không có nội dung.

 Khi lớp có phương thức ảo thuần tuý, lớp trở thành lớp cơ sở trừu tượng. Ta không thể tạo đối tượng thuộc lớp cơ sở thuần tuý.

 Ta có thể định nghĩa phương thức ảo thuần tuý, nhưng chỉ có các đối tượng thuộc lớp con có thể gọi nó. Xem pta_tt2 sau đây

1: //Implementing pure virtual functions

(17)

3: #include <iostream.h>

4:

5: enum BOOL { FALSE, TRUE };

6:

7: class Shape 8: {

9: public:

10: Shape(){}

11: ~Shape(){}

12: virtual long GetArea() = 0; // error 13: virtual long GetPerim()= 0;

14: virtual void Draw() = 0;

15: private:

16: };

17:

18: void Shape::Draw() 19: {

20: cout << "Abstract drawing mechanism!\n";

21: } 22:

23: class Circle : public Shape 24: {

25: public:

26: Circle(int radius):itsRadius(radius){}

27: ~Circle(){}

28: long GetArea() { return 3 * itsRadius * itsRadius; } 29: long GetPerim() { return 9 * itsRadius; }

30: void Draw();

31: private:

32: int itsRadius;

33: int itsCircumference;

34: };

35:

36: void Circle::Draw() 37: {

38: cout << "Circle drawing routine here!\n";

39: Shape::Draw();

40: } 41:

42:

43: class Rectangle : public Shape 44: {

45: public:

46: Rectangle(int len, int width):

47: itsLength(len), itsWidth(width){}

48: ~Rectangle(){}

49: long GetArea() { return itsLength * itsWidth; } 50: long GetPerim() {return 2*itsLength + 2*itsWidth; } 51: virtual int GetLength() { return itsLength; }

52: virtual int GetWidth() { return itsWidth; } 53: void Draw();

54: private:

55: int itsWidth;

56: int itsLength;

57: };

58:

59: void Rectangle::Draw()

(18)

60: {

61: for (int i = 0; i<itsLength; i++) 62: {

63: for (int j = 0; j<itsWidth; j++) 64: cout << "x ";

65:

66: cout << "\n";

67: }

68: Shape::Draw();

69: } 70:

71:

72: class Square : public Rectangle 73: {

74: public:

75: Square(int len);

76: Square(int len, int width);

77: ~Square(){}

78: long GetPerim() {return 4 * GetLength();}

79: };

80:

81: Square::Square(int len):

82: Rectangle(len,len) 83: {}

84:

85: Square::Square(int len, int width):

86: Rectangle(len,width) 87:

88: {

89: if (GetLength() != GetWidth())

90: cout << "Error, not a square... a Rectangle??\n";

91: } 92:

93: int main() 94: {

95: int choice;

96: BOOL fQuit = FALSE;

97: Shape * sp;

98:

99: while (1) 100: {

101: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";

102: cin >> choice;

103:

104: switch (choice) 105: {

106: case 1: sp = new Circle(5);

107: break;

108: case 2: sp = new Rectangle(4,6);

109: break;

110: case 3: sp = new Square (5);

111: break;

112: default: fQuit = TRUE;

113: break;

114: }

(19)

117:

118: sp->Draw();

119: cout << "\n";

120: }

121: return 0;

122: }

Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2 x x x x x x

x x x x x x x x x x x x x x x x x x

Abstract drawing mechanism!

(1)Circle (2)Rectangle (3)Square (0)Quit: 3 x x x x x

x x x x x x x x x x x x x x x x x x x x

Abstract drawing mechanism!

(1)Circle (2)Rectangle (3)Square (0)Quit: 0

Trong ví dụ trên, các hàm thành phần trong lớp Shape là phương thức ảo thuần tuý.

Nó bảo đảm không thể tạo được đối tượng thuộc lớp Shape. Ví dụ trên cũng định nghĩa nội dung cho phương thức ảo thuần tuý, nhưng chỉ có các đối tượng thuộc lớp con có thể gọi.

Phương thức ảo thuần tuý có ý nghĩa cho việc tổ chức sơ đồ phân cấp các lớp, nó đóng vai trò chừa sẵn chỗ trống cho các lớp con điền vào với phiên bản phù hợp.

Bản thân các lớp con của một lớp cơ sở trừu tượng cũng có thể là lớp cơ sở trừu tượng.

Như ví dụ sau:

1: // Listing 13.10

2: // Deriving ADTs from other ADTs 3: #include <iostream.h>

4:

5: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; 6: enum BOOL { FALSE, TRUE };

7:

8: class Animal // common base to both horse and bird 9: {

10: public:

11: Animal(int);

12: virtual ~Animal() { cout << "Animal destructor...\n"; } 13: virtual int GetAge() const { return itsAge; }

14: virtual void SetAge(int age) { itsAge = age; } 15: virtual void Sleep() const = 0;

16: virtual void Eat() const = 0;

17: virtual void Reproduce() const = 0;

18: virtual void Move() const = 0;

19: virtual void Speak() const = 0;

20: private:

(20)

21: int itsAge;

22: };

23:

24: Animal::Animal(int age):

25: itsAge(age) 26: {

27: cout << "Animal constructor...\n";

28: } 29:

30: class Mammal : public Animal 31: {

32: public:

33: Mammal(int age):Animal(age)

34: { cout << "Mammal constructor...\n";}

35: ~Mammal() { cout << "Mammal destructor...\n";}

36: virtual void Reproduce() const

37: { cout << "Mammal reproduction depicted...\n"; } 38: };

39:

40: class Fish : public Animal 41: {

42: public:

43: Fish(int age):Animal(age)

44: { cout << "Fish constructor...\n";}

45: virtual ~Fish() {cout << "Fish destructor...\n"; }

46: virtual void Sleep() const { cout << "fish snoring...\n"; } 47: virtual void Eat() const { cout << "fish feeding...\n"; } 48: virtual void Reproduce() const

49: { cout << "fish laying eggs...\n"; } 50: virtual void Move() const

51: { cout << "fish swimming...\n"; } 52: virtual void Speak() const { }

53: };

54:

55: class Horse : public Mammal 56: {

57: public:

58: Horse(int age, COLOR color ):

59: Mammal(age), itsColor(color)

60: { cout << "Horse constructor...\n"; }

61: virtual ~Horse() { cout << "Horse destructor...\n"; } 62: virtual void Speak()const { cout << "Whinny!... \n"; } 63: virtual COLOR GetItsColor() const { return itsColor; } 64: virtual void Sleep() const

65: { cout << "Horse snoring...\n"; }

66: virtual void Eat() const { cout << "Horse feeding...\n"; } 67: virtual void Move() const { cout << "Horse running...\n";}

68:

69: protected:

70: COLOR itsColor;

71: };

72:

73: class Dog : public Mammal 74: {

75: public:

(21)

78: { cout << "Dog constructor...\n"; }

79: virtual ~Dog() { cout << "Dog destructor...\n"; } 80: virtual void Speak()const { cout << "Whoof!... \n"; } 81: virtual void Sleep() const { cout << "Dog snoring...\n"; } 82: virtual void Eat() const { cout << "Dog eating...\n"; } 83: virtual void Move() const { cout << "Dog running...\n"; } 84: virtual void Reproduce() const

85: { cout << "Dogs reproducing...\n"; } 86:

87: protected:

88: COLOR itsColor;

89: };

90:

91: int main() 92: {

93: Animal *pAnimal=0;

94: int choice;

95: BOOL fQuit = FALSE;

96:

97: while (1) 98: {

99: cout << "(1)Dog (2)Horse (3)Fish (0)Quit: ";

100: cin >> choice;

101:

102: switch (choice) 103: {

104: case 1: pAnimal = new Dog(5,Brown);

105: break;

106: case 2: pAnimal = new Horse(4,Black);

107: break;

108: case 3: pAnimal = new Fish (5);

109: break;

110: default: fQuit = TRUE;

111: break;

112: }

113: if (fQuit) 114: break;

115:

116: pAnimal->Speak();

117: pAnimal->Eat();

118: pAnimal->Reproduce();

119: pAnimal->Move();

120: pAnimal->Sleep();

121: delete pAnimal;

122: cout << "\n";

123: }

124: return 0 125: }

Output: (1)Dog (2)Horse (3)Bird (0)Quit: 1 Animal constructor...

Mammal constructor...

Dog constructor...

Whoof!...

Dog eating...

Dog reproducing....

Dog running...

(22)

Dog snoring...

Dog destructor...

Mammal destructor...

Animal destructor...

(1)Dog (2)Horse (3)Bird (0)Quit: 0

Tài liệu tham khảo

Tài liệu liên quan