1. Trang chủ
  2. » Công Nghệ Thông Tin

Kỹ thuật lập trình - Chương 6

26 3 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 26
Dung lượng 268,5 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Tham khảo tài liệu ''kỹ thuật lập trình - chương 6'', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Trang 1

Chơng 6Tơ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

public:

void xuat(){

cout << "\n Lop B " ;}

};

class C:public B{

public:

void xuat(){

cout << "\n Lop C " ;}

ơng thức cần gọi

Trang 2

Bây giờ chúng ta hãy xét các lời gọi không phải từ

một biến đối tợng mà từ một con trỏ Xét các câu

Phép gán con trỏ: Con trỏ của lớp cơ sở có thể

dùng để chứa địa chỉ các đối tợng của lớp dẫn xuất

Nh vậy cả 3 phép gán sau đều hợp lệ:

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

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ứcxuat() 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ôngọi tới phơng thức A::xuat() vì con trỏ p kiểu A Nhvậy bốn câu lệnh:

#include <conio.h>

#include <stdio.h>

Trang 3

}void xuat(){

cout << "\nLop B: "<<getN();

}};

class C:public A{

public:

C():A(){}C(int n1):A(n1){

}void xuat(){

cout << "\nLop C: "<<getN();

}};

class D:public C{

public:

D():C()

Trang 4

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áodanh 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ọnhoặc in hoặc không) Chơng trình dới đây sử dụnglớp TS (Thí sinh) đáp ứng đợc yêu cầu đặt ra

//CT6-02// Han che phuong thuc tinh// Lop TS

cout << "\nHo ten: " ;fflush(stdin); gets(ht);

cout << "So bao danh: " ;cin >> sobd;

cout << "Tong diem: " ;

Trang 5

cout << "\nHo ten: " << ht ;

cout << "\nCo in khong? - C/K" ;

Cụ thể lớp TS2 đợc định nghĩa nh sau:

class TS2:public TS{

private:

char dc[30] ; // Dia chipublic:

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ứcxem_in, mà sẽ dùng phơng thức xem_in của lớp TS Ch-

ơng trình mới nh sau:

Trang 6

void xem_in(){

//kieu TS)}

} ;class TS2:public TS{

private:

char dc[30] ; // Dia chipublic:

void nhap(){

TS::nhap();

cout << "Dia chi: " ;fflush(stdin); gets(dc);

}void in(){

TS::in();

Trang 7

Khi thực hiện chơng trình này, chúng ta nhận thấy:

Dữ liệu in ra vẫn không có địa chỉ Điều này có thể

giải thích nh sau:

Xét câu lệnh (thứ 2 từ dới lên trong hàm main):

t[i].xem_in() ;

Câu lệnh này gọi tới phơng thức xem_in của lớp TS2

(vì t[i] là đối tợng của lớp TS2) Nhng lớp TS2 không

this->in() ;

sẽ đợc thực hiện Mặc dù địa chỉ của t[i] (là đối tợngcủa lớp TS2) đợc truyền cho con trỏ this, thế nhngcâ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ơngthứ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() (trongcác lớp TS và TS2) đã không đáp ứng đợc yêu cầu pháttriể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ựctiế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ùngtên, trùng các đối) Để định nghĩa các phơng thứcnày là các phơng thức ảo, ta chỉ cần:

Trang 8

+ 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

} ;class D : public A{

void hien_thi(){

cout << “\n Đây là lớp D” ;};

} ;

Cách 2:

class 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” ;};

} ;

Trang 9

Chú ý: Từ khoá virtual không đợc đặt bên ngoài

định nghĩa lớp Ví dụ nếu viết nh sau là sai (CTBD sẽ

virtual void hien_thi() ; } ;

void hien_thi() // Đúng{

cout << “\n Đây là lớp A” ;};

3.2 Quy tắc gọi phơng thức ảo

Để có sự so sánh với phơng thức tĩnh, ta nhắc lạiquy tắc gọi phơng thức tĩnh nêu trong Đ1

3.2.1 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êncủ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

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 đợcgọ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ỏ cha cho biết rõphơng thức nào (trong số các phơng thức ảo trùng têncủa các lớp có quan hệ thừa kế) sẽ đợc gọi Điều nàyphụ thuộc vào đối tợng cụ thể mà con trỏ đang trỏ tới:

Trang 10

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

Chúng ta nhận thấy cùng một câu lệnh

p->hien_thi();

tơng ứng với nhiều phơng thức khác nhau Đây chính

là tơng ứng bội Khả năng này rõ ràng cho phép xử lý

nhiều đối tợng khác nhau, nhiều công việc, thậm chí

nhiều thuật toán khác nhau theo cùng một cách thức,

cùng một lợc đồ Điều này sẽ đợc minh hoạ trong các

mục tiếp theo

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 DNếu hien_thi() là các phơng thức tĩnh, thì dù pchứa địa chỉ của các đối tợng a, b, c hay d, thì lờigọi:

