Một Số Lớp Cơ Sở Xuất Hiện Nhiều Lần Trong Lớp Dẫn Xuất



A

B

C

D

E

F

G

H

Có thể bạn quan tâm!

Xem toàn bộ 256 trang tài liệu này.


Sơ đồ 3.1

Lập trình hướng đối tượng - 14

Trong sơ đồ 3.1:

Lớp A, lớp B là lớp cơ sở cđa lớp D

Lớp A, lớp B, lớp D là lớp cơ sở cđa lớp F, lớp G Lớp B, lớp C là lớp cơ sở cđa lớp E

Lớp B, lớp C, lớp E là lớp cơ sở cđa lớp G, lớp H


3.4.2. Sự thừa kế nhiều mức

Theo tính thừa kế: lớp dẫn xuất được thừa kế tất cả các thành phần (thuộc tính và phương thức) cđa lớp cơ sở, kể cả các thành phần mà lớp cơ sở được thừa kế. Hay có thể nói lớp dẫn xuất được thừa kế tất cả các thành phần cđa các lớp cơ sở có liên quan (các lớp tiền bối).

Trong sơ đồ dẫn xuất 3.1:

Lớp D được thừa kế tất cả các thành phần cđa lớp A, B

Lớp F, G được thừa kế tất cả các thành phần cđa lớp D, A, B Lớp E được thừa kế tất cả các thành phần cđa lớp B, lớp C

Lớp H, lớp G được thừa kế tất cả các thành phần cđa lớp B, lớp C, lớp E


3.4.3. Sự trùng tên

Nguyên tắc đặt tên khi xây dựng các lớp đối tượng như sau:

Tên các lớp không được trùng nhau

Tên các thuộc tính được khai báo trong cùng một lớp không được trùng nhau

Tên các thuộc tính, phương thức cđa các lớp khác nhau có thể đặt trùng nhau

Nguyên tắc thứ ba, và sự dẫn xuất nhiều mức sẽ dẫn đến hiện tượng trong một lớp sẽ có nhiều thành phần trùng tên nhau (do một số thành phần được thừa kế).

Ví dụ 3.8 :


class A

{

protected : int x, y ;

};

class B

{

public :

int x, z ;

};

class C : public A, public B

{

private :

int y, z, t ;

};

Trong ví dụ này lớp C sẽ có các thuộc tính có tên là: y, z, t, x. Trong đó, hai thuộc tính có tên x, một được thừa kế từ A, một được thừa kế từ B; hai thuộc tính có tên là y, một là thuộc riêng cđa C, một được thừa kế từ A; hai thuộc tính có tên là z.


3.4.4. Sử dụng các thành phần trong lớp dẫn xuất

Trong sơ dẫn xuất nhiều mức có thể xuất hiện hiện tượng trùng tên. Để phân biệt các thành phần trùng tên này, ta cần sử dụng tên_lớp và toán tử phạm vi ( :: ) trước tên thành phần.

Trong ví dụ 3.8, trong phương thức cđa lớp C để truy nhập vào thuộc tính x ta cần viết: A::x để truy nhập vào thuộc tính x được thừa kế từ A hoặc B::x để truy nhập vào thuộc tính x được thừa kế từ B.

Trong trường hợp ta không sử dụng tên lớp và toán tử phạm trước tên thành phần, chương trình dịch sẽ phải tự xác định để biết thành phần đó thuộc lớp nào theo quy tắc: Trước tiên nó xem thành phần đang xét có trùng tên với thành phần nào cđa lớp dẫn xuất hay không. nếu không trùng thì tiếp tục xét đến các lớp cơ sở có quan hệ gần nhất với lớp dẫn xuất trong sơ đồ thừa kế (lớp cha-> lớp ông->...). nếu thành phần đang xét xuất hiện trong hai lớp cơ sở đồng cấp thì


chương trình dịch không thể xác định được thành phần này thuộc lớp nào và phải

đưa ra thông báo lỗi. Ví dụ thuộc tính x trong ví dụ 3.8.


3.5. Lớp cơ sở ảo


