Bài giảng Ngôn ngữ lập trình - Bài 8: Đa hình và hàm ảo - Lê Nguyễn Tuấn Thành

Tóm tắt Bài giảng Ngôn ngữ lập trình - Bài 8: Đa hình và hàm ảo - Lê Nguyễn Tuấn Thành: ... SỐ BÁN HÀNG (2/2)  Chương trình phải:  Tính toán số lượng lớn bán hàng mỗi ngày  Tính toán lượng bán hàng lớn nhất, nhỏ nhất trong ngày  Có thể là lượng bán hàng trung bình trong ngày  Tất cả đều đến từ những hóa đơn riêng lẻ  Nhưng sau này nhiều hàm để tính hóa đơn sẽ được th...t cả các đối tượng của lớp con DiscountSale!  Thay vì phiên bản mặc định được định nghĩa trong lớp cha Sale!  Nhớ lại: lớp Sale được viết trước lớp con DiscountSale  Hàm thành viên savings và toán tử < được biên dịch ngay cả trước khi có ý tưởng về tạo lớp con DiscountSale!  Dis...trước:  Một đối tượng DiscountSale “là” một Sale, nhưng điều ngược lại không đúng 23 TƯƠNG THÍCH KIỂU – VÍ DỤ  class Pet { public: string name; virtual void print() const; }; class Dog : public Pet { public: string breed; virtual void print() const; }; 24 SỬ...

pdf32 trang | Chia sẻ: havih72 | Lượt xem: 258 | Lượt tải: 0download
Nội dung tài liệu Bài giảng Ngôn ngữ lập trình - Bài 8: Đa hình và hàm ảo - Lê Nguyễn Tuấn Thành, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
NGÔN NGỮ LẬP TRÌNH 
Bài 8: 
Đa Hình và Hàm Ảo 
Giảng viên: Lê Nguyễn Tuấn Thành 
Email: thanhlnt@tlu.edu.vn 
Bộ Môn Công Nghệ Phần Mềm – Khoa CNTT 
Trường Đại Học Thủy Lợi 
NỘI DUNG 
1. Đa hình (Polymorphism) 
2. Cơ bản về Hàm ảo (Virtual Function) 
 Gắn kết trễ (Late binding) 
 Cài đặt hàm ảo 
 Khi nào sử dụng hàm ảo? 
 Hàm ảo thuần (Pure Virtual Function) và 
Lớp trừu tượng (Abstract Class) 
3. Con trỏ và Hàm ảo 
 Mở rộng tương thích kiểu 
 Ép kiểu lên (Upcasting) 
 Ép kiểu xuống (Downcasting) 
2 
Bài giảng có sử dụng hình vẽ trong cuốn sách “Practical Debugging in C++, 
A. Ford and T. Teorey, Prentice Hall, 2002” 
ĐA HÌNH 
(POLYMORPHISM) 
 Một trong ba trụ cột quan trọng trong OOP 
 Đa hình (Polymorphism) là hiện tượng các đối 
tượng thuộc các lớp khác nhau hiểu cùng một 
thông điệp theo các cách khác nhau 
 Ví dụ: cùng là thông điệp “nhảy”, một con 
kangaroo và một con cóc sẽ nhảy hai kiểu khác 
nhau. 
 Chúng có cùng hành vi “nhảy” nhưng nội dung của 
hành vi này là khác nhau 
3 
CƠ BẢN VỀ HÀM ẢO 
 Hàm ảo 
 Hàm ảo cung cấp khả năng đa hình này 
 Hàm có thể được “sử dụng” trước khi thực sự được định 
nghĩa 
4 
VÍ DỤ VỚI CÁC LỚP MÔ TẢ HÌNH VẼ (1/5) 
HÀM THÀNH VIÊN DRAW() 
 Xây dựng các lớp cho nhiều kiểu hình vẽ khác 
nhau 
 Hình chữ nhật, hình tròn, hình oval  
 Mỗi hình cụ thể là đối tượng của những lớp này 
 Dữ liệu hình chữ nhật: chiều cao, chiều rộng 
 Dữ liệu hình tròn: tâm, bán kính 
 Tất cả các lớp này đều kế thừa từ một lớp cha: 
Figure 
 Các lớp này đều có hàm draw() 
 Mục đích là vẽ hình này trên màn hình 
 Mỗi lớp có cài đặt khác nhau tương ứng với mỗi loại 