p->hien_thi() ; luôn luôn gọi tới phơng thức A::hien_thi()

Nh vậy một lời gọi (xuất phát từ con trỏ) tới phơngthức tĩnh luôn luôn liên kết với một phơng thức cố

định và sự liên kết này xác định trong quá trìnhbiên dịch chơng trình

Cũng với lời gọi:

p->hien_thi() ;

nh trên, nhng nếu hien_thi() là các phơng thức ảo, thìlời gọi này không liên kết cứng với một phơng thức cụthể nào Phơng thức mà nó liên kết (gọi tới) còn chaxác định trong giai đoạn dịch chơng trình Lời gọinày sẽ:

+ 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

Trang 11

+ 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

+ 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:

int n;

public:

A()

Trang 12

class C:public A{

public:

C():A(){}C(int n1):A(n1){

}void xuat(){

cout << "\nLop C: "<<getN();

}};

class D:public C{

public:

D():C(){}D(int n1):C(n1){

}void xuat(){

cout << "\nLop D: "<<getN();

Trang 13

(câu lệnh gần cuối trong hàm main) sẽ gọi tới C::xuat()

, phơng thức này đợc kế thừa trong lớp D (vì D dẫn

xuất từ C)

Đ 4 Sự linh hoạt của phơng thức ảo trong phát

triển nâng cấp chơng trình

Ví dụ về các lớp TS và TS2 trong Đ2 đã chỉ ra sự hạnchế của phơng thức tĩnh trong việc sử dụng tínhthừa kế để nâng cấp, phát triển chơng trình Trong

Đ2 cũng đã chỉ ra lớp TS2 cha đáp ứng đợc yêu cầunêu ra là in địa chỉ của thí sinh Giải pháp cho vấn

đề này rất đơn giản: Thay các phơng thức tĩnh in()bằng cách dùng chúng nh các phơng thức ảo Chơngtrình khi đó sẽ nh sau:

//CT6-03B// Sự linh hoạt của phơng thức ảo// Lop TS TS2

cout << "\nHo ten: " ;fflush(stdin); gets(ht);

Trang 14

cout << "So bao danh: " ;

cout << "\nHo ten: " << ht ;

cout << "\nCo in khong? - C/K" ;

ch = toupper(getch());

if (ch=='C')

this->in(); // Vì in() là phơng thức ảo nên

//có thể gọi đến TS::in() hoặc TS2::in()

cout << "Dia chi: " ;fflush(stdin); gets(dc);

}void in(){

TS::in();

fprintf(stdprn,"\nDia chi: %s", dc);

}};

void main(){

Dữ liệu thí sinh in ra đã có địa chỉ Điều này có thểgiải thích nh sau:

Xét câu lệnh (thứ 2 từ dới lên trong hàm main):

t[i].xem_in() ;

Trang 15

Câu lệnh này gọi tới phơng thức xem_in của lớp TS2

(vì t[i] là đối tợng của lớp TS2) Nhng lớp TS2 không

cout << "\nHo ten: " << ht ;

cout << "\nCo in khong? - C/K" ;

ch = toupper(getch());

this->in(); // Vì in() là phơng thức ảo nên

//có thể gọi đến TS::in() hoặc

TS2::in()

}

Các lệnh đầu của phơng thức sẽ in họ tên thí sinh

Nếu chọn Có (bấm phím C), thì câu lệnh:

this->in() ;

sẽ đợc thực hiện Địa chỉ của t[i] (là đối tợng của lớp

TS2) đợc truyền cho con trỏ this (của lớp cơ sở TS) Vì

in() là phơng thức ảo và vì this đang trỏ tới đối tợng

t[i] của lớp TS2, nên câu lệnh này gọi tới phơng thức

TS2::in() Trong phơng thức TS2::in() có in đị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)

Đ 5 Lớp cơ sở trừu tợng

5.1 Lớp cơ sở trừu tợng

Một lớp cơ sở trừu tợng là một lớp chỉ đợc dùnglàm cơ sở cho các lớp khác Không hề có đối tợng nàocủa một lớp trừu tợng đợc tạo ra cả, bởi vì nó chỉ đợcdùng để định nghĩa một số khái niệm tổng quát,chung cho các lớp khác Một ví dụ về lớp trừu tợng là lớpCON_VAT (con vật), nó sẽ dùng làm cơ sở để xây dựngcác lớp con vật cụ thể nh lớp CON_CHO (con chó),CON_MEO (con mèo), (xem ví dụ bên dới)

Trong C++ , thuật ngữ “Lớp trừu tợng” đặc biệt ápdụng cho các lớp có chứa các phơng thức ảo thuần tuý

Phơng thức ảo thuần tuý là một phơng thức ảo mà nộidung của nó không có gì Cách thức định nghĩa mộtphơng thức ảo thuần tuý nh sau:

virtual void tên_phơng_thức() = 0 ;

Ví dụ:

class A{public:

virtual void nhap() = 0 ;virtual void xuat() = 0 ;void chuong();

} ;Trong ví dụ trên, thì A là lớp cơ sở trừu tợng Các ph-

ơng thức nhap và xuat đợc khai báo là các lớp ảo thuầntuý (bằng cách gán số 0 cho chúng thay cho việc cài

đặt các phơng thức này) Phơng thức chuong() làmột phơng thức bình thờng và sẽ phải có một địnhnghĩa ở đâu đó cho phơng thức này

Không có đối tợng nào của một lớp trừu tợng lại cóthể đợc phát sinh Tuy nhiên các con trỏ và các biến

Trang 16

tham chiếu đến các đối tợng của lớp trừu tợng thì vẫn

hợp lệ Bất kỳ lớp nào dẫn xuất từ một lớp cớ sở trừu tợng

phải định nghĩa lại tất cả các phơng thức thuần ảo

mà nó thừa hởng, hoặc bằng các phơng thức ảo

thuần tuý, hoặc bằng những định nghĩa thực sự Ví

dụ:

class B : public A

{

public:

virtual void nhap() = 0 ;

virtual void xuat()

{

// Các câu lệnh

}

} ;

Theo ý nghĩa về hớng đối tợng, ta vẫn có thể có

một lớp trừu tợng mà không nhất thiết phải chứa đựng

những phơng thức thuần tuý ảo

Một cách tổng quát mà nói thì bất kỳ lớp nào mà

nó chỉ đợc dùng làm cơ sở cho những lớp khác đều có

thể đợc gọi là “lớp trừu tợng” Một cách dễ dàng để

nhận biết một lớp trừu tợng là xem có dùng lớp đó để

khai báo các đối tợng hay không? Nếu không thì đó

là lớp cơ sở trừu tợng

5.2 Ví dụ

Giả sử có 20 ô, mỗi ô có thể nuôi một con chó hoặc

một con mèo Yêu cầu xây dựng chơng trình gồm các

chức năng:

+ Nhập một con vật mới mua (hoặc chó, hoặc mèo)

vào ô rỗng đầu tiên

+ Xuất (đem bán) một con vật (hoặc chó, hoặcmèo)

+ Thống kê các con vật đang nuôi trong 20 ô

đối tợng kiểu CON_MEO hoặc CON_CHO

Lớp sẽ có 3 phơng thức để thực hiện 3 chức năngnêu trên của chơng trình Nội dung chơng trình nhsau:

//CT6-04// Lop co so truu tuong// Lop CON_VAT

protected:

char *ten;

public:

Trang 17

CON_CHO() : CON_VAT(){

}CON_CHO(char *ten1) : CON_VAT(ten1){

}virtual void xung_ten(){

cout << "\nToi la chu cho: " << ten ;}

Trang 18

so_con_vat ;return c;

}elsereturn NULL;

}void DS_CON_VAT::thong_ke(){

if (so_con_vat){

cout << "\n" ;for (int i=0; i<max_so_con_vat; ++i)

if (h[i])h[i]->xung_ten();

}}CON_CHO c1("MUC");

Trang 19

Chú ý: Theo quan điểm chung về cách thức sử

dụng, thì lớp CON_VAT là lớp cơ sở trừu tợng Tuy nhiên

theo quan điểm của C++ thì lớp này cha phải là lớp

cơ sở trừu tợng, vì trong lớp không có các phơng thức

thuần tuý ảo Phơng thức xung_ten:

virtual void xung_ten()

{

}

là phơng thức ảo, đợc định nghĩa đầy đủ , mặc dùthân của nó là rỗng

Do vậy khai báo:

CON_VAT cv(“Con vat chung”);

CON_VAT cv(“Con vat chung”);

sẽ bị C++ bắt lỗi với thông báo:

Cannot create instance of abstruct class ‘CON_VAT’

Đ 6 Sử dụng tơng ứng bội và phơng thức ảo 6.1 Chiến lợc sử dụng tơng ứng bội

Tơng ứng bội cho phép xét các vấn đề khác nhau,các đối tợng khác nhau, các phơng pháp khác nhau,các cách giải quyết khác nhau theo cùng một lợc đồchung

Các bớc áp dụng tơng ứng bội có thể tổng kết lại nhsau:

1 Xây dựng lớp cơ sở trừu tợng bao gồm nhữngthuộc tính chung nhất của các thực thể cần quản lý Đ-

a vào các phơng thức ảo hay thuần ảo dùng để xâydựng các nhóm phơng thức ảo cho các lớp dẫn xuấtsau này Mỗi nhóm phơng thức ảo sẽ thực hiện mộtchức năng nào đó trên các lớp

Ngày đăng: 11/05/2021, 01:52

🧩 Sản phẩm bạn có thể quan tâm

w