3.5.1. Một số lớp cơ sở xuất hiện nhiều lần trong lớp dẫn xuất


A

B

C

D

E

F

G

H


Sơ đồ 3.2

Trong sơ đồ dẫn xuất trên ta thấy lớp cơ sở B được đề cập hai lần trong lớp dẫn xuất G thông qua hai lớp cơ sở trung gian D và E. Khi đó, trong lớp G nếu ta truy nhập vào một thành phần cđa lớp B thì chương trình dịch sẽ không thể phân biệt

được thành phần đó được thừa kế thông qua lớp D hay lớp E và đưa ra thông báo Member is ambigous


3.5.2. Các lớp cơ sở ảo

Giải pháp cho vấn đề một lớp cơ sở xuất hiện nhiều lần trong lớp dẫn xuất ta sử dụng lớp cơ sở ảo. Các lớp cơ sở ảo sẽ được kết hợp để tạo một lớp cơ sở duy nhất cho bất kỳ lớp nào dẫn xuất từ chúng. Trong sơ đồ dẫn xuất 3.2 ta cần khai báo lớp B là lớp cơ sở ảo. Để một lớp trở thành lớp cơ sở ảo, ta chỉ cần thêm từ khoá virtual trước tên lớp cơ sở khi xây dựng lớp dẫn xuất.

Ví dụ 3.9:

class B

{

//Khai báo các thành phần cđa lớp B;

};

class D : virtual public B

{

//Khai báo các thành phần cđa lớp D


};

class E: virtual B

{

//Khai báo các thành phần cđa lớp E

};


3.6. Toán tử gán cđa lớp dẫn xuất


3.6.1. Khi nào cần xây dựng toán tử gán

Khi lớp dẫn xuất có các thuộc tính kiểu con trỏ hoặc tham chiều (kể cả các thuộc tính được thừa kế) thì không được sử dụng toán tử gán mặc định mà cần xây dựng toán tử gán tường minh cho lớp dẫn xuất.


3.6.2. Cách xây dựng toán tử gán cho lớp dẫn xuất

Để xây dựng toán tử gán cho lớp dẫn xuất ta thực hiện các bước sau: B1. Xây dựng toán tử gán cho các lớp cơ sở và các lớp thành phần

B2. Xây dựng toán tử gán cho lớp dẫn xuất trong đó có gọi toán tử gán cđa lớp cơ sở và toán tử gán cđa các lớp thành phần. Để sử dụng toán tử gán cđa lớp cơ sở cần ép kiểu theo cú pháp:

Tên_lớp_cơ_sở(*this)= Tên_lớp_cơ_sở::operator=(Tham số) Ví dụ 3.10:

D &operator=(D &h)

{

B(*this) = B::operator=(h);

//các lệnh gán giá trị cho các thuộc tính cđa lớp D

}


3.7. Hàm tạo sao chép cđa lớp dẫn xuất


3.7.1. Khi nào cần xây dựng hàm tạo sao chép

Khi lớp dẫn xuất có các thuộc tính kiểu con trỏ hoặc tham chiều (kể cả các thuộc tính được thừa kế) thì không được sử dụng hàm tạo sao chép mặc định mà cần xây dựng hàm tạo sao chép tường minh cho lớp dẫn xuất.


3.7.2. Cách xây dựng hàm tạo sao chép cho lớp dẫn xuất

Để xây dựng hàm tạo sao chép cho lớp dẫn xuất ta thực hiện các bước sau: B1. Xây dựng hàm tạo cho các lớp cơ sở và các lớp thành phần

B2. Xây dựng hàm tạo sao chép cho lớp dẫn xuất trong đó có gọi hàm tạo sao chép cđa lớp cơ sở và hàm tạo sao chép cđa các lớp thành phần.

Ví dụ 3.11:

#include <iostream.h>

#include <conio.h> class point

{

float x,y; public:

point() {x = 0; y = 0;}

point(float ox, float oy) {x = ox; y = oy; } point(point &p) {x = p.x; y = p.y;}

void display()

{

cout<<"Goi ham point::display() n"; cout<<"Toa do :"<<x<<" "<<y<<endl;

}

void move(float dx, float dy)

{

x += dx; y += dy;

}

};