hình vẽ 5 
VÍ DỤ VỚI CÁC LỚP MÔ TẢ HÌNH VẼ (2/5) 
SỬ DỤNG HÀM THÀNH VIÊN DRAW() 
 Mỗi lớp con cần định nghĩa hàm draw() riêng 
 Có thể gọi hàm draw() của mỗi lớp, ví dụ: 
 Rectangle r; 
Circle c; 
r.draw(); // Gọi hàm draw của lớp Rectangle 
c.draw(); // Gọi hàm draw của lớp Circle 
 Điều này là bình thường, chưa có gì đặc biệt ở đây! 
6 
VÍ DỤ VỚI CÁC LỚP MÔ TẢ HÌNH VẼ (3/5): 
HÀM THÀNH VIÊN CENTER() 
 Lớp cha Figure bao gồm những hàm có thể áp 
dụng cho “tất cả” hình vẽ 
 Xét hàm center() để di chuyển một hình vẽ từ vị 
trí hiện tại tới vị trí trung tâm màn hình 
 Cách làm: xóa hình vị ở vị trí hiện tại, sau đó vẽ lại tại 
vị trí trung tâm màn hình 
 Hàm Figure::center() sẽ sử dụng (gọi) hàm draw() để 
vẽ lại hình 
 Câu hỏi: 
 Hàm draw() nào sẽ được gọi? 
 Từ lớp nào? 
7 
VÍ DỤ VỚI CÁC LỚP MÔ TẢ HÌNH VẼ (4/5): 
ĐỊNH NGHĨA LỚP HÌNH VẼ MỚI 
 Xét một lớp hình vẽ mới: lớp Triangle kế thừa từ lớp 
Figure 
 Hàm center() của lớp Triangle kế thừa từ lớp cha 
Figure 
 Liệu hàm này có hoạt động được với lớp Triangle? 
 Hàm này sử dụng hàm draw() riêng của lớp Triangle! 
 Nếu hàm này sử dụng hàm Figure::draw() -> không hoạt 
động đúng với lớp Triangle 
 Muốn: kế thừa hàm center() để sử dụng hàm 
Triangle::draw() chứ KHÔNG PHẢI hàm 
Figure::draw() 
 Nhưng lớp Triangle CHƯA ĐƯỢC định nghĩa khi hàm 
Figure::center() định nghĩa! 
 Không biết sự tồn tại lớp Triangle 
8 
VÍ DỤ VỚI CÁC LỚP MÔ TẢ HÌNH VẼ (5/5): 
HÀM ẢO 
 Hàm ảo là câu trả lời cho vấn đề trên 
 Nói với trình biên dịch: 
 Không biết hàm sẽ được cài đặt như thế nào 
 Đợi cho đến khi được sử dụng trong chương trình 
 Sau đó lấy phần cài đặt từ đối tượng cụ thể 
 Được gọi là gắn kết trễ (late binding) hoặc gắn kết 
động (dynamic binding) 
 Những hàm ảo cài đặt cơ chế late binding 
9 
VÍ DỤ DOANH SỐ BÁN HÀNG (1/2) 
 Xây dựng chương trình giúp lưu trữ hồ sơ cho một 
cửa hàng phụ tùng ô tô. 
 Mục đích: lưu trữ doanh số bán hàng 
 Không lường trước hết tất cả loại doanh số bán hàng 
 Đầu tiên chỉ là doanh số bán lẻ thông thường 
 Sau đó: doanh số bán hàng giảm giá, doanh số bán 
hàng qua thư điện tử,  
 Phụ thuộc vào nhiều yếu tố như giá, thuế  
10 
 VÍ DỤ DOANH SỐ BÁN HÀNG (2/2) 
 Chương trình phải: 
 Tính toán số lượng lớn bán hàng mỗi ngày 
 Tính toán lượng bán hàng lớn nhất, nhỏ nhất trong 
ngày 
 Có thể là lượng bán hàng trung bình trong ngày 
 Tất cả đều đến từ những hóa đơn riêng lẻ 
 Nhưng sau này nhiều hàm để tính hóa đơn sẽ được 
thêm vào! 
 Khi những loại doanh số bán hàng khác nhau được 
thêm vào 
 Vì thế hàm để tính toán một hóa đơn sẽ là hàm 
ảo! 11 
ĐỊNH NGHĨA LỚP SALE 
 class Sale 
{ 
public: 
 Sale(); 
 Sale(double thePrice); 
 double getPrice() const; 
 virtual double bill() const; 
 double savings(const Sale& other) const; 
private: 
 double price; 
}; 
12 
HÀM THÀNH VIÊN SAVINGS VÀ 
TOÁN TỬ < 
 double Sale::savings(const Sale& other) const 
{ 
 return (bill() – other.bill()); 
} 
 bool operator < ( const Sale& first, 
 const Sale& second) 
{ 
 return (first.bill() < second.bill()); 
} 
 Lưu ý: CẢ HAI hàm này đều sử dụng hàm bill()! 
13 
LỚP SALE 
 Biểu diễn doanh số bán hàng cho mỗi mục đơn lẻ 
mà không tính tới yếu tố giảm giá hay phí tăng 
thêm 
 Chú ý từ khóa virtual trong khai báo của hàm 
thành viên bill() 
 Tác dụng: sau đó, những lớp kế thừa của lớp Sale có 
thể định nghĩa những phiên bản hàm bill() của riêng 
chúng 
 Những hàm thành viên khác của lớp Sale sẽ sử dụng 
phiên bản hàm bill() dựa trên đối tượng của lớp con! 
 Chúng sẽ không tự động sử dụng phiên bản hàm bill() 
của lớp cha Sale! 
14 
ĐỊNH NGHĨA LỚP CON DISCOUNTSALE 
 class DiscountSale : public Sale 
{ 
public: 
 DiscountSale(); 
 DiscountSale( double thePrice, 
 double the Discount); 
 double getDiscount() const; 
 void setDiscount(double newDiscount); 
 double bill() const; 
private: 
 double discount; 
 }; 
 15 
CÀI ĐẶT HÀM BILL CỦA LỚP CON 
DISCOUNTSALE 
 double DiscountSale::bill() const 
{ 
 double fraction = discount/100; 
 return (1 – fraction)*getPrice(); 
} 
 Từ khóa virtual không xuất hiện trong cài đặt thực tế 
của hàm ảo 
 Tự động là hàm ảo trong lớp con 
 Khai báo (trong giao diện) cũng không yêu cầu phải có từ 
khóa virtual (nhưng thường được sử dụng) 
 Hàm ảo trong lớp cơ sở sẽ tự động là hàm ảo trong lớp 
kế thừa 
 Khai báo lớp con (trong giao diện) 
 Không yêu cầu phải có từ khóa virtual 
 Nhưng có thể viết thêm để dễ đọc, dễ phân biệt 
16 
LỚP CON DISCOUNTSALE 
 Hàm thành viên bill() của lớp DiscountSale được cài 
đặt khác so với hàm này trong lớp cha Sale 
 Riêng biệt cho việc bán hàng giảm giá 
 Hàm thành viên savings và toán tử < 
 Sẽ sử dụng định nghĩa này của hàm bill() cho tất cả các đối 
tượng của lớp con DiscountSale! 
 Thay vì phiên bản mặc định được định nghĩa trong lớp cha 
Sale! 
 Nhớ lại: lớp Sale được viết trước lớp con DiscountSale 
 Hàm thành viên savings và toán tử < được biên dịch ngay 
cả trước khi có ý tưởng về tạo lớp con DiscountSale! 
 DiscountSale d1; 
d1.savings(d2); 
 Lời gọi trong hàm savings này tới hàm bill() sẽ biết sử dụng 
định nghĩa hàm bill() từ lớp DiscountSale! 
17 
THỰC THI HÀM ẢO BẰNG CÁCH NÀO? 
 Để giải thích liên quan đến khái niệm gắn kết trễ 
(late binding) 
 Hàm ảo cài đặt late binding 
 Nói trình biên dịch đợi cho đến khi hàm được sử dụng 
trong chương trình 
 Quyết định phiên bản nào của hàm được sử dụng dựa 
trên đối tượng gọi 
 Một khái niệm rất quan trọng trong OOP 
18 
GHI ĐÈ (OVERRIDING) 
 Định nghĩa hàm ảo thay đổi trong một lớp kế thừa 
 Chúng ta gọi đó là “ghi đè” (overidden) 
 Khác với nạp chồng (overloading) như thế nào ? 
 Tương tự như định nghĩa lại cho các hàm chuẩn 
 Phân biệt: 
 Hàm ảo thay đổi: ghi đè (overidden) 
 Hàm bình thường thay đổi: định nghĩa lại (redefined) 
19 
ĐIỂM YẾU CỦA VIỆC SỬ DỤNG HÀM ẢO 
 Bỏ qua tất cả những lợi ích của hàm ảo như chúng 
ta đã thấy 
 Hàm ảo có một bất lợi lớn: phụ phí (overhead)! 
 Sử dụng nhiều bộ nhớ hơn 
 Gắn kết trễ (late binding) khiến chương trình chạy 
chậm hơn 
 Vì vậy nếu hàm ảo không thật cần thiết thì không 
nên sử dụng 
20 
HÀM ẢO THUẦN 
(PURE VIRTUAL FUNCTIONS) 
 Lớp cơ sở có thể không có định nghĩa có nghĩa cho 
một vài thành viên của nó! 
 Mục đích của nó đơn giản là để cho những lớp khác kế 
thừa 
 Nhớ lại lớp Figure 
 Tất cả các hình vẽ là đối tượng của lớp kế thừa cụ thể. 
Ví dụ: Rectangle, Circle, Triangle,  
 Lớp Figure không có ý niệm về việc bằng cách nào có 
thể vẽ được! 
 Tạo một hàm ảo thuần: 
 virtual void draw() = 0; 
21 
LỚP CƠ SỞ TRỪU TƯỢNG 
(ABSTRACT BASE CLASSES) 
 Các hàm ảo thuần không yêu cầu định nghĩa 
 Bắt buộc các lớp kế thừa phải định nghĩa phiên bản 
hàm riêng của nó 
 Lớp với một hay nhiều hàm ảo thuần gọi là: lớp cơ 
sở trừu tượng 
 Chỉ có thể được sử dụng như lớp cơ sở 
 Không thể tạo đối tượng từ lớp trừu tượng này. Bởi vì 
nó không có định nghĩa hoàn thiện của tất cả các 
thành viên! 
 Nếu lớp thừa kế không định nghĩa tất cả hàm ảo 
thuần => Nó cũng sẽ là một lớp cơ sở trừu tượng 
22 
MỞ RỘNG TƯƠNG THÍCH KIỂU 
(TYPE COMPATIBILITY) 
 Giả sử D là lớp kế thừa từ lớp cơ sở B 
 Đối tượng của lớp D có thể được gán cho đối tượng của 
lớp cơ sở B 
 Nhưng ngược lại thì không thể! 
 Xét ví dụ trước: 
 Một đối tượng DiscountSale “là” một Sale, nhưng điều 
ngược lại không đúng 
23 
TƯƠNG THÍCH KIỂU – VÍ DỤ 
 class Pet 
{ 
public: 
 string name; 
 virtual void print() const; 
}; 
class Dog : public Pet 
{ 
public: 
 string breed; 
 virtual void print() const; 
}; 
24 
SỬ DỤNG HAI LỚP PET VÀ DOG 
 Xét khai báo sau: 
 Dog vdog; 
Pet vpet; 
 Chú ý các biến thành viên name và breed đều 
public! Chỉ nhằm mục đích minh họa 
 Tất cả mọi thứ “là” dog thì đều “là” pet 
 vdog.name = "Tiny"; 
vdog.breed = "Great Dane"; 
vpet = vdog; 
 Có thể gán giá trị về kiểu của lớp cha, nhưng 
không có chiều ngược lại 
 Một pet “không là” một dog 
25 
VẤN ĐỀ MẤT MÁT THÔNG TIN 
(SLICING) 
 Chú ý khi giá trị được gán về vpet, biến thành 
viên breed của nó bị mất đi 
 cout << vpet.breed; // sẽ tạo ra một thông báo lỗi 
 Được gọi là vấn đề mất mát thông tin (slicing) 
 Điều này là hợp lý 
 Khi đối tượng của lớp Dog chuyển thành đối tượng của 
lớp Pet, nó sẽ được đối xử như một Pet 
 Do đó không còn các thuộc tính của một Dog 
 Vấn đề slicing gây phiền toái 
 vpet vẫn là một Greet Dane có tên là Tiny 
 Chúng ta muốn tham chiếu đến biến thành viên breed 
của nó kể cả khi nó được đối xử như một Pet 
 Có thể làm thế với con trỏ trỏ đến những biến động 
26 
GIẢI QUYẾT VẤN ĐỀ SLICING 
 Pet *ppet; 
Dog *pdog; 
pdog = new Dog; 
pdog->name = "Tiny"; 
pdog->breed = "Great Dane"; 
ppet = pdog; 
 Không thể truy cập trường breed của đối tượng 
được trỏ tới bởi pet: 
 cout breed; // Không hợp lệ! 
 Phải sử dụng hàm ảo thành viên: ppet->print(); 
 Gọi hàm thành viên print() trong lớp Dog! 
 Bởi vì nó là hàm ảo 
 C++ sẽ đợi để nhìn đối tượng con trỏ nào mà ppet thực 
sự trỏ tới trước khi lời gọi được gắn kết (binding) 
27 
HÀM HỦY ẢO 
(VIRTUAL DESTRUCTORS) 
 Hàm hủy cần giải phóng động dữ liệu được cấp 
phát 
 Xét ví dụ: 
 Base *pBase = new Derived; 
delete pBase; 
 Sẽ gọi hàm hủy của lớp cơ sở mặc dù pBase đang trỏ 
tới đối tượng của lớp Derived! 
 Xây dựng hàm hủy ảo sẽ giải quyết vấn đề này! 
 Cách tốt là định nghĩa tất cả hàm hủy là hàm ảo 
28 
ÉP KIỂU (CASTING) 
 Xét ví dụ: 
 Pet vpet; 
Dog vdog; 
vdog = static_cast(vpet); // Không hợp lệ! 
 Không thể ép một pet thành một dog, nhưng: 
 vpet = vdog; // Hợp lệ! 
vpet = static_cast(vdog); // Hợp lệ! 
 Ép kiểu lên (upcasting) là hợp lệ 
 Từ kiểu con cháu lên kiểu tổ tiên 
29 
ÉP KIỂU XUỐNG (DOWNCASTING) 
 Ép kiểu xuống rất nguy hiểm! 
 Ép từ kiểu tổ tiên thành kiểu con cháu 
 Giả sử thông tin được thêm vào 
 Có thể được thực hiện với dynamic_cast 
 Pet *ppet; 
ppet = new Dog; 
Dog *pdog = dynamic_cast(ppet); 
 Hợp lệ, nhưng nguy hiểm 
 Ép kiểu xuống hiếm khi dùng do một số nhược 
điểm 
 Phải kiểm tra xem tất cả thông tin có được thêm vào 
hay không 
 Tất cả hàm thành viên phải là hàm ảo 
30 
TÓM TẮT 
 Gắn kết trễ (late binding) trì hoãn quyết định về việc 
hàm thành viên nào được gọi cho đến khi chạy chương 
trình 
 Trong C++, hàm ảo sử dụng cơ chế gắn kết trễ 
 Hàm ảo thuần không có định nghĩa 
 Một lớp với ít nhất một hàm ảo thuần gọi là lớp trừu tượng 
 Không thể tạo đối tượng từ lớp trừu tượng 
 Được sử dụng chặt chẽ như là cơ sở của những lớp kế thừa 
khác 
 Đối tượng của lớp kế thừa có thể được gán cho đối 
tượng của lớp cơ sở 
 Có thể một vài thông tin của lớp kế thừa bị mất => vấn đề 
cắt lát 
 Gán con trỏ và đối tượng động cho phép giải quyết vấn đề 
mất mát thông tin (slicing) 
 Nên định nghĩa tất cả hàm hủy là hàm ảo 
 Đảm bảo bộ nhớ được giải phóng đúng cách 31 
GIÁO TRÌNH THAM KHẢO 
 Giáo trình chính: W. Savitch, Absolute C++, 
Addison Wesley, 2002 
 Tham khảo: 
 A. Ford and T. Teorey, Practical Debugging in C++, 
Prentice Hall, 2002 
 Nguyễn Thanh Thủy, Kĩ thuật lập trình C++, NXB 
Khoa học và Kĩ Thuật, 2006 
32 

File đính kèm:

  • pdfbai_giang_ngon_ngu_lap_trinh_bai_8_da_hinh_va_ham_ao_le_nguy.pdf