/*lớp coloredpoint dẫn xuất từ lớp point*/

class coloredpoint : public point

{

unsigned int color; public: coloredpoint():point()

{


color =0;

}

coloredpoint(float ox, float oy, unsigned int c):point(ox,oy)

{

color = c;

}

coloredpoint(coloredpoint &b):point((point &)b)

{

color = b.color;

}

void display()

{

cout<<"Ham coloredpoint::display()n"; point::display();

cout<<"Mau "<<color<<endl;

}

};

void main()

{

clrscr();

coloredpoint m(2,3,5); cout<<"Diem m n"; m.display(); cout<<"coloredpoint p =m;n"; coloredpoint p =m; cout<<"Diem p n"; p.display();

getch();

}


3.8. Phương thức tĩnh

Ta xét ví dụ sau: Ví dụ 3.12 :


#include<iostream.h>

#include<conio.h> class Car

{

private:

//Khai báo các thuộc tính cđa lớp Car public:

void show()

{

cout<<Day la lop Car;

}

};

class Bus: public Car

{

private:

//Khai báo các thuộc tính cđa lớp Bus public:

void show()

{

cout<<Day la lop Bus;

}

};

void main()

{

Bus myBus;

Car *ptrCar = &myBus; myBus.show(); cout<<n; ptrCar->show();

getch();

}

Chạy chương trình trên cho kết quả là: Day la lop Bus

Day la lop Car.


Như vậy, khi gọi phương thức show từ đối tượng cđa lớp Bus (myBus) thì chương trình thực hiện phương thức show được khai báo trong lớp Bus. Còn khi gọi phương thức show từ con trỏ ptrCar là con trỏ kiểu lớp Car thì chương trình lại thực hiện phương thức show() cđa lớp Car, mà không gọi tới phương thức show() cđa lớp Bus mặc dù con trỏ ptrCar đang trỏ tới đối tượng myBus cđa lớp Bus vì phương thức show() là một phương thức tĩnh. Đây chính là nhược điểm cđa phương thức tĩnh.

Như vậy, ta có thể đưa ra quy tắc gọi các phương thức tĩnh như sau:

nếu lời gọi phương thức tĩnh 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

nếu lời gọi phương thức tĩnh 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 mà không phụ thuộc vào con trỏ đó

đang chứa địa chỉ cđa đối tượng cđa lớp nào.


3.9. Sự hạn chề cđa phương thức tĩnh

Sự thừa kế trong C++ cho phép có sự tương ứng giữa lớp cơ sở và các lớp dẫn xuất trong sơ đồ thừa kế:

Một con trỏ có kiểu lớp cơ sở luôn có thể trỏ đến địa chỉ cđa một đối tượng cđa lớp dẫn xuất.

Tuy nhiên, khi thực hiện lời gọi một phương thức tĩnh cđa lớp từ một con trỏ, trình biên dịch sẽ quan tâm đến kiểu cđa con trỏ chứ không phải đối tượng mà con trỏ đang trỏ tới: phương thức cđa lớp mà con trỏ có kiểu được gọi chứ không phải phương thức cđa đối tượng mà con trỏ đang trỏ tới được gọi. Điều này được thể hiện trong ví dụ 3.12. Đây chính là nhược điểm cđa phương thức tĩnh.

Để giải quyết vấn đề này, C++ đưa ra một khái niệm là phương thức ảo. Bằng cách sử dụng phương thức ảo. Khi gọi một phương thức từ một con trỏ đối tượng, trình biên dịch sẽ xác định kiểu cđa đối tượng mà con trỏ đang trỏ đến, sau đó nó sẽ gọi phương thức tương ứng với đối tượng mà con trỏ đang trỏ tới.


3.10. Phương thức ảo và tương ứng bội


3.10.1. Cách định nghĩa phương thức ảo

Phương thức ảo (còn gọi là phương thức trừu tượng) được khai báo với từ khoá

virtual:

..... Xem trang tiếp theo?
⇦ Trang trước - Trang tiếp theo ⇨

Ngày đăng: 03/07/